about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
committerMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
commit80ec1decc49c72efec2a8b87c06245c92c0ab807 (patch)
treee3b229aa94e8dcf0590f2317664176e7b8f7607b /src
parent65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (diff)
New upstream version 1.29.90 upstream/1.29.90
Diffstat (limited to 'src')
-rw-r--r--src/core/NetworkManagerUtils.c (renamed from src/NetworkManagerUtils.c)259
-rw-r--r--src/core/NetworkManagerUtils.h (renamed from src/NetworkManagerUtils.h)29
-rw-r--r--src/core/devices/adsl/meson.build (renamed from src/devices/adsl/meson.build)19
-rw-r--r--src/core/devices/adsl/nm-atm-manager.c (renamed from src/devices/adsl/nm-atm-manager.c)10
-rw-r--r--src/core/devices/adsl/nm-device-adsl.c (renamed from src/devices/adsl/nm-device-adsl.c)34
-rw-r--r--src/core/devices/adsl/nm-device-adsl.h (renamed from src/devices/adsl/nm-device-adsl.h)2
-rw-r--r--src/core/devices/bluetooth/meson.build61
-rw-r--r--src/core/devices/bluetooth/nm-bluez-common.h (renamed from src/devices/bluetooth/nm-bluez-common.h)2
-rw-r--r--src/core/devices/bluetooth/nm-bluez-manager.c (renamed from src/devices/bluetooth/nm-bluez-manager.c)7
-rw-r--r--src/core/devices/bluetooth/nm-bluez-manager.h (renamed from src/devices/bluetooth/nm-bluez-manager.h)2
-rw-r--r--src/core/devices/bluetooth/nm-bluez5-dun.c (renamed from src/devices/bluetooth/nm-bluez5-dun.c)4
-rw-r--r--src/core/devices/bluetooth/nm-bluez5-dun.h (renamed from src/devices/bluetooth/nm-bluez5-dun.h)2
-rw-r--r--src/core/devices/bluetooth/nm-bt-error.c (renamed from src/devices/bluetooth/nm-bt-error.c)4
-rw-r--r--src/core/devices/bluetooth/nm-bt-error.h (renamed from src/devices/bluetooth/nm-bt-error.h)2
-rw-r--r--src/core/devices/bluetooth/nm-device-bt.c (renamed from src/devices/bluetooth/nm-device-bt.c)7
-rw-r--r--src/core/devices/bluetooth/nm-device-bt.h (renamed from src/devices/bluetooth/nm-device-bt.h)2
-rw-r--r--src/core/devices/bluetooth/tests/nm-bt-test.c (renamed from src/devices/bluetooth/tests/nm-bt-test.c)4
-rw-r--r--src/core/devices/meson.build (renamed from src/devices/meson.build)2
-rw-r--r--src/core/devices/nm-acd-manager.c (renamed from src/devices/nm-acd-manager.c)5
-rw-r--r--src/core/devices/nm-acd-manager.h (renamed from src/devices/nm-acd-manager.h)2
-rw-r--r--src/core/devices/nm-device-6lowpan.c (renamed from src/devices/nm-device-6lowpan.c)26
-rw-r--r--src/core/devices/nm-device-6lowpan.h (renamed from src/devices/nm-device-6lowpan.h)2
-rw-r--r--src/core/devices/nm-device-bond.c (renamed from src/devices/nm-device-bond.c)46
-rw-r--r--src/core/devices/nm-device-bond.h (renamed from src/devices/nm-device-bond.h)2
-rw-r--r--src/core/devices/nm-device-bridge.c (renamed from src/devices/nm-device-bridge.c)33
-rw-r--r--src/core/devices/nm-device-bridge.h (renamed from src/devices/nm-device-bridge.h)2
-rw-r--r--src/core/devices/nm-device-dummy.c (renamed from src/devices/nm-device-dummy.c)26
-rw-r--r--src/core/devices/nm-device-dummy.h (renamed from src/devices/nm-device-dummy.h)2
-rw-r--r--src/core/devices/nm-device-ethernet-utils.c (renamed from src/devices/nm-device-ethernet-utils.c)4
-rw-r--r--src/core/devices/nm-device-ethernet-utils.h (renamed from src/devices/nm-device-ethernet-utils.h)2
-rw-r--r--src/core/devices/nm-device-ethernet.c (renamed from src/devices/nm-device-ethernet.c)113
-rw-r--r--src/core/devices/nm-device-ethernet.h (renamed from src/devices/nm-device-ethernet.h)2
-rw-r--r--src/core/devices/nm-device-factory.c (renamed from src/devices/nm-device-factory.c)6
-rw-r--r--src/core/devices/nm-device-factory.h (renamed from src/devices/nm-device-factory.h)2
-rw-r--r--src/core/devices/nm-device-generic.c (renamed from src/devices/nm-device-generic.c)24
-rw-r--r--src/core/devices/nm-device-generic.h (renamed from src/devices/nm-device-generic.h)2
-rw-r--r--src/core/devices/nm-device-infiniband.c (renamed from src/devices/nm-device-infiniband.c)30
-rw-r--r--src/core/devices/nm-device-infiniband.h (renamed from src/devices/nm-device-infiniband.h)2
-rw-r--r--src/core/devices/nm-device-ip-tunnel.c (renamed from src/devices/nm-device-ip-tunnel.c)31
-rw-r--r--src/core/devices/nm-device-ip-tunnel.h (renamed from src/devices/nm-device-ip-tunnel.h)2
-rw-r--r--src/core/devices/nm-device-logging.h (renamed from src/devices/nm-device-logging.h)65
-rw-r--r--src/core/devices/nm-device-macsec.c (renamed from src/devices/nm-device-macsec.c)28
-rw-r--r--src/core/devices/nm-device-macsec.h (renamed from src/devices/nm-device-macsec.h)2
-rw-r--r--src/core/devices/nm-device-macvlan.c (renamed from src/devices/nm-device-macvlan.c)30
-rw-r--r--src/core/devices/nm-device-macvlan.h (renamed from src/devices/nm-device-macvlan.h)2
-rw-r--r--src/core/devices/nm-device-ppp.c (renamed from src/devices/nm-device-ppp.c)26
-rw-r--r--src/core/devices/nm-device-ppp.h (renamed from src/devices/nm-device-ppp.h)2
-rw-r--r--src/core/devices/nm-device-private.h (renamed from src/devices/nm-device-private.h)8
-rw-r--r--src/core/devices/nm-device-tun.c (renamed from src/devices/nm-device-tun.c)26
-rw-r--r--src/core/devices/nm-device-tun.h (renamed from src/devices/nm-device-tun.h)2
-rw-r--r--src/core/devices/nm-device-veth.c (renamed from src/devices/nm-device-veth.c)96
-rw-r--r--src/core/devices/nm-device-veth.h (renamed from src/devices/nm-device-veth.h)2
-rw-r--r--src/core/devices/nm-device-vlan.c (renamed from src/devices/nm-device-vlan.c)32
-rw-r--r--src/core/devices/nm-device-vlan.h (renamed from src/devices/nm-device-vlan.h)2
-rw-r--r--src/core/devices/nm-device-vrf.c (renamed from src/devices/nm-device-vrf.c)6
-rw-r--r--src/core/devices/nm-device-vrf.h (renamed from src/devices/nm-device-vrf.h)2
-rw-r--r--src/core/devices/nm-device-vxlan.c (renamed from src/devices/nm-device-vxlan.c)72
-rw-r--r--src/core/devices/nm-device-vxlan.h (renamed from src/devices/nm-device-vxlan.h)2
-rw-r--r--src/core/devices/nm-device-wireguard.c (renamed from src/devices/nm-device-wireguard.c)135
-rw-r--r--src/core/devices/nm-device-wireguard.h (renamed from src/devices/nm-device-wireguard.h)2
-rw-r--r--src/core/devices/nm-device-wpan.c (renamed from src/devices/nm-device-wpan.c)26
-rw-r--r--src/core/devices/nm-device-wpan.h (renamed from src/devices/nm-device-wpan.h)2
-rw-r--r--src/core/devices/nm-device.c (renamed from src/devices/nm-device.c)1524
-rw-r--r--src/core/devices/nm-device.h (renamed from src/devices/nm-device.h)36
-rw-r--r--src/core/devices/nm-lldp-listener.c (renamed from src/devices/nm-lldp-listener.c)10
-rw-r--r--src/core/devices/nm-lldp-listener.h (renamed from src/devices/nm-lldp-listener.h)2
-rw-r--r--src/core/devices/ovs/meson.build32
-rw-r--r--src/core/devices/ovs/nm-device-ovs-bridge.c (renamed from src/devices/ovs/nm-device-ovs-bridge.c)61
-rw-r--r--src/core/devices/ovs/nm-device-ovs-bridge.h (renamed from src/devices/ovs/nm-device-ovs-bridge.h)7
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.c (renamed from src/devices/ovs/nm-device-ovs-interface.c)87
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.h (renamed from src/devices/ovs/nm-device-ovs-interface.h)2
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.c (renamed from src/devices/ovs/nm-device-ovs-port.c)24
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.h (renamed from src/devices/ovs/nm-device-ovs-port.h)2
-rw-r--r--src/core/devices/ovs/nm-ovs-factory.c (renamed from src/devices/ovs/nm-ovs-factory.c)79
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c (renamed from src/devices/ovs/nm-ovsdb.c)1636
-rw-r--r--src/core/devices/ovs/nm-ovsdb.h (renamed from src/devices/ovs/nm-ovsdb.h)14
-rw-r--r--src/core/devices/team/meson.build (renamed from src/devices/team/meson.build)29
-rw-r--r--src/core/devices/team/nm-device-team.c (renamed from src/devices/team/nm-device-team.c)30
-rw-r--r--src/core/devices/team/nm-device-team.h (renamed from src/devices/team/nm-device-team.h)2
-rw-r--r--src/core/devices/team/nm-team-factory.c (renamed from src/devices/team/nm-team-factory.c)6
-rw-r--r--src/core/devices/tests/meson.build (renamed from src/devices/tests/meson.build)4
-rw-r--r--src/core/devices/tests/test-acd.c (renamed from src/devices/tests/test-acd.c)6
-rw-r--r--src/core/devices/tests/test-lldp.c (renamed from src/devices/tests/test-lldp.c)30
-rw-r--r--src/core/devices/wifi/meson.build (renamed from src/devices/wifi/meson.build)24
-rw-r--r--src/core/devices/wifi/nm-device-iwd.c (renamed from src/devices/wifi/nm-device-iwd.c)1472
-rw-r--r--src/core/devices/wifi/nm-device-iwd.h (renamed from src/devices/wifi/nm-device-iwd.h)4
-rw-r--r--src/core/devices/wifi/nm-device-olpc-mesh.c (renamed from src/devices/wifi/nm-device-olpc-mesh.c)26
-rw-r--r--src/core/devices/wifi/nm-device-olpc-mesh.h (renamed from src/devices/wifi/nm-device-olpc-mesh.h)2
-rw-r--r--src/core/devices/wifi/nm-device-wifi-p2p.c (renamed from src/devices/wifi/nm-device-wifi-p2p.c)6
-rw-r--r--src/core/devices/wifi/nm-device-wifi-p2p.h (renamed from src/devices/wifi/nm-device-wifi-p2p.h)2
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c (renamed from src/devices/wifi/nm-device-wifi.c)94
-rw-r--r--src/core/devices/wifi/nm-device-wifi.h (renamed from src/devices/wifi/nm-device-wifi.h)2
-rw-r--r--src/core/devices/wifi/nm-iwd-manager.c (renamed from src/devices/wifi/nm-iwd-manager.c)707
-rw-r--r--src/core/devices/wifi/nm-iwd-manager.h (renamed from src/devices/wifi/nm-iwd-manager.h)5
-rw-r--r--src/core/devices/wifi/nm-wifi-ap.c (renamed from src/devices/wifi/nm-wifi-ap.c)68
-rw-r--r--src/core/devices/wifi/nm-wifi-ap.h (renamed from src/devices/wifi/nm-wifi-ap.h)38
-rw-r--r--src/core/devices/wifi/nm-wifi-common.c (renamed from src/devices/wifi/nm-wifi-common.c)4
-rw-r--r--src/core/devices/wifi/nm-wifi-common.h (renamed from src/devices/wifi/nm-wifi-common.h)2
-rw-r--r--src/core/devices/wifi/nm-wifi-factory.c (renamed from src/devices/wifi/nm-wifi-factory.c)6
-rw-r--r--src/core/devices/wifi/nm-wifi-p2p-peer.c (renamed from src/devices/wifi/nm-wifi-p2p-peer.c)7
-rw-r--r--src/core/devices/wifi/nm-wifi-p2p-peer.h (renamed from src/devices/wifi/nm-wifi-p2p-peer.h)2
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.c (renamed from src/devices/wifi/nm-wifi-utils.c)68
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.h (renamed from src/devices/wifi/nm-wifi-utils.h)9
-rw-r--r--src/core/devices/wifi/tests/test-devices-wifi.c (renamed from src/devices/wifi/tests/test-devices-wifi.c)4
-rw-r--r--src/core/devices/wwan/libnm-wwan.ver (renamed from src/devices/wwan/libnm-wwan.ver)0
-rw-r--r--src/core/devices/wwan/meson.build (renamed from src/devices/wwan/meson.build)70
-rw-r--r--src/core/devices/wwan/nm-device-modem.c (renamed from src/devices/wwan/nm-device-modem.c)6
-rw-r--r--src/core/devices/wwan/nm-device-modem.h (renamed from src/devices/wwan/nm-device-modem.h)2
-rw-r--r--src/core/devices/wwan/nm-modem-broadband.c (renamed from src/devices/wwan/nm-modem-broadband.c)4
-rw-r--r--src/core/devices/wwan/nm-modem-broadband.h (renamed from src/devices/wwan/nm-modem-broadband.h)2
-rw-r--r--src/core/devices/wwan/nm-modem-manager.c (renamed from src/devices/wwan/nm-modem-manager.c)4
-rw-r--r--src/core/devices/wwan/nm-modem-manager.h (renamed from src/devices/wwan/nm-modem-manager.h)2
-rw-r--r--src/core/devices/wwan/nm-modem-ofono.c (renamed from src/devices/wwan/nm-modem-ofono.c)32
-rw-r--r--src/core/devices/wwan/nm-modem-ofono.h (renamed from src/devices/wwan/nm-modem-ofono.h)2
-rw-r--r--src/core/devices/wwan/nm-modem.c (renamed from src/devices/wwan/nm-modem.c)4
-rw-r--r--src/core/devices/wwan/nm-modem.h (renamed from src/devices/wwan/nm-modem.h)2
-rw-r--r--src/core/devices/wwan/nm-service-providers.c (renamed from src/devices/wwan/nm-service-providers.c)4
-rw-r--r--src/core/devices/wwan/nm-service-providers.h (renamed from src/devices/wwan/nm-service-providers.h)2
-rw-r--r--src/core/devices/wwan/nm-wwan-factory.c (renamed from src/devices/wwan/nm-wwan-factory.c)6
-rw-r--r--src/core/devices/wwan/tests/test-service-providers.c (renamed from src/devices/wwan/tests/test-service-providers.c)8
-rw-r--r--src/core/devices/wwan/tests/test-service-providers.xml (renamed from src/devices/wwan/tests/test-service-providers.xml)0
-rw-r--r--src/core/dhcp/meson.build18
-rw-r--r--src/core/dhcp/nm-dhcp-client-logging.h (renamed from src/dhcp/nm-dhcp-client-logging.h)2
-rw-r--r--src/core/dhcp/nm-dhcp-client.c (renamed from src/dhcp/nm-dhcp-client.c)55
-rw-r--r--src/core/dhcp/nm-dhcp-client.h (renamed from src/dhcp/nm-dhcp-client.h)5
-rw-r--r--src/core/dhcp/nm-dhcp-dhclient-utils.c (renamed from src/dhcp/nm-dhcp-dhclient-utils.c)5
-rw-r--r--src/core/dhcp/nm-dhcp-dhclient-utils.h (renamed from src/dhcp/nm-dhcp-dhclient-utils.h)2
-rw-r--r--src/core/dhcp/nm-dhcp-dhclient.c (renamed from src/dhcp/nm-dhcp-dhclient.c)10
-rw-r--r--src/core/dhcp/nm-dhcp-dhcpcanon.c (renamed from src/dhcp/nm-dhcp-dhcpcanon.c)4
-rw-r--r--src/core/dhcp/nm-dhcp-dhcpcd.c (renamed from src/dhcp/nm-dhcp-dhcpcd.c)86
-rw-r--r--src/core/dhcp/nm-dhcp-helper-api.h (renamed from src/dhcp/nm-dhcp-helper-api.h)2
-rw-r--r--src/core/dhcp/nm-dhcp-helper.c (renamed from src/dhcp/nm-dhcp-helper.c)4
-rw-r--r--src/core/dhcp/nm-dhcp-listener.c (renamed from src/dhcp/nm-dhcp-listener.c)4
-rw-r--r--src/core/dhcp/nm-dhcp-listener.h (renamed from src/dhcp/nm-dhcp-listener.h)2
-rw-r--r--src/core/dhcp/nm-dhcp-manager.c (renamed from src/dhcp/nm-dhcp-manager.c)4
-rw-r--r--src/core/dhcp/nm-dhcp-manager.h (renamed from src/dhcp/nm-dhcp-manager.h)2
-rw-r--r--src/core/dhcp/nm-dhcp-nettools.c (renamed from src/dhcp/nm-dhcp-nettools.c)912
-rw-r--r--src/core/dhcp/nm-dhcp-options.c (renamed from src/dhcp/nm-dhcp-options.c)272
-rw-r--r--src/core/dhcp/nm-dhcp-options.h (renamed from src/dhcp/nm-dhcp-options.h)55
-rw-r--r--src/core/dhcp/nm-dhcp-systemd.c (renamed from src/dhcp/nm-dhcp-systemd.c)134
-rw-r--r--src/core/dhcp/nm-dhcp-utils.c (renamed from src/dhcp/nm-dhcp-utils.c)289
-rw-r--r--src/core/dhcp/nm-dhcp-utils.h (renamed from src/dhcp/nm-dhcp-utils.h)33
-rw-r--r--src/core/dhcp/tests/meson.build (renamed from src/dhcp/tests/meson.build)4
-rw-r--r--src/core/dhcp/tests/test-dhclient-commented-duid.leases (renamed from src/dhcp/tests/test-dhclient-commented-duid.leases)0
-rw-r--r--src/core/dhcp/tests/test-dhclient-duid.leases (renamed from src/dhcp/tests/test-dhclient-duid.leases)0
-rw-r--r--src/core/dhcp/tests/test-dhcp-dhclient.c (renamed from src/dhcp/tests/test-dhcp-dhclient.c)6
-rw-r--r--src/core/dhcp/tests/test-dhcp-utils.c (renamed from src/dhcp/tests/test-dhcp-utils.c)57
-rw-r--r--src/core/dns/nm-dns-dnsmasq.c (renamed from src/dns/nm-dns-dnsmasq.c)8
-rw-r--r--src/core/dns/nm-dns-dnsmasq.h (renamed from src/dns/nm-dns-dnsmasq.h)2
-rw-r--r--src/core/dns/nm-dns-manager.c (renamed from src/dns/nm-dns-manager.c)277
-rw-r--r--src/core/dns/nm-dns-manager.h (renamed from src/dns/nm-dns-manager.h)9
-rw-r--r--src/core/dns/nm-dns-plugin.c (renamed from src/dns/nm-dns-plugin.c)4
-rw-r--r--src/core/dns/nm-dns-plugin.h (renamed from src/dns/nm-dns-plugin.h)4
-rw-r--r--src/core/dns/nm-dns-systemd-resolved.c (renamed from src/dns/nm-dns-systemd-resolved.c)10
-rw-r--r--src/core/dns/nm-dns-systemd-resolved.h (renamed from src/dns/nm-dns-systemd-resolved.h)2
-rw-r--r--src/core/dns/nm-dns-unbound.c (renamed from src/dns/nm-dns-unbound.c)4
-rw-r--r--src/core/dns/nm-dns-unbound.h (renamed from src/dns/nm-dns-unbound.h)2
-rw-r--r--src/core/dnsmasq/nm-dnsmasq-manager.c (renamed from src/dnsmasq/nm-dnsmasq-manager.c)6
-rw-r--r--src/core/dnsmasq/nm-dnsmasq-manager.h (renamed from src/dnsmasq/nm-dnsmasq-manager.h)2
-rw-r--r--src/core/dnsmasq/nm-dnsmasq-utils.c (renamed from src/dnsmasq/nm-dnsmasq-utils.c)4
-rw-r--r--src/core/dnsmasq/nm-dnsmasq-utils.h (renamed from src/dnsmasq/nm-dnsmasq-utils.h)2
-rw-r--r--src/core/dnsmasq/tests/meson.build (renamed from src/dnsmasq/tests/meson.build)4
-rw-r--r--src/core/dnsmasq/tests/test-dnsmasq-utils.c (renamed from src/dnsmasq/tests/test-dnsmasq-utils.c)4
-rw-r--r--src/core/initrd/meson.build31
-rw-r--r--src/core/initrd/nm-initrd-generator.c (renamed from src/initrd/nm-initrd-generator.c)78
-rw-r--r--src/core/initrd/nm-initrd-generator.h (renamed from src/initrd/nm-initrd-generator.h)8
-rw-r--r--src/core/initrd/nmi-cmdline-reader.c (renamed from src/initrd/nmi-cmdline-reader.c)75
-rw-r--r--src/core/initrd/nmi-dt-reader.c (renamed from src/initrd/nmi-dt-reader.c)5
-rw-r--r--src/core/initrd/nmi-ibft-reader.c (renamed from src/initrd/nmi-ibft-reader.c)5
-rw-r--r--src/core/initrd/tests/meson.build (renamed from src/initrd/tests/meson.build)8
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/class/net/eth0/address (renamed from src/initrd/tests/sysfs-bad-dns1/class/net/eth0/address)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name (renamed from src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/class/net/eth0/address (renamed from src/initrd/tests/sysfs-bad-dns2/class/net/eth0/address)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name (renamed from src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/class/net/eth0/address (renamed from src/initrd/tests/sysfs-bad-gateway/class/net/eth0/address)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name (renamed from src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address (renamed from src/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port)0
-rw-r--r--src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name (renamed from src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/class/net/eth0/address (renamed from src/initrd/tests/sysfs-dhcp/class/net/eth0/address)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/class/net/eth1/address (renamed from src/initrd/tests/sysfs-dhcp/class/net/eth1/address)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port)0
-rw-r--r--src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name (renamed from src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name)0
-rw-r--r--src/core/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath (renamed from src/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath)bin108 -> 108 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request)bin273 -> 273 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response)bin300 -> 300 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath)bin37 -> 37 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip)0
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip)0
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name)bin9 -> 9 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name)bin15 -> 15 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip)0
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name)bin7 -> 7 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip)bin4 -> 4 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path)bin1 -> 1 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip)0
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file)bin11 -> 11 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options)bin1 -> 1 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type)bin8 -> 8 bytes
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address)0
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address)0
-rw-r--r--src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name (renamed from src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name)bin9 -> 9 bytes
-rw-r--r--src/core/initrd/tests/sysfs-static/class/net/eth0/address (renamed from src/initrd/tests/sysfs-static/class/net/eth0/address)0
-rw-r--r--src/core/initrd/tests/sysfs-static/class/net/eth1/address (renamed from src/initrd/tests/sysfs-static/class/net/eth1/address)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan (renamed from src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name (renamed from src/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/target0/index (renamed from src/initrd/tests/sysfs-static/firmware/ibft/target0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr (renamed from src/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/target0/lun (renamed from src/initrd/tests/sysfs-static/firmware/ibft/target0/lun)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/target0/port (renamed from src/initrd/tests/sysfs-static/firmware/ibft/target0/port)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/target0/target-name (renamed from src/initrd/tests/sysfs-static/firmware/ibft/target0/target-name)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/target1/index (renamed from src/initrd/tests/sysfs-static/firmware/ibft/target1/index)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr (renamed from src/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/target1/lun (renamed from src/initrd/tests/sysfs-static/firmware/ibft/target1/lun)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/target1/port (renamed from src/initrd/tests/sysfs-static/firmware/ibft/target1/port)0
-rw-r--r--src/core/initrd/tests/sysfs-static/firmware/ibft/target1/target-name (renamed from src/initrd/tests/sysfs-static/firmware/ibft/target1/target-name)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/class/net/eth0/address (renamed from src/initrd/tests/sysfs-vlan/class/net/eth0/address)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/index (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/target0/index)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/port (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/target0/port)0
-rw-r--r--src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name (renamed from src/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name)0
-rw-r--r--src/core/initrd/tests/sysfs/class/net/eth0/address (renamed from src/initrd/tests/sysfs/class/net/eth0/address)0
-rw-r--r--src/core/initrd/tests/sysfs/class/net/eth2/address (renamed from src/initrd/tests/sysfs/class/net/eth2/address)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id (renamed from src/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id (renamed from src/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/signature (renamed from src/initrd/tests/sysfs/firmware/ibft/acpi_header/signature)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/flags (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/flags)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/index (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/index)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/mac (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/mac)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/origin (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/origin)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/flags (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/flags)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/index (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/index)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/mac (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/mac)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/origin (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/origin)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan (renamed from src/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/initiator/flags (renamed from src/initrd/tests/sysfs/firmware/ibft/initiator/flags)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/initiator/index (renamed from src/initrd/tests/sysfs/firmware/ibft/initiator/index)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name (renamed from src/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/initiator/isns-server (renamed from src/initrd/tests/sysfs/firmware/ibft/initiator/isns-server)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server (renamed from src/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server (renamed from src/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/initiator/slp-server (renamed from src/initrd/tests/sysfs/firmware/ibft/initiator/slp-server)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target0/chap-type (renamed from src/initrd/tests/sysfs/firmware/ibft/target0/chap-type)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target0/flags (renamed from src/initrd/tests/sysfs/firmware/ibft/target0/flags)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target0/index (renamed from src/initrd/tests/sysfs/firmware/ibft/target0/index)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target0/ip-addr (renamed from src/initrd/tests/sysfs/firmware/ibft/target0/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target0/lun (renamed from src/initrd/tests/sysfs/firmware/ibft/target0/lun)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc (renamed from src/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target0/port (renamed from src/initrd/tests/sysfs/firmware/ibft/target0/port)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target2/chap-type (renamed from src/initrd/tests/sysfs/firmware/ibft/target2/chap-type)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target2/flags (renamed from src/initrd/tests/sysfs/firmware/ibft/target2/flags)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target2/index (renamed from src/initrd/tests/sysfs/firmware/ibft/target2/index)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target2/ip-addr (renamed from src/initrd/tests/sysfs/firmware/ibft/target2/ip-addr)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target2/lun (renamed from src/initrd/tests/sysfs/firmware/ibft/target2/lun)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc (renamed from src/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target2/port (renamed from src/initrd/tests/sysfs/firmware/ibft/target2/port)0
-rw-r--r--src/core/initrd/tests/sysfs/firmware/ibft/target2/target-name (renamed from src/initrd/tests/sysfs/firmware/ibft/target2/target-name)0
-rw-r--r--src/core/initrd/tests/test-cmdline-reader.c (renamed from src/initrd/tests/test-cmdline-reader.c)670
-rw-r--r--src/core/initrd/tests/test-dt-reader.c (renamed from src/initrd/tests/test-dt-reader.c)9
-rw-r--r--src/core/initrd/tests/test-ibft-reader.c (renamed from src/initrd/tests/test-ibft-reader.c)9
-rw-r--r--src/core/main-utils.c (renamed from src/main-utils.c)4
-rw-r--r--src/core/main-utils.h (renamed from src/main-utils.h)2
-rw-r--r--src/core/main.c (renamed from src/main.c)9
-rw-r--r--src/core/meson.build333
-rw-r--r--src/core/ndisc/nm-fake-ndisc.c (renamed from src/ndisc/nm-fake-ndisc.c)144
-rw-r--r--src/core/ndisc/nm-fake-ndisc.h (renamed from src/ndisc/nm-fake-ndisc.h)16
-rw-r--r--src/core/ndisc/nm-lndp-ndisc.c (renamed from src/ndisc/nm-lndp-ndisc.c)348
-rw-r--r--src/core/ndisc/nm-lndp-ndisc.h (renamed from src/ndisc/nm-lndp-ndisc.h)2
-rw-r--r--src/core/ndisc/nm-ndisc-private.h (renamed from src/ndisc/nm-ndisc-private.h)15
-rw-r--r--src/core/ndisc/nm-ndisc.c (renamed from src/ndisc/nm-ndisc.c)902
-rw-r--r--src/core/ndisc/nm-ndisc.h (renamed from src/ndisc/nm-ndisc.h)117
-rw-r--r--src/core/ndisc/tests/meson.build (renamed from src/ndisc/tests/meson.build)6
-rw-r--r--src/core/ndisc/tests/test-ndisc-fake.c657
-rw-r--r--src/core/ndisc/tests/test-ndisc-linux.c (renamed from src/ndisc/tests/test-ndisc-linux.c)4
-rw-r--r--src/core/nm-act-request.c (renamed from src/nm-act-request.c)160
-rw-r--r--src/core/nm-act-request.h (renamed from src/nm-act-request.h)12
-rw-r--r--src/core/nm-active-connection.c (renamed from src/nm-active-connection.c)4
-rw-r--r--src/core/nm-active-connection.h (renamed from src/nm-active-connection.h)2
-rw-r--r--src/core/nm-audit-manager.c (renamed from src/nm-audit-manager.c)4
-rw-r--r--src/core/nm-audit-manager.h (renamed from src/nm-audit-manager.h)2
-rw-r--r--src/core/nm-auth-manager.c (renamed from src/nm-auth-manager.c)4
-rw-r--r--src/core/nm-auth-manager.h (renamed from src/nm-auth-manager.h)2
-rw-r--r--src/core/nm-auth-utils.c (renamed from src/nm-auth-utils.c)4
-rw-r--r--src/core/nm-auth-utils.h (renamed from src/nm-auth-utils.h)2
-rw-r--r--src/core/nm-checkpoint-manager.c (renamed from src/nm-checkpoint-manager.c)4
-rw-r--r--src/core/nm-checkpoint-manager.h (renamed from src/nm-checkpoint-manager.h)2
-rw-r--r--src/core/nm-checkpoint.c (renamed from src/nm-checkpoint.c)4
-rw-r--r--src/core/nm-checkpoint.h (renamed from src/nm-checkpoint.h)2
-rw-r--r--src/core/nm-config-data.c (renamed from src/nm-config-data.c)16
-rw-r--r--src/core/nm-config-data.h (renamed from src/nm-config-data.h)2
-rw-r--r--src/core/nm-config.c (renamed from src/nm-config.c)9
-rw-r--r--src/core/nm-config.h (renamed from src/nm-config.h)3
-rw-r--r--src/core/nm-connectivity.c (renamed from src/nm-connectivity.c)4
-rw-r--r--src/core/nm-connectivity.h (renamed from src/nm-connectivity.h)2
-rw-r--r--src/core/nm-core-utils.c (renamed from src/nm-core-utils.c)106
-rw-r--r--src/core/nm-core-utils.h (renamed from src/nm-core-utils.h)31
-rw-r--r--src/core/nm-dbus-manager.c (renamed from src/nm-dbus-manager.c)4
-rw-r--r--src/core/nm-dbus-manager.h (renamed from src/nm-dbus-manager.h)2
-rw-r--r--src/core/nm-dbus-object.c (renamed from src/nm-dbus-object.c)4
-rw-r--r--src/core/nm-dbus-object.h (renamed from src/nm-dbus-object.h)2
-rw-r--r--src/core/nm-dbus-utils.c (renamed from src/nm-dbus-utils.c)4
-rw-r--r--src/core/nm-dbus-utils.h (renamed from src/nm-dbus-utils.h)2
-rw-r--r--src/core/nm-dcb.c (renamed from src/nm-dcb.c)4
-rw-r--r--src/core/nm-dcb.h (renamed from src/nm-dcb.h)2
-rw-r--r--src/core/nm-default-daemon.h26
-rw-r--r--src/core/nm-dhcp-config.c (renamed from src/nm-dhcp-config.c)4
-rw-r--r--src/core/nm-dhcp-config.h (renamed from src/nm-dhcp-config.h)2
-rw-r--r--src/core/nm-dispatcher.c (renamed from src/nm-dispatcher.c)241
-rw-r--r--src/core/nm-dispatcher.h (renamed from src/nm-dispatcher.h)2
-rw-r--r--src/core/nm-firewall-manager.c (renamed from src/nm-firewall-manager.c)4
-rw-r--r--src/core/nm-firewall-manager.h (renamed from src/nm-firewall-manager.h)2
-rw-r--r--src/core/nm-hostname-manager.c (renamed from src/nm-hostname-manager.c)4
-rw-r--r--src/core/nm-hostname-manager.h (renamed from src/nm-hostname-manager.h)2
-rw-r--r--src/core/nm-iface-helper.c (renamed from src/nm-iface-helper.c)6
-rw-r--r--src/core/nm-ip-config.c (renamed from src/nm-ip-config.c)4
-rw-r--r--src/core/nm-ip-config.h (renamed from src/nm-ip-config.h)2
-rw-r--r--src/core/nm-ip4-config.c (renamed from src/nm-ip4-config.c)22
-rw-r--r--src/core/nm-ip4-config.h (renamed from src/nm-ip4-config.h)64
-rw-r--r--src/core/nm-ip6-config.c (renamed from src/nm-ip6-config.c)45
-rw-r--r--src/core/nm-ip6-config.h (renamed from src/nm-ip6-config.h)2
-rw-r--r--src/core/nm-keep-alive.c (renamed from src/nm-keep-alive.c)4
-rw-r--r--src/core/nm-keep-alive.h (renamed from src/nm-keep-alive.h)2
-rw-r--r--src/core/nm-l3-config-data.c (renamed from src/nm-l3-config-data.c)95
-rw-r--r--src/core/nm-l3-config-data.h (renamed from src/nm-l3-config-data.h)15
-rw-r--r--src/core/nm-l3-ipv4ll.c1059
-rw-r--r--src/core/nm-l3-ipv4ll.h118
-rw-r--r--src/core/nm-l3cfg.c (renamed from src/nm-l3cfg.c)2335
-rw-r--r--src/core/nm-l3cfg.h (renamed from src/nm-l3cfg.h)106
-rw-r--r--src/core/nm-manager.c (renamed from src/nm-manager.c)106
-rw-r--r--src/core/nm-manager.h (renamed from src/nm-manager.h)23
-rw-r--r--src/core/nm-netns.c (renamed from src/nm-netns.c)21
-rw-r--r--src/core/nm-netns.h (renamed from src/nm-netns.h)6
-rw-r--r--src/core/nm-pacrunner-manager.c (renamed from src/nm-pacrunner-manager.c)67
-rw-r--r--src/core/nm-pacrunner-manager.h (renamed from src/nm-pacrunner-manager.h)2
-rw-r--r--src/core/nm-policy.c (renamed from src/nm-policy.c)418
-rw-r--r--src/core/nm-policy.h (renamed from src/nm-policy.h)4
-rw-r--r--src/core/nm-proxy-config.c (renamed from src/nm-proxy-config.c)6
-rw-r--r--src/core/nm-proxy-config.h (renamed from src/nm-proxy-config.h)2
-rw-r--r--src/core/nm-rfkill-manager.c (renamed from src/nm-rfkill-manager.c)10
-rw-r--r--src/core/nm-rfkill-manager.h (renamed from src/nm-rfkill-manager.h)2
-rw-r--r--src/core/nm-session-monitor.c (renamed from src/nm-session-monitor.c)4
-rw-r--r--src/core/nm-session-monitor.h (renamed from src/nm-session-monitor.h)2
-rw-r--r--src/core/nm-sleep-monitor.c (renamed from src/nm-sleep-monitor.c)4
-rw-r--r--src/core/nm-sleep-monitor.h (renamed from src/nm-sleep-monitor.h)2
-rw-r--r--src/core/nm-test-utils-core.h (renamed from src/nm-test-utils-core.h)4
-rw-r--r--src/core/nm-types.h (renamed from src/nm-types.h)38
-rw-r--r--src/core/org.freedesktop.NetworkManager.conf (renamed from src/org.freedesktop.NetworkManager.conf)0
-rw-r--r--src/core/platform/linux/nl802154.h (renamed from src/platform/linux/nl802154.h)0
-rw-r--r--src/core/platform/nm-fake-platform.c (renamed from src/platform/nm-fake-platform.c)6
-rw-r--r--src/core/platform/nm-fake-platform.h (renamed from src/platform/nm-fake-platform.h)2
-rw-r--r--src/core/platform/nm-linux-platform.c (renamed from src/platform/nm-linux-platform.c)65
-rw-r--r--src/core/platform/nm-linux-platform.h (renamed from src/platform/nm-linux-platform.h)2
-rw-r--r--src/core/platform/nm-platform-private.h (renamed from src/platform/nm-platform-private.h)2
-rw-r--r--src/core/platform/nm-platform.c (renamed from src/platform/nm-platform.c)150
-rw-r--r--src/core/platform/nm-platform.h (renamed from src/platform/nm-platform.h)186
-rw-r--r--src/core/platform/nmp-object.c (renamed from src/platform/nmp-object.c)14
-rw-r--r--src/core/platform/nmp-object.h (renamed from src/platform/nmp-object.h)106
-rw-r--r--src/core/platform/nmp-rules-manager.c (renamed from src/platform/nmp-rules-manager.c)4
-rw-r--r--src/core/platform/nmp-rules-manager.h (renamed from src/platform/nmp-rules-manager.h)2
-rw-r--r--src/core/platform/tests/meson.build (renamed from src/platform/tests/meson.build)6
-rw-r--r--src/core/platform/tests/monitor.c (renamed from src/platform/tests/monitor.c)4
-rw-r--r--src/core/platform/tests/test-address.c (renamed from src/platform/tests/test-address.c)4
-rw-r--r--src/core/platform/tests/test-cleanup.c (renamed from src/platform/tests/test-cleanup.c)4
-rw-r--r--src/core/platform/tests/test-common.c (renamed from src/platform/tests/test-common.c)192
-rw-r--r--src/core/platform/tests/test-common.h (renamed from src/platform/tests/test-common.h)11
-rw-r--r--src/core/platform/tests/test-link.c (renamed from src/platform/tests/test-link.c)28
-rw-r--r--src/core/platform/tests/test-nmp-object.c (renamed from src/platform/tests/test-nmp-object.c)8
-rw-r--r--src/core/platform/tests/test-platform-general.c (renamed from src/platform/tests/test-platform-general.c)6
-rw-r--r--src/core/platform/tests/test-route.c (renamed from src/platform/tests/test-route.c)6
-rw-r--r--src/core/platform/tests/test-tc.c (renamed from src/platform/tests/test-tc.c)8
-rw-r--r--src/core/platform/wifi/nm-wifi-utils-nl80211.c (renamed from src/platform/wifi/nm-wifi-utils-nl80211.c)291
-rw-r--r--src/core/platform/wifi/nm-wifi-utils-nl80211.h (renamed from src/platform/wifi/nm-wifi-utils-nl80211.h)4
-rw-r--r--src/core/platform/wifi/nm-wifi-utils-private.h (renamed from src/platform/wifi/nm-wifi-utils-private.h)20
-rw-r--r--src/core/platform/wifi/nm-wifi-utils-wext.c (renamed from src/platform/wifi/nm-wifi-utils-wext.c)45
-rw-r--r--src/core/platform/wifi/nm-wifi-utils-wext.h (renamed from src/platform/wifi/nm-wifi-utils-wext.h)2
-rw-r--r--src/core/platform/wifi/nm-wifi-utils.c (renamed from src/platform/wifi/nm-wifi-utils.c)29
-rw-r--r--src/core/platform/wifi/nm-wifi-utils.h (renamed from src/platform/wifi/nm-wifi-utils.h)23
-rw-r--r--src/core/platform/wpan/nm-wpan-utils.c (renamed from src/platform/wpan/nm-wpan-utils.c)8
-rw-r--r--src/core/platform/wpan/nm-wpan-utils.h (renamed from src/platform/wpan/nm-wpan-utils.h)4
-rw-r--r--src/core/ppp/meson.build (renamed from src/ppp/meson.build)27
-rw-r--r--src/core/ppp/nm-ppp-manager-call.c (renamed from src/ppp/nm-ppp-manager-call.c)4
-rw-r--r--src/core/ppp/nm-ppp-manager-call.h (renamed from src/ppp/nm-ppp-manager-call.h)2
-rw-r--r--src/core/ppp/nm-ppp-manager.c (renamed from src/ppp/nm-ppp-manager.c)7
-rw-r--r--src/core/ppp/nm-ppp-manager.h (renamed from src/ppp/nm-ppp-manager.h)2
-rw-r--r--src/core/ppp/nm-ppp-plugin-api.h (renamed from src/ppp/nm-ppp-plugin-api.h)2
-rw-r--r--src/core/ppp/nm-ppp-plugin.ver (renamed from src/ppp/nm-ppp-plugin.ver)0
-rw-r--r--src/core/ppp/nm-ppp-status.h (renamed from src/ppp/nm-ppp-status.h)2
-rw-r--r--src/core/ppp/nm-pppd-plugin.c (renamed from src/ppp/nm-pppd-plugin.c)4
-rw-r--r--src/core/ppp/nm-pppd-plugin.h (renamed from src/ppp/nm-pppd-plugin.h)2
-rw-r--r--src/core/settings/nm-agent-manager.c (renamed from src/settings/nm-agent-manager.c)4
-rw-r--r--src/core/settings/nm-agent-manager.h (renamed from src/settings/nm-agent-manager.h)2
-rw-r--r--src/core/settings/nm-secret-agent.c (renamed from src/settings/nm-secret-agent.c)6
-rw-r--r--src/core/settings/nm-secret-agent.h (renamed from src/settings/nm-secret-agent.h)2
-rw-r--r--src/core/settings/nm-settings-connection.c (renamed from src/settings/nm-settings-connection.c)4
-rw-r--r--src/core/settings/nm-settings-connection.h (renamed from src/settings/nm-settings-connection.h)6
-rw-r--r--src/core/settings/nm-settings-plugin.c (renamed from src/settings/nm-settings-plugin.c)4
-rw-r--r--src/core/settings/nm-settings-plugin.h (renamed from src/settings/nm-settings-plugin.h)2
-rw-r--r--src/core/settings/nm-settings-storage.c (renamed from src/settings/nm-settings-storage.c)4
-rw-r--r--src/core/settings/nm-settings-storage.h (renamed from src/settings/nm-settings-storage.h)2
-rw-r--r--src/core/settings/nm-settings-utils.c (renamed from src/settings/nm-settings-utils.c)4
-rw-r--r--src/core/settings/nm-settings-utils.h (renamed from src/settings/nm-settings-utils.h)2
-rw-r--r--src/core/settings/nm-settings.c (renamed from src/settings/nm-settings.c)14
-rw-r--r--src/core/settings/nm-settings.h (renamed from src/settings/nm-settings.h)2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/meson.build (renamed from src/settings/plugins/ifcfg-rh/meson.build)46
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf (renamed from src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml (renamed from src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nm-ifdown (renamed from src/settings/plugins/ifcfg-rh/nm-ifdown)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nm-ifup (renamed from src/settings/plugins/ifcfg-rh/nm-ifup)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h (renamed from src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h)2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c (renamed from src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c)4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h (renamed from src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h)2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c (renamed from src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c)136
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h (renamed from src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h)2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c (renamed from src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c)4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h (renamed from src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h)2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c (renamed from src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c)21
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h (renamed from src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h)6
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c (renamed from src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c)34
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h (renamed from src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h)2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/shvar.c (renamed from src/settings/plugins/ifcfg-rh/shvar.c)4
-rw-r--r--src/core/settings/plugins/ifcfg-rh/shvar.h (renamed from src/settings/plugins/ifcfg-rh/shvar.h)2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/meson.build16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls22
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem)0
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 (renamed from src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12)bin2848 -> 2848 bytes
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c (renamed from src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c)44
-rw-r--r--src/core/settings/plugins/ifupdown/meson.build (renamed from src/settings/plugins/ifupdown/meson.build)16
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c (renamed from src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c)4
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h (renamed from src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h)2
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c (renamed from src/settings/plugins/ifupdown/nms-ifupdown-parser.c)4
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h (renamed from src/settings/plugins/ifupdown/nms-ifupdown-parser.h)2
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c (renamed from src/settings/plugins/ifupdown/nms-ifupdown-plugin.c)4
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h (renamed from src/settings/plugins/ifupdown/nms-ifupdown-plugin.h)2
-rw-r--r--src/core/settings/plugins/ifupdown/tests/meson.build15
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test-ifupdown.c (renamed from src/settings/plugins/ifupdown/tests/test-ifupdown.c)6
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test1 (renamed from src/settings/plugins/ifupdown/tests/test1)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test11 (renamed from src/settings/plugins/ifupdown/tests/test11)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test12 (renamed from src/settings/plugins/ifupdown/tests/test12)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test13 (renamed from src/settings/plugins/ifupdown/tests/test13)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test14 (renamed from src/settings/plugins/ifupdown/tests/test14)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test15 (renamed from src/settings/plugins/ifupdown/tests/test15)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test16 (renamed from src/settings/plugins/ifupdown/tests/test16)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4 (renamed from src/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6 (renamed from src/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen (renamed from src/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test2 (renamed from src/settings/plugins/ifupdown/tests/test2)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test20-source-stanza (renamed from src/settings/plugins/ifupdown/tests/test20-source-stanza)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth0 (renamed from src/settings/plugins/ifupdown/tests/test20-source-stanza.eth0)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth1 (renamed from src/settings/plugins/ifupdown/tests/test20-source-stanza.eth1)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza (renamed from src/settings/plugins/ifupdown/tests/test21-source-dir-stanza)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0 (renamed from src/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas (renamed from src/settings/plugins/ifupdown/tests/test22-duplicate-stanzas)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test3 (renamed from src/settings/plugins/ifupdown/tests/test3)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test4 (renamed from src/settings/plugins/ifupdown/tests/test4)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test5 (renamed from src/settings/plugins/ifupdown/tests/test5)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test6 (renamed from src/settings/plugins/ifupdown/tests/test6)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test7 (renamed from src/settings/plugins/ifupdown/tests/test7)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test8 (renamed from src/settings/plugins/ifupdown/tests/test8)0
-rw-r--r--src/core/settings/plugins/ifupdown/tests/test9 (renamed from src/settings/plugins/ifupdown/tests/test9)0
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-plugin.c (renamed from src/settings/plugins/keyfile/nms-keyfile-plugin.c)6
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-plugin.h (renamed from src/settings/plugins/keyfile/nms-keyfile-plugin.h)2
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-reader.c (renamed from src/settings/plugins/keyfile/nms-keyfile-reader.c)6
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-reader.h (renamed from src/settings/plugins/keyfile/nms-keyfile-reader.h)2
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-storage.c (renamed from src/settings/plugins/keyfile/nms-keyfile-storage.c)4
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-storage.h (renamed from src/settings/plugins/keyfile/nms-keyfile-storage.h)2
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-utils.c (renamed from src/settings/plugins/keyfile/nms-keyfile-utils.c)6
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-utils.h (renamed from src/settings/plugins/keyfile/nms-keyfile-utils.h)2
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-writer.c (renamed from src/settings/plugins/keyfile/nms-keyfile-writer.c)16
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-writer.h (renamed from src/settings/plugins/keyfile/nms-keyfile-writer.h)2
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT (renamed from src/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain (renamed from src/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2 (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6 (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1 (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2 (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1 (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2 (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3 (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4 (renamed from src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem (renamed from src/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem (renamed from src/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem)0
-rw-r--r--src/core/settings/plugins/keyfile/tests/meson.build15
-rw-r--r--src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c (renamed from src/settings/plugins/keyfile/tests/test-keyfile-settings.c)10
-rw-r--r--src/core/settings/plugins/meson.build (renamed from src/settings/plugins/meson.build)2
-rw-r--r--src/core/supplicant/nm-supplicant-config.c (renamed from src/supplicant/nm-supplicant-config.c)53
-rw-r--r--src/core/supplicant/nm-supplicant-config.h (renamed from src/supplicant/nm-supplicant-config.h)2
-rw-r--r--src/core/supplicant/nm-supplicant-interface.c (renamed from src/supplicant/nm-supplicant-interface.c)71
-rw-r--r--src/core/supplicant/nm-supplicant-interface.h (renamed from src/supplicant/nm-supplicant-interface.h)4
-rw-r--r--src/core/supplicant/nm-supplicant-manager.c (renamed from src/supplicant/nm-supplicant-manager.c)9
-rw-r--r--src/core/supplicant/nm-supplicant-manager.h (renamed from src/supplicant/nm-supplicant-manager.h)2
-rw-r--r--src/core/supplicant/nm-supplicant-settings-verify.c (renamed from src/supplicant/nm-supplicant-settings-verify.c)9
-rw-r--r--src/core/supplicant/nm-supplicant-settings-verify.h (renamed from src/supplicant/nm-supplicant-settings-verify.h)2
-rw-r--r--src/core/supplicant/nm-supplicant-types.h (renamed from src/supplicant/nm-supplicant-types.h)5
-rw-r--r--src/core/supplicant/tests/certs/test-ca-cert.pem (renamed from src/supplicant/tests/certs/test-ca-cert.pem)0
-rw-r--r--src/core/supplicant/tests/certs/test-cert.p12 (renamed from src/supplicant/tests/certs/test-cert.p12)bin4092 -> 4092 bytes
-rw-r--r--src/core/supplicant/tests/meson.build (renamed from src/supplicant/tests/meson.build)4
-rw-r--r--src/core/supplicant/tests/test-supplicant-config.c (renamed from src/supplicant/tests/test-supplicant-config.c)92
-rw-r--r--src/core/systemd/meson.build62
-rw-r--r--src/core/systemd/nm-default-systemd.h18
-rw-r--r--src/core/systemd/nm-sd-utils-core.c (renamed from src/systemd/nm-sd-utils-core.c)4
-rw-r--r--src/core/systemd/nm-sd-utils-core.h (renamed from src/systemd/nm-sd-utils-core.h)2
-rw-r--r--src/core/systemd/nm-sd-utils-dhcp.c (renamed from src/systemd/nm-sd-utils-dhcp.c)4
-rw-r--r--src/core/systemd/nm-sd-utils-dhcp.h (renamed from src/systemd/nm-sd-utils-dhcp.h)2
-rw-r--r--src/core/systemd/nm-sd.c (renamed from src/systemd/nm-sd.c)4
-rw-r--r--src/core/systemd/nm-sd.h (renamed from src/systemd/nm-sd.h)2
-rw-r--r--src/core/systemd/sd-adapt-core/condition.h (renamed from src/systemd/sd-adapt-core/condition.h)0
-rw-r--r--src/core/systemd/sd-adapt-core/conf-parser.h (renamed from src/systemd/sd-adapt-core/conf-parser.h)0
-rw-r--r--src/core/systemd/sd-adapt-core/device-util.h (renamed from src/systemd/sd-adapt-core/device-util.h)0
-rw-r--r--src/core/systemd/sd-adapt-core/khash.h (renamed from src/systemd/sd-adapt-core/khash.h)0
-rw-r--r--src/core/systemd/sd-adapt-core/network-util.h (renamed from src/systemd/sd-adapt-core/sd-daemon.h)0
-rw-r--r--src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c (renamed from src/systemd/sd-adapt-core/nm-sd-adapt-core.c)4
-rw-r--r--src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h (renamed from src/systemd/sd-adapt-core/nm-sd-adapt-core.h)7
-rw-r--r--src/core/systemd/sd-adapt-core/sd-daemon.h (renamed from src/systemd/sd-adapt-core/sd-device.h)0
-rw-r--r--src/core/systemd/sd-adapt-core/sd-device.h (renamed from src/systemd/sd-adapt-core/udev-util.h)0
-rw-r--r--src/core/systemd/sd-adapt-core/udev-util.h3
-rw-r--r--src/core/systemd/src/libsystemd-network/arp-util.c (renamed from src/systemd/src/libsystemd-network/arp-util.c)2
-rw-r--r--src/core/systemd/src/libsystemd-network/arp-util.h (renamed from src/systemd/src/libsystemd-network/arp-util.h)2
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-identifier.c (renamed from src/systemd/src/libsystemd-network/dhcp-identifier.c)8
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-identifier.h (renamed from src/systemd/src/libsystemd-network/dhcp-identifier.h)2
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-internal.h (renamed from src/systemd/src/libsystemd-network/dhcp-internal.h)10
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h (renamed from src/systemd/src/libsystemd-network/dhcp-lease-internal.h)2
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-network.c (renamed from src/systemd/src/libsystemd-network/dhcp-network.c)42
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-option.c (renamed from src/systemd/src/libsystemd-network/dhcp-option.c)2
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-packet.c (renamed from src/systemd/src/libsystemd-network/dhcp-packet.c)2
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp-protocol.h (renamed from src/systemd/src/libsystemd-network/dhcp-protocol.h)2
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp6-internal.h (renamed from src/systemd/src/libsystemd-network/dhcp6-internal.h)2
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h (renamed from src/systemd/src/libsystemd-network/dhcp6-lease-internal.h)2
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp6-network.c (renamed from src/systemd/src/libsystemd-network/dhcp6-network.c)2
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp6-option.c (renamed from src/systemd/src/libsystemd-network/dhcp6-option.c)2
-rw-r--r--src/core/systemd/src/libsystemd-network/dhcp6-protocol.h (renamed from src/systemd/src/libsystemd-network/dhcp6-protocol.h)2
-rw-r--r--src/core/systemd/src/libsystemd-network/lldp-internal.h (renamed from src/systemd/src/libsystemd-network/lldp-internal.h)2
-rw-r--r--src/core/systemd/src/libsystemd-network/lldp-neighbor.c (renamed from src/systemd/src/libsystemd-network/lldp-neighbor.c)2
-rw-r--r--src/core/systemd/src/libsystemd-network/lldp-neighbor.h (renamed from src/systemd/src/libsystemd-network/lldp-neighbor.h)2
-rw-r--r--src/core/systemd/src/libsystemd-network/lldp-network.c (renamed from src/systemd/src/libsystemd-network/lldp-network.c)2
-rw-r--r--src/core/systemd/src/libsystemd-network/lldp-network.h (renamed from src/systemd/src/libsystemd-network/lldp-network.h)2
-rw-r--r--src/core/systemd/src/libsystemd-network/network-internal.c245
-rw-r--r--src/core/systemd/src/libsystemd-network/network-internal.h31
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-dhcp-client.c (renamed from src/systemd/src/libsystemd-network/sd-dhcp-client.c)308
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c (renamed from src/systemd/src/libsystemd-network/sd-dhcp-lease.c)8
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c (renamed from src/systemd/src/libsystemd-network/sd-dhcp6-client.c)29
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c (renamed from src/systemd/src/libsystemd-network/sd-dhcp6-lease.c)2
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-ipv4acd.c (renamed from src/systemd/src/libsystemd-network/sd-ipv4acd.c)51
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-ipv4ll.c (renamed from src/systemd/src/libsystemd-network/sd-ipv4ll.c)27
-rw-r--r--src/core/systemd/src/libsystemd-network/sd-lldp.c (renamed from src/systemd/src/libsystemd-network/sd-lldp.c)5
-rw-r--r--src/core/systemd/src/libsystemd/sd-event/event-source.h (renamed from src/systemd/src/libsystemd/sd-event/event-source.h)14
-rw-r--r--src/core/systemd/src/libsystemd/sd-event/event-util.c (renamed from src/systemd/src/libsystemd/sd-event/event-util.c)2
-rw-r--r--src/core/systemd/src/libsystemd/sd-event/event-util.h (renamed from src/systemd/src/libsystemd/sd-event/event-util.h)2
-rw-r--r--src/core/systemd/src/libsystemd/sd-event/sd-event.c (renamed from src/systemd/src/libsystemd/sd-event/sd-event.c)991
-rw-r--r--src/core/systemd/src/libsystemd/sd-id128/id128-util.c (renamed from src/systemd/src/libsystemd/sd-id128/id128-util.c)10
-rw-r--r--src/core/systemd/src/libsystemd/sd-id128/id128-util.h (renamed from src/systemd/src/libsystemd/sd-id128/id128-util.h)6
-rw-r--r--src/core/systemd/src/libsystemd/sd-id128/sd-id128.c (renamed from src/systemd/src/libsystemd/sd-id128/sd-id128.c)2
-rw-r--r--src/core/systemd/src/systemd/_sd-common.h (renamed from src/systemd/src/systemd/_sd-common.h)2
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp-client.h (renamed from src/systemd/src/systemd/sd-dhcp-client.h)5
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp-lease.h (renamed from src/systemd/src/systemd/sd-dhcp-lease.h)2
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp-option.h (renamed from src/systemd/src/systemd/sd-dhcp-option.h)2
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp6-client.h (renamed from src/systemd/src/systemd/sd-dhcp6-client.h)2
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp6-lease.h (renamed from src/systemd/src/systemd/sd-dhcp6-lease.h)2
-rw-r--r--src/core/systemd/src/systemd/sd-dhcp6-option.h (renamed from src/systemd/src/systemd/sd-dhcp6-option.h)2
-rw-r--r--src/core/systemd/src/systemd/sd-event.h (renamed from src/systemd/src/systemd/sd-event.h)7
-rw-r--r--src/core/systemd/src/systemd/sd-id128.h (renamed from src/systemd/src/systemd/sd-id128.h)2
-rw-r--r--src/core/systemd/src/systemd/sd-ipv4acd.h (renamed from src/systemd/src/systemd/sd-ipv4acd.h)4
-rw-r--r--src/core/systemd/src/systemd/sd-ipv4ll.h (renamed from src/systemd/src/systemd/sd-ipv4ll.h)4
-rw-r--r--src/core/systemd/src/systemd/sd-lldp.h (renamed from src/systemd/src/systemd/sd-lldp.h)2
-rw-r--r--src/core/systemd/src/systemd/sd-ndisc.h (renamed from src/systemd/src/systemd/sd-ndisc.h)2
-rw-r--r--src/core/tests/config/NetworkManager-warn.conf (renamed from src/tests/config/NetworkManager-warn.conf)0
-rw-r--r--src/core/tests/config/NetworkManager.conf (renamed from src/tests/config/NetworkManager.conf)0
-rw-r--r--src/core/tests/config/NetworkManager.state (renamed from src/tests/config/NetworkManager.state)0
-rw-r--r--src/core/tests/config/bad.conf (renamed from src/tests/config/bad.conf)0
-rw-r--r--src/core/tests/config/conf.d/00-overrides.conf (renamed from src/tests/config/conf.d/00-overrides.conf)0
-rw-r--r--src/core/tests/config/conf.d/10-more.conf (renamed from src/tests/config/conf.d/10-more.conf)0
-rw-r--r--src/core/tests/config/conf.d/20-config-enable-1.conf (renamed from src/tests/config/conf.d/20-config-enable-1.conf)0
-rw-r--r--src/core/tests/config/conf.d/90-last.conf (renamed from src/tests/config/conf.d/90-last.conf)0
-rw-r--r--src/core/tests/config/global-dns-invalid.conf (renamed from src/tests/config/global-dns-invalid.conf)0
-rw-r--r--src/core/tests/config/meson.build (renamed from src/tests/config/meson.build)4
-rw-r--r--src/core/tests/config/nm-test-device.c (renamed from src/tests/config/nm-test-device.c)4
-rw-r--r--src/core/tests/config/nm-test-device.h (renamed from src/tests/config/nm-test-device.h)2
-rw-r--r--src/core/tests/config/test-config.c (renamed from src/tests/config/test-config.c)8
-rw-r--r--src/core/tests/meson.build (renamed from src/tests/meson.build)36
-rw-r--r--src/core/tests/test-core-with-expect.c (renamed from src/tests/test-core-with-expect.c)45
-rw-r--r--src/core/tests/test-core.c (renamed from src/tests/test-core.c)75
-rw-r--r--src/core/tests/test-dcb.c (renamed from src/tests/test-dcb.c)4
-rw-r--r--src/core/tests/test-ip4-config.c (renamed from src/tests/test-ip4-config.c)4
-rw-r--r--src/core/tests/test-ip6-config.c (renamed from src/tests/test-ip6-config.c)4
-rw-r--r--src/core/tests/test-l3cfg.c795
-rwxr-xr-xsrc/core/tests/test-secret-agent.py (renamed from src/tests/test-secret-agent.py)0
-rw-r--r--src/core/tests/test-systemd.c (renamed from src/tests/test-systemd.c)4
-rw-r--r--src/core/tests/test-utils.c (renamed from src/tests/test-utils.c)5
-rw-r--r--src/core/tests/test-wired-defname.c (renamed from src/tests/test-wired-defname.c)4
-rw-r--r--src/core/vpn/nm-vpn-connection.c (renamed from src/vpn/nm-vpn-connection.c)36
-rw-r--r--src/core/vpn/nm-vpn-connection.h (renamed from src/vpn/nm-vpn-connection.h)2
-rw-r--r--src/core/vpn/nm-vpn-manager.c (renamed from src/vpn/nm-vpn-manager.c)4
-rw-r--r--src/core/vpn/nm-vpn-manager.h (renamed from src/vpn/nm-vpn-manager.h)2
-rw-r--r--src/devices/bluetooth/meson.build60
-rw-r--r--src/devices/ovs/meson.build33
-rw-r--r--src/dhcp/meson.build23
-rw-r--r--src/initrd/meson.build37
-rw-r--r--src/meson.build318
-rw-r--r--src/ndisc/tests/test-ndisc-fake.c538
-rw-r--r--src/nm-logging.c1036
-rw-r--r--src/nm-logging.h201
-rw-r--r--src/platform/nm-netlink.c1518
-rw-r--r--src/platform/nm-netlink.h616
-rw-r--r--src/platform/nm-platform-utils.c1785
-rw-r--r--src/platform/nm-platform-utils.h140
-rw-r--r--src/platform/nmp-netns.c764
-rw-r--r--src/platform/nmp-netns.h53
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/meson.build18
-rw-r--r--src/settings/plugins/ifupdown/tests/meson.build17
-rw-r--r--src/settings/plugins/keyfile/tests/meson.build17
-rw-r--r--src/systemd/meson.build50
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.c890
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.h74
-rw-r--r--src/tests/test-l3cfg.c362
972 files changed, 16066 insertions, 16356 deletions
diff --git a/src/NetworkManagerUtils.c b/src/core/NetworkManagerUtils.c
index 4739e9dd..54072210 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/core/NetworkManagerUtils.c
@@ -1,15 +1,16 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2016 Red Hat, Inc.
  * Copyright (C) 2005 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "NetworkManagerUtils.h"
 
 #include <linux/fib_rules.h>
 #include <linux/pkt_sched.h>
+#include <linux/if_ether.h>
 
 #include "nm-glib-aux/nm-c-list.h"
 
@@ -152,7 +153,7 @@ nm_utils_get_ip_config_method(NMConnection *connection, int addr_family)
 
     s_con = nm_connection_get_setting_connection(connection);
 
-    if (addr_family == AF_INET) {
+    if (NM_IS_IPv4(addr_family)) {
         g_return_val_if_fail(s_con != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
         s_ip = nm_connection_get_setting_ip4_config(connection);
@@ -164,19 +165,15 @@ nm_utils_get_ip_config_method(NMConnection *connection, int addr_family)
         return method;
     }
 
-    if (addr_family == AF_INET6) {
-        g_return_val_if_fail(s_con != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+    g_return_val_if_fail(s_con != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
 
-        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_ip);
-        g_return_val_if_fail(method != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
-        return method;
-    }
+    s_ip = nm_connection_get_setting_ip6_config(connection);
+    if (!s_ip)
+        return NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
 
-    g_return_val_if_reached("" /* bogus */);
+    method = nm_setting_ip_config_get_method(s_ip);
+    g_return_val_if_fail(method != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+    return method;
 }
 
 gboolean
@@ -204,7 +201,7 @@ nm_utils_connection_has_default_route(NMConnection *connection,
     }
 
     method = nm_utils_get_ip_config_method(connection, addr_family);
-    if (addr_family == AF_INET) {
+    if (NM_IS_IPv4(addr_family)) {
         if (NM_IN_STRSET(method,
                          NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
                          NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
@@ -1312,7 +1309,7 @@ nm_utils_ip_route_attribute_to_platform(int                addr_family,
     else
         r->table_coerced = nm_platform_route_table_coerce(table ?: (route_table ?: RT_TABLE_MAIN));
 
-    if (addr_family == AF_INET) {
+    if (NM_IS_IPv4(addr_family)) {
         guint8 scope;
 
         GET_ATTR(NM_IP_ROUTE_ATTRIBUTE_TOS, r4->tos, BYTE, byte, 0);
@@ -1338,14 +1335,14 @@ nm_utils_ip_route_attribute_to_platform(int                addr_family,
     if ((variant = nm_ip_route_get_attribute(s_route, NM_IP_ROUTE_ATTRIBUTE_SRC))
         && g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)) {
         if (inet_pton(addr_family, g_variant_get_string(variant, NULL), &addr) == 1) {
-            if (addr_family == AF_INET)
+            if (NM_IS_IPv4(addr_family))
                 r4->pref_src = addr.addr4;
             else
                 r6->pref_src = addr.addr6;
         }
     }
 
-    if (addr_family == AF_INET6
+    if (!NM_IS_IPv4(addr_family)
         && (variant = nm_ip_route_get_attribute(s_route, NM_IP_ROUTE_ATTRIBUTE_FROM))
         && g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)) {
         int prefix;
@@ -1392,7 +1389,7 @@ nm_utils_ip_addresses_to_dbus(int                          addr_family,
                               GVariant **                  out_address_data,
                               GVariant **                  out_addresses)
 {
-    const gboolean  IS_IPv4 = NM_IS_IPv4(addr_family);
+    const int       IS_IPv4 = NM_IS_IPv4(addr_family);
     GVariantBuilder builder_data;
     GVariantBuilder builder_legacy;
     char            addr_str[NM_UTILS_INET_ADDRSTRLEN];
@@ -1495,15 +1492,12 @@ nm_utils_ip_addresses_to_dbus(int                          addr_family,
                 g_variant_builder_add(
                     &builder_legacy,
                     "(@ayu@ay)",
-                    g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, &address->a6.address, 16, 1),
+                    nm_g_variant_new_ay_in6addr(&address->a6.address),
                     address->a6.plen,
-                    g_variant_new_fixed_array(
-                        G_VARIANT_TYPE_BYTE,
+                    nm_g_variant_new_ay_in6addr(
                         (i == 0 && best_default_route)
                             ? &NMP_OBJECT_CAST_IP6_ROUTE(best_default_route)->gateway
-                            : &in6addr_any,
-                        16,
-                        1));
+                            : &in6addr_any));
             }
         }
     }
@@ -1519,7 +1513,7 @@ nm_utils_ip_routes_to_dbus(int                          addr_family,
                            GVariant **                  out_route_data,
                            GVariant **                  out_routes)
 {
-    const gboolean   IS_IPv4 = NM_IS_IPv4(addr_family);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
     NMDedupMultiIter iter;
     const NMPObject *obj;
     GVariantBuilder  builder_data;
@@ -1622,13 +1616,12 @@ nm_utils_ip_routes_to_dbus(int                          addr_family,
                                                                 4,
                                                                 sizeof(guint32)));
             } else {
-                g_variant_builder_add(
-                    &builder_legacy,
-                    "(@ayu@ayu)",
-                    g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, &r->r6.network, 16, 1),
-                    (guint32) r->r6.plen,
-                    g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, &r->r6.gateway, 16, 1),
-                    (guint32) r->r6.metric);
+                g_variant_builder_add(&builder_legacy,
+                                      "(@ayu@ayu)",
+                                      nm_g_variant_new_ay_in6addr(&r->r6.network),
+                                      (guint32) r->r6.plen,
+                                      nm_g_variant_new_ay_in6addr(&r->r6.gateway),
+                                      (guint32) r->r6.metric);
             }
         }
     }
@@ -1636,3 +1629,203 @@ nm_utils_ip_routes_to_dbus(int                          addr_family,
     NM_SET_OUT(out_route_data, g_variant_builder_end(&builder_data));
     NM_SET_OUT(out_routes, g_variant_builder_end(&builder_legacy));
 }
+
+/*****************************************************************************/
+
+typedef struct {
+    char *table;
+    char *rule;
+} ShareRule;
+
+struct _NMUtilsShareRules {
+    GArray *rules;
+};
+
+static void
+_share_rule_clear(gpointer data)
+{
+    ShareRule *rule = data;
+
+    g_free(rule->table);
+    g_free(rule->rule);
+}
+
+NMUtilsShareRules *
+nm_utils_share_rules_new(void)
+{
+    NMUtilsShareRules *self;
+
+    self  = g_slice_new(NMUtilsShareRules);
+    *self = (NMUtilsShareRules){
+        .rules = g_array_sized_new(FALSE, FALSE, sizeof(ShareRule), 10),
+    };
+
+    g_array_set_clear_func(self->rules, _share_rule_clear);
+    return self;
+}
+
+void
+nm_utils_share_rules_free(NMUtilsShareRules *self)
+{
+    if (!self)
+        return;
+
+    g_array_unref(self->rules);
+    nm_g_slice_free(self);
+}
+
+void
+nm_utils_share_rules_add_rule_take(NMUtilsShareRules *self, const char *table, char *rule_take)
+{
+    ShareRule *rule;
+
+    g_return_if_fail(self);
+    g_return_if_fail(table);
+    g_return_if_fail(rule_take);
+
+    rule  = nm_g_array_append_new(self->rules, ShareRule);
+    *rule = (ShareRule){
+        .table = g_strdup(table),
+        .rule  = g_steal_pointer(&rule_take),
+    };
+}
+
+void
+nm_utils_share_rules_apply(NMUtilsShareRules *self, gboolean shared)
+{
+    guint i;
+
+    g_return_if_fail(self);
+
+    if (self->rules->len == 0)
+        return;
+
+    /* depending on whether we share or unshare, we add/remote the rules
+     * in opposite order. */
+    if (shared)
+        i = self->rules->len - 1;
+    else
+        i = 0;
+
+    for (;;) {
+        gs_free_error GError *error = NULL;
+        ShareRule *           rule;
+        gs_free const char ** argv = NULL;
+        gs_free char *        cmd  = NULL;
+        int                   status;
+
+        rule = &g_array_index(self->rules, ShareRule, i);
+
+        cmd  = g_strdup_printf("%s --table %s %s %s",
+                              IPTABLES_PATH,
+                              rule->table,
+                              shared ? "--insert" : "--delete",
+                              rule->rule);
+        argv = nm_utils_strsplit_set(cmd, " ");
+
+        nm_log_info(LOGD_SHARING, "Executing: %s", cmd);
+        if (!g_spawn_sync("/",
+                          (char **) argv,
+                          (char **) NM_PTRARRAY_EMPTY(const char *),
+                          G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
+                          NULL,
+                          NULL,
+                          NULL,
+                          NULL,
+                          &status,
+                          &error)) {
+            nm_log_warn(LOGD_SHARING, "Error executing command: %s", error->message);
+            goto next;
+        }
+        if (WEXITSTATUS(status)) {
+            nm_log_warn(LOGD_SHARING, "** Command returned exit status %d.", WEXITSTATUS(status));
+        }
+
+next:
+        if (shared) {
+            if (i == 0)
+                break;
+            i--;
+        } else {
+            i++;
+            if (i >= self->rules->len)
+                break;
+        }
+    }
+}
+
+void
+nm_utils_share_rules_add_all_rules(NMUtilsShareRules *self,
+                                   const char *       ip_iface,
+                                   in_addr_t          addr,
+                                   guint              plen)
+{
+    in_addr_t netmask;
+    in_addr_t network;
+    char      str_mask[NM_UTILS_INET_ADDRSTRLEN];
+    char      str_addr[NM_UTILS_INET_ADDRSTRLEN];
+
+    nm_assert(self);
+
+    netmask = _nm_utils_ip4_prefix_to_netmask(plen);
+    _nm_utils_inet4_ntop(netmask, str_mask);
+
+    network = addr & netmask;
+    _nm_utils_inet4_ntop(network, str_addr);
+
+    nm_utils_share_rules_add_rule_v(
+        self,
+        "nat",
+        "POSTROUTING --source %s/%s ! --destination %s/%s --jump MASQUERADE",
+        str_addr,
+        str_mask,
+        str_addr,
+        str_mask);
+    nm_utils_share_rules_add_rule_v(
+        self,
+        "filter",
+        "FORWARD --destination %s/%s --out-interface %s --match state --state "
+        "ESTABLISHED,RELATED --jump ACCEPT",
+        str_addr,
+        str_mask,
+        ip_iface);
+    nm_utils_share_rules_add_rule_v(self,
+                                    "filter",
+                                    "FORWARD --source %s/%s --in-interface %s --jump ACCEPT",
+                                    str_addr,
+                                    str_mask,
+                                    ip_iface);
+    nm_utils_share_rules_add_rule_v(self,
+                                    "filter",
+                                    "FORWARD --in-interface %s --out-interface %s --jump ACCEPT",
+                                    ip_iface,
+                                    ip_iface);
+    nm_utils_share_rules_add_rule_v(self,
+                                    "filter",
+                                    "FORWARD --out-interface %s --jump REJECT",
+                                    ip_iface);
+    nm_utils_share_rules_add_rule_v(self,
+                                    "filter",
+                                    "FORWARD --in-interface %s --jump REJECT",
+                                    ip_iface);
+    nm_utils_share_rules_add_rule_v(
+        self,
+        "filter",
+        "INPUT --in-interface %s --protocol udp --destination-port 67 --jump ACCEPT",
+        ip_iface);
+    nm_utils_share_rules_add_rule_v(
+        self,
+        "filter",
+        "INPUT --in-interface %s --protocol tcp --destination-port 67 --jump ACCEPT",
+        ip_iface);
+    nm_utils_share_rules_add_rule_v(
+        self,
+        "filter",
+        "INPUT --in-interface %s --protocol udp --destination-port 53 --jump ACCEPT",
+        ip_iface);
+    nm_utils_share_rules_add_rule_v(
+        self,
+        "filter",
+        "INPUT --in-interface %s --protocol tcp --destination-port 53 --jump ACCEPT",
+        ip_iface);
+}
diff --git a/src/NetworkManagerUtils.h b/src/core/NetworkManagerUtils.h
index e961c785..2afb5a3e 100644
--- a/src/NetworkManagerUtils.h
+++ b/src/core/NetworkManagerUtils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2016 Red Hat, Inc.
  * Copyright (C) 2005 - 2008 Novell, Inc.
@@ -224,4 +224,31 @@ NM_AUTO_DEFINE_FCN(NMDhcpLease *, _nm_auto_unref_dhcplease, nm_dhcp_lease_unref)
 
 /*****************************************************************************/
 
+typedef struct _NMUtilsShareRules NMUtilsShareRules;
+
+NMUtilsShareRules *nm_utils_share_rules_new(void);
+
+void nm_utils_share_rules_free(NMUtilsShareRules *self);
+
+void
+nm_utils_share_rules_add_rule_take(NMUtilsShareRules *self, const char *table, char *rule_take);
+
+static inline void
+nm_utils_share_rules_add_rule(NMUtilsShareRules *self, const char *table, const char *rule)
+{
+    nm_utils_share_rules_add_rule_take(self, table, g_strdup(rule));
+}
+
+#define nm_utils_share_rules_add_rule_v(self, table, ...) \
+    nm_utils_share_rules_add_rule_take((self), (table), g_strdup_printf(__VA_ARGS__))
+
+void nm_utils_share_rules_add_all_rules(NMUtilsShareRules *self,
+                                        const char *       ip_iface,
+                                        in_addr_t          addr,
+                                        guint              plen);
+
+void nm_utils_share_rules_apply(NMUtilsShareRules *self, gboolean shared);
+
+/*****************************************************************************/
+
 #endif /* __NETWORKMANAGER_UTILS_H__ */
diff --git a/src/devices/adsl/meson.build b/src/core/devices/adsl/meson.build
index 79834d7f..95f61d95 100644
--- a/src/devices/adsl/meson.build
+++ b/src/core/devices/adsl/meson.build
@@ -1,14 +1,12 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-sources = files(
-  'nm-atm-manager.c',
-  'nm-device-adsl.c',
-)
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 libnm_device_plugin_adsl = shared_module(
   'nm-device-plugin-adsl',
-  sources: sources,
-  dependencies: daemon_nm_default_dep,
+  sources: files(
+    'nm-atm-manager.c',
+    'nm-device-adsl.c',
+  ),
+  dependencies: core_plugin_dep,
   c_args: daemon_c_flags,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
@@ -21,5 +19,8 @@ core_plugins += libnm_device_plugin_adsl
 test(
   'check-local-devices-adsl',
   check_exports,
-  args: [libnm_device_plugin_adsl.full_path(), linker_script_devices],
+  args: [
+    libnm_device_plugin_adsl.full_path(),
+    linker_script_devices,
+  ],
 )
diff --git a/src/devices/adsl/nm-atm-manager.c b/src/core/devices/adsl/nm-atm-manager.c
index 8e9f555d..9be9b5ce 100644
--- a/src/devices/adsl/nm-atm-manager.c
+++ b/src/core/devices/adsl/nm-atm-manager.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 - 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <gmodule.h>
 #include <libudev.h>
@@ -53,7 +53,7 @@ NM_DEVICE_FACTORY_DECLARE_TYPES(
 G_MODULE_EXPORT NMDeviceFactory *
                 nm_device_factory_create(GError **error)
 {
-    return (NMDeviceFactory *) g_object_new(NM_TYPE_ATM_MANAGER, NULL);
+    return g_object_new(NM_TYPE_ATM_MANAGER, NULL);
 }
 
 /*****************************************************************************/
@@ -239,7 +239,7 @@ nm_atm_manager_init(NMAtmManager *self)
 {
     NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE(self);
 
-    priv->udev_client = nm_udev_client_new((const char *[]){"atm", NULL}, handle_uevent, self);
+    priv->udev_client = nm_udev_client_new(NM_MAKE_STRV("atm"), handle_uevent, self);
 }
 
 static void
@@ -253,7 +253,7 @@ dispose(GObject *object)
         g_object_weak_unref(G_OBJECT(iter->data), device_destroyed, self);
     nm_clear_pointer(&priv->devices, g_slist_free);
 
-    priv->udev_client = nm_udev_client_unref(priv->udev_client);
+    priv->udev_client = nm_udev_client_destroy(priv->udev_client);
 
     G_OBJECT_CLASS(nm_atm_manager_parent_class)->dispose(object);
 }
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/core/devices/adsl/nm-device-adsl.c
index 32615623..34c062a8 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/core/devices/adsl/nm-device-adsl.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Pantelis Koukousoulas <pktoss@gmail.com>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-adsl.h"
 
@@ -23,8 +23,8 @@
 #include "nm-setting-adsl.h"
 #include "nm-utils.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceAdsl
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceAdsl);
 
 /*****************************************************************************/
 
@@ -645,20 +645,20 @@ nm_device_adsl_new(const char *udi, const char *iface, const char *driver, int a
     g_return_val_if_fail(udi != NULL, NULL);
     g_return_val_if_fail(atm_index >= 0, NULL);
 
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_ADSL,
-                                     NM_DEVICE_UDI,
-                                     udi,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_DRIVER,
-                                     driver,
-                                     NM_DEVICE_ADSL_ATM_INDEX,
-                                     atm_index,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "ADSL",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_ADSL,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_ADSL,
+                        NM_DEVICE_UDI,
+                        udi,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_DRIVER,
+                        driver,
+                        NM_DEVICE_ADSL_ATM_INDEX,
+                        atm_index,
+                        NM_DEVICE_TYPE_DESC,
+                        "ADSL",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_ADSL,
+                        NULL);
 }
 
 static void
diff --git a/src/devices/adsl/nm-device-adsl.h b/src/core/devices/adsl/nm-device-adsl.h
index 95740830..c5c18901 100644
--- a/src/devices/adsl/nm-device-adsl.h
+++ b/src/core/devices/adsl/nm-device-adsl.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Author: Pantelis Koukousoulas <pktoss@gmail.com>
  * Copyright (C) 2009 - 2011 Red Hat Inc.
diff --git a/src/core/devices/bluetooth/meson.build b/src/core/devices/bluetooth/meson.build
new file mode 100644
index 00000000..d5f26068
--- /dev/null
+++ b/src/core/devices/bluetooth/meson.build
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+libnm_device_plugin_bluetooth_static = static_library(
+  'nm-device-plugin-bluetooth-static',
+  sources: files(
+    'nm-bluez-manager.c',
+    'nm-bt-error.c',
+    'nm-device-bt.c',
+  ) + (enable_bluez5_dun ? files('nm-bluez5-dun.c') : files()),
+  dependencies: [
+    core_default_dep,
+    libnm_wwan_dep,
+    bluez5_dep,
+  ],
+  c_args: daemon_c_flags,
+)
+
+libnm_device_plugin_bluetooth_static_dep = declare_dependency(
+  link_whole: libnm_device_plugin_bluetooth_static,
+)
+
+libnm_device_plugin_bluetooth = shared_module(
+  'nm-device-plugin-bluetooth',
+  dependencies: [
+    core_plugin_dep,
+    libnm_wwan_dep,
+    bluez5_dep,
+    libnm_device_plugin_bluetooth_static_dep,
+  ],
+  link_args: ldflags_linker_script_devices,
+  link_depends: linker_script_devices,
+  install: true,
+  install_dir: nm_plugindir,
+  install_rpath: nm_plugindir,
+)
+
+core_plugins += libnm_device_plugin_bluetooth
+
+test(
+  'check-local-devices-bluetooth',
+  check_exports,
+  args: [
+    libnm_device_plugin_bluetooth.full_path(),
+    linker_script_devices
+  ],
+)
+
+if enable_tests
+  executable(
+    'nm-bt-test',
+    'tests/nm-bt-test.c',
+    dependencies: [
+      libNetworkManagerTest_dep,
+      core_default_dep,
+      libnm_wwan_dep,
+      bluez5_dep,
+      libnm_device_plugin_bluetooth_static_dep,
+    ],
+    c_args: test_c_flags,
+  )
+endif
diff --git a/src/devices/bluetooth/nm-bluez-common.h b/src/core/devices/bluetooth/nm-bluez-common.h
index e70ec3f7..868a985e 100644
--- a/src/devices/bluetooth/nm-bluez-common.h
+++ b/src/core/devices/bluetooth/nm-bluez-common.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/core/devices/bluetooth/nm-bluez-manager.c
index df0bcbbb..dd998d29 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/core/devices/bluetooth/nm-bluez-manager.c
@@ -1,15 +1,16 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 - 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-bluez-manager.h"
 
 #include <signal.h>
 #include <stdlib.h>
 #include <gmodule.h>
+#include <linux/if_ether.h>
 
 #include "nm-glib-aux/nm-dbus-aux.h"
 #include "nm-glib-aux/nm-c-list.h"
@@ -194,7 +195,7 @@ NM_DEVICE_FACTORY_DECLARE_TYPES(NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(
 G_MODULE_EXPORT NMDeviceFactory *
                 nm_device_factory_create(GError **error)
 {
-    return (NMDeviceFactory *) g_object_new(NM_TYPE_BLUEZ_MANAGER, NULL);
+    return g_object_new(NM_TYPE_BLUEZ_MANAGER, NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/bluetooth/nm-bluez-manager.h b/src/core/devices/bluetooth/nm-bluez-manager.h
index 40a2fcdd..04bfea7d 100644
--- a/src/devices/bluetooth/nm-bluez-manager.h
+++ b/src/core/devices/bluetooth/nm-bluez-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2009 - 2019 Red Hat, Inc.
  */
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/core/devices/bluetooth/nm-bluez5-dun.c
index 422c8516..e29884d8 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/core/devices/bluetooth/nm-bluez5-dun.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <sys/socket.h>
 #include <bluetooth/sdp.h>
diff --git a/src/devices/bluetooth/nm-bluez5-dun.h b/src/core/devices/bluetooth/nm-bluez5-dun.h
index 94fa5550..020d4119 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.h
+++ b/src/core/devices/bluetooth/nm-bluez5-dun.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
diff --git a/src/devices/bluetooth/nm-bt-error.c b/src/core/devices/bluetooth/nm-bt-error.c
index 8f5265f8..2e49eb7a 100644
--- a/src/devices/bluetooth/nm-bt-error.c
+++ b/src/core/devices/bluetooth/nm-bt-error.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-bt-error.h"
 
diff --git a/src/devices/bluetooth/nm-bt-error.h b/src/core/devices/bluetooth/nm-bt-error.h
index 766d2d39..432ae5b4 100644
--- a/src/devices/bluetooth/nm-bt-error.h
+++ b/src/core/devices/bluetooth/nm-bt-error.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/core/devices/bluetooth/nm-device-bt.c
index c31961df..c07be2d3 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/core/devices/bluetooth/nm-device-bt.c
@@ -1,13 +1,14 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 - 2011 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-bt.h"
 
 #include <stdio.h>
+#include <linux/if_ether.h>
 
 #include "nm-core-internal.h"
 #include "nm-bluez-common.h"
@@ -30,8 +31,8 @@
 #include "devices/wwan/nm-modem-manager.h"
 #include "devices/wwan/nm-modem.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceBt
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceBt);
 
 /*****************************************************************************/
 
diff --git a/src/devices/bluetooth/nm-device-bt.h b/src/core/devices/bluetooth/nm-device-bt.h
index e8a35a21..c2d3bc18 100644
--- a/src/devices/bluetooth/nm-device-bt.h
+++ b/src/core/devices/bluetooth/nm-device-bt.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 Red Hat, Inc.
  */
diff --git a/src/devices/bluetooth/tests/nm-bt-test.c b/src/core/devices/bluetooth/tests/nm-bt-test.c
index a57bb076..0fc8aa87 100644
--- a/src/devices/bluetooth/tests/nm-bt-test.c
+++ b/src/core/devices/bluetooth/tests/nm-bt-test.c
@@ -1,6 +1,6 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <glib-unix.h>
 
diff --git a/src/devices/meson.build b/src/core/devices/meson.build
index 40a5c9dc..66f7aa92 100644
--- a/src/devices/meson.build
+++ b/src/core/devices/meson.build
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 subdir('adsl')
 
diff --git a/src/devices/nm-acd-manager.c b/src/core/devices/nm-acd-manager.c
index c9992ff5..b95f90fd 100644
--- a/src/devices/nm-acd-manager.c
+++ b/src/core/devices/nm-acd-manager.c
@@ -1,15 +1,16 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-acd-manager.h"
 
 #include <netinet/in.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <linux/if_ether.h>
 
 #include "platform/nm-platform.h"
 #include "nm-utils.h"
diff --git a/src/devices/nm-acd-manager.h b/src/core/devices/nm-acd-manager.h
index 21001c74..e8ef6f2b 100644
--- a/src/devices/nm-acd-manager.h
+++ b/src/core/devices/nm-acd-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-6lowpan.c b/src/core/devices/nm-device-6lowpan.c
index bf63fb73..fe116dd4 100644
--- a/src/devices/nm-device-6lowpan.c
+++ b/src/core/devices/nm-device-6lowpan.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-6lowpan.h"
 
@@ -14,8 +14,8 @@
 #include "nm-setting-6lowpan.h"
 #include "nm-utils.h"
 
+#define _NMLOG_DEVICE_TYPE NMDevice6Lowpan
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDevice6Lowpan);
 
 /*****************************************************************************/
 
@@ -285,16 +285,16 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_6LOWPAN,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "6LoWPAN",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_6LOWPAN,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_6LOWPAN,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_6LOWPAN,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "6LoWPAN",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_6LOWPAN,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_6LOWPAN,
+                        NULL);
 }
 
 static const char *
diff --git a/src/devices/nm-device-6lowpan.h b/src/core/devices/nm-device-6lowpan.h
index 60207d76..34ea3c8d 100644
--- a/src/devices/nm-device-6lowpan.h
+++ b/src/core/devices/nm-device-6lowpan.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index 3ef14a9e..f68c080b 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-bond.h"
 
@@ -17,8 +17,8 @@
 #include "nm-core-internal.h"
 #include "nm-ip4-config.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceBond
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceBond);
 
 /*****************************************************************************/
 
@@ -149,6 +149,7 @@ ignore_option(NMSettingBond *s_bond, const char *option, const char *value)
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
+    NMDeviceBond * self    = NM_DEVICE_BOND(device);
     NMSettingBond *s_bond  = nm_connection_get_setting_bond(connection);
     int            ifindex = nm_device_get_ifindex(device);
     NMBondMode     mode    = NM_BOND_MODE_UNKNOWN;
@@ -197,7 +198,10 @@ update_connection(NMDevice *device, NMConnection *connection)
                 }
             }
 
-            nm_setting_bond_add_option(s_bond, option, value);
+            if (!_nm_setting_bond_validate_option(option, value, NULL))
+                _LOGT(LOGD_BOND, "cannot set invalid bond option '%s' = '%s'", option, value);
+            else
+                nm_setting_bond_add_option(s_bond, option, value);
         }
     }
 }
@@ -264,7 +268,7 @@ set_arp_targets(NMDevice *device, const char *cur_arp_ip_target, const char *new
     if (cur_len == 0 && new_len == 0)
         return;
 
-    if (_nm_utils_strv_equal((char **) cur_strv, (char **) new_strv))
+    if (nm_utils_strv_equal(cur_strv, new_strv))
         return;
 
     for (i = 0; i < cur_len; i++)
@@ -327,6 +331,7 @@ apply_bonding_config(NMDeviceBond *self)
     NMSettingBond *s_bond;
     NMBondMode     mode;
     const char *   mode_str;
+    gs_free char * device_bond_mode = NULL;
 
     s_bond = nm_device_get_applied_setting(device, NM_TYPE_SETTING_BOND);
     g_return_val_if_fail(s_bond, FALSE);
@@ -338,6 +343,13 @@ apply_bonding_config(NMDeviceBond *self)
     /* Set mode first, as some other options (e.g. arp_interval) are valid
      * only for certain modes.
      */
+    device_bond_mode = nm_platform_sysctl_master_get_option(nm_device_get_platform(device),
+                                                            nm_device_get_ifindex(device),
+                                                            NM_SETTING_BOND_OPTION_MODE);
+    /* Need to release all slaves before we can change bond mode */
+    if (!nm_streq0(device_bond_mode, mode_str))
+        nm_device_master_release_slaves(device);
+
     set_bond_attr_or_default(device, s_bond, NM_SETTING_BOND_OPTION_MODE);
 
     set_bond_arp_ip_targets(device, s_bond);
@@ -616,18 +628,18 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_BOND,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_DRIVER,
-                                     "bonding",
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Bond",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_BOND,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_BOND,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_BOND,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_DRIVER,
+                        "bonding",
+                        NM_DEVICE_TYPE_DESC,
+                        "Bond",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_BOND,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_BOND,
+                        NULL);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
diff --git a/src/devices/nm-device-bond.h b/src/core/devices/nm-device-bond.h
index 46be7c23..19301793 100644
--- a/src/devices/nm-device-bond.h
+++ b/src/core/devices/nm-device-bond.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c
index 48dcec1b..c919d85d 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/core/devices/nm-device-bridge.c
@@ -1,13 +1,14 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 - 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-bridge.h"
 
 #include <stdlib.h>
+#include <linux/if_ether.h>
 
 #include "NetworkManagerUtils.h"
 #include "nm-device-private.h"
@@ -15,8 +16,8 @@
 #include "nm-device-factory.h"
 #include "nm-core-internal.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceBridge
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceBridge);
 
 /*****************************************************************************/
 
@@ -169,7 +170,7 @@ static void
 to_sysfs_group_address_sys(const char *group_address, NMEtherAddr *out_addr)
 {
     if (group_address == NULL) {
-        *out_addr = (NMEtherAddr) NM_ETHER_ADDR_INIT(NM_BRIDGE_GROUP_ADDRESS_DEF_BIN);
+        *out_addr = NM_ETHER_ADDR_INIT(NM_BRIDGE_GROUP_ADDRESS_DEF_BIN);
         return;
     }
     if (!nm_utils_hwaddr_aton(group_address, out_addr, ETH_ALEN))
@@ -1197,18 +1198,18 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_BRIDGE,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_DRIVER,
-                                     "bridge",
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Bridge",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_BRIDGE,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_BRIDGE,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_BRIDGE,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_DRIVER,
+                        "bridge",
+                        NM_DEVICE_TYPE_DESC,
+                        "Bridge",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_BRIDGE,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_BRIDGE,
+                        NULL);
 }
 
 static gboolean
diff --git a/src/devices/nm-device-bridge.h b/src/core/devices/nm-device-bridge.h
index c942813e..32437947 100644
--- a/src/devices/nm-device-bridge.h
+++ b/src/core/devices/nm-device-bridge.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-dummy.c b/src/core/devices/nm-device-dummy.c
index 4f6b8cc2..13cfd3b0 100644
--- a/src/devices/nm-device-dummy.c
+++ b/src/core/devices/nm-device-dummy.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-dummy.h"
 
@@ -18,8 +18,8 @@
 #include "nm-setting-dummy.h"
 #include "nm-core-internal.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceDummy
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceDummy);
 
 /*****************************************************************************/
 
@@ -162,16 +162,16 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_DUMMY,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Dummy",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_DUMMY,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_DUMMY,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_DUMMY,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "Dummy",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_DUMMY,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_DUMMY,
+                        NULL);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
diff --git a/src/devices/nm-device-dummy.h b/src/core/devices/nm-device-dummy.h
index b3670ef9..2845b4eb 100644
--- a/src/devices/nm-device-dummy.h
+++ b/src/core/devices/nm-device-dummy.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-ethernet-utils.c b/src/core/devices/nm-device-ethernet-utils.c
index 5c35db83..a36d12b7 100644
--- a/src/devices/nm-device-ethernet-utils.c
+++ b/src/core/devices/nm-device-ethernet-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-ethernet-utils.h"
 
diff --git a/src/devices/nm-device-ethernet-utils.h b/src/core/devices/nm-device-ethernet-utils.h
index d67b9fa1..133340fc 100644
--- a/src/devices/nm-device-ethernet-utils.h
+++ b/src/core/devices/nm-device-ethernet-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c
index 2569c426..44428869 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/core/devices/nm-device-ethernet.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2014 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-ethernet.h"
 
@@ -12,6 +12,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <libudev.h>
+#include <linux/if_ether.h>
 
 #include "nm-device-private.h"
 #include "nm-act-request.h"
@@ -24,7 +25,7 @@
 #include "ppp/nm-ppp-manager-call.h"
 #include "ppp/nm-ppp-status.h"
 #include "platform/nm-platform.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "nm-dcb.h"
 #include "settings/nm-settings-connection.h"
 #include "nm-config.h"
@@ -34,9 +35,10 @@
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 #include "nm-udev-aux/nm-udev-utils.h"
+#include "nm-device-veth.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceEthernet
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceEthernet);
 
 /*****************************************************************************/
 
@@ -347,7 +349,9 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
              ->check_connection_compatible(device, connection, error))
         return FALSE;
 
-    if (nm_connection_is_type(connection, NM_SETTING_PPPOE_SETTING_NAME)) {
+    if (nm_connection_is_type(connection, NM_SETTING_PPPOE_SETTING_NAME)
+        || (nm_connection_is_type(connection, NM_SETTING_VETH_SETTING_NAME)
+            && NM_IS_DEVICE_VETH(device))) {
         s_wired = nm_connection_get_setting_wired(connection);
     } else {
         s_wired =
@@ -891,9 +895,9 @@ link_negotiation_set(NMDevice *device)
     NMSettingWired *         s_wired;
     gboolean                 autoneg = TRUE;
     gboolean                 link_autoneg;
-    NMPlatformLinkDuplexType duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN;
-    NMPlatformLinkDuplexType link_duplex;
-    guint32                  speed = 0;
+    NMPlatformLinkDuplexType duplex      = NM_PLATFORM_LINK_DUPLEX_UNKNOWN;
+    NMPlatformLinkDuplexType link_duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN;
+    guint32                  speed       = 0;
     guint32                  link_speed;
 
     s_wired = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRED);
@@ -1360,6 +1364,9 @@ wake_on_lan_enable(NMDevice *device)
 
     s_wired = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRED);
 
+    if (NM_IS_DEVICE_VETH(device))
+        return FALSE;
+
     if (s_wired) {
         wol      = nm_setting_wired_get_wake_on_lan(s_wired);
         password = nm_setting_wired_get_wake_on_lan_password(s_wired);
@@ -1385,7 +1392,7 @@ wake_on_lan_enable(NMDevice *device)
 found:
     return nm_platform_ethtool_set_wake_on_lan(nm_device_get_platform(device),
                                                nm_device_get_ifindex(device),
-                                               wol,
+                                               _NM_SETTING_WIRED_WAKE_ON_LAN_CAST(wol),
                                                password);
 }
 
@@ -1493,6 +1500,12 @@ act_stage3_ip_config_start(NMDevice *           device,
 {
     NMSettingConnection *s_con;
     const char *         connection_type;
+    int                  ifindex;
+
+    ifindex = nm_device_get_ifindex(device);
+
+    if (ifindex <= 0)
+        return NM_ACT_STAGE_RETURN_FAILURE;
 
     if (addr_family == AF_INET) {
         s_con = nm_device_get_applied_setting(device, NM_TYPE_SETTING_CONNECTION);
@@ -1525,6 +1538,7 @@ deactivate(NMDevice *device)
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
     NMSettingDcb *           s_dcb;
     GError *                 error = NULL;
+    int                      ifindex;
 
     nm_clear_g_source(&priv->pppoe_wait_id);
     nm_clear_g_signal_handler(self, &priv->carrier_id);
@@ -1553,7 +1567,8 @@ deactivate(NMDevice *device)
     if (nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPPOE))
         priv->last_pppoe_time = nm_utils_get_monotonic_timestamp_sec();
 
-    if (priv->ethtool_prev_set) {
+    ifindex = nm_device_get_ifindex(device);
+    if (ifindex > 0 && priv->ethtool_prev_set) {
         priv->ethtool_prev_set = FALSE;
 
         _LOGD(LOGD_DEVICE,
@@ -1562,7 +1577,7 @@ deactivate(NMDevice *device)
               priv->ethtool_prev_speed,
               nm_platform_link_duplex_type_to_string(priv->ethtool_prev_duplex));
         if (!nm_platform_ethtool_set_link_settings(nm_device_get_platform(device),
-                                                   nm_device_get_ifindex(device),
+                                                   ifindex,
                                                    priv->ethtool_prev_autoneg,
                                                    priv->ethtool_prev_speed,
                                                    priv->ethtool_prev_duplex)) {
@@ -1582,6 +1597,60 @@ complete_connection(NMDevice *           device,
     NMSettingWired *s_wired;
     NMSettingPppoe *s_pppoe;
 
+    if (nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_VETH_SETTING_NAME)) {
+        NMSettingVeth *s_veth;
+        const char *   peer_name     = NULL;
+        const char *   con_peer_name = NULL;
+        int            ifindex;
+
+        nm_utils_complete_generic(nm_device_get_platform(device),
+                                  connection,
+                                  NM_SETTING_VETH_SETTING_NAME,
+                                  existing_connections,
+                                  NULL,
+                                  _("Veth connection"),
+                                  "veth",
+                                  NULL,
+                                  TRUE);
+
+        s_veth = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VETH);
+        if (!s_veth) {
+            s_veth = (NMSettingVeth *) nm_setting_veth_new();
+            nm_connection_add_setting(connection, NM_SETTING(s_veth));
+        }
+
+        ifindex = nm_device_get_ip_ifindex(device);
+        if (ifindex > 0) {
+            const NMPlatformLink *pllink;
+
+            pllink = nm_platform_link_get(nm_device_get_platform(device), ifindex);
+            if (pllink && pllink->type == NM_LINK_TYPE_VETH && pllink->parent > 0) {
+                pllink = nm_platform_link_get(nm_device_get_platform(device), pllink->parent);
+
+                if (pllink && pllink->type == NM_LINK_TYPE_VETH) {
+                    peer_name = pllink->name;
+                }
+            }
+        }
+
+        if (!peer_name) {
+            nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "cannot find peer for veth device");
+            return FALSE;
+        }
+
+        con_peer_name = nm_setting_veth_get_peer(s_veth);
+        if (con_peer_name) {
+            nm_utils_error_set(error,
+                               NM_UTILS_ERROR_UNKNOWN,
+                               "mismatching veth peer \"%s\"",
+                               con_peer_name);
+            return FALSE;
+        } else
+            g_object_set(s_veth, NM_SETTING_VETH_PEER, peer_name, NULL);
+
+        return TRUE;
+    }
+
     s_pppoe = nm_connection_get_setting_pppoe(connection);
 
     /* We can't telepathically figure out the service name or username, so if
@@ -1803,7 +1872,7 @@ static void
 link_changed(NMDevice *device, const NMPlatformLink *pllink)
 {
     NM_DEVICE_CLASS(nm_device_ethernet_parent_class)->link_changed(device, pllink);
-    if (pllink->initialized)
+    if (!NM_IS_DEVICE_VETH(device) && pllink->initialized)
         _update_s390_subchannels((NMDeviceEthernet *) device);
 }
 
@@ -2021,16 +2090,16 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_ETHERNET,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Ethernet",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_ETHERNET,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_ETHERNET,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_ETHERNET,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "Ethernet",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_ETHERNET,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_ETHERNET,
+                        NULL);
 }
 
 static gboolean
diff --git a/src/devices/nm-device-ethernet.h b/src/core/devices/nm-device-ethernet.h
index 36b7bc57..6e134d71 100644
--- a/src/devices/nm-device-ethernet.h
+++ b/src/core/devices/nm-device-ethernet.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2010 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
diff --git a/src/devices/nm-device-factory.c b/src/core/devices/nm-device-factory.c
index a238ce1b..81124a8d 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/core/devices/nm-device-factory.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-factory.h"
 
@@ -301,7 +301,7 @@ _load_internal_factory(GType                             factory_gtype,
 {
     gs_unref_object NMDeviceFactory *factory = NULL;
 
-    factory = (NMDeviceFactory *) g_object_new(factory_gtype, NULL);
+    factory = g_object_new(factory_gtype, NULL);
     _add_factory(factory, NULL, callback, user_data);
 }
 
diff --git a/src/devices/nm-device-factory.h b/src/core/devices/nm-device-factory.h
index 5e7a8acd..d9b50563 100644
--- a/src/devices/nm-device-factory.h
+++ b/src/core/devices/nm-device-factory.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2007 - 2014 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-generic.c b/src/core/devices/nm-device-generic.c
index ccd56366..a319666a 100644
--- a/src/devices/nm-device-generic.c
+++ b/src/core/devices/nm-device-generic.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-generic.h"
 
@@ -165,16 +165,16 @@ nm_device_generic_new(const NMPlatformLink *plink, gboolean nm_plugin_missing)
 {
     g_return_val_if_fail(plink != NULL, NULL);
 
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_GENERIC,
-                                     NM_DEVICE_IFACE,
-                                     plink->name,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Generic",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_GENERIC,
-                                     NM_DEVICE_NM_PLUGIN_MISSING,
-                                     nm_plugin_missing,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_GENERIC,
+                        NM_DEVICE_IFACE,
+                        plink->name,
+                        NM_DEVICE_TYPE_DESC,
+                        "Generic",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_GENERIC,
+                        NM_DEVICE_NM_PLUGIN_MISSING,
+                        nm_plugin_missing,
+                        NULL);
 }
 
 static void
diff --git a/src/devices/nm-device-generic.h b/src/core/devices/nm-device-generic.h
index 4acc4dc9..48c43523 100644
--- a/src/devices/nm-device-generic.h
+++ b/src/core/devices/nm-device-generic.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-infiniband.c b/src/core/devices/nm-device-infiniband.c
index bae5db64..f54ffcf0 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/core/devices/nm-device-infiniband.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-infiniband.h"
 
@@ -443,19 +443,19 @@ create_device(NMDeviceFactory *     factory,
                            && nm_setting_infiniband_get_mac_address(s_infiniband));
     }
 
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_INFINIBAND,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "InfiniBand",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_INFINIBAND,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_INFINIBAND,
-                                     /* NOTE: Partition should probably be a different link type! */
-                                     NM_DEVICE_INFINIBAND_IS_PARTITION,
-                                     is_partition,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_INFINIBAND,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "InfiniBand",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_INFINIBAND,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_INFINIBAND,
+                        /* NOTE: Partition should probably be a different link type! */
+                        NM_DEVICE_INFINIBAND_IS_PARTITION,
+                        is_partition,
+                        NULL);
 }
 
 static const char *
diff --git a/src/devices/nm-device-infiniband.h b/src/core/devices/nm-device-infiniband.h
index d8be8f21..69994a2d 100644
--- a/src/devices/nm-device-infiniband.h
+++ b/src/core/devices/nm-device-infiniband.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/core/devices/nm-device-ip-tunnel.c
index e01cc7ff..da6afb3b 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/core/devices/nm-device-ip-tunnel.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-ip-tunnel.h"
 
@@ -12,6 +12,7 @@
 #include <linux/ip.h>
 #include <linux/if_tunnel.h>
 #include <linux/ip6_tunnel.h>
+#include <linux/if_ether.h>
 
 #include "nm-device-private.h"
 #include "nm-manager.h"
@@ -22,8 +23,8 @@
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceIPTunnel
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceIPTunnel);
 
 /*****************************************************************************/
 
@@ -1233,18 +1234,18 @@ create_device(NMDeviceFactory *     factory,
     if (mode == NM_IP_TUNNEL_MODE_UNKNOWN || link_type == NM_LINK_TYPE_UNKNOWN)
         return NULL;
 
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_IP_TUNNEL,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "IPTunnel",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_IP_TUNNEL,
-                                     NM_DEVICE_LINK_TYPE,
-                                     link_type,
-                                     NM_DEVICE_IP_TUNNEL_MODE,
-                                     mode,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_IP_TUNNEL,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "IPTunnel",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_IP_TUNNEL,
+                        NM_DEVICE_LINK_TYPE,
+                        link_type,
+                        NM_DEVICE_IP_TUNNEL_MODE,
+                        mode,
+                        NULL);
 }
 
 static const char *
diff --git a/src/devices/nm-device-ip-tunnel.h b/src/core/devices/nm-device-ip-tunnel.h
index 0d9211ec..0a941cc2 100644
--- a/src/devices/nm-device-ip-tunnel.h
+++ b/src/core/devices/nm-device-ip-tunnel.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-logging.h b/src/core/devices/nm-device-logging.h
index d1291020..844e9949 100644
--- a/src/devices/nm-device-logging.h
+++ b/src/core/devices/nm-device-logging.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
@@ -8,36 +8,45 @@
 
 #include "nm-device.h"
 
-#define _LOG_DECLARE_SELF(t)                                                  \
-    _nm_unused static inline NMDevice *_nm_device_log_self_to_device(t *self) \
-    {                                                                         \
-        return (NMDevice *) self;                                             \
-    }
+#if !_NM_CC_SUPPORT_GENERIC
+    #define _NM_DEVICE_CAST(self) ((NMDevice *) (self))
+#elif !defined(_NMLOG_DEVICE_TYPE)
+    #define _NM_DEVICE_CAST(self)                         \
+        _Generic((self), NMDevice *                       \
+                 : ((NMDevice *) (self)), NMDevice *const \
+                 : ((NMDevice *) (self)))
+#else
+    #define _NM_DEVICE_CAST(self) \
+        _Generic((self), \
+                 _NMLOG_DEVICE_TYPE *      : ((NMDevice *) (self)), \
+                 _NMLOG_DEVICE_TYPE * const: ((NMDevice *) (self)), \
+                 NMDevice *                : ((NMDevice *) (self)), \
+                 NMDevice *           const: ((NMDevice *) (self)))
+#endif
 
 #undef _NMLOG_ENABLED
 #define _NMLOG_ENABLED(level, domain) (nm_logging_enabled((level), (domain)))
-#define _NMLOG(level, domain, ...)                                               \
-    G_STMT_START                                                                 \
-    {                                                                            \
-        const NMLogLevel  _level  = (level);                                     \
-        const NMLogDomain _domain = (domain);                                    \
-                                                                                 \
-        if (nm_logging_enabled(_level, _domain)) {                               \
-            typeof(*self) *const _self = (self);                                 \
-            const char *const    _ifname =                                       \
-                _nm_device_get_iface(_nm_device_log_self_to_device(_self));      \
-                                                                                 \
-            nm_log_obj(_level,                                                   \
-                       _domain,                                                  \
-                       _ifname,                                                  \
-                       NULL,                                                     \
-                       _self,                                                    \
-                       "device",                                                 \
-                       "%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),            \
-                       NM_PRINT_FMT_QUOTED(_ifname, "(", _ifname, ")", "[null]") \
-                           _NM_UTILS_MACRO_REST(__VA_ARGS__));                   \
-        }                                                                        \
-    }                                                                            \
+#define _NMLOG(level, domain, ...)                                                       \
+    G_STMT_START                                                                         \
+    {                                                                                    \
+        const NMLogLevel  _level  = (level);                                             \
+        const NMLogDomain _domain = (domain);                                            \
+                                                                                         \
+        if (nm_logging_enabled(_level, _domain)) {                                       \
+            typeof(*self) *const _self   = (self);                                       \
+            const char *const    _ifname = _nm_device_get_iface(_NM_DEVICE_CAST(_self)); \
+                                                                                         \
+            nm_log_obj(_level,                                                           \
+                       _domain,                                                          \
+                       _ifname,                                                          \
+                       NULL,                                                             \
+                       _self,                                                            \
+                       "device",                                                         \
+                       "%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                    \
+                       NM_PRINT_FMT_QUOTED(_ifname, "(", _ifname, ")", "[null]")         \
+                           _NM_UTILS_MACRO_REST(__VA_ARGS__));                           \
+        }                                                                                \
+    }                                                                                    \
     G_STMT_END
 
 #endif /* __NETWORKMANAGER_DEVICE_LOGGING_H__ */
diff --git a/src/devices/nm-device-macsec.c b/src/core/devices/nm-device-macsec.c
index 7d738b42..51b820a1 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/core/devices/nm-device-macsec.c
@@ -1,12 +1,14 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-macsec.h"
 
+#include <linux/if_ether.h>
+
 #include "nm-act-request.h"
 #include "nm-device-private.h"
 #include "platform/nm-platform.h"
@@ -18,8 +20,8 @@
 #include "supplicant/nm-supplicant-interface.h"
 #include "supplicant/nm-supplicant-config.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceMacsec
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceMacsec);
 
 /*****************************************************************************/
 
@@ -1023,16 +1025,16 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_MACSEC,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Macsec",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_MACSEC,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_MACSEC,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_MACSEC,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "Macsec",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_MACSEC,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_MACSEC,
+                        NULL);
 }
 
 static const char *
diff --git a/src/devices/nm-device-macsec.h b/src/core/devices/nm-device-macsec.h
index 9a2f230b..e91fe51c 100644
--- a/src/devices/nm-device-macsec.h
+++ b/src/core/devices/nm-device-macsec.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-macvlan.c b/src/core/devices/nm-device-macvlan.c
index d9e71c8c..e8b39ed6 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/core/devices/nm-device-macvlan.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 - 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-macvlan.h"
 
@@ -21,8 +21,8 @@
 #include "nm-ip4-config.h"
 #include "nm-utils.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceMacvlan
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceMacvlan);
 
 /*****************************************************************************/
 
@@ -598,18 +598,18 @@ create_device(NMDeviceFactory *     factory,
 
     link_type = tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN;
 
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_MACVLAN,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Macvlan",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_MACVLAN,
-                                     NM_DEVICE_LINK_TYPE,
-                                     link_type,
-                                     NM_DEVICE_MACVLAN_TAP,
-                                     tap,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_MACVLAN,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "Macvlan",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_MACVLAN,
+                        NM_DEVICE_LINK_TYPE,
+                        link_type,
+                        NM_DEVICE_MACVLAN_TAP,
+                        tap,
+                        NULL);
 }
 
 static const char *
diff --git a/src/devices/nm-device-macvlan.h b/src/core/devices/nm-device-macvlan.h
index f36a7a4a..109a2bcd 100644
--- a/src/devices/nm-device-macvlan.h
+++ b/src/core/devices/nm-device-macvlan.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-ppp.c b/src/core/devices/nm-device-ppp.c
index 82861bda..4040f2d3 100644
--- a/src/devices/nm-device-ppp.c
+++ b/src/core/devices/nm-device-ppp.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-ppp.h"
 
@@ -18,8 +18,8 @@
 #include "ppp/nm-ppp-manager-call.h"
 #include "ppp/nm-ppp-status.h"
 
+#define _NMLOG_DEVICE_TYPE NMDevicePpp
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDevicePpp);
 
 /*****************************************************************************/
 
@@ -316,16 +316,16 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_PPP,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Ppp",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_PPP,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_PPP,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_PPP,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "Ppp",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_PPP,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_PPP,
+                        NULL);
 }
 
 static gboolean
diff --git a/src/devices/nm-device-ppp.h b/src/core/devices/nm-device-ppp.h
index 99132a3e..24c119ab 100644
--- a/src/devices/nm-device-ppp.h
+++ b/src/core/devices/nm-device-ppp.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-private.h b/src/core/devices/nm-device-private.h
index 5f8eff18..8675a699 100644
--- a/src/devices/nm-device-private.h
+++ b/src/core/devices/nm-device-private.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2007 - 2008 Novell, Inc.
  * Copyright (C) 2007 - 2011 Red Hat, Inc.
@@ -54,9 +54,7 @@ gboolean nm_device_set_ip_iface(NMDevice *self, const char *iface);
 
 void nm_device_activate_schedule_stage3_ip_config_start(NMDevice *device);
 
-gboolean nm_device_activate_stage3_ip4_start(NMDevice *self);
-
-gboolean nm_device_activate_stage3_ip6_start(NMDevice *self);
+gboolean nm_device_activate_stage3_ip_start(NMDevice *self, int addr_family);
 
 gboolean nm_device_bring_up(NMDevice *self, gboolean wait, gboolean *no_firmware);
 
@@ -129,6 +127,8 @@ void nm_device_recheck_available_connections(NMDevice *device);
 void
 nm_device_master_check_slave_physical_port(NMDevice *self, NMDevice *slave, NMLogDomain log_domain);
 
+void nm_device_master_release_slaves(NMDevice *self);
+
 void nm_device_set_carrier(NMDevice *self, gboolean carrier);
 
 void nm_device_queue_recheck_assume(NMDevice *device);
diff --git a/src/devices/nm-device-tun.c b/src/core/devices/nm-device-tun.c
index 94805255..edca69e9 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/core/devices/nm-device-tun.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 - 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-tun.h"
 
@@ -19,8 +19,8 @@
 #include "nm-setting-tun.h"
 #include "nm-core-internal.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceTun
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceTun);
 
 /*****************************************************************************/
 
@@ -550,16 +550,16 @@ create_device(NMDeviceFactory *     factory,
                                           NM_SETTING_TUN_SETTING_NAME),
                          NULL);
 
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_TUN,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Tun",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_TUN,
-                                     NM_DEVICE_LINK_TYPE,
-                                     (guint) NM_LINK_TYPE_TUN,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_TUN,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "Tun",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_TUN,
+                        NM_DEVICE_LINK_TYPE,
+                        (guint) NM_LINK_TYPE_TUN,
+                        NULL);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
diff --git a/src/devices/nm-device-tun.h b/src/core/devices/nm-device-tun.h
index 32d515c1..84497ea0 100644
--- a/src/devices/nm-device-tun.h
+++ b/src/core/devices/nm-device-tun.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-veth.c b/src/core/devices/nm-device-veth.c
index f61bc99f..e0ba843d 100644
--- a/src/devices/nm-device-veth.c
+++ b/src/core/devices/nm-device-veth.c
@@ -1,20 +1,22 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <stdlib.h>
 
+#include "nm-core-internal.h"
 #include "nm-device-veth.h"
 #include "nm-device-private.h"
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
 #include "nm-device-factory.h"
+#include "nm-setting-veth.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceVeth
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceVeth);
 
 /*****************************************************************************/
 
@@ -42,9 +44,10 @@ update_properties(NMDevice *device)
 
     ifindex = nm_device_get_ifindex(device);
 
-    if (!nm_platform_link_veth_get_properties(nm_device_get_platform(device),
-                                              ifindex,
-                                              &peer_ifindex))
+    if (ifindex <= 0
+        || !nm_platform_link_veth_get_properties(nm_device_get_platform(device),
+                                                 ifindex,
+                                                 &peer_ifindex))
         peer_ifindex = 0;
 
     nm_device_parent_set_ifindex(device, peer_ifindex);
@@ -71,6 +74,53 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
     update_properties(device);
 }
 
+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);
+    NMSettingVeth *s_veth;
+    int            r;
+
+    s_veth = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VETH);
+    if (!s_veth) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_CREATION_FAILED,
+                    "Profile %s (%s) is not a suitable veth profile",
+                    nm_connection_get_id(connection),
+                    nm_connection_get_uuid(connection));
+        return FALSE;
+    }
+
+    r = nm_platform_link_veth_add(nm_device_get_platform(device),
+                                  iface,
+                                  nm_setting_veth_get_peer(s_veth),
+                                  out_plink);
+    if (r < 0) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_CREATION_FAILED,
+                    "Failed to create veth interface '%s' for '%s': %s",
+                    iface,
+                    nm_connection_get_id(connection),
+                    nm_strerror(r));
+        return FALSE;
+    }
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static NMDeviceCapabilities
+get_generic_capabilities(NMDevice *device)
+{
+    return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
 /*****************************************************************************/
 
 static void
@@ -136,6 +186,8 @@ nm_device_veth_class_init(NMDeviceVethClass *klass)
     device_class->can_unmanaged_external_down = can_unmanaged_external_down;
     device_class->link_changed                = link_changed;
     device_class->parent_changed_notify       = parent_changed_notify;
+    device_class->create_and_realize          = create_and_realize;
+    device_class->get_generic_capabilities    = get_generic_capabilities;
 
     obj_properties[PROP_PEER] = g_param_spec_string(NM_DEVICE_VETH_PEER,
                                                     "",
@@ -159,20 +211,22 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_VETH,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Veth",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_VETH,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_VETH,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_VETH,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "Veth",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_VETH,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_VETH,
+                        NULL);
 }
 
-NM_DEVICE_FACTORY_DEFINE_INTERNAL(VETH,
-                                  Veth,
-                                  veth,
-                                  NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_VETH),
-                                  factory_class->create_device = create_device;);
+NM_DEVICE_FACTORY_DEFINE_INTERNAL(
+    VETH,
+    Veth,
+    veth,
+    NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_VETH)
+        NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_VETH_SETTING_NAME),
+    factory_class->create_device = create_device;);
diff --git a/src/devices/nm-device-veth.h b/src/core/devices/nm-device-veth.h
index e3867446..d43a0a4b 100644
--- a/src/devices/nm-device-veth.h
+++ b/src/core/devices/nm-device-veth.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-vlan.c b/src/core/devices/nm-device-vlan.c
index bb6fdc05..bfde60ef 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/core/devices/nm-device-vlan.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 - 2012 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-vlan.h"
 
@@ -22,8 +22,8 @@
 #include "nm-core-internal.h"
 #include "platform/nmp-object.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceVlan
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceVlan);
 
 /*****************************************************************************/
 
@@ -117,7 +117,7 @@ parent_hwaddr_maybe_changed(NMDevice *parent, GParamSpec *pspec, gpointer user_d
          */
         s_ip6 = nm_connection_get_setting_ip6_config(connection);
         if (s_ip6)
-            nm_device_reactivate_ip6_config(device, s_ip6, s_ip6);
+            nm_device_reactivate_ip_config(device, AF_INET6, s_ip6, s_ip6);
     }
 }
 
@@ -614,18 +614,18 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_VLAN,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_DRIVER,
-                                     "8021q",
-                                     NM_DEVICE_TYPE_DESC,
-                                     "VLAN",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_VLAN,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_VLAN,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_VLAN,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_DRIVER,
+                        "8021q",
+                        NM_DEVICE_TYPE_DESC,
+                        "VLAN",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_VLAN,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_VLAN,
+                        NULL);
 }
 
 static const char *
diff --git a/src/devices/nm-device-vlan.h b/src/core/devices/nm-device-vlan.h
index 5bf11cd9..5270706f 100644
--- a/src/devices/nm-device-vlan.h
+++ b/src/core/devices/nm-device-vlan.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-vrf.c b/src/core/devices/nm-device-vrf.c
index ae0ecc60..4fec59ba 100644
--- a/src/devices/nm-device-vrf.c
+++ b/src/core/devices/nm-device-vrf.c
@@ -1,6 +1,6 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-vrf.h"
 
@@ -12,8 +12,8 @@
 #include "platform/nm-platform.h"
 #include "settings/nm-settings.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceVrf
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceVrf);
 
 /*****************************************************************************/
 
diff --git a/src/devices/nm-device-vrf.h b/src/core/devices/nm-device-vrf.h
index eb09bd95..5169041c 100644
--- a/src/devices/nm-device-vrf.h
+++ b/src/core/devices/nm-device-vrf.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #ifndef __NETWORKMANAGER_DEVICE_VRF_H__
 #define __NETWORKMANAGER_DEVICE_VRF_H__
diff --git a/src/devices/nm-device-vxlan.c b/src/core/devices/nm-device-vxlan.c
index 083f29c6..f16a52c4 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/core/devices/nm-device-vxlan.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 - 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-vxlan.h"
 
@@ -19,8 +19,8 @@
 #include "nm-ip4-config.h"
 #include "nm-core-internal.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceVxlan
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceVxlan);
 
 /*****************************************************************************/
 
@@ -162,11 +162,10 @@ create_and_realize(NMDevice *             device,
     NMPlatformLnkVxlan props = {};
     NMSettingVxlan *   s_vxlan;
     const char *       str;
-    int                ret;
     int                r;
 
     s_vxlan = nm_connection_get_setting_vxlan(connection);
-    g_assert(s_vxlan);
+    g_return_val_if_fail(s_vxlan, FALSE);
 
     if (parent)
         props.parent_ifindex = nm_device_get_ifindex(parent);
@@ -175,19 +174,17 @@ create_and_realize(NMDevice *             device,
 
     str = nm_setting_vxlan_get_local(s_vxlan);
     if (str) {
-        ret = inet_pton(AF_INET, str, &props.local);
-        if (ret != 1)
-            ret = inet_pton(AF_INET6, str, &props.local6);
-        if (ret != 1)
+        if (!nm_utils_parse_inaddr_bin(AF_INET, str, NULL, &props.local)
+            && !nm_utils_parse_inaddr_bin(AF_INET6, str, NULL, &props.local6))
             return FALSE;
     }
 
     str = nm_setting_vxlan_get_remote(s_vxlan);
-    ret = inet_pton(AF_INET, str, &props.group);
-    if (ret != 1)
-        ret = inet_pton(AF_INET6, str, &props.group6);
-    if (ret != 1)
-        return FALSE;
+    if (str) {
+        if (!nm_utils_parse_inaddr_bin(AF_INET, str, NULL, &props.group)
+            && !nm_utils_parse_inaddr_bin(AF_INET6, str, NULL, &props.group6))
+            return FALSE;
+    }
 
     props.tos          = nm_setting_vxlan_get_tos(s_vxlan);
     props.ttl          = nm_setting_vxlan_get_ttl(s_vxlan);
@@ -218,21 +215,26 @@ create_and_realize(NMDevice *             device,
 }
 
 static gboolean
-address_matches(const char *str, in_addr_t addr4, struct in6_addr *addr6)
+address_matches(const char *candidate, in_addr_t addr4, struct in6_addr *addr6)
 {
-    in_addr_t       new_addr4 = 0;
-    struct in6_addr new_addr6 = {};
+    NMIPAddr candidate_addr;
+    int      addr_family;
 
-    if (!str) {
-        return addr4 == 0 && !memcmp(addr6, &in6addr_any, sizeof(in6addr_any));
-    }
+    if (!candidate)
+        return addr4 == 0u && IN6_IS_ADDR_UNSPECIFIED(addr6);
 
-    if (inet_pton(AF_INET, str, &new_addr4) == 1)
-        return new_addr4 == addr4;
-    else if (inet_pton(AF_INET6, str, &new_addr6) == 1)
-        return !memcmp(&new_addr6, addr6, sizeof(new_addr6));
-    else
+    if (!nm_utils_parse_inaddr_bin(AF_UNSPEC, candidate, &addr_family, &candidate_addr))
+        return FALSE;
+
+    if (!nm_ip_addr_equal(addr_family,
+                          &candidate_addr,
+                          NM_IS_IPv4(addr_family) ? (gpointer) &addr4 : addr6))
         return FALSE;
+
+    if (NM_IS_IPv4(addr_family))
+        return IN6_IS_ADDR_UNSPECIFIED(addr6);
+    else
+        return addr4 == 0u;
 }
 
 static gboolean
@@ -757,16 +759,16 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_VXLAN,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Vxlan",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_VXLAN,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_VXLAN,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_VXLAN,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "Vxlan",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_VXLAN,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_VXLAN,
+                        NULL);
 }
 
 static const char *
diff --git a/src/devices/nm-device-vxlan.h b/src/core/devices/nm-device-vxlan.h
index 89683b02..4c4165e5 100644
--- a/src/devices/nm-device-vxlan.h
+++ b/src/core/devices/nm-device-vxlan.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013, 2014 Red Hat, Inc.
  */
diff --git a/src/devices/nm-device-wireguard.c b/src/core/devices/nm-device-wireguard.c
index 9bd23e65..fd057ded 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/core/devices/nm-device-wireguard.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Javier Arteaga <jarteaga@jbeta.is>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-wireguard.h"
 
@@ -22,8 +22,8 @@
 #include "nm-act-request.h"
 #include "dns/nm-dns-manager.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceWireGuard
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceWireGuard);
 
 /*****************************************************************************/
 
@@ -118,6 +118,9 @@ typedef struct {
     CList       lst_peers_head;
     GHashTable *peers;
 
+    /* counts the numbers of peers that are currently resolving. */
+    guint peers_resolving_cnt;
+
     gint64 resolve_next_try_at;
     gint64 link_config_last_at;
 
@@ -506,9 +509,52 @@ _peers_find(NMDeviceWireGuardPrivate *priv, NMWireGuardPeer *peer)
     return g_hash_table_lookup(priv->peers, &peer);
 }
 
+static guint
+_peers_resolving_cnt(NMDeviceWireGuardPrivate *priv)
+{
+    nm_assert(priv);
+#if NM_MORE_ASSERTS > 3
+    {
+        PeerData *peer_data;
+        guint     cnt = 0;
+
+        c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+            if (peer_data->ep_resolv.cancellable)
+                cnt++;
+        }
+        nm_assert(cnt == priv->peers_resolving_cnt);
+    }
+#endif
+
+    return priv->peers_resolving_cnt;
+}
+
+static void
+_peers_resolving_cnt_decrement(NMDeviceWireGuard *self)
+{
+    NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
+
+    nm_assert(priv);
+    nm_assert(priv->peers_resolving_cnt > 0);
+
+    priv->peers_resolving_cnt--;
+
+    nm_assert(_peers_resolving_cnt(priv) == priv->peers_resolving_cnt);
+
+    if (priv->peers_resolving_cnt == 0) {
+        if (nm_device_get_state(NM_DEVICE(self)) == NM_DEVICE_STATE_CONFIG) {
+            _LOGT(LOGD_DEVICE,
+                  "activation delayed to resolve DNS names of peers: completed, proceed now");
+            nm_device_activate_schedule_stage2_device_config(NM_DEVICE(self), FALSE);
+        }
+    }
+}
+
 static void
-_peers_remove(NMDeviceWireGuardPrivate *priv, PeerData *peer_data)
+_peers_remove(NMDeviceWireGuard *self, PeerData *peer_data)
 {
+    NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
+
     nm_assert(peer_data);
     nm_assert(g_hash_table_lookup(priv->peers, peer_data) == peer_data);
 
@@ -517,13 +563,16 @@ _peers_remove(NMDeviceWireGuardPrivate *priv, PeerData *peer_data)
 
     c_list_unlink_stale(&peer_data->lst_peers);
     nm_wireguard_peer_unref(peer_data->peer);
-    nm_clear_g_cancellable(&peer_data->ep_resolv.cancellable);
+    if (nm_clear_g_cancellable(&peer_data->ep_resolv.cancellable))
+        _peers_resolving_cnt_decrement(self);
     g_slice_free(PeerData, peer_data);
 
     if (c_list_is_empty(&priv->lst_peers_head)) {
         nm_clear_g_source(&priv->resolve_next_try_id);
         nm_clear_g_source(&priv->link_config_delayed_id);
     }
+
+    nm_assert(_peers_resolving_cnt(priv) == priv->peers_resolving_cnt);
 }
 
 static PeerData *
@@ -675,8 +724,9 @@ _peers_retry_in_msec(PeerData *peer_data, gboolean after_failure)
 static void
 _peers_resolve_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
-    NMDeviceWireGuard *self;
-    PeerData *         peer_data;
+    NMDeviceWireGuard *       self;
+    NMDeviceWireGuardPrivate *priv;
+    PeerData *                peer_data;
     gs_free_error GError *resolv_error = NULL;
     GList *               list;
     gboolean              changed = FALSE;
@@ -692,10 +742,13 @@ _peers_resolve_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 
     peer_data = user_data;
     self      = peer_data->self;
+    priv      = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
 
-    g_clear_object(&peer_data->ep_resolv.cancellable);
+    if (nm_clear_g_object(&peer_data->ep_resolv.cancellable))
+        _peers_resolving_cnt_decrement(self);
 
     nm_assert((!resolv_error) != (!list));
+    nm_assert(_peers_resolving_cnt(priv) == priv->peers_resolving_cnt);
 
 #define _retry_in_msec_to_string(retry_in_msec, s_retry)                               \
     ({                                                                                 \
@@ -797,8 +850,6 @@ _peers_resolve_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
     _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);
@@ -812,6 +863,7 @@ _peers_resolve_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 static void
 _peers_resolve_start(NMDeviceWireGuard *self, PeerData *peer_data)
 {
+    NMDeviceWireGuardPrivate *priv      = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
     gs_unref_object GResolver *resolver = NULL;
     const char *               host;
 
@@ -820,6 +872,7 @@ _peers_resolve_start(NMDeviceWireGuard *self, PeerData *peer_data)
     nm_assert(!peer_data->ep_resolv.cancellable);
 
     peer_data->ep_resolv.cancellable = g_cancellable_new();
+    priv->peers_resolving_cnt++;
 
     /* set a special next-try timestamp. It is positive, and indicates
      * that we are in the process of trying.
@@ -841,6 +894,8 @@ _peers_resolve_start(NMDeviceWireGuard *self, PeerData *peer_data)
           nm_wireguard_peer_get_public_key(peer_data->peer),
           host,
           nm_wireguard_peer_get_endpoint(peer_data->peer));
+
+    nm_assert(_peers_resolving_cnt(priv) == priv->peers_resolving_cnt);
 }
 
 static void
@@ -915,7 +970,8 @@ _peers_update(NMDeviceWireGuard *self,
     if (nm_sock_addr_union_cmp(&peer_data->ep_resolv.sockaddr, &sockaddr) != 0)
         changed = TRUE;
 
-    nm_clear_g_cancellable(&peer_data->ep_resolv.cancellable);
+    if (nm_clear_g_cancellable(&peer_data->ep_resolv.cancellable))
+        _peers_resolving_cnt_decrement(self);
 
     peer_data->ep_resolv = (PeerEndpointResolveData){
         .sockaddr          = sockaddr,
@@ -948,12 +1004,13 @@ _peers_update(NMDeviceWireGuard *self,
 }
 
 static void
-_peers_remove_all(NMDeviceWireGuardPrivate *priv)
+_peers_remove_all(NMDeviceWireGuard *self)
 {
-    PeerData *peer_data;
+    NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
+    PeerData *                peer_data;
 
     while ((peer_data = c_list_first_entry(&priv->lst_peers_head, PeerData, lst_peers)))
-        _peers_remove(priv, peer_data);
+        _peers_remove(self, peer_data);
 }
 
 static void
@@ -984,7 +1041,7 @@ _peers_update_all(NMDeviceWireGuard *self, NMSettingWireGuard *s_wg, gboolean *o
 
     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_remove(self, peer_data);
             peers_removed = TRUE;
         }
     }
@@ -1515,9 +1572,11 @@ link_config_delayed_resolver_cb(gpointer user_data)
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceSysIfaceState sys_iface_state;
-    NMDeviceStateReason   failure_reason;
-    NMActStageReturn      ret;
+    NMDeviceWireGuard *       self = NM_DEVICE_WIREGUARD(device);
+    NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
+    NMDeviceSysIfaceState     sys_iface_state;
+    NMDeviceStateReason       failure_reason;
+    NMActStageReturn          ret;
 
     sys_iface_state = nm_device_sys_iface_state_get(device);
 
@@ -1533,17 +1592,25 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
                                                                           : LINK_CONFIG_MODE_FULL,
                     &failure_reason);
 
-    if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME) {
-        /* this never fails. */
-        return NM_ACT_STAGE_RETURN_SUCCESS;
-    }
-
     if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+        if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME) {
+            /* this never fails. */
+            return NM_ACT_STAGE_RETURN_SUCCESS;
+        }
+
         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;
     }
 
+    nm_assert(NM_IN_SET(ret, NM_ACT_STAGE_RETURN_SUCCESS, NM_ACT_STAGE_RETURN_POSTPONE));
+
+    if (ret == NM_ACT_STAGE_RETURN_SUCCESS && _peers_resolving_cnt(priv) > 0u) {
+        _LOGT(LOGD_DEVICE,
+              "activation delayed to resolve DNS names of peers: resolving and waiting...");
+        return NM_ACT_STAGE_RETURN_POSTPONE;
+    }
+
     return ret;
 }
 
@@ -1732,7 +1799,7 @@ _device_cleanup(NMDeviceWireGuard *self)
 {
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
 
-    _peers_remove_all(priv);
+    _peers_remove_all(self);
 
     _secrets_cancel(self);
 
@@ -2005,16 +2072,16 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_WIREGUARD,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "WireGuard",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_WIREGUARD,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_WIREGUARD,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_WIREGUARD,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "WireGuard",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_WIREGUARD,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_WIREGUARD,
+                        NULL);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
diff --git a/src/devices/nm-device-wireguard.h b/src/core/devices/nm-device-wireguard.h
index f562f356..7e18bdba 100644
--- a/src/devices/nm-device-wireguard.h
+++ b/src/core/devices/nm-device-wireguard.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Javier Arteaga <jarteaga@jbeta.is>
  */
diff --git a/src/devices/nm-device-wpan.c b/src/core/devices/nm-device-wpan.c
index 195603dc..2f3b16ff 100644
--- a/src/devices/nm-device-wpan.c
+++ b/src/core/devices/nm-device-wpan.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-manager.h"
 #include "nm-device-wpan.h"
@@ -20,8 +20,8 @@
 #include "nm-setting-wpan.h"
 #include "nm-core-internal.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceWpan
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceWpan);
 
 /*****************************************************************************/
 
@@ -236,16 +236,16 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_WPAN,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "WPAN",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_WPAN,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_WPAN,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_WPAN,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "WPAN",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_WPAN,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_WPAN,
+                        NULL);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
diff --git a/src/devices/nm-device-wpan.h b/src/core/devices/nm-device-wpan.h
index 4bc4f243..969929ae 100644
--- a/src/devices/nm-device-wpan.h
+++ b/src/core/devices/nm-device-wpan.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk>
  */
diff --git a/src/devices/nm-device.c b/src/core/devices/nm-device.c
index 0be05f23..040dd0b4 100644
--- a/src/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -1,14 +1,13 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device.h"
 
-#include <netinet/in.h>
 #include <unistd.h>
 #include <sys/ioctl.h>
 #include <signal.h>
@@ -16,16 +15,20 @@
 #include <sys/wait.h>
 #include <arpa/inet.h>
 #include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <linux/if.h>
 #include <linux/if_addr.h>
-#include <linux/if_arp.h>
 #include <linux/rtnetlink.h>
+#include <linux/if_ether.h>
+#include <linux/if_infiniband.h>
 
 #include "nm-std-aux/unaligned.h"
 #include "nm-glib-aux/nm-dedup-multi.h"
 #include "nm-glib-aux/nm-random-utils.h"
 #include "systemd/nm-sd-utils-shared.h"
 
-#include "nm-libnm-core-intern/nm-ethtool-utils.h"
+#include "nm-base/nm-ethtool-base.h"
 #include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-device-private.h"
 #include "nm-l3cfg.h"
@@ -33,7 +36,7 @@
 #include "NetworkManagerUtils.h"
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "platform/nmp-object.h"
 #include "platform/nmp-rules-manager.h"
 #include "ndisc/nm-ndisc.h"
@@ -52,6 +55,7 @@
 #include "settings/nm-settings-connection.h"
 #include "settings/nm-settings.h"
 #include "nm-setting-ethtool.h"
+#include "nm-setting-ovs-external-ids.h"
 #include "nm-setting-user.h"
 #include "nm-auth-utils.h"
 #include "nm-keep-alive.h"
@@ -74,7 +78,6 @@
 #include "nm-device-wireguard.h"
 
 #include "nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDevice);
 
 /*****************************************************************************/
 
@@ -131,7 +134,7 @@ typedef struct {
     NMPlatformAsyncCallback callback;
     gpointer                callback_data;
     guint                   num_vfs;
-    NMTernary               autoprobe;
+    NMOptionBool            autoprobe;
 } SriovOp;
 
 typedef void (*AcdCallback)(NMDevice *, NMIP4Config **, gboolean);
@@ -202,11 +205,28 @@ struct _NMDeviceConnectivityHandle {
 typedef struct {
     int                     ifindex;
     NMEthtoolFeatureStates *features;
-    NMTernary               requested[_NM_ETHTOOL_ID_FEATURE_NUM];
+    NMOptionBool            requested[_NM_ETHTOOL_ID_FEATURE_NUM];
     NMEthtoolCoalesceState *coalesce;
     NMEthtoolRingState *    ring;
 } EthtoolState;
 
+typedef enum {
+    RESOLVER_WAIT_ADDRESS = 0,
+    RESOLVER_IN_PROGRESS,
+    RESOLVER_DONE,
+} ResolverState;
+
+typedef struct {
+    ResolverState state;
+    GResolver *   resolver;
+    GInetAddress *address;
+    GCancellable *cancellable;
+    char *        hostname;
+    NMDevice *    device;
+    guint         timeout_id; /* Used when waiting for the address */
+    int           addr_family;
+} HostnameResolver;
+
 /*****************************************************************************/
 
 enum {
@@ -219,6 +239,7 @@ enum {
     REMOVED,
     RECHECK_AUTO_ACTIVATE,
     RECHECK_ASSUME,
+    DNS_LOOKUP_DONE,
     LAST_SIGNAL,
 };
 static guint signals[LAST_SIGNAL] = {0};
@@ -326,6 +347,14 @@ typedef struct _NMDevicePrivate {
     int auth_retries;
 
     union {
+        struct {
+            HostnameResolver *hostname_resolver_6;
+            HostnameResolver *hostname_resolver_4;
+        };
+        HostnameResolver *hostname_resolver_x[2];
+    };
+
+    union {
         const guint8 hw_addr_len; /* read-only */
         guint8       hw_addr_len_;
     };
@@ -357,6 +386,8 @@ typedef struct _NMDevicePrivate {
     bool  assume_state_guess_assume : 1;
     char *assume_state_connection_uuid;
 
+    guint64 udi_id;
+
     GHashTable *available_connections;
     char *      hw_addr;
     char *      hw_addr_perm;
@@ -411,6 +442,7 @@ typedef struct _NMDevicePrivate {
     guint             carrier_defer_id;
     guint             carrier_wait_id;
     gulong            config_changed_id;
+    gulong            ifindex_changed_id;
     guint32           mtu;
     guint32           ip6_mtu;
     guint32           mtu_initial;
@@ -611,6 +643,7 @@ typedef struct _NMDevicePrivate {
     /* master interface for bridge/bond/team slave */
     NMDevice *master;
     gulong    master_ready_id;
+    int       master_ifindex;
 
     /* slave management */
     CList slaves; /* list of SlaveInfo */
@@ -695,6 +728,8 @@ static guint32 default_route_metric_penalty_get(NMDevice *self, int addr_family)
 
 static guint _prop_get_ipv4_dad_timeout(NMDevice *self);
 
+static NMIP6Config *dad6_get_pending_addresses(NMDevice *self);
+
 static void   _carrier_wait_check_queued_act_request(NMDevice *self);
 static gint64 _get_carrier_wait_ms(NMDevice *self);
 
@@ -726,23 +761,10 @@ static void concheck_update_state(NMDevice *          self,
                                   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 sriov_op_cb(GError *error, gpointer user_data);
 
-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,
-};
+static void device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self);
+static gboolean device_link_changed(NMDevice *self);
 
 /*****************************************************************************/
 
@@ -879,6 +901,22 @@ static NM_UTILS_LOOKUP_STR_DEFINE(mtu_source_to_str,
 
 /*****************************************************************************/
 
+static void
+_hostname_resolver_free(HostnameResolver *resolver)
+{
+    if (!resolver)
+        return;
+
+    nm_clear_g_source(&resolver->timeout_id);
+    nm_clear_g_cancellable(&resolver->cancellable);
+    nm_g_object_unref(resolver->resolver);
+    nm_g_object_unref(resolver->address);
+    g_free(resolver->hostname);
+    nm_g_slice_free(resolver);
+}
+
+/*****************************************************************************/
+
 static NMSettingIP6ConfigPrivacy
 _ip6_privacy_clamp(NMSettingIP6ConfigPrivacy use_tempaddr)
 {
@@ -1294,7 +1332,7 @@ _prop_get_ipvx_route_table(NMDevice *self, int addr_family)
 
     /* the route table setting affects how we sync routes. We shall
      * not change it while the device is active, hence, cache it. */
-    if (addr_family == AF_INET) {
+    if (NM_IS_IPv4(addr_family)) {
         if (priv->v4_route_table_initialized)
             return priv->v4_route_table;
     } else {
@@ -1312,7 +1350,7 @@ _prop_get_ipvx_route_table(NMDevice *self, int addr_family)
         gint64 v;
 
         v = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
-                                                        addr_family == AF_INET
+                                                        NM_IS_IPv4(addr_family)
                                                             ? NM_CON_DEFAULT("ipv4.route-table")
                                                             : NM_CON_DEFAULT("ipv6.route-table"),
                                                         self,
@@ -1348,7 +1386,7 @@ _prop_get_ipvx_route_table(NMDevice *self, int addr_family)
     if (klass->coerce_route_table)
         route_table = klass->coerce_route_table(self, addr_family, route_table, is_user_config);
 
-    if (addr_family == AF_INET) {
+    if (NM_IS_IPv4(addr_family)) {
         priv->v4_route_table_initialized = TRUE;
         priv->v4_route_table             = route_table;
     } else {
@@ -1358,7 +1396,7 @@ _prop_get_ipvx_route_table(NMDevice *self, int addr_family)
 
     _LOGT(LOGD_DEVICE,
           "ipv%c.route-table = %u%s",
-          addr_family == AF_INET ? '4' : '6',
+          nm_utils_addr_family_to_char(addr_family),
           (guint)(route_table ?: RT_TABLE_MAIN),
           route_table != 0u ? "" : " (policy routing not enabled)");
 
@@ -1437,7 +1475,7 @@ _prop_get_ipvx_dhcp_timeout(NMDevice *self, int addr_family)
         goto out;
 
     timeout = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
-                                                          addr_family == AF_INET
+                                                          NM_IS_IPv4(addr_family)
                                                               ? NM_CON_DEFAULT("ipv4.dhcp-timeout")
                                                               : NM_CON_DEFAULT("ipv6.dhcp-timeout"),
                                                           self,
@@ -1468,6 +1506,7 @@ out:
  * @self: the #NMDevice
  * @addr_family: the address family
  * @connection: the connection
+ * @log_silent: whether to log the result.
  * @out_is_explicit: on return, %TRUE if the user set a valid IAID in
  *   the connection or in global configuration; %FALSE if the connection
  *   property was empty and no valid global configuration was provided.
@@ -1478,8 +1517,10 @@ static guint32
 _prop_get_ipvx_dhcp_iaid(NMDevice *    self,
                          int           addr_family,
                          NMConnection *connection,
+                         gboolean      log_silent,
                          gboolean *    out_is_explicit)
 {
+    const int          IS_IPv4 = NM_IS_IPv4(addr_family);
     NMSettingIPConfig *s_ip;
     const char *       iaid_str;
     gs_free char *     iaid_str_free = NULL;
@@ -1493,18 +1534,19 @@ _prop_get_ipvx_dhcp_iaid(NMDevice *    self,
     if (!iaid_str) {
         iaid_str_free = nm_config_data_get_connection_default(
             NM_CONFIG_GET_DATA,
-            addr_family == AF_INET ? NM_CON_DEFAULT("ipv4.dhcp-iaid")
-                                   : NM_CON_DEFAULT("ipv6.dhcp-iaid"),
+            IS_IPv4 ? NM_CON_DEFAULT("ipv4.dhcp-iaid") : NM_CON_DEFAULT("ipv6.dhcp-iaid"),
             self);
         iaid_str = iaid_str_free;
         if (!iaid_str) {
             iaid_str    = NM_IAID_IFNAME;
             is_explicit = FALSE;
         } else if (!_nm_utils_iaid_verify(iaid_str, NULL)) {
-            _LOGW(LOGD_DEVICE,
-                  "invalid global default '%s' for ipv%c.dhcp-iaid",
-                  iaid_str,
-                  nm_utils_addr_family_to_char(addr_family));
+            if (!log_silent) {
+                _LOGW(LOGD_DEVICE,
+                      "invalid global default '%s' for ipv%c.dhcp-iaid",
+                      iaid_str,
+                      nm_utils_addr_family_to_char(addr_family));
+            }
             iaid_str    = NM_IAID_IFNAME;
             is_explicit = FALSE;
         }
@@ -1578,23 +1620,25 @@ _prop_get_ipvx_dhcp_iaid(NMDevice *    self,
 
 out_fail:
     nm_assert(fail_reason);
-    _LOGW(addr_family == AF_INET ? (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4)
-                                 : (LOGD_DEVICE | LOGD_DHCP6 | LOGD_IP6),
-          "ipv%c.dhcp-iaid: failure to generate IAID: %s. Using interface-name based IAID",
-          nm_utils_addr_family_to_char(addr_family),
-          fail_reason);
+    if (!log_silent) {
+        _LOGW(LOGD_DEVICE | LOGD_DHCPX(IS_IPv4) | LOGD_IPX(IS_IPv4),
+              "ipv%c.dhcp-iaid: failure to generate IAID: %s. Using interface-name based IAID",
+              nm_utils_addr_family_to_char(addr_family),
+              fail_reason);
+    }
     is_explicit = FALSE;
     iface       = nm_device_get_ip_iface(self);
     iaid        = nm_utils_create_dhcp_iaid(TRUE, (const guint8 *) iface, strlen(iface));
 out_good:
-    _LOGD(addr_family == AF_INET ? (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4)
-                                 : (LOGD_DEVICE | LOGD_DHCP6 | LOGD_IP6),
-          "ipv%c.dhcp-iaid: using %u (0x%08x) IAID (str: '%s', explicit %d)",
-          nm_utils_addr_family_to_char(addr_family),
-          iaid,
-          iaid,
-          iaid_str,
-          is_explicit);
+    if (!log_silent) {
+        _LOGD(LOGD_DEVICE | LOGD_DHCPX(IS_IPv4) | LOGD_IPX(IS_IPv4),
+              "ipv%c.dhcp-iaid: using %u (0x%08x) IAID (str: '%s', explicit %d)",
+              nm_utils_addr_family_to_char(addr_family),
+              iaid,
+              iaid,
+              iaid_str,
+              is_explicit);
+    }
     NM_SET_OUT(out_is_explicit, is_explicit);
     return iaid;
 }
@@ -1622,8 +1666,8 @@ _prop_get_ipvx_dhcp_hostname_flags(NMDevice *self, int addr_family)
 
     flags = nm_config_data_get_connection_default_int64(
         NM_CONFIG_GET_DATA,
-        addr_family == AF_INET ? NM_CON_DEFAULT("ipv4.dhcp-hostname-flags")
-                               : NM_CON_DEFAULT("ipv6.dhcp-hostname-flags"),
+        NM_IS_IPv4(addr_family) ? NM_CON_DEFAULT("ipv4.dhcp-hostname-flags")
+                                : NM_CON_DEFAULT("ipv6.dhcp-hostname-flags"),
         self,
         0,
         NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS,
@@ -1631,9 +1675,9 @@ _prop_get_ipvx_dhcp_hostname_flags(NMDevice *self, int addr_family)
 
     if (!_nm_utils_validate_dhcp_hostname_flags(flags, addr_family, &error)) {
         _LOGW(LOGD_DEVICE,
-              "invalid global default value 0x%x for ipv%d.%s: %s",
+              "invalid global default value 0x%x for ipv%c.%s: %s",
               (guint) flags,
-              addr_family == AF_INET ? 4 : 6,
+              nm_utils_addr_family_to_char(addr_family),
               NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS,
               error->message);
         flags = NM_DHCP_HOSTNAME_FLAG_NONE;
@@ -1642,7 +1686,7 @@ _prop_get_ipvx_dhcp_hostname_flags(NMDevice *self, int addr_family)
     if (flags != NM_DHCP_HOSTNAME_FLAG_NONE)
         return flags;
 
-    if (addr_family == AF_INET)
+    if (NM_IS_IPv4(addr_family))
         return NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP4;
     else
         return NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP6;
@@ -1752,12 +1796,37 @@ _prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *
     }
 
     if (nm_streq(client_id, "duid")) {
-        guint32 iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET, connection, NULL);
+        guint32 iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET, connection, FALSE, NULL);
 
         result = nm_utils_dhcp_client_id_systemd_node_specific(iaid);
         goto out_good;
     }
 
+    if (nm_streq(client_id, "ipv6-duid")) {
+        gs_unref_bytes GBytes *duid = NULL;
+        gboolean               iaid_is_explicit;
+        guint32                iaid;
+        const guint8 *         duid_arr;
+        gsize                  duid_len;
+
+        iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET, connection, FALSE, &iaid_is_explicit);
+        if (!iaid_is_explicit)
+            iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, FALSE, &iaid_is_explicit);
+
+        duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, NULL);
+
+        nm_assert(duid);
+
+        duid_arr = g_bytes_get_data(duid, &duid_len);
+
+        nm_assert(duid_arr);
+        nm_assert(duid_len >= 2u + 1u);
+        nm_assert(duid_len <= 2u + 128u);
+
+        result = nm_utils_dhcp_client_id_duid(iaid, duid_arr, duid_len);
+        goto out_good;
+    }
+
     if (nm_streq(client_id, "stable")) {
         nm_auto_free_checksum GChecksum *sum = NULL;
         guint8                           digest[NM_UTILS_CHECKSUM_LENGTH_SHA1];
@@ -2338,8 +2407,8 @@ nm_device_ip_config_new(NMDevice *self, int addr_family)
 {
     nm_assert_addr_family(addr_family);
 
-    return addr_family == AF_INET ? (gpointer) nm_device_ip4_config_new(self)
-                                  : (gpointer) nm_device_ip6_config_new(self);
+    return NM_IS_IPv4(addr_family) ? (gpointer) nm_device_ip4_config_new(self)
+                                   : (gpointer) nm_device_ip6_config_new(self);
 }
 
 NML3ConfigData *
@@ -2754,7 +2823,7 @@ static void
 _set_ip_state(NMDevice *self, int addr_family, NMDeviceIPState new_state)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     nm_assert_addr_family(addr_family);
 
@@ -2775,7 +2844,7 @@ _set_ip_state(NMDevice *self, int addr_family, NMDeviceIPState new_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_IS_IPv4(addr_family)
                                                ? NM_ACTIVATION_STATE_FLAG_IP4_READY
                                                : NM_ACTIVATION_STATE_FLAG_IP6_READY);
     }
@@ -2820,6 +2889,8 @@ _set_ifindex(NMDevice *self, int ifindex, gboolean is_ip_ifindex)
     if (!is_ip_ifindex)
         _notify(self, PROP_IFINDEX);
 
+    if (priv->manager)
+        nm_manager_emit_device_ifindex_changed(priv->manager, self);
     return TRUE;
 }
 
@@ -3559,7 +3630,7 @@ static gboolean
 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);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     /* currently we don't differentiate between IPv4 and IPv6 when detecting
      * connectivity. */
@@ -3575,10 +3646,8 @@ default_route_metric_penalty_get(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    nm_assert_addr_family(addr_family);
-
-    if (addr_family == AF_INET ? priv->default_route_metric_penalty_ip4_has
-                               : priv->default_route_metric_penalty_ip6_has)
+    if (NM_IS_IPv4(addr_family) ? priv->default_route_metric_penalty_ip4_has
+                                : priv->default_route_metric_penalty_ip6_has)
         return 20000;
     return 0;
 }
@@ -3612,8 +3681,8 @@ nm_device_get_route_metric(NMDevice *self, int addr_family)
     /* 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 ? NM_CON_DEFAULT("ipv4.route-metric")
-                                          : NM_CON_DEFAULT("ipv6.route-metric");
+    property     = NM_IS_IPv4(addr_family) ? 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,
@@ -3644,7 +3713,8 @@ nm_device_get_route_table(NMDevice *self, int addr_family)
 static NMIPRouteTableSyncMode
 _get_route_table_sync_mode_stateful(NMDevice *self, int addr_family)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
     NMDedupMultiIter ipconf_iter;
     gboolean         all_sync_now;
     gboolean         all_sync_eff;
@@ -3652,25 +3722,14 @@ _get_route_table_sync_mode_stateful(NMDevice *self, int addr_family)
     all_sync_now = _prop_get_ipvx_route_table(self, addr_family) != 0u;
 
     if (!all_sync_now) {
+        const NMPlatformIPRoute *route;
+
         /* If there's a local route switch to all-sync in order
          * to properly manage the local table */
-        if (addr_family == AF_INET) {
-            const NMPlatformIP4Route *route;
-
-            nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, priv->con_ip_config_4, &route) {
-                if (nm_platform_route_type_uncoerce(route->type_coerced) == RTN_LOCAL) {
-                    all_sync_now = TRUE;
-                    break;
-                }
-            }
-        } else {
-            const NMPlatformIP6Route *route;
-
-            nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, priv->con_ip_config_6, &route) {
-                if (nm_platform_route_type_uncoerce(route->type_coerced) == RTN_LOCAL) {
-                    all_sync_now = TRUE;
-                    break;
-                }
+        nm_ip_config_iter_ip_route_for_each (&ipconf_iter, priv->con_ip_config_x[IS_IPv4], &route) {
+            if (nm_platform_route_type_uncoerce(route->type_coerced) == RTN_LOCAL) {
+                all_sync_now = TRUE;
+                break;
             }
         }
     }
@@ -3685,13 +3744,13 @@ _get_route_table_sync_mode_stateful(NMDevice *self, int addr_family)
          * The purpose of this is to support reapply of route-table (and thus the
          * all-sync mode). If reapply toggles from all-sync to no-all-sync, we must
          * sync one last time. */
-        if (addr_family == AF_INET)
+        if (NM_IS_IPv4(addr_family))
             all_sync_eff = priv->v4_route_table_all_sync_before;
         else
             all_sync_eff = priv->v6_route_table_all_sync_before;
     }
 
-    if (addr_family == AF_INET)
+    if (NM_IS_IPv4(addr_family))
         priv->v4_route_table_all_sync_before = all_sync_now;
     else
         priv->v6_route_table_all_sync_before = all_sync_now;
@@ -3762,8 +3821,11 @@ get_type_description(NMDevice *self)
         gs_free char *s = NULL;
 
         typename = G_OBJECT_TYPE_NAME(self);
-        if (g_str_has_prefix(typename, "NMDevice"))
+        if (g_str_has_prefix(typename, "NMDevice")) {
             typename += 8;
+            if (nm_streq(typename, "Veth"))
+                typename = "Ethernet";
+        }
         s                                     = g_ascii_strdown(typename, -1);
         klass->default_type_description       = g_intern_string(s);
         klass->default_type_description_klass = klass;
@@ -3943,7 +4005,7 @@ 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);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     /* we always cancel whatever was pending. */
     if (nm_clear_g_source(&priv->concheck_x[IS_IPv4].p_cur_id))
@@ -3998,7 +4060,7 @@ concheck_periodic_schedule_set(NMDevice *self, int addr_family, ConcheckSchedule
     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);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     if (priv->concheck_x[IS_IPv4].p_max_interval == 0) {
         /* periodic check is disabled. Nothing to do. */
@@ -4158,7 +4220,7 @@ 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);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     new_interval = nm_connectivity_get_interval(concheck_get_mgr(self));
 
@@ -4202,7 +4264,7 @@ concheck_update_state(NMDevice *          self,
                       gboolean            allow_periodic_bump)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     /* @state is a result of the connectivity check. We only expect a precise
      * number of possible values. */
@@ -4283,14 +4345,14 @@ 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;
+    const int      IS_IPv4 = NM_IS_IPv4(addr_family);
 
     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))) {
+    if ((IS_IPv4 && nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
+        || (!IS_IPv4 && 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;
@@ -4307,7 +4369,7 @@ nm_device_get_effective_ip_config_method(NMDevice *self, int addr_family)
 static void
 concheck_handle_complete(NMDeviceConnectivityHandle *handle, GError *error)
 {
-    const gboolean IS_IPv4 = (handle->addr_family == AF_INET);
+    const int IS_IPv4 = NM_IS_IPv4(handle->addr_family);
 
     /* The moment we invoke the callback, we unlink it. It signals
      * that @handle is handled -- as far as the callee of callback
@@ -4495,7 +4557,7 @@ concheck_start(NMDevice *                   self,
           (long long unsigned) handle->seq,
           is_periodic ? ", periodic-check" : "");
 
-    if (addr_family == AF_INET && !priv->concheck_rp_filter_checked) {
+    if (NM_IS_IPv4(addr_family) && !priv->concheck_rp_filter_checked) {
         if ((ifname = nm_device_get_ip_iface_from_platform(self))) {
             gboolean due_to_all;
             int      val;
@@ -4560,17 +4622,15 @@ NMConnectivityState
 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);
 
     priv = NM_DEVICE_GET_PRIVATE(self);
 
     switch (addr_family) {
     case AF_INET:
     case AF_INET6:
-        return priv->concheck_x[IS_IPv4].state;
+        return priv->concheck_x[NM_IS_IPv4(addr_family)].state;
     default:
         nm_assert(addr_family == AF_UNSPEC);
         return NM_MAX_WITH_CMP(nm_connectivity_state_cmp,
@@ -4653,10 +4713,10 @@ nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *co
      */
     if (success) {
         if (priv->ip_state_4 == NM_DEVICE_IP_STATE_WAIT)
-            nm_device_activate_stage3_ip4_start(self);
+            nm_device_activate_stage3_ip_start(self, AF_INET);
 
         if (priv->ip_state_6 == NM_DEVICE_IP_STATE_WAIT)
-            nm_device_activate_stage3_ip6_start(self);
+            nm_device_activate_stage3_ip_start(self, AF_INET6);
     }
 
     /* Since slave devices don't have their own IP configuration,
@@ -4716,7 +4776,7 @@ nm_device_master_release_one_slave(NMDevice *          self,
     nm_assert(slave == info->slave);
 
     /* first, let subclasses handle the release ... */
-    if (info->slave_is_enslaved || force)
+    if (info->slave_is_enslaved || nm_device_sys_iface_state_is_external(slave) || force)
         NM_DEVICE_GET_CLASS(self)->release_slave(self, slave, configure);
 
     /* raise notifications about the release, including clearing is_enslaved. */
@@ -4887,9 +4947,9 @@ carrier_changed(NMDevice *self, gboolean carrier)
             /* If needed, also resume IP configuration that is
              * waiting for carrier. */
             if (nm_device_activate_ip4_state_in_wait(self))
-                nm_device_activate_stage3_ip4_start(self);
+                nm_device_activate_stage3_ip_start(self, AF_INET);
             if (nm_device_activate_ip6_state_in_wait(self))
-                nm_device_activate_stage3_ip6_start(self);
+                nm_device_activate_stage3_ip_start(self, AF_INET6);
             return;
         }
         /* fall-through and change state of device */
@@ -5051,7 +5111,7 @@ device_recheck_slave_status(NMDevice *self, const NMPlatformLink *plink)
     g_return_if_fail(plink);
 
     if (plink->master <= 0)
-        return;
+        goto out;
 
     master                  = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, plink->master);
     plink_master            = nm_platform_link_get(nm_device_get_platform(self), plink->master);
@@ -5060,15 +5120,17 @@ device_recheck_slave_status(NMDevice *self, const NMPlatformLink *plink)
     if (master == NULL && plink_master && nm_streq0(plink_master->name, "ovs-system")
         && plink_master->type == NM_LINK_TYPE_OPENVSWITCH) {
         _LOGD(LOGD_DEVICE, "the device claimed by openvswitch");
-        return;
+        goto out;
     }
 
+    priv->master_ifindex = plink->master;
+
     if (priv->master) {
         if (plink->master > 0 && plink->master == nm_device_get_ifindex(priv->master)) {
             /* call add-slave again. We expect @self already to be added to
              * the master, but this also triggers a recheck-assume. */
             nm_device_master_add_slave(priv->master, self, FALSE);
-            return;
+            goto out;
         }
 
         nm_device_master_release_one_slave(priv->master,
@@ -5078,43 +5140,72 @@ device_recheck_slave_status(NMDevice *self, const NMPlatformLink *plink)
                                            NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
     }
 
-    if (master && NM_DEVICE_GET_CLASS(master)->enslave_slave)
+    if (master && NM_DEVICE_GET_CLASS(master)->enslave_slave) {
         nm_device_master_add_slave(master, self, FALSE);
-    else if (master) {
-        _LOGI(LOGD_DEVICE,
+        goto out;
+    }
+
+    if (master) {
+        _LOGD(LOGD_DEVICE,
               "enslaved to non-master-type device %s; ignoring",
               nm_device_get_iface(master));
     } else {
-        _LOGW(LOGD_DEVICE,
+        _LOGD(LOGD_DEVICE,
               "enslaved to unknown device %d (%s%s%s)",
               plink->master,
               NM_PRINT_FMT_QUOTED(plink_master, "\"", plink_master->name, "\"", "??"));
     }
+    if (!priv->ifindex_changed_id) {
+        priv->ifindex_changed_id = g_signal_connect(nm_device_get_manager(self),
+                                                    NM_MANAGER_DEVICE_IFINDEX_CHANGED,
+                                                    G_CALLBACK(device_ifindex_changed_cb),
+                                                    self);
+    }
+    return;
+
+out:
+    nm_clear_g_signal_handler(nm_device_get_manager(self), &priv->ifindex_changed_id);
+}
+
+static void
+device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    if (priv->master_ifindex != nm_device_get_ifindex(device_changed))
+        return;
+
+    _LOGD(LOGD_DEVICE,
+          "master %s with ifindex %d appeared",
+          nm_device_get_iface(device_changed),
+          nm_device_get_ifindex(device_changed));
+    if (!priv->device_link_changed_id)
+        priv->device_link_changed_id = g_idle_add((GSourceFunc) device_link_changed, self);
 }
 
 static void
 ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
 {
-    NMDevicePrivate *            priv = NM_DEVICE_GET_PRIVATE(self);
-    gint32                       now;
-    GArray *                     addresses, *dns_servers, *dns_domains;
-    guint                        len, i;
+    NMDevicePrivate *priv                    = NM_DEVICE_GET_PRIVATE(self);
+    gs_unref_array GArray *addresses         = NULL;
+    gs_unref_array GArray *dns_servers       = NULL;
+    gs_unref_array GArray *      dns_domains = NULL;
+    guint                        len;
+    guint                        i;
     const NMDedupMultiHeadEntry *head_entry;
     NMDedupMultiIter             ipconf_iter;
 
     if (nm_ndisc_get_node_type(ndisc) != NM_NDISC_NODE_TYPE_ROUTER)
         return;
 
-    now = nm_utils_get_monotonic_timestamp_sec();
-
     head_entry = nm_ip6_config_lookup_addresses(priv->ip_config_6);
     addresses =
         g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscAddress), head_entry ? head_entry->len : 0);
     nm_dedup_multi_iter_for_each (&ipconf_iter, head_entry) {
         const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS(ipconf_iter.current->obj);
         NMNDiscAddress *            ndisc_addr;
-        guint32                     lifetime, preferred;
-        gint32                      base;
+        guint32                     lifetime;
+        guint32                     preferred;
 
         if (IN6_IS_ADDR_UNSPECIFIED(&addr->address) || IN6_IS_ADDR_LINKLOCAL(&addr->address))
             continue;
@@ -5125,31 +5216,21 @@ ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
         if (addr->plen != 64)
             continue;
 
-        /* resolve the timestamps relative to a new base.
-         *
-         * Note that for convenience, platform @addr might have timestamp and/or
-         * lifetime unset. We don't allow that flexibility for ndisc and require
-         * well defined timestamps. */
-        if (addr->timestamp) {
-            nm_assert(addr->timestamp < G_MAXINT32);
-            base = addr->timestamp;
-        } else
-            base = now;
-
         lifetime = nm_utils_lifetime_get(addr->timestamp,
                                          addr->lifetime,
                                          addr->preferred,
-                                         base,
+                                         NM_NDISC_EXPIRY_BASE_TIMESTAMP / 1000,
                                          &preferred);
         if (!lifetime)
             continue;
 
         g_array_set_size(addresses, addresses->len + 1);
-        ndisc_addr            = &g_array_index(addresses, NMNDiscAddress, addresses->len - 1);
-        ndisc_addr->address   = addr->address;
-        ndisc_addr->timestamp = base;
-        ndisc_addr->lifetime  = lifetime;
-        ndisc_addr->preferred = preferred;
+        ndisc_addr          = &g_array_index(addresses, NMNDiscAddress, addresses->len - 1);
+        ndisc_addr->address = addr->address;
+        ndisc_addr->expiry_msec =
+            _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, lifetime);
+        ndisc_addr->expiry_preferred_msec =
+            _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, preferred);
     }
 
     len         = nm_ip6_config_get_num_nameservers(priv->ip_config_6);
@@ -5159,10 +5240,10 @@ ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
         const struct in6_addr *nameserver = nm_ip6_config_get_nameserver(priv->ip_config_6, i);
         NMNDiscDNSServer *     ndisc_nameserver;
 
-        ndisc_nameserver            = &g_array_index(dns_servers, NMNDiscDNSServer, i);
-        ndisc_nameserver->address   = *nameserver;
-        ndisc_nameserver->timestamp = now;
-        ndisc_nameserver->lifetime  = NM_NDISC_ROUTER_LIFETIME;
+        ndisc_nameserver          = &g_array_index(dns_servers, NMNDiscDNSServer, i);
+        ndisc_nameserver->address = *nameserver;
+        ndisc_nameserver->expiry_msec =
+            _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, NM_NDISC_ROUTER_LIFETIME);
     }
 
     len         = nm_ip6_config_get_num_searches(priv->ip_config_6);
@@ -5172,16 +5253,13 @@ ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
         const char *      search = nm_ip6_config_get_search(priv->ip_config_6, i);
         NMNDiscDNSDomain *ndisc_search;
 
-        ndisc_search            = &g_array_index(dns_domains, NMNDiscDNSDomain, i);
-        ndisc_search->domain    = (char *) search;
-        ndisc_search->timestamp = now;
-        ndisc_search->lifetime  = NM_NDISC_ROUTER_LIFETIME;
+        ndisc_search         = &g_array_index(dns_domains, NMNDiscDNSDomain, i);
+        ndisc_search->domain = (char *) search;
+        ndisc_search->expiry_msec =
+            _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, NM_NDISC_ROUTER_LIFETIME);
     }
 
     nm_ndisc_set_config(ndisc, addresses, dns_servers, dns_domains);
-    g_array_unref(addresses);
-    g_array_unref(dns_servers);
-    g_array_unref(dns_domains);
 }
 
 static void
@@ -5776,7 +5854,7 @@ sriov_op_queue_op(NMDevice *self, SriovOp *op)
 static void
 sriov_op_queue(NMDevice *              self,
                guint                   num_vfs,
-               NMTernary               autoprobe,
+               NMOptionBool            autoprobe,
                NMPlatformAsyncCallback callback,
                gpointer                callback_data)
 {
@@ -5825,7 +5903,7 @@ device_init_static_sriov_num_vfs(NMDevice *self)
                                                  NULL);
         num_vfs = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXINT32, -1);
         if (num_vfs >= 0)
-            sriov_op_queue(self, num_vfs, NM_TERNARY_DEFAULT, NULL, NULL);
+            sriov_op_queue(self, num_vfs, NM_OPTION_BOOL_DEFAULT, NULL, NULL);
     }
 }
 
@@ -5883,7 +5961,6 @@ realize_start_setup(NMDevice *            self,
     NMDevicePrivate *    priv;
     NMDeviceClass *      klass;
     NMPlatform *         platform;
-    static guint32       id           = 0;
     NMDeviceCapabilities capabilities = 0;
     NMConfig *           config;
     guint                real_rate;
@@ -5969,7 +6046,12 @@ realize_start_setup(NMDevice *            self,
 
     if (!priv->udi) {
         /* Use a placeholder UDI until we get a real one */
-        priv->udi = g_strdup_printf("/virtual/device/placeholder/%d", id++);
+        if (priv->udi_id == 0) {
+            static guint64 udi_id_counter = 0;
+
+            priv->udi_id = ++udi_id_counter;
+        }
+        priv->udi = g_strdup_printf("/virtual/device/placeholder/%" G_GUINT64_FORMAT, priv->udi_id);
         _notify(self, PROP_UDI);
     }
 
@@ -6153,6 +6235,8 @@ nm_device_unrealize(NMDevice *self, gboolean remove_resources, GError **error)
     if (nm_clear_g_free(&priv->ip_iface_))
         _notify(self, PROP_IP_IFACE);
 
+    priv->master_ifindex = 0;
+
     _set_mtu(self, 0);
 
     if (priv->driver_version) {
@@ -6193,6 +6277,7 @@ nm_device_unrealize(NMDevice *self, gboolean remove_resources, GError **error)
     _notify(self, PROP_CAPABILITIES);
 
     nm_clear_g_signal_handler(nm_config_get(), &priv->config_changed_id);
+    nm_clear_g_signal_handler(priv->manager, &priv->ifindex_changed_id);
 
     priv->real = FALSE;
     _notify(self, PROP_REAL);
@@ -6444,7 +6529,7 @@ nm_device_master_check_slave_physical_port(NMDevice *self, NMDevice *slave, NMLo
 }
 
 /* release all slaves */
-static void
+void
 nm_device_master_release_slaves(NMDevice *self)
 {
     NMDevicePrivate *   priv = NM_DEVICE_GET_PRIVATE(self);
@@ -7659,7 +7744,7 @@ static void
 activation_source_clear(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     if (priv->activation_source_id_x[IS_IPv4] != 0) {
         _LOGD(LOGD_DEVICE,
@@ -7676,7 +7761,7 @@ static gboolean
 activation_source_handle_cb(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *    priv;
-    const gboolean       IS_IPv4 = (addr_family == AF_INET);
+    const int            IS_IPv4 = NM_IS_IPv4(addr_family);
     ActivationHandleFunc activation_source_func;
     guint                activation_source_id;
 
@@ -7726,7 +7811,7 @@ static void
 activation_source_schedule(NMDevice *self, ActivationHandleFunc func, int addr_family)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
     guint            new_id  = 0;
 
     if (priv->activation_source_id_x[IS_IPv4] != 0
@@ -7769,7 +7854,7 @@ static void
 activation_source_invoke_sync(NMDevice *self, ActivationHandleFunc func, int addr_family)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     if (priv->activation_source_id_x[IS_IPv4] == 0) {
         _LOGD(LOGD_DEVICE,
@@ -7923,13 +8008,13 @@ sriov_vf_config_to_platform(NMDevice *self, NMSriovVF *vf, GError **error)
 }
 
 static void
-sriov_params_cb(GError *error, gpointer data)
+sriov_params_cb(GError *error, gpointer user_data)
 {
     NMDevice *       self;
     NMDevicePrivate *priv;
     nm_auto_freev NMPlatformVF **plat_vfs = NULL;
 
-    nm_utils_user_data_unpack(data, &self, &plat_vfs);
+    nm_utils_user_data_unpack(user_data, &self, &plat_vfs);
 
     if (nm_utils_error_is_cancelled_or_disposing(error))
         return;
@@ -7998,7 +8083,8 @@ activate_stage1_device_prepare(NMDevice *self)
             gs_free_error GError *error           = NULL;
             NMSriovVF *           vf;
             NMTernary             autoprobe;
-            guint                 i, num;
+            guint                 num;
+            guint                 i;
 
             autoprobe = nm_setting_sriov_get_autoprobe_drivers(s_sriov);
             if (autoprobe == NM_TERNARY_DEFAULT) {
@@ -8006,9 +8092,9 @@ activate_stage1_device_prepare(NMDevice *self)
                     NM_CONFIG_GET_DATA,
                     NM_CON_DEFAULT("sriov.autoprobe-drivers"),
                     self,
-                    NM_TERNARY_FALSE,
-                    NM_TERNARY_TRUE,
-                    NM_TERNARY_TRUE);
+                    NM_OPTION_BOOL_FALSE,
+                    NM_OPTION_BOOL_TRUE,
+                    NM_OPTION_BOOL_TRUE);
             }
 
             num      = nm_setting_sriov_get_num_vfs(s_sriov);
@@ -8035,7 +8121,7 @@ activate_stage1_device_prepare(NMDevice *self)
              */
             sriov_op_queue(self,
                            nm_setting_sriov_get_total_vfs(s_sriov),
-                           autoprobe,
+                           NM_TERNARY_TO_OPTION_BOOL(autoprobe),
                            sriov_params_cb,
                            nm_utils_user_data_pack(self, g_steal_pointer(&plat_vfs)));
             priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_PENDING;
@@ -8758,7 +8844,7 @@ ensure_con_ip_config(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
     NMConnection *   connection;
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
     NMIPConfig *     con_ip_config;
 
     if (priv->con_ip_config_x[IS_IPv4])
@@ -8835,7 +8921,7 @@ ip_config_merge_and_apply(NMDevice *self, int addr_family, gboolean commit)
     gboolean                     ignore_default_routes = FALSE;
     GSList *                     iter;
     const char *                 ip6_addr_gen_token = NULL;
-    const gboolean               IS_IPv4            = (addr_family == AF_INET);
+    const int                    IS_IPv4            = NM_IS_IPv4(addr_family);
 
     if (nm_device_sys_iface_state_is_external(self))
         commit = FALSE;
@@ -9079,12 +9165,12 @@ static gboolean
 dhcp_grace_period_expired(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     priv->dhcp_data_x[IS_IPv4].grace_id      = 0;
     priv->dhcp_data_x[IS_IPv4].grace_pending = FALSE;
 
-    _LOGI(LOGD_DHCP_from_addr_family(addr_family),
+    _LOGI(LOGD_DHCPX(IS_IPv4),
           "DHCPv%c: grace period expired",
           nm_utils_addr_family_to_char(addr_family));
 
@@ -9110,7 +9196,7 @@ static gboolean
 dhcp_grace_period_start(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
     guint32          timeout;
 
     /* In any other case (expired lease, assumed connection, etc.),
@@ -9125,12 +9211,12 @@ dhcp_grace_period_start(NMDevice *self, int addr_family)
      * by a constant factor. */
     timeout = _prop_get_ipvx_dhcp_timeout(self, addr_family);
     if (timeout == NM_DHCP_TIMEOUT_INFINITY)
-        _LOGI(LOGD_DHCP_from_addr_family(addr_family),
+        _LOGI(LOGD_DHCPX(IS_IPv4),
               "DHCPv%c: trying to acquire a new lease",
               nm_utils_addr_family_to_char(addr_family));
     else {
         timeout = dhcp_grace_period_from_timeout(timeout);
-        _LOGI(LOGD_DHCP_from_addr_family(addr_family),
+        _LOGI(LOGD_DHCPX(IS_IPv4),
               "DHCPv%c: trying to acquire a new lease within %u seconds",
               nm_utils_addr_family_to_char(addr_family),
               timeout);
@@ -9434,7 +9520,7 @@ connection_ip_method_requires_carrier(NMConnection *connection,
 
     method = nm_utils_get_ip_config_method(connection, addr_family);
 
-    if (addr_family == AF_INET) {
+    if (NM_IS_IPv4(addr_family)) {
         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,
@@ -9778,7 +9864,7 @@ dhcp6_start_with_link_ready(NMDevice *self, NMConnection *connection)
     if (pllink)
         hwaddr = nmp_link_address_get_as_bytes(&pllink->l_address);
 
-    iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, &iaid_explicit);
+    iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, TRUE, &iaid_explicit);
     duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, &enforce_duid);
 
     priv->dhcp_data_6.client = nm_dhcp_manager_start_ip6(
@@ -9862,13 +9948,16 @@ gboolean
 nm_device_dhcp6_renew(NMDevice *self, gboolean release)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMNDiscDHCPLevel mode;
 
     g_return_val_if_fail(priv->dhcp_data_6.client != NULL, FALSE);
 
     _LOGI(LOGD_DHCP6, "DHCPv6 lease renewal requested");
 
     /* Terminate old DHCP instance and release the old lease */
+    mode = priv->dhcp6.mode;
     dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, release);
+    priv->dhcp6.mode = mode;
 
     /* Start DHCP again on the interface */
     return dhcp6_start(self, FALSE);
@@ -10969,7 +11058,7 @@ ip_requires_slaves(NMDevice *self, int addr_family)
 
     method = nm_device_get_effective_ip_config_method(self, addr_family);
 
-    if (addr_family == AF_INET)
+    if (NM_IS_IPv4(addr_family))
         return nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
     /* SLAAC, DHCP, and Link-Local depend on connectivity (and thus slaves)
@@ -10986,7 +11075,7 @@ act_stage3_ip_config_start(NMDevice *           self,
                            gpointer *           out_config,
                            NMDeviceStateReason *out_failure_reason)
 {
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
     NMConnection *   connection;
     NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
@@ -11208,103 +11297,62 @@ act_stage3_ip_config_start(NMDevice *           self,
     }
 }
 
-/**
- * nm_device_activate_stage3_ip4_start:
- * @self: the device
- *
- * Try starting IPv4 configuration.
- */
 gboolean
-nm_device_activate_stage3_ip4_start(NMDevice *self)
+nm_device_activate_stage3_ip_start(NMDevice *self, int addr_family)
 {
-    NMDevicePrivate *   priv = NM_DEVICE_GET_PRIVATE(self);
+    const int           IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMDevicePrivate *   priv    = NM_DEVICE_GET_PRIVATE(self);
     NMActStageReturn    ret;
-    NMDeviceStateReason failure_reason      = NM_DEVICE_STATE_REASON_NONE;
-    gs_unref_object NMIP4Config *ip4_config = NULL;
+    NMDeviceStateReason failure_reason    = NM_DEVICE_STATE_REASON_NONE;
+    gs_unref_object NMIPConfig *ip_config = NULL;
 
-    g_assert(priv->ip_state_4 == NM_DEVICE_IP_STATE_WAIT);
+    g_assert(priv->ip_state_x[IS_IPv4] == NM_DEVICE_IP_STATE_WAIT);
 
     if (nm_device_sys_iface_state_is_external(self)) {
-        _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_DONE);
+        _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_DONE);
         check_ip_state(self, FALSE, TRUE);
         return TRUE;
     }
 
-    _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, 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, NM_DEVICE_IP_STATE_WAIT);
-    } else
-        g_assert(ret == NM_ACT_STAGE_RETURN_POSTPONE);
-
-    return TRUE;
-}
+    _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_CONF);
 
-/**
- * nm_device_activate_stage3_ip6_start:
- * @self: the device
- *
- * Try starting IPv6 configuration.
- */
-gboolean
-nm_device_activate_stage3_ip6_start(NMDevice *self)
-{
-    NMDevicePrivate *   priv = NM_DEVICE_GET_PRIVATE(self);
-    NMActStageReturn    ret;
-    NMDeviceStateReason failure_reason      = NM_DEVICE_STATE_REASON_NONE;
-    gs_unref_object NMIP6Config *ip6_config = NULL;
-
-    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, NM_DEVICE_IP_STATE_DONE);
-        check_ip_state(self, FALSE, TRUE);
-        return TRUE;
-    }
-
-    _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,
+                                                                addr_family,
+                                                                (gpointer *) &ip_config,
                                                                 &failure_reason);
-    if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
-        if (!ip6_config)
-            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_ip_config_result(self, AF_INET6, NULL);
-    } else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
-        _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
+
+    switch (ret) {
+    case NM_ACT_STAGE_RETURN_SUCCESS:
+        if (!IS_IPv4) {
+            /* Here we get a static IPv6 config, like for Shared where it's
+             * autogenerated or from modems where it comes from ModemManager.
+            */
+            if (!ip_config)
+                ip_config = nm_device_ip_config_new(self, addr_family);
+            nm_assert(!applied_config_get_current(&priv->ac_ip6_config));
+            applied_config_init(&priv->ac_ip6_config, ip_config);
+            ip_config = NULL;
+        }
+        nm_device_activate_schedule_ip_config_result(self, addr_family, ip_config);
+        break;
+    case NM_ACT_STAGE_RETURN_IP_DONE:
+        _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_DONE);
         check_ip_state(self, FALSE, TRUE);
-    } else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+        break;
+    case 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) {
+    case NM_ACT_STAGE_RETURN_IP_FAIL:
         /* Activation not wanted */
-        _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_FAIL);
-    } else if (ret == NM_ACT_STAGE_RETURN_IP_WAIT) {
+        _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_FAIL);
+        break;
+    case NM_ACT_STAGE_RETURN_IP_WAIT:
         /* Wait for something to try IP config again */
-        _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
-    } else
+        _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_WAIT);
+        break;
+    default:
         g_assert(ret == NM_ACT_STAGE_RETURN_POSTPONE);
+    }
 
     return TRUE;
 }
@@ -11334,12 +11382,12 @@ activate_stage3_ip_config_start(NMDevice *self)
               "interface %s not up for IP configuration",
               nm_device_get_ip_iface(self));
 
-    /* IPv4 */
-    if (nm_device_activate_ip4_state_in_wait(self) && !nm_device_activate_stage3_ip4_start(self))
+    if (nm_device_activate_ip4_state_in_wait(self)
+        && !nm_device_activate_stage3_ip_start(self, AF_INET))
         return;
 
-    /* IPv6 */
-    if (nm_device_activate_ip6_state_in_wait(self) && !nm_device_activate_stage3_ip6_start(self))
+    if (nm_device_activate_ip6_state_in_wait(self)
+        && !nm_device_activate_stage3_ip_start(self, AF_INET6))
         return;
 
     /* Proxy */
@@ -11438,28 +11486,31 @@ void
 nm_device_activate_schedule_stage3_ip_config_start(NMDevice *self)
 {
     NMDevicePrivate *priv;
+    int              ifindex;
 
     g_return_if_fail(NM_IS_DEVICE(self));
 
     priv = NM_DEVICE_GET_PRIVATE(self);
     g_return_if_fail(priv->act_request.obj);
+    ifindex = nm_device_get_ip_ifindex(self);
 
     /* Add the interface to the specified firewall zone */
     if (priv->fw_state == FIREWALL_STATE_UNMANAGED) {
-        if (!nm_device_sys_iface_state_is_external(self)) {
+        if (nm_device_sys_iface_state_is_external(self)) {
+            /* fake success */
+            priv->fw_state = FIREWALL_STATE_INITIALIZED;
+        } else if (ifindex > 0) {
             priv->fw_state = FIREWALL_STATE_WAIT_STAGE_3;
             fw_change_zone(self);
             return;
         }
-
-        /* fake success. */
-        priv->fw_state = FIREWALL_STATE_INITIALIZED;
+        /* no ifindex, nothing to do for now */
     } else if (priv->fw_state == FIREWALL_STATE_WAIT_STAGE_3) {
         /* a firewall call for stage3 is pending. Return and wait. */
         return;
     }
 
-    nm_assert(priv->fw_state == FIREWALL_STATE_INITIALIZED);
+    nm_assert(ifindex <= 0 || priv->fw_state == FIREWALL_STATE_INITIALIZED);
 
     activation_source_schedule(self, activate_stage3_ip_config_start, AF_INET);
 }
@@ -11479,37 +11530,45 @@ act_stage4_ip_config_timeout(NMDevice *           self,
     return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
-/*
- * nm_device_activate_stage4_ip4_config_timeout
- *
- * Time out on retrieving the IPv4 config.
- *
- */
 static void
-activate_stage4_ip_config_timeout_4(NMDevice *self)
+activate_stage4_ip_config_timeout_x(NMDevice *self, int addr_family)
 {
-    NMActStageReturn    ret            = NM_ACT_STAGE_RETURN_FAILURE;
     NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
+    NMActStageReturn    ret;
+
+    ret =
+        NM_DEVICE_GET_CLASS(self)->act_stage4_ip_config_timeout(self, addr_family, &failure_reason);
 
-    ret = NM_DEVICE_GET_CLASS(self)->act_stage4_ip_config_timeout(self, AF_INET, &failure_reason);
     if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
         return;
-    else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+
+    if (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);
 
-    _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_FAIL);
-
+    _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_FAIL);
     check_ip_state(self, FALSE, TRUE);
 }
 
+static void
+activate_stage4_ip_config_timeout_4(NMDevice *self)
+{
+    activate_stage4_ip_config_timeout_x(self, AF_INET);
+}
+
+static void
+activate_stage4_ip_config_timeout_6(NMDevice *self)
+{
+    activate_stage4_ip_config_timeout_x(self, AF_INET6);
+}
+
 void
 nm_device_activate_schedule_ip_config_timeout(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv;
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     g_return_if_fail(NM_IS_DEVICE(self));
     g_return_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6));
@@ -11518,43 +11577,25 @@ nm_device_activate_schedule_ip_config_timeout(NMDevice *self, int addr_family)
 
     g_return_if_fail(priv->act_request.obj);
 
-    activation_source_schedule(self, activate_stage4_ip_config_timeout_x[IS_IPv4], addr_family);
-}
-
-static void
-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_ip_config_timeout(self, AF_INET6, &failure_reason);
-    if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
-        return;
-    if (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);
-
-    _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_FAIL);
-
-    check_ip_state(self, FALSE, TRUE);
+    activation_source_schedule(self,
+                               IS_IPv4 ? activate_stage4_ip_config_timeout_4
+                                       : activate_stage4_ip_config_timeout_6,
+                               addr_family);
 }
 
 static gboolean
 share_init(NMDevice *self, GError **error)
 {
-    char * modules[] = {"ip_tables",
-                       "iptable_nat",
-                       "nf_nat_ftp",
-                       "nf_nat_irc",
-                       "nf_nat_sip",
-                       "nf_nat_tftp",
-                       "nf_nat_pptp",
-                       "nf_nat_h323",
-                       NULL};
-    char **iter;
-    int    errsv;
+    const char *const modules[] = {"ip_tables",
+                                   "iptable_nat",
+                                   "nf_nat_ftp",
+                                   "nf_nat_irc",
+                                   "nf_nat_sip",
+                                   "nf_nat_tftp",
+                                   "nf_nat_pptp",
+                                   "nf_nat_h323"};
+    guint             i;
+    int               errsv;
 
     if (nm_platform_sysctl_get_int32(nm_device_get_platform(self),
                                      NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_forward"),
@@ -11592,35 +11633,24 @@ share_init(NMDevice *self, GError **error)
               nm_strerror_native(errsv));
     }
 
-    for (iter = modules; *iter; iter++)
-        nm_utils_modprobe(NULL, FALSE, *iter, NULL);
+    for (i = 0; i < G_N_ELEMENTS(modules); i++)
+        nm_utils_modprobe(NULL, FALSE, modules[i], NULL);
 
     return TRUE;
 }
 
-#define add_share_rule(req, table, ...)                  \
-    G_STMT_START                                         \
-    {                                                    \
-        char *_cmd = g_strdup_printf(__VA_ARGS__);       \
-        nm_act_request_add_share_rule(req, table, _cmd); \
-        g_free(_cmd);                                    \
-    }                                                    \
-    G_STMT_END
-
 static gboolean
 start_sharing(NMDevice *self, NMIP4Config *config, GError **error)
 {
     NMDevicePrivate *           priv = NM_DEVICE_GET_PRIVATE(self);
     NMActRequest *              req;
-    char                        str_addr[INET_ADDRSTRLEN];
-    char                        str_mask[INET_ADDRSTRLEN];
-    guint32                     netmask, network;
     const NMPlatformIP4Address *ip4_addr = NULL;
     const char *                ip_iface;
     GError *                    local = NULL;
     NMConnection *              conn;
     NMSettingConnection *       s_con;
     gboolean                    announce_android_metered;
+    NMUtilsShareRules *         share_rules;
 
     g_return_val_if_fail(config, FALSE);
 
@@ -11645,57 +11675,13 @@ start_sharing(NMDevice *self, NMIP4Config *config, GError **error)
     req = nm_device_get_act_request(self);
     g_return_val_if_fail(req, FALSE);
 
-    netmask = _nm_utils_ip4_prefix_to_netmask(ip4_addr->plen);
-    _nm_utils_inet4_ntop(netmask, str_mask);
-
-    network = ip4_addr->address & netmask;
-    _nm_utils_inet4_ntop(network, str_addr);
-
-    add_share_rule(req,
-                   "nat",
-                   "POSTROUTING --source %s/%s ! --destination %s/%s --jump MASQUERADE",
-                   str_addr,
-                   str_mask,
-                   str_addr,
-                   str_mask);
-    add_share_rule(req,
-                   "filter",
-                   "FORWARD --destination %s/%s --out-interface %s --match state --state "
-                   "ESTABLISHED,RELATED --jump ACCEPT",
-                   str_addr,
-                   str_mask,
-                   ip_iface);
-    add_share_rule(req,
-                   "filter",
-                   "FORWARD --source %s/%s --in-interface %s --jump ACCEPT",
-                   str_addr,
-                   str_mask,
-                   ip_iface);
-    add_share_rule(req,
-                   "filter",
-                   "FORWARD --in-interface %s --out-interface %s --jump ACCEPT",
-                   ip_iface,
-                   ip_iface);
-    add_share_rule(req, "filter", "FORWARD --out-interface %s --jump REJECT", ip_iface);
-    add_share_rule(req, "filter", "FORWARD --in-interface %s --jump REJECT", ip_iface);
-    add_share_rule(req,
-                   "filter",
-                   "INPUT --in-interface %s --protocol udp --destination-port 67 --jump ACCEPT",
-                   ip_iface);
-    add_share_rule(req,
-                   "filter",
-                   "INPUT --in-interface %s --protocol tcp --destination-port 67 --jump ACCEPT",
-                   ip_iface);
-    add_share_rule(req,
-                   "filter",
-                   "INPUT --in-interface %s --protocol udp --destination-port 53 --jump ACCEPT",
-                   ip_iface);
-    add_share_rule(req,
-                   "filter",
-                   "INPUT --in-interface %s --protocol tcp --destination-port 53 --jump ACCEPT",
-                   ip_iface);
-
-    nm_act_request_set_shared(req, TRUE);
+    share_rules = nm_utils_share_rules_new();
+
+    nm_utils_share_rules_add_all_rules(share_rules, ip_iface, ip4_addr->address, ip4_addr->plen);
+
+    nm_utils_share_rules_apply(share_rules, TRUE);
+
+    nm_act_request_set_shared(req, share_rules);
 
     conn  = nm_act_request_get_applied_connection(req);
     s_con = nm_connection_get_setting_connection(conn);
@@ -11730,7 +11716,7 @@ start_sharing(NMDevice *self, NMIP4Config *config, GError **error)
                     "could not start dnsmasq due to %s",
                     local->message);
         g_error_free(local);
-        nm_act_request_set_shared(req, FALSE);
+        nm_act_request_set_shared(req, NULL);
         return FALSE;
     }
 
@@ -11798,12 +11784,14 @@ nm_device_arp_announce(NMDevice *self)
 }
 
 static void
-activate_stage5_ip_config_result_4(NMDevice *self)
+activate_stage5_ip_config_result_x(NMDevice *self, int addr_family)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
     NMActRequest *   req;
     const char *     method;
     int              ip_ifindex;
+    int              errsv;
     gboolean         do_announce = FALSE;
 
     req = nm_device_get_act_request(self);
@@ -11811,6 +11799,8 @@ activate_stage5_ip_config_result_4(NMDevice *self)
 
     /* Interface must be IFF_UP before IP config can be applied */
     ip_ifindex = nm_device_get_ip_ifindex(self);
+    g_return_if_fail(ip_ifindex);
+
     if (!nm_platform_link_is_up(nm_device_get_platform(self), ip_ifindex)
         && !nm_device_sys_iface_state_is_external_or_assume(self)) {
         nm_platform_link_set_up(nm_device_get_platform(self), ip_ifindex, NULL);
@@ -11820,86 +11810,172 @@ activate_stage5_ip_config_result_4(NMDevice *self)
                   nm_device_get_ip_iface(self));
     }
 
-    if (!ip_config_merge_and_apply(self, AF_INET, TRUE)) {
-        _LOGD(LOGD_DEVICE | LOGD_IP4, "Activation: Stage 5 of 5 (IPv4 Commit) failed");
-        nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+    if (!ip_config_merge_and_apply(self, addr_family, TRUE)) {
+        _LOGD(LOGD_DEVICE | LOGD_IPX(IS_IPv4),
+              "Activation: Stage 5 of 5 (IPv%c Commit) failed",
+              nm_utils_addr_family_to_char(addr_family));
+        nm_device_ip_method_failed(self, addr_family, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
         return;
     }
 
-    /* Start IPv4 sharing if we need it */
-    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 (!IS_IPv4) {
+        if (priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE
+            && 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.
+                 */
+                nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP6_CHANGE,
+                                          self,
+                                          NULL,
+                                          NULL,
+                                          NULL,
+                                          NULL);
+            } else {
+                /* still waiting for first dhcp6 lease. */
+                return;
+            }
+        }
+    }
 
-        if (!start_sharing(self, priv->ip_config_4, &error)) {
-            _LOGW(LOGD_SHARING,
-                  "Activation: Stage 5 of 5 (IPv4 Commit) start sharing failed: %s",
-                  error->message);
-            nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
-            return;
+    /* Start IPv4 sharing/IPv6 forwarding if we need it */
+    method = nm_device_get_effective_ip_config_method(self, addr_family);
+    if (IS_IPv4) {
+        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)) {
+                _LOGW(LOGD_SHARING,
+                      "Activation: Stage 5 of 5 (IPv4 Commit) start sharing failed: %s",
+                      error->message);
+                nm_device_ip_method_failed(self,
+                                           AF_INET,
+                                           NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
+                return;
+            }
+        }
+    } else {
+        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,
+                      nm_strerror_native(errsv));
+                nm_device_ip_method_failed(self,
+                                           AF_INET6,
+                                           NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
+                return;
+            }
         }
     }
 
-    if (priv->dhcp_data_4.client) {
-        gs_free_error GError *error = NULL;
+    if (IS_IPv4) {
+        if (priv->dhcp_data_4.client) {
+            gs_free_error GError *error = NULL;
 
-        if (!nm_dhcp_client_accept(priv->dhcp_data_4.client, &error)) {
-            _LOGW(LOGD_DHCP4,
-                  "Activation: Stage 5 of 5 (IPv4 Commit) error accepting lease: %s",
-                  error->message);
-            nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_DHCP_ERROR);
-            return;
+            if (!nm_dhcp_client_accept(priv->dhcp_data_4.client, &error)) {
+                _LOGW(LOGD_DHCP4,
+                      "Activation: Stage 5 of 5 (IPv4 Commit) error accepting lease: %s",
+                      error->message);
+                nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_DHCP_ERROR);
+                return;
+            }
+        }
+
+        /* If IPv4 wasn't the first to complete, and DHCP was used, then ensure
+         * dispatcher scripts get the DHCP lease information.
+         */
+        if (priv->dhcp_data_4.client && nm_device_activate_ip4_state_in_conf(self)
+            && (nm_device_get_state(self) > NM_DEVICE_STATE_IP_CONFIG)) {
+            nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP4_CHANGE,
+                                      self,
+                                      NULL,
+                                      NULL,
+                                      NULL,
+                                      NULL);
         }
     }
 
-    /* If IPv4 wasn't the first to complete, and DHCP was used, then ensure
-     * dispatcher scripts get the DHCP lease information.
-     */
-    if (priv->dhcp_data_4.client && nm_device_activate_ip4_state_in_conf(self)
-        && (nm_device_get_state(self) > NM_DEVICE_STATE_IP_CONFIG)) {
-        nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP4_CHANGE, self, NULL, NULL, NULL, NULL);
+    if (!IS_IPv4) {
+        /* Check if we have to wait for DAD */
+        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
+                priv->dad6_ip6_config = dad6_get_pending_addresses(self);
+
+            if (priv->dad6_ip6_config) {
+                _LOGD(LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: awaiting termination");
+            } else {
+                _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
+                check_ip_state(self, FALSE, TRUE);
+            }
+        }
     }
 
-    /* Send ARP announcements */
+    if (IS_IPv4) {
+        /* Send ARP announcements */
 
-    if (nm_device_is_master(self)) {
-        CList *    iter;
-        SlaveInfo *info;
+        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;
+            /* 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;
+            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;
+        } else
+            do_announce = TRUE;
 
-    if (do_announce)
-        nm_device_arp_announce(self);
+        if (do_announce)
+            nm_device_arp_announce(self);
+    }
 
-    /* Enter the IP_CHECK state if this is the first method to complete */
-    _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_DONE);
-    check_ip_state(self, FALSE, TRUE);
+    if (IS_IPv4) {
+        /* Enter the IP_CHECK state if this is the first method to complete */
+        _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_DONE);
+        check_ip_state(self, FALSE, TRUE);
+    }
+}
+
+static void
+activate_stage5_ip_config_result_4(NMDevice *self)
+{
+    activate_stage5_ip_config_result_x(self, AF_INET);
 }
 
+static void
+activate_stage5_ip_config_result_6(NMDevice *self)
+{
+    activate_stage5_ip_config_result_x(self, AF_INET6);
+}
+
+#define activate_stage5_ip_config_result_x_fcn(addr_family)       \
+    (NM_IS_IPv4(addr_family) ? activate_stage5_ip_config_result_4 \
+                             : activate_stage5_ip_config_result_6)
+
 void
 nm_device_activate_schedule_ip_config_result(NMDevice *self, int addr_family, NMIPConfig *config)
 {
     NMDevicePrivate *priv;
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     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));
+    g_return_if_fail(!config || (IS_IPv4 && nm_ip_config_get_addr_family(config) == AF_INET));
 
     priv = NM_DEVICE_GET_PRIVATE(self);
 
@@ -11913,13 +11989,15 @@ nm_device_activate_schedule_ip_config_result(NMDevice *self, int addr_family, NM
             _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
     }
 
-    activation_source_schedule(self, activate_stage5_ip_config_result_x[IS_IPv4], addr_family);
+    activation_source_schedule(self,
+                               activate_stage5_ip_config_result_x_fcn(addr_family),
+                               addr_family);
 }
 
 NMDeviceIPState
 nm_device_activate_get_ip_state(NMDevice *self, int addr_family)
 {
-    const gboolean IS_IPv4 = (addr_family == AF_INET);
+    const int IS_IPv4 = NM_IS_IPv4(addr_family);
 
     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);
@@ -11994,89 +12072,6 @@ dad6_get_pending_addresses(NMDevice *self)
     return dad6_config;
 }
 
-static void
-activate_stage5_ip_config_result_6(NMDevice *self)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMActRequest *   req;
-    const char *     method;
-    int              ip_ifindex;
-    int              errsv;
-
-    req = nm_device_get_act_request(self);
-    g_assert(req);
-
-    /* Interface must be IFF_UP before IP config can be applied */
-    ip_ifindex = nm_device_get_ip_ifindex(self);
-    g_return_if_fail(ip_ifindex);
-
-    if (!nm_platform_link_is_up(nm_device_get_platform(self), ip_ifindex)
-        && !nm_device_sys_iface_state_is_external_or_assume(self)) {
-        nm_platform_link_set_up(nm_device_get_platform(self), ip_ifindex, NULL);
-        if (!nm_platform_link_is_up(nm_device_get_platform(self), ip_ifindex))
-            _LOGW(LOGD_DEVICE,
-                  "interface %s not up for IP configuration",
-                  nm_device_get_ip_iface(self));
-    }
-
-    if (ip_config_merge_and_apply(self, AF_INET6, TRUE)) {
-        if (priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE
-            && 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.
-                 */
-                nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP6_CHANGE,
-                                          self,
-                                          NULL,
-                                          NULL,
-                                          NULL,
-                                          NULL);
-            } else {
-                /* still waiting for first dhcp6 lease. */
-                return;
-            }
-        }
-
-        /* Start IPv6 forwarding if we need it */
-        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,
-                      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->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
-                priv->dad6_ip6_config = dad6_get_pending_addresses(self);
-
-            if (priv->dad6_ip6_config) {
-                _LOGD(LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: awaiting termination");
-            } else {
-                _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
-                check_ip_state(self, FALSE, TRUE);
-            }
-        }
-    } else {
-        _LOGW(LOGD_DEVICE | LOGD_IP6, "Activation: Stage 5 of 5 (IPv6 Commit) failed");
-        nm_device_ip_method_failed(self, AF_INET6, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-    }
-}
-
 /*****************************************************************************/
 
 static void
@@ -12230,7 +12225,7 @@ static void
 _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);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_NONE);
 
@@ -12326,55 +12321,86 @@ _nm_device_hash_check_invalid_keys(GHashTable *       hash,
 }
 
 void
-nm_device_reactivate_ip4_config(NMDevice *         self,
-                                NMSettingIPConfig *s_ip4_old,
-                                NMSettingIPConfig *s_ip4_new)
+nm_device_reactivate_ip_config(NMDevice *         self,
+                               int                addr_family,
+                               NMSettingIPConfig *s_ip_old,
+                               NMSettingIPConfig *s_ip_new)
 {
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
     NMDevicePrivate *priv;
-    const char *     method_old, *method_new;
+    const char *     method_old;
+    const char *     method_new;
 
     g_return_if_fail(NM_IS_DEVICE(self));
+
     priv = NM_DEVICE_GET_PRIVATE(self);
 
-    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);
+    if (priv->ip_state_x[IS_IPv4] == NM_DEVICE_IP_STATE_NONE)
+        return;
+
+    g_clear_object(&priv->con_ip_config_x[IS_IPv4]);
+    g_clear_object(&priv->ext_ip_config_x[IS_IPv4]);
+    if (IS_IPv4) {
         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);
+    } else {
+        g_clear_object(&priv->ac_ip6_config.current);
+        g_clear_object(&priv->dhcp6.ip6_config.current);
+    }
+    g_clear_object(&priv->dev2_ip_config_x[IS_IPv4].current);
+
+    if (!IS_IPv4) {
+        if (priv->ipv6ll_handle && !IN6_IS_ADDR_UNSPECIFIED(&priv->ipv6ll_addr))
+            priv->ipv6ll_has = TRUE;
+    }
+
+    priv->con_ip_config_x[IS_IPv4] = nm_device_ip_config_new(self, addr_family);
+
+    if (IS_IPv4) {
         nm_ip4_config_merge_setting(priv->con_ip_config_4,
-                                    s_ip4_new,
+                                    s_ip_new,
                                     _prop_get_connection_mdns(self),
                                     _prop_get_connection_llmnr(self),
                                     nm_device_get_route_table(self, AF_INET),
                                     nm_device_get_route_metric(self, AF_INET));
+    } else {
+        nm_ip6_config_merge_setting(priv->con_ip_config_6,
+                                    s_ip_new,
+                                    nm_device_get_route_table(self, AF_INET6),
+                                    nm_device_get_route_metric(self, AF_INET6));
+    }
 
-        method_old = s_ip4_old ? nm_setting_ip_config_get_method(s_ip4_old)
-                               : NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
-        method_new = s_ip4_new ? nm_setting_ip_config_get_method(s_ip4_new)
-                               : NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+    method_old = (s_ip_old ? nm_setting_ip_config_get_method(s_ip_old) : NULL)
+                     ?: (IS_IPv4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED
+                                 : NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
+    method_new = (s_ip_new ? nm_setting_ip_config_get_method(s_ip_new) : NULL)
+                     ?: (IS_IPv4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED
+                                 : NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
 
-        if (!nm_streq0(method_old, method_new)) {
-            _cleanup_ip_pre(self, AF_INET, CLEANUP_TYPE_DECONFIGURE);
-            _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;
+    if (!nm_streq0(method_old, method_new)) {
+        _cleanup_ip_pre(self, addr_family, CLEANUP_TYPE_DECONFIGURE);
+        _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_WAIT);
+        if (!nm_device_activate_stage3_ip_start(self, addr_family)) {
+            _LOGW(LOGD_IP4,
+                  "Failed to apply IPv%c configuration",
+                  nm_utils_addr_family_to_char(addr_family));
         }
+        return;
+    }
 
-        if (s_ip4_old && s_ip4_new) {
-            gint64 metric_old, metric_new;
+    if (s_ip_old && s_ip_new) {
+        gint64 metric_old, metric_new;
 
-            /* For dynamic IP methods (DHCP, IPv4LL, WWAN) the route metric is
-             * set at activation/renewal time using the value from static
-             * configuration. To support runtime change we need to update the
-             * dynamic configuration in place and tell the DHCP client the new
-             * value to use for future renewals.
-             */
-            metric_old = nm_setting_ip_config_get_route_metric(s_ip4_old);
-            metric_new = nm_setting_ip_config_get_route_metric(s_ip4_new);
+        /* For dynamic IP methods (DHCP, IPv4LL, WWAN) the route metric is
+         * set at activation/renewal time using the value from static
+         * configuration. To support runtime change we need to update the
+         * dynamic configuration in place and tell the DHCP client the new
+         * value to use for future renewals.
+         */
+        metric_old = nm_setting_ip_config_get_route_metric(s_ip_old);
+        metric_new = nm_setting_ip_config_get_route_metric(s_ip_new);
 
-            if (metric_old != metric_new) {
+        if (metric_old != metric_new) {
+            if (IS_IPv4) {
                 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));
@@ -12387,60 +12413,7 @@ nm_device_reactivate_ip4_config(NMDevice *         self,
                     nm_dhcp_client_set_route_metric(priv->dhcp_data_4.client,
                                                     nm_device_get_route_metric(self, AF_INET));
                 }
-            }
-        }
-
-        if (nm_device_get_ip_ifindex(self) > 0 && !ip_config_merge_and_apply(self, AF_INET, TRUE))
-            _LOGW(LOGD_IP4, "Failed to reapply IPv4 configuration");
-    }
-}
-
-void
-nm_device_reactivate_ip6_config(NMDevice *         self,
-                                NMSettingIPConfig *s_ip6_old,
-                                NMSettingIPConfig *s_ip6_new)
-{
-    NMDevicePrivate *priv;
-    const char *     method_old, *method_new;
-
-    g_return_if_fail(NM_IS_DEVICE(self));
-    priv = NM_DEVICE_GET_PRIVATE(self);
-
-    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->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 = 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),
-                                    nm_device_get_route_metric(self, AF_INET6));
-
-        method_old = s_ip6_old ? nm_setting_ip_config_get_method(s_ip6_old)
-                               : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
-        method_new = s_ip6_new ? nm_setting_ip_config_get_method(s_ip6_new)
-                               : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
-
-        if (!nm_streq0(method_old, method_new)) {
-            _cleanup_ip_pre(self, AF_INET6, CLEANUP_TYPE_DECONFIGURE);
-            _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;
-        }
-
-        if (s_ip6_old && s_ip6_new) {
-            gint64 metric_old, metric_new;
-
-            /* 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);
-
-            if (metric_old != metric_new) {
+            } else {
                 if (priv->ac_ip6_config.orig) {
                     nm_ip6_config_update_routes_metric((NMIP6Config *) priv->ac_ip6_config.orig,
                                                        nm_device_get_route_metric(self, AF_INET6));
@@ -12459,9 +12432,12 @@ nm_device_reactivate_ip6_config(NMDevice *         self,
                 }
             }
         }
+    }
 
-        if (nm_device_get_ip_ifindex(self) > 0 && !ip_config_merge_and_apply(self, AF_INET6, TRUE))
-            _LOGW(LOGD_IP4, "Failed to reapply IPv6 configuration");
+    if (nm_device_get_ip_ifindex(self) > 0 && !ip_config_merge_and_apply(self, addr_family, TRUE)) {
+        _LOGW(LOGD_IPX(IS_IPv4),
+              "Failed to reapply IPv%c configuration",
+              nm_utils_addr_family_to_char(addr_family));
     }
 }
 
@@ -12518,28 +12494,41 @@ can_reapply_change(NMDevice *  self,
                                                  NM_SETTING_CONNECTION_LLDP,
                                                  NM_SETTING_CONNECTION_MDNS,
                                                  NM_SETTING_CONNECTION_LLMNR);
-    } else if (NM_IN_STRSET(setting_name,
-                            NM_SETTING_USER_SETTING_NAME,
-                            NM_SETTING_PROXY_SETTING_NAME,
-                            NM_SETTING_IP4_CONFIG_SETTING_NAME,
-                            NM_SETTING_IP6_CONFIG_SETTING_NAME)) {
+    }
+
+    if (NM_IN_STRSET(setting_name,
+                     NM_SETTING_USER_SETTING_NAME,
+                     NM_SETTING_PROXY_SETTING_NAME,
+                     NM_SETTING_IP4_CONFIG_SETTING_NAME,
+                     NM_SETTING_IP6_CONFIG_SETTING_NAME))
+        return TRUE;
+
+    if (nm_streq(setting_name, NM_SETTING_WIRED_SETTING_NAME)) {
+        if (NM_IN_SET(NM_DEVICE_GET_CLASS(self)->get_configured_mtu,
+                      nm_device_get_configured_mtu_wired_parent,
+                      nm_device_get_configured_mtu_for_wired)) {
+            return nm_device_hash_check_invalid_keys(diffs,
+                                                     NM_SETTING_WIRED_SETTING_NAME,
+                                                     error,
+                                                     NM_SETTING_WIRED_MTU);
+        }
+        goto out_fail;
+    }
+
+    if (nm_streq(setting_name, NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME)
+        && NM_DEVICE_GET_CLASS(self)->can_reapply_change_ovs_external_ids) {
+        /* TODO: this means, you cannot reapply changes to the external-ids for
+         * OVS system interfaces. */
         return TRUE;
-    } else if (nm_streq(setting_name, NM_SETTING_WIRED_SETTING_NAME)
-               && NM_IN_SET(NM_DEVICE_GET_CLASS(self)->get_configured_mtu,
-                            nm_device_get_configured_mtu_wired_parent,
-                            nm_device_get_configured_mtu_for_wired)) {
-        return nm_device_hash_check_invalid_keys(diffs,
-                                                 NM_SETTING_WIRED_SETTING_NAME,
-                                                 error,
-                                                 NM_SETTING_WIRED_MTU);
-    } else {
-        g_set_error(error,
-                    NM_DEVICE_ERROR,
-                    NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
-                    "Can't reapply any changes to '%s' setting",
-                    setting_name);
-        return FALSE;
     }
+
+out_fail:
+    g_set_error(error,
+                NM_DEVICE_ERROR,
+                NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
+                "Can't reapply any changes to '%s' setting",
+                setting_name);
+    return FALSE;
 }
 
 static void
@@ -12682,6 +12671,8 @@ check_and_reapply_connection(NMDevice *    self,
 
         con_old = applied_clone = nm_simple_connection_new_clone(applied);
         con_new                 = applied;
+        /* FIXME(applied-connection-immutable): we should not modify the applied
+         *   connection but replace it with a new (immutable) instance. */
         nm_connection_replace_settings_from_connection(applied, connection_clean);
         nm_connection_clear_secrets(applied);
     } else
@@ -12716,8 +12707,8 @@ check_and_reapply_connection(NMDevice *    self,
         /* Allow reapply of MTU */
         priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
 
-        nm_device_reactivate_ip4_config(self, s_ip4_old, s_ip4_new);
-        nm_device_reactivate_ip6_config(self, s_ip6_old, s_ip6_new);
+        nm_device_reactivate_ip_config(self, AF_INET, s_ip4_old, s_ip4_new);
+        nm_device_reactivate_ip_config(self, AF_INET6, s_ip6_old, s_ip6_new);
 
         _routing_rules_sync(self, NM_TERNARY_TRUE);
 
@@ -13328,6 +13319,12 @@ nm_device_disconnect_active_connection(NMActiveConnection *          active,
 
     if (NM_ACTIVE_CONNECTION(priv->act_request.obj) == active) {
         if (priv->state < NM_DEVICE_STATE_DEACTIVATING) {
+            /* When the user actively deactivates a profile, we set
+             * the sys-iface-state to managed so that we deconfigure/cleanup the interface.
+             * But for external connections that go down otherwise, we don't want to touch the interface. */
+            if (nm_device_sys_iface_state_is_external(self))
+                nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
+
             nm_device_state_changed(self, NM_DEVICE_STATE_DEACTIVATING, device_reason);
         } else {
             /* @active is the current ac of @self, but it's going down already.
@@ -13457,7 +13454,7 @@ nm_device_set_proxy_config(NMDevice *self, const char *pac_url)
 NMDhcpConfig *
 nm_device_get_dhcp_config(NMDevice *self, int addr_family)
 {
-    const gboolean IS_IPv4 = (addr_family == AF_INET);
+    const int IS_IPv4 = NM_IS_IPv4(addr_family);
 
     g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
 
@@ -13482,7 +13479,7 @@ nm_device_set_ip_config(NMDevice *  self,
                         GPtrArray * ip4_dev_route_blacklist)
 {
     NMDevicePrivate *     priv    = NM_DEVICE_GET_PRIVATE(self);
-    const gboolean        IS_IPv4 = (addr_family == AF_INET);
+    const int             IS_IPv4 = NM_IS_IPv4(addr_family);
     NMIPConfig *          old_config;
     gboolean              has_changes = FALSE;
     gboolean              success     = TRUE;
@@ -13498,7 +13495,7 @@ nm_device_set_ip_config(NMDevice *  self,
                   })));
     nm_assert(IS_IPv4 || !ip4_dev_route_blacklist);
 
-    _LOGD(LOGD_IP_from_af(addr_family),
+    _LOGD(LOGD_IPX(IS_IPv4),
           "ip%c-config: update (commit=%d, new-config=%p)",
           nm_utils_addr_family_to_char(addr_family),
           commit,
@@ -13535,7 +13532,7 @@ nm_device_set_ip_config(NMDevice *  self,
          * this causes a re-read and reset. This should only happen for relevant changes */
         nm_ip_config_replace(old_config, new_config, &has_changes);
         if (has_changes) {
-            _LOGD(LOGD_IP_from_af(addr_family),
+            _LOGD(LOGD_IPX(IS_IPv4),
                   "ip%c-config: update IP Config instance (%s)",
                   nm_utils_addr_family_to_char(addr_family),
                   nm_dbus_object_get_path(NM_DBUS_OBJECT(old_config)));
@@ -13546,14 +13543,14 @@ nm_device_set_ip_config(NMDevice *  self,
         if (!nm_dbus_object_is_exported(NM_DBUS_OBJECT(new_config)))
             nm_dbus_object_export(NM_DBUS_OBJECT(new_config));
 
-        _LOGD(LOGD_IP_from_af(addr_family),
+        _LOGD(LOGD_IPX(IS_IPv4),
               "ip%c-config: set IP Config instance (%s)",
               nm_utils_addr_family_to_char(addr_family),
               nm_dbus_object_get_path(NM_DBUS_OBJECT(new_config)));
     } else if (old_config /*&& !new_config*/) {
         has_changes                = TRUE;
         priv->ip_config_x[IS_IPv4] = NULL;
-        _LOGD(LOGD_IP_from_af(addr_family),
+        _LOGD(LOGD_IPX(IS_IPv4),
               "ip%c-config: clear IP Config instance (%s)",
               nm_utils_addr_family_to_char(addr_family),
               nm_dbus_object_get_path(NM_DBUS_OBJECT(old_config)));
@@ -13671,7 +13668,7 @@ void
 nm_device_set_dev2_ip_config(NMDevice *self, int addr_family, NMIPConfig *config)
 {
     NMDevicePrivate *priv;
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     g_return_if_fail(NM_IS_DEVICE(self));
     g_return_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6));
@@ -14221,7 +14218,7 @@ update_ext_ip_config(NMDevice *self, int addr_family, gboolean intersect_configs
 
     is_up = nm_platform_link_is_up(nm_device_get_platform(self), ifindex);
 
-    if (addr_family == AF_INET) {
+    if (NM_IS_IPv4(addr_family)) {
         g_clear_object(&priv->ext_ip_config_4);
         priv->ext_ip_config_4 = nm_ip4_config_capture(nm_device_get_multi_index(self),
                                                       nm_device_get_platform(self),
@@ -14270,7 +14267,7 @@ update_ext_ip_config(NMDevice *self, int addr_family, gboolean intersect_configs
         }
 
     } else {
-        nm_assert(addr_family == AF_INET6);
+        nm_assert(!NM_IS_IPv4(addr_family));
 
         g_clear_object(&priv->ext_ip_config_6);
         g_clear_object(&priv->ext_ip6_config_captured);
@@ -14345,13 +14342,13 @@ update_ip_config(NMDevice *self, int addr_family)
 
     nm_assert_addr_family(addr_family);
 
-    if (addr_family == AF_INET)
+    if (NM_IS_IPv4(addr_family))
         priv->update_ip_config_completed_v4 = TRUE;
     else
         priv->update_ip_config_completed_v6 = TRUE;
 
     if (update_ext_ip_config(self, addr_family, TRUE)) {
-        if (addr_family == AF_INET) {
+        if (NM_IS_IPv4(addr_family)) {
             if (priv->ext_ip_config_4)
                 ip_config_merge_and_apply(self, AF_INET, FALSE);
         } else {
@@ -14376,7 +14373,7 @@ static gboolean
 queued_ip_config_change(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv;
-    const gboolean   IS_IPv4 = (addr_family == AF_INET);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
     g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
 
@@ -14391,7 +14388,8 @@ queued_ip_config_change(NMDevice *self, int addr_family)
      * update in such case.
      */
     if (priv->activation_source_id_x[IS_IPv4] != 0
-        && priv->activation_source_func_x[IS_IPv4] == activate_stage5_ip_config_result_x[IS_IPv4])
+        && priv->activation_source_func_x[IS_IPv4]
+               == activate_stage5_ip_config_result_x_fcn(addr_family))
         return G_SOURCE_CONTINUE;
 
     priv->queued_ip_config_id_x[IS_IPv4] = 0;
@@ -15414,6 +15412,15 @@ check_connection_available(NMDevice *                     self,
     if (nm_device_is_master(self))
         return TRUE;
 
+    if (!priv->up) {
+        /* If the device is !IFF_UP it also has no carrier. But we assume that if we
+         * would start activating the device (and thereby set the device IFF_UP),
+         * that we would get a carrier. We only know after we set the device up,
+         * and we only set it up after we start activating it. So presumably, this
+         * profile would be available (but we just don't know). */
+        return TRUE;
+    }
+
     nm_utils_error_set_literal(error,
                                NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
                                "device has no carrier");
@@ -15729,6 +15736,7 @@ _cancel_activation(NMDevice *self)
         priv->fw_state = FIREWALL_STATE_INITIALIZED;
     }
 
+    dispatcher_cleanup(self);
     ip_check_gw_ping_cleanup(self);
 
     /* Break the activation chain */
@@ -15740,6 +15748,7 @@ static void
 _cleanup_generic_pre(NMDevice *self, CleanupType cleanup_type)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    guint            i;
 
     _cancel_activation(self);
 
@@ -15764,6 +15773,9 @@ _cleanup_generic_pre(NMDevice *self, CleanupType cleanup_type)
 
     nm_clear_pointer(&priv->shared_ip_handle, nm_netns_shared_ip_release);
 
+    for (i = 0; i < 2; i++)
+        nm_clear_pointer(&priv->hostname_resolver_x[i], _hostname_resolver_free);
+
     _cleanup_ip_pre(self, AF_INET, cleanup_type);
     _cleanup_ip_pre(self, AF_INET6, cleanup_type);
 }
@@ -16431,9 +16443,9 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
                 nm_device_hw_addr_reset(self, "unmanage");
                 set_nm_ipv6ll(self, FALSE);
                 restore_ip6_properties(self);
-                break;
             }
         }
+        nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
         break;
     case NM_DEVICE_STATE_UNAVAILABLE:
         if (old_state == NM_DEVICE_STATE_UNMANAGED) {
@@ -16559,7 +16571,7 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
                 priv->sriov_reset_pending++;
                 sriov_op_queue(self,
                                0,
-                               NM_TERNARY_TRUE,
+                               NM_OPTION_BOOL_TRUE,
                                sriov_reset_on_deactivate_cb,
                                nm_utils_user_data_pack(self, GINT_TO_POINTER(reason)));
             }
@@ -16611,7 +16623,7 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
         if (priv->ifindex > 0
             && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) {
             priv->sriov_reset_pending++;
-            sriov_op_queue(self, 0, NM_TERNARY_TRUE, sriov_reset_on_failure_cb, self);
+            sriov_op_queue(self, 0, NM_OPTION_BOOL_TRUE, sriov_reset_on_failure_cb, self);
             break;
         }
         /* Schedule the transition to DISCONNECTED.  The device can't transition
@@ -16621,8 +16633,22 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
         nm_device_queue_state(self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE);
         break;
     case NM_DEVICE_STATE_IP_CHECK:
-        if (priv->fw_state >= FIREWALL_STATE_INITIALIZED && priv->ip_iface
-            && !nm_device_sys_iface_state_is_external(self)) {
+    {
+        gboolean change_zone = FALSE;
+
+        if (!nm_device_sys_iface_state_is_external(self)) {
+            if (priv->ip_iface) {
+                /* The device now has a @ip_iface different from the
+                 * @iface on which we previously set the zone. */
+                change_zone = TRUE;
+            } else if (priv->fw_state == FIREWALL_STATE_UNMANAGED && priv->ifindex > 0) {
+                /* We didn't set the zone earlier because there was
+                 * no ifindex. */
+                change_zone = TRUE;
+            }
+        }
+
+        if (change_zone) {
             priv->fw_state = FIREWALL_STATE_WAIT_IP_CONFIG;
             fw_change_zone(self);
         } else
@@ -16633,6 +16659,7 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
          */
         notify_ip_properties(self);
         break;
+    }
     case NM_DEVICE_STATE_SECONDARIES:
         ip_check_gw_ping_cleanup(self);
         _LOGD(LOGD_DEVICE, "device entered SECONDARIES state");
@@ -17418,7 +17445,10 @@ nm_device_get_permanent_hw_address_full(NMDevice *self,
 {
     NMDevicePrivate *priv;
 
-    g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
+    g_return_val_if_fail(NM_IS_DEVICE(self), ({
+                             NM_SET_OUT(out_is_fake, FALSE);
+                             NULL;
+                         }));
 
     priv = NM_DEVICE_GET_PRIVATE(self);
 
@@ -17589,6 +17619,209 @@ nm_device_auth_retries_try_next(NMDevice *self)
     return TRUE;
 }
 
+static void
+hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+    HostnameResolver *resolver;
+    NMDevice *        self;
+    gs_free char *    hostname  = NULL;
+    gs_free char *    addr_str  = NULL;
+    gs_free_error GError *error = NULL;
+
+    hostname = g_resolver_lookup_by_address_finish(G_RESOLVER(source), result, &error);
+    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        return;
+
+    resolver           = user_data;
+    self               = resolver->device;
+    resolver->state    = RESOLVER_DONE;
+    resolver->hostname = g_strdup(hostname);
+
+    _LOGD(LOGD_DNS,
+          "hostname-from-dns: lookup done for %s, result %s%s%s",
+          (addr_str = g_inet_address_to_string(resolver->address)),
+          NM_PRINT_FMT_QUOTE_STRING(hostname));
+
+    nm_clear_g_cancellable(&resolver->cancellable);
+    g_signal_emit(self, signals[DNS_LOOKUP_DONE], 0);
+}
+
+static gboolean
+hostname_dns_address_timeout(gpointer user_data)
+{
+    HostnameResolver *resolver = user_data;
+    NMDevice *        self     = resolver->device;
+
+    g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
+
+    nm_assert(resolver->state == RESOLVER_WAIT_ADDRESS);
+    nm_assert(!resolver->address);
+    nm_assert(!resolver->cancellable);
+
+    _LOGT(LOGD_DNS,
+          "hostname-from-dns: timed out while waiting IPv%c address",
+          nm_utils_addr_family_to_char(resolver->addr_family));
+
+    resolver->timeout_id = 0;
+    resolver->state      = RESOLVER_DONE;
+    g_signal_emit(self, signals[DNS_LOOKUP_DONE], 0);
+
+    return G_SOURCE_REMOVE;
+}
+
+static const char *
+_resolver_state_to_string(ResolverState state)
+{
+    switch (state) {
+    case RESOLVER_WAIT_ADDRESS:
+        return "wait-address";
+    case RESOLVER_IN_PROGRESS:
+        return "in-progress";
+    case RESOLVER_DONE:
+        return "done";
+    default:
+        nm_assert_not_reached();
+        return "unknown";
+    }
+}
+
+void
+nm_device_clear_dns_lookup_data(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    guint            i;
+
+    for (i = 0; i < 2; i++)
+        nm_clear_pointer(&priv->hostname_resolver_x[i], _hostname_resolver_free);
+}
+
+/* return value is valid only immediately */
+const char *
+nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean *out_wait)
+{
+    NMDevicePrivate * priv;
+    const int         IS_IPv4 = NM_IS_IPv4(addr_family);
+    HostnameResolver *resolver;
+    NMIPConfig *      ip_config;
+    const char *      method;
+    gboolean          address_changed         = FALSE;
+    gs_unref_object GInetAddress *new_address = NULL;
+
+    g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
+    priv = NM_DEVICE_GET_PRIVATE(self);
+
+    /* If the device is not supposed to have addresses,
+     * return an immediate empty result.*/
+    if (!nm_device_get_applied_connection(self)) {
+        NM_SET_OUT(out_wait, FALSE);
+        return NULL;
+    }
+
+    method = nm_device_get_effective_ip_config_method(self, addr_family);
+    if (IS_IPv4) {
+        if (NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) {
+            nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free);
+            NM_SET_OUT(out_wait, FALSE);
+            return NULL;
+        }
+    } else {
+        if (NM_IN_STRSET(method,
+                         NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
+                         NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+            nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free);
+            NM_SET_OUT(out_wait, FALSE);
+            return NULL;
+        }
+    }
+
+    resolver = priv->hostname_resolver_x[IS_IPv4];
+    if (!resolver) {
+        resolver  = g_slice_new(HostnameResolver);
+        *resolver = (HostnameResolver){
+            .resolver    = g_resolver_get_default(),
+            .device      = self,
+            .addr_family = addr_family,
+            .state       = RESOLVER_WAIT_ADDRESS,
+        };
+        priv->hostname_resolver_x[IS_IPv4] = resolver;
+    }
+
+    /* Determine the first address of the interface and
+     * whether it changed from the previous lookup */
+    ip_config = priv->ip_config_x[IS_IPv4];
+    if (ip_config) {
+        const NMPlatformIPAddress *addr;
+
+        addr = nm_ip_config_get_first_address(ip_config);
+        if (addr) {
+            new_address = g_inet_address_new_from_bytes(addr->address_ptr,
+                                                        IS_IPv4 ? G_SOCKET_FAMILY_IPV4
+                                                                : G_SOCKET_FAMILY_IPV6);
+        }
+    }
+
+    if (new_address && resolver->address) {
+        if (!g_inet_address_equal(new_address, resolver->address))
+            address_changed = TRUE;
+    } else if (new_address != resolver->address)
+        address_changed = TRUE;
+
+    {
+        gs_free char *old_str = NULL;
+        gs_free char *new_str = NULL;
+
+        _LOGT(LOGD_DNS,
+              "hostname-from-dns: ipv%c resolver state %s, old address %s, new address %s",
+              nm_utils_addr_family_to_char(resolver->addr_family),
+              _resolver_state_to_string(resolver->state),
+              resolver->address ? (old_str = g_inet_address_to_string(resolver->address))
+                                : "(null)",
+              new_address ? (new_str = g_inet_address_to_string(new_address)) : "(null)");
+    }
+
+    /* In every state, if the address changed, we restart
+     * the resolution with the new address */
+    if (address_changed) {
+        nm_clear_g_cancellable(&resolver->cancellable);
+        g_clear_object(&resolver->address);
+        resolver->state = RESOLVER_WAIT_ADDRESS;
+    }
+
+    if (address_changed && new_address) {
+        gs_free char *str = NULL;
+
+        _LOGT(LOGD_DNS,
+              "hostname-from-dns: starting lookup for address %s",
+              (str = g_inet_address_to_string(new_address)));
+
+        resolver->state       = RESOLVER_IN_PROGRESS;
+        resolver->cancellable = g_cancellable_new();
+        resolver->address     = g_steal_pointer(&new_address);
+        g_resolver_lookup_by_address_async(resolver->resolver,
+                                           resolver->address,
+                                           resolver->cancellable,
+                                           hostname_dns_lookup_callback,
+                                           resolver);
+        nm_clear_g_source(&resolver->timeout_id);
+    }
+
+    switch (resolver->state) {
+    case RESOLVER_WAIT_ADDRESS:
+        if (!resolver->timeout_id)
+            resolver->timeout_id = g_timeout_add(30000, hostname_dns_address_timeout, resolver);
+        NM_SET_OUT(out_wait, TRUE);
+        return NULL;
+    case RESOLVER_IN_PROGRESS:
+        NM_SET_OUT(out_wait, TRUE);
+        return NULL;
+    case RESOLVER_DONE:
+        NM_SET_OUT(out_wait, FALSE);
+        return resolver->hostname;
+    }
+
+    return nm_assert_unreachable_val(NULL);
+}
+
 /*****************************************************************************/
 
 static const char *
@@ -18084,6 +18317,7 @@ dispose(GObject *object)
     arp_cleanup(self);
 
     nm_clear_g_signal_handler(nm_config_get(), &priv->config_changed_id);
+    nm_clear_g_signal_handler(priv->manager, &priv->ifindex_changed_id);
 
     dispatcher_cleanup(self);
 
@@ -18766,6 +19000,16 @@ nm_device_class_init(NMDeviceClass *klass)
                                            NULL,
                                            G_TYPE_NONE,
                                            0);
+
+    signals[DNS_LOOKUP_DONE] = g_signal_new(NM_DEVICE_DNS_LOOKUP_DONE,
+                                            G_OBJECT_CLASS_TYPE(object_class),
+                                            G_SIGNAL_RUN_FIRST,
+                                            0,
+                                            NULL,
+                                            NULL,
+                                            NULL,
+                                            G_TYPE_NONE,
+                                            0);
 }
 
 /* Connection defaults from plugins */
diff --git a/src/devices/nm-device.h b/src/core/devices/nm-device.h
index 3eae9318..72777d0a 100644
--- a/src/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
@@ -116,6 +116,7 @@ nm_device_state_reason_check(NMDeviceStateReason reason)
 #define NM_DEVICE_HAS_PENDING_ACTION "has-pending-action" /* Internal only */
 
 /* Internal signals */
+#define NM_DEVICE_DNS_LOOKUP_DONE       "dns-lookup-done"
 #define NM_DEVICE_IP4_CONFIG_CHANGED    "ip4-config-changed"
 #define NM_DEVICE_IP6_CONFIG_CHANGED    "ip6-config-changed"
 #define NM_DEVICE_IP6_PREFIX_DELEGATED  "ip6-prefix-delegated"
@@ -243,6 +244,16 @@ typedef struct _NMDeviceClass {
      */
     bool mtu_force_set : 1;
 
+    /* Control whether to call stage1 and stage2 callbacks also for assuming
+     * a device or for external activations. In this case, the callback must
+     * take care not to touch the device's configuration. */
+    bool act_stage1_prepare_also_for_external_or_assume : 1;
+    bool act_stage2_config_also_for_external_or_assume : 1;
+
+    bool act_stage1_prepare_set_hwaddr_ethernet : 1;
+
+    bool can_reapply_change_ovs_external_ids : 1;
+
     void (*state_changed)(NMDevice *          device,
                           NMDeviceState       new_state,
                           NMDeviceState       old_state,
@@ -448,14 +459,6 @@ typedef struct _NMDeviceClass {
 
     gboolean (*set_platform_mtu)(NMDevice *self, guint32 mtu);
 
-    /* Control whether to call stage1 and stage2 callbacks also for assuming
-     * a device or for external activations. In this case, the callback must
-     * take care not to touch the device's configuration. */
-    bool act_stage1_prepare_also_for_external_or_assume : 1;
-    bool act_stage2_config_also_for_external_or_assume : 1;
-
-    bool act_stage1_prepare_set_hwaddr_ethernet : 1;
-
 } NMDeviceClass;
 
 GType nm_device_get_type(void);
@@ -800,12 +803,10 @@ void     nm_device_reapply_settings_immediately(NMDevice *self);
 
 void nm_device_update_firewall_zone(NMDevice *self);
 void nm_device_update_metered(NMDevice *self);
-void nm_device_reactivate_ip4_config(NMDevice *         device,
-                                     NMSettingIPConfig *s_ip4_old,
-                                     NMSettingIPConfig *s_ip4_new);
-void nm_device_reactivate_ip6_config(NMDevice *         device,
-                                     NMSettingIPConfig *s_ip6_old,
-                                     NMSettingIPConfig *s_ip6_new);
+void nm_device_reactivate_ip_config(NMDevice *         device,
+                                    int                addr_family,
+                                    NMSettingIPConfig *s_ip_old,
+                                    NMSettingIPConfig *s_ip_new);
 
 gboolean nm_device_update_hw_address(NMDevice *self);
 void     nm_device_update_initial_hw_address(NMDevice *self);
@@ -865,4 +866,9 @@ const char *nm_device_state_reason_to_str(NMDeviceStateReason reason);
 
 gboolean nm_device_is_vpn(NMDevice *self);
 
+const char *
+nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean *out_pending);
+
+void nm_device_clear_dns_lookup_data(NMDevice *self);
+
 #endif /* __NETWORKMANAGER_DEVICE_H__ */
diff --git a/src/devices/nm-lldp-listener.c b/src/core/devices/nm-lldp-listener.c
index 25d863c8..c60fb3ad 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/core/devices/nm-lldp-listener.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-lldp-listener.h"
 
@@ -103,8 +103,8 @@ typedef struct {
 static void
 lldp_neighbor_get_raw(LldpNeighbor *neigh, const guint8 **out_raw_data, gsize *out_raw_len)
 {
-    gconstpointer raw_data;
-    gsize         raw_len;
+    gconstpointer raw_data = NULL;
+    gsize         raw_len  = 0;
     int           r;
 
     nm_assert(neigh);
@@ -1075,7 +1075,7 @@ nm_lldp_listener_init(NMLldpListener *self)
 NMLldpListener *
 nm_lldp_listener_new(void)
 {
-    return (NMLldpListener *) g_object_new(NM_TYPE_LLDP_LISTENER, NULL);
+    return g_object_new(NM_TYPE_LLDP_LISTENER, NULL);
 }
 
 static void
diff --git a/src/devices/nm-lldp-listener.h b/src/core/devices/nm-lldp-listener.h
index 8c83cd67..9d3e2436 100644
--- a/src/devices/nm-lldp-listener.h
+++ b/src/core/devices/nm-lldp-listener.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
diff --git a/src/core/devices/ovs/meson.build b/src/core/devices/ovs/meson.build
new file mode 100644
index 00000000..81c29bd6
--- /dev/null
+++ b/src/core/devices/ovs/meson.build
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+libnm_device_plugin_ovs = shared_module(
+  'nm-device-plugin-ovs',
+  sources: files(
+    'nm-device-ovs-bridge.c',
+    'nm-device-ovs-interface.c',
+    'nm-device-ovs-port.c',
+    'nm-ovsdb.c',
+    'nm-ovs-factory.c',
+  ),
+  dependencies: [
+    core_plugin_dep,
+    jansson_dep,
+  ],
+  c_args: daemon_c_flags,
+  link_args: ldflags_linker_script_devices,
+  link_depends: linker_script_devices,
+  install: true,
+  install_dir: nm_plugindir,
+)
+
+core_plugins += libnm_device_plugin_ovs
+
+test(
+  'check-local-devices-ovs',
+  check_exports,
+  args: [
+    libnm_device_plugin_ovs.full_path(),
+    linker_script_devices,
+  ],
+)
diff --git a/src/devices/ovs/nm-device-ovs-bridge.c b/src/core/devices/ovs/nm-device-ovs-bridge.c
index a7b7a539..3ae8a481 100644
--- a/src/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/core/devices/ovs/nm-device-ovs-bridge.c
@@ -1,11 +1,13 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-ovs-bridge.h"
+
+#include "nm-device-ovs-interface.h"
 #include "nm-device-ovs-port.h"
 #include "nm-ovsdb.h"
 
@@ -13,9 +15,11 @@
 #include "nm-active-connection.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-ovs-bridge.h"
+#include "nm-setting-ovs-external-ids.h"
+#include "nm-core-internal.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceOvsBridge
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceOvsBridge);
 
 /*****************************************************************************/
 
@@ -87,6 +91,39 @@ static void
 release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
 {}
 
+void
+nm_device_ovs_reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new)
+{
+    NMDeviceType device_type;
+    GType        type;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(g_type_parent(G_TYPE_FROM_INSTANCE(self)) == NM_TYPE_DEVICE);
+
+    /* NMDevice's reapply_connection() doesn't do anything. No need to call the parent
+     * implementation. */
+
+    _LOGD(LOGD_DEVICE, "reapplying settings for OVS device");
+
+    type = G_OBJECT_TYPE(self);
+    if (type == NM_TYPE_DEVICE_OVS_INTERFACE)
+        device_type = NM_DEVICE_TYPE_OVS_INTERFACE;
+    else if (type == NM_TYPE_DEVICE_OVS_PORT)
+        device_type = NM_DEVICE_TYPE_OVS_PORT;
+    else {
+        nm_assert(type == NM_TYPE_DEVICE_OVS_BRIDGE);
+        device_type = NM_DEVICE_TYPE_OVS_BRIDGE;
+    }
+
+    nm_ovsdb_set_external_ids(
+        nm_ovsdb_get(),
+        device_type,
+        nm_device_get_ip_iface(self),
+        nm_connection_get_uuid(con_new),
+        _nm_connection_get_setting(con_old, NM_TYPE_SETTING_OVS_EXTERNAL_IDS),
+        _nm_connection_get_setting(con_new, NM_TYPE_SETTING_OVS_EXTERNAL_IDS));
+}
+
 /*****************************************************************************/
 
 static void
@@ -114,12 +151,14 @@ nm_device_ovs_bridge_class_init(NMDeviceOvsBridgeClass *klass)
     device_class->connection_type_check_compatible = NM_SETTING_OVS_BRIDGE_SETTING_NAME;
     device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES();
 
-    device_class->is_master                  = TRUE;
-    device_class->get_type_description       = get_type_description;
-    device_class->create_and_realize         = create_and_realize;
-    device_class->unrealize                  = unrealize;
-    device_class->get_generic_capabilities   = get_generic_capabilities;
-    device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
-    device_class->enslave_slave              = enslave_slave;
-    device_class->release_slave              = release_slave;
+    device_class->is_master                           = TRUE;
+    device_class->get_type_description                = get_type_description;
+    device_class->create_and_realize                  = create_and_realize;
+    device_class->unrealize                           = unrealize;
+    device_class->get_generic_capabilities            = get_generic_capabilities;
+    device_class->act_stage3_ip_config_start          = act_stage3_ip_config_start;
+    device_class->enslave_slave                       = enslave_slave;
+    device_class->release_slave                       = release_slave;
+    device_class->can_reapply_change_ovs_external_ids = TRUE;
+    device_class->reapply_connection                  = nm_device_ovs_reapply_connection;
 }
diff --git a/src/devices/ovs/nm-device-ovs-bridge.h b/src/core/devices/ovs/nm-device-ovs-bridge.h
index ba04bfeb..2b893343 100644
--- a/src/devices/ovs/nm-device-ovs-bridge.h
+++ b/src/core/devices/ovs/nm-device-ovs-bridge.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
@@ -22,4 +22,9 @@ typedef struct _NMDeviceOvsBridgeClass NMDeviceOvsBridgeClass;
 
 GType nm_device_ovs_bridge_get_type(void);
 
+/*****************************************************************************/
+
+void
+nm_device_ovs_reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new);
+
 #endif /* __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__ */
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c
index 16b6e04d..5d07c211 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/core/devices/ovs/nm-device-ovs-interface.c
@@ -1,11 +1,13 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-ovs-interface.h"
+
+#include "nm-device-ovs-bridge.h"
 #include "nm-ovsdb.h"
 
 #include "devices/nm-device-private.h"
@@ -14,13 +16,14 @@
 #include "nm-setting-ovs-interface.h"
 #include "nm-setting-ovs-port.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceOvsInterface
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceOvsInterface);
 
 /*****************************************************************************/
 
 typedef struct {
-    bool waiting_for_interface : 1;
+    NMOvsdb *ovsdb;
+    bool     waiting_for_interface : 1;
 } NMDeviceOvsInterfacePrivate;
 
 struct _NMDeviceOvsInterface {
@@ -68,7 +71,10 @@ get_generic_capabilities(NMDevice *device)
 static gboolean
 is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
-    return TRUE;
+    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+
+    return nm_ovsdb_is_ready(priv->ovsdb);
 }
 
 static gboolean
@@ -150,6 +156,9 @@ set_platform_mtu_cb(GError *error, gpointer user_data)
 static gboolean
 set_platform_mtu(NMDevice *device, guint32 mtu)
 {
+    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+
     /*
      * If the MTU is not set in ovsdb, Open vSwitch will change
      * the MTU of an internal interface to match the minimum of
@@ -160,7 +169,7 @@ set_platform_mtu(NMDevice *device, guint32 mtu)
      * it can be stopped during shutdown.
      */
     if (_is_internal_interface(device)) {
-        nm_ovsdb_set_interface_mtu(nm_ovsdb_get(),
+        nm_ovsdb_set_interface_mtu(priv->ovsdb,
                                    nm_device_get_ip_iface(device),
                                    mtu,
                                    set_platform_mtu_cb,
@@ -359,8 +368,41 @@ can_update_from_platform_link(NMDevice *device, const NMPlatformLink *plink)
 /*****************************************************************************/
 
 static void
+ovsdb_ready(NMOvsdb *ovsdb, NMDeviceOvsInterface *self)
+{
+    NMDevice *device = NM_DEVICE(self);
+
+    nm_device_queue_recheck_available(device,
+                                      NM_DEVICE_STATE_REASON_NONE,
+                                      NM_DEVICE_STATE_REASON_NONE);
+    nm_device_recheck_available_connections(device);
+    nm_device_emit_recheck_auto_activate(device);
+}
+
+static void
 nm_device_ovs_interface_init(NMDeviceOvsInterface *self)
-{}
+{
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+
+    priv->ovsdb = g_object_ref(nm_ovsdb_get());
+
+    if (!nm_ovsdb_is_ready(priv->ovsdb))
+        g_signal_connect(priv->ovsdb, NM_OVSDB_READY, G_CALLBACK(ovsdb_ready), self);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(object);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
+
+    if (priv->ovsdb) {
+        g_signal_handlers_disconnect_by_func(priv->ovsdb, G_CALLBACK(ovsdb_ready), self);
+        g_clear_object(&priv->ovsdb);
+    }
+
+    G_OBJECT_CLASS(nm_device_ovs_interface_parent_class)->dispose(object);
+}
 
 static const NMDBusInterfaceInfoExtended interface_info_device_ovs_interface = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
@@ -372,9 +414,12 @@ static const NMDBusInterfaceInfoExtended interface_info_device_ovs_interface = {
 static void
 nm_device_ovs_interface_class_init(NMDeviceOvsInterfaceClass *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->dispose = dispose;
+
     dbus_object_class->interface_infos =
         NM_DBUS_INTERFACE_INFOS(&interface_info_device_ovs_interface);
 
@@ -382,17 +427,19 @@ nm_device_ovs_interface_class_init(NMDeviceOvsInterfaceClass *klass)
     device_class->connection_type_check_compatible = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
     device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_OPENVSWITCH);
 
-    device_class->can_update_from_platform_link = can_update_from_platform_link;
-    device_class->deactivate                    = deactivate;
-    device_class->deactivate_async              = deactivate_async;
-    device_class->get_type_description          = get_type_description;
-    device_class->create_and_realize            = create_and_realize;
-    device_class->get_generic_capabilities      = get_generic_capabilities;
-    device_class->is_available                  = is_available;
-    device_class->check_connection_compatible   = check_connection_compatible;
-    device_class->link_changed                  = link_changed;
-    device_class->act_stage3_ip_config_start    = act_stage3_ip_config_start;
-    device_class->can_unmanaged_external_down   = can_unmanaged_external_down;
-    device_class->set_platform_mtu              = set_platform_mtu;
-    device_class->get_configured_mtu            = nm_device_get_configured_mtu_for_wired;
+    device_class->can_update_from_platform_link       = can_update_from_platform_link;
+    device_class->deactivate                          = deactivate;
+    device_class->deactivate_async                    = deactivate_async;
+    device_class->get_type_description                = get_type_description;
+    device_class->create_and_realize                  = create_and_realize;
+    device_class->get_generic_capabilities            = get_generic_capabilities;
+    device_class->is_available                        = is_available;
+    device_class->check_connection_compatible         = check_connection_compatible;
+    device_class->link_changed                        = link_changed;
+    device_class->act_stage3_ip_config_start          = act_stage3_ip_config_start;
+    device_class->can_unmanaged_external_down         = can_unmanaged_external_down;
+    device_class->set_platform_mtu                    = set_platform_mtu;
+    device_class->get_configured_mtu                  = nm_device_get_configured_mtu_for_wired;
+    device_class->can_reapply_change_ovs_external_ids = TRUE;
+    device_class->reapply_connection                  = nm_device_ovs_reapply_connection;
 }
diff --git a/src/devices/ovs/nm-device-ovs-interface.h b/src/core/devices/ovs/nm-device-ovs-interface.h
index 902847de..03b0e9a4 100644
--- a/src/devices/ovs/nm-device-ovs-interface.h
+++ b/src/core/devices/ovs/nm-device-ovs-interface.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c
index de98e29e..2ecb95e8 100644
--- a/src/devices/ovs/nm-device-ovs-port.c
+++ b/src/core/devices/ovs/nm-device-ovs-port.c
@@ -1,12 +1,14 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-ovs-port.h"
+
 #include "nm-device-ovs-interface.h"
+#include "nm-device-ovs-bridge.h"
 #include "nm-ovsdb.h"
 
 #include "devices/nm-device-private.h"
@@ -15,8 +17,8 @@
 #include "nm-setting-ovs-port.h"
 #include "nm-setting-ovs-port.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceOvsPort
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceOvsPort);
 
 /*****************************************************************************/
 
@@ -182,11 +184,13 @@ nm_device_ovs_port_class_init(NMDeviceOvsPortClass *klass)
     device_class->connection_type_check_compatible = NM_SETTING_OVS_PORT_SETTING_NAME;
     device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES();
 
-    device_class->is_master                  = TRUE;
-    device_class->get_type_description       = get_type_description;
-    device_class->create_and_realize         = create_and_realize;
-    device_class->get_generic_capabilities   = get_generic_capabilities;
-    device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
-    device_class->enslave_slave              = enslave_slave;
-    device_class->release_slave              = release_slave;
+    device_class->is_master                           = TRUE;
+    device_class->get_type_description                = get_type_description;
+    device_class->create_and_realize                  = create_and_realize;
+    device_class->get_generic_capabilities            = get_generic_capabilities;
+    device_class->act_stage3_ip_config_start          = act_stage3_ip_config_start;
+    device_class->enslave_slave                       = enslave_slave;
+    device_class->release_slave                       = release_slave;
+    device_class->can_reapply_change_ovs_external_ids = TRUE;
+    device_class->reapply_connection                  = nm_device_ovs_reapply_connection;
 }
diff --git a/src/devices/ovs/nm-device-ovs-port.h b/src/core/devices/ovs/nm-device-ovs-port.h
index 3f5d7793..f4516c01 100644
--- a/src/devices/ovs/nm-device-ovs-port.h
+++ b/src/core/devices/ovs/nm-device-ovs-port.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/src/devices/ovs/nm-ovs-factory.c b/src/core/devices/ovs/nm-ovs-factory.c
index e1feacbd..e7af38d8 100644
--- a/src/devices/ovs/nm-ovs-factory.c
+++ b/src/core/devices/ovs/nm-ovs-factory.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-manager.h"
 #include "nm-ovsdb.h"
@@ -64,7 +64,7 @@ G_MODULE_EXPORT NMDeviceFactory *
                 nm_device_factory_create(GError **error)
 {
     nm_manager_set_capability(NM_MANAGER_GET, NM_CAPABILITY_OVS);
-    return (NMDeviceFactory *) g_object_new(NM_TYPE_OVS_FACTORY, NULL);
+    return g_object_new(NM_TYPE_OVS_FACTORY, NULL);
 }
 
 static NMDevice *
@@ -108,10 +108,21 @@ new_device_from_type(const char *name, NMDeviceType device_type)
 static void
 ovsdb_device_added(NMOvsdb *        ovsdb,
                    const char *     name,
-                   NMDeviceType     device_type,
+                   guint            device_type_i,
+                   const char *     subtype,
                    NMDeviceFactory *self)
 {
-    NMDevice *device = NULL;
+    const NMDeviceType device_type = device_type_i;
+    NMDevice *         device;
+
+    if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE
+        && !NM_IN_STRSET(subtype, "internal", "patch")) {
+        /* system interfaces refer to kernel devices and
+         * don't need to be created by this factory. Ignore
+         * anything that is not an internal or patch
+         * interface. */
+        return;
+    }
 
     device = new_device_from_type(name, device_type);
     if (!device)
@@ -124,23 +135,67 @@ ovsdb_device_added(NMOvsdb *        ovsdb,
 static void
 ovsdb_device_removed(NMOvsdb *        ovsdb,
                      const char *     name,
-                     NMDeviceType     device_type,
+                     guint            device_type_i,
+                     const char *     subtype,
                      NMDeviceFactory *self)
 {
-    NMDevice *    device;
-    NMDeviceState device_state;
+    const NMDeviceType device_type = device_type_i;
+    NMDevice *         device      = NULL;
+    NMDeviceState      device_state;
+    gboolean           is_system_interface = FALSE;
+
+    if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE
+        && !NM_IN_STRSET(subtype, "internal", "patch", "system"))
+        return;
+
+    if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE && nm_streq0(subtype, "system")) {
+        NMDevice *             d;
+        const CList *          list;
+        NMSettingOvsInterface *s_ovs_int;
+
+        /* The device associated to an OVS system interface can be of
+         * any kind. Find an interface with the same name and which has
+         * the OVS-interface setting. */
+        is_system_interface = TRUE;
+        nm_manager_for_each_device (NM_MANAGER_GET, d, list) {
+            if (!nm_streq0(nm_device_get_iface(d), name))
+                continue;
+            s_ovs_int = nm_device_get_applied_setting(d, NM_TYPE_SETTING_OVS_INTERFACE);
+            if (!s_ovs_int)
+                continue;
+            if (!nm_streq0(nm_setting_ovs_interface_get_interface_type(s_ovs_int), "system"))
+                continue;
+            /* Failing the system interface device is almost always the right
+             * thing to do when the ovsdb entry is removed. However, to avoid
+             * that a late device-removed signal tears down a different,
+             * newly-activated connection, let's also check that we have a master.
+             * Or in alternative, that the device is assumed/external: in such
+             * case it's always fine to fail the device.
+             */
+            if (!nm_device_get_master(d) && !nm_device_sys_iface_state_is_external_or_assume(d))
+                continue;
+
+            device = d;
+        }
+    } else {
+        device = nm_manager_get_device(NM_MANAGER_GET, name, device_type);
+    }
 
-    device = nm_manager_get_device(NM_MANAGER_GET, name, device_type);
     if (!device)
         return;
 
     device_state = nm_device_get_state(device);
-    if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE && device_state > NM_DEVICE_STATE_DISCONNECTED
+
+    if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE && nm_device_get_act_request(device)
         && device_state < NM_DEVICE_STATE_DEACTIVATING) {
         nm_device_state_changed(device,
                                 NM_DEVICE_STATE_DEACTIVATING,
-                                NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED);
-    } else if (device_state == NM_DEVICE_STATE_UNMANAGED) {
+                                NM_DEVICE_STATE_REASON_REMOVED);
+        return;
+    }
+
+    /* OVS system interfaces still exist even without the ovsdb entry */
+    if (!is_system_interface && device_state == NM_DEVICE_STATE_UNMANAGED) {
         nm_device_unrealize(device, TRUE, NULL);
     }
 }
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index 27ef789f..da3a7989 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-ovsdb.h"
 
@@ -11,9 +11,16 @@
 #include <gio/gunixsocketaddress.h>
 
 #include "nm-glib-aux/nm-jansson.h"
+#include "nm-glib-aux/nm-str-buf.h"
 #include "nm-core-utils.h"
 #include "nm-core-internal.h"
 #include "devices/nm-device.h"
+#include "nm-manager.h"
+#include "nm-setting-ovs-external-ids.h"
+
+/*****************************************************************************/
+
+#define OVSDB_MAX_FAILURES 3
 
 /*****************************************************************************/
 
@@ -22,26 +29,91 @@
 #endif
 
 typedef struct {
+    char *     port_uuid;
     char *     name;
     char *     connection_uuid;
     GPtrArray *interfaces; /* interface uuids */
+    GArray *   external_ids;
 } OpenvswitchPort;
 
 typedef struct {
+    char *     bridge_uuid;
     char *     name;
     char *     connection_uuid;
     GPtrArray *ports; /* port uuids */
+    GArray *   external_ids;
 } OpenvswitchBridge;
 
 typedef struct {
-    char *name;
-    char *type;
-    char *connection_uuid;
+    char *  interface_uuid;
+    char *  name;
+    char *  type;
+    char *  connection_uuid;
+    GArray *external_ids;
 } OpenvswitchInterface;
 
 /*****************************************************************************/
 
-enum { DEVICE_ADDED, DEVICE_REMOVED, INTERFACE_FAILED, LAST_SIGNAL };
+typedef void (*OvsdbMethodCallback)(NMOvsdb *self,
+                                    json_t * response,
+                                    GError * error,
+                                    gpointer user_data);
+
+typedef enum {
+    OVSDB_MONITOR,
+    OVSDB_ADD_INTERFACE,
+    OVSDB_DEL_INTERFACE,
+    OVSDB_SET_INTERFACE_MTU,
+    OVSDB_SET_EXTERNAL_IDS,
+} OvsdbCommand;
+
+#define CALL_ID_UNSPEC G_MAXUINT64
+
+typedef union {
+    struct {
+    } monitor;
+    struct {
+        NMConnection *bridge;
+        NMConnection *port;
+        NMConnection *interface;
+        NMDevice *    bridge_device;
+        NMDevice *    interface_device;
+    } add_interface;
+    struct {
+        char *ifname;
+    } del_interface;
+    struct {
+        char *  ifname;
+        guint32 mtu;
+    } set_interface_mtu;
+    struct {
+        NMDeviceType device_type;
+        char *       ifname;
+        char *       connection_uuid;
+        GHashTable * exid_old;
+        GHashTable * exid_new;
+    } set_external_ids;
+} OvsdbMethodPayload;
+
+typedef struct {
+    NMOvsdb *           self;
+    CList               calls_lst;
+    guint64             call_id;
+    OvsdbCommand        command;
+    OvsdbMethodCallback callback;
+    gpointer            user_data;
+    OvsdbMethodPayload  payload;
+} OvsdbMethodCall;
+
+/*****************************************************************************/
+
+enum {
+    DEVICE_ADDED,
+    DEVICE_REMOVED,
+    INTERFACE_FAILED,
+    READY,
+    LAST_SIGNAL,
+};
 
 static guint signals[LAST_SIGNAL] = {0};
 
@@ -53,13 +125,17 @@ typedef struct {
     size_t             bufp;      /* Last decoded byte in the input buffer. */
     GString *          input;     /* JSON stream waiting for decoding. */
     GString *          output;    /* JSON stream to be sent. */
-    gint64             seq;
-    GArray *           calls;      /* Method calls waiting for a response. */
-    GHashTable *       interfaces; /* interface uuid => OpenvswitchInterface */
-    GHashTable *       ports;      /* port uuid => OpenvswitchPort */
-    GHashTable *       bridges;    /* bridge uuid => OpenvswitchBridge */
-    char *             db_uuid;
-    guint              num_failures;
+    guint64            call_id_counter;
+
+    CList calls_lst_head;
+
+    GHashTable *interfaces; /* interface uuid => OpenvswitchInterface */
+    GHashTable *ports;      /* port uuid => OpenvswitchPort */
+    GHashTable *bridges;    /* bridge uuid => OpenvswitchBridge */
+    char *      db_uuid;
+    guint       num_failures;
+    guint       num_pending_deletions;
+    bool        ready : 1;
 } NMOvsdbPrivate;
 
 struct _NMOvsdb {
@@ -75,9 +151,6 @@ G_DEFINE_TYPE(NMOvsdb, nm_ovsdb, G_TYPE_OBJECT)
 
 #define NM_OVSDB_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMOvsdb, NM_IS_OVSDB)
 
-#define _NMLOG_DOMAIN      LOGD_DEVICE
-#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "ovsdb", __VA_ARGS__)
-
 NM_DEFINE_SINGLETON_GETTER(NMOvsdb, nm_ovsdb_get, NM_TYPE_OVSDB);
 
 /*****************************************************************************/
@@ -90,89 +163,198 @@ static void ovsdb_next_command(NMOvsdb *self);
 
 /*****************************************************************************/
 
-/* ovsdb command abstraction. */
+#define _NMLOG_DOMAIN      LOGD_DEVICE
+#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "ovsdb", __VA_ARGS__)
 
-typedef void (*OvsdbMethodCallback)(NMOvsdb *self,
-                                    json_t * response,
-                                    GError * error,
-                                    gpointer user_data);
+#define _NMLOG_call(level, call, ...)                                                  \
+    _NMLOG((level),                                                                    \
+           "call[" NM_HASH_OBFUSCATE_PTR_FMT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+           NM_HASH_OBFUSCATE_PTR((call)) _NM_UTILS_MACRO_REST(__VA_ARGS__))
 
-typedef enum {
-    OVSDB_MONITOR,
-    OVSDB_ADD_INTERFACE,
-    OVSDB_DEL_INTERFACE,
-    OVSDB_SET_INTERFACE_MTU,
-} OvsdbCommand;
+#define _LOGT_call(call, ...) _NMLOG_call(LOGL_TRACE, (call), __VA_ARGS__)
 
-typedef struct {
-    gint64 id;
-#define COMMAND_PENDING -1 /* id not yet assigned */
-    OvsdbCommand        command;
-    OvsdbMethodCallback callback;
-    gpointer            user_data;
-    union {
-        struct {
-            char *  ifname;
-            guint32 mtu;
-        };
-        struct {
-            NMConnection *bridge;
-            NMConnection *port;
-            NMConnection *interface;
-            NMDevice *    bridge_device;
-            NMDevice *    interface_device;
-        };
-    };
-} OvsdbMethodCall;
+/*****************************************************************************/
 
-#define OVSDB_MAX_FAILURES 3
+#define OVSDB_METHOD_PAYLOAD_MONITOR() \
+    (&((const OvsdbMethodPayload){     \
+        .monitor = {},                 \
+    }))
+
+#define OVSDB_METHOD_PAYLOAD_ADD_INTERFACE(xbridge,           \
+                                           xport,             \
+                                           xinterface,        \
+                                           xbridge_device,    \
+                                           xinterface_device) \
+    (&((const OvsdbMethodPayload){                            \
+        .add_interface =                                      \
+            {                                                 \
+                .bridge           = (xbridge),                \
+                .port             = (xport),                  \
+                .interface        = (xinterface),             \
+                .bridge_device    = (xbridge_device),         \
+                .interface_device = (xinterface_device),      \
+            },                                                \
+    }))
+
+#define OVSDB_METHOD_PAYLOAD_DEL_INTERFACE(xifname)               \
+    (&((const OvsdbMethodPayload){                                \
+        .del_interface =                                          \
+            {                                                     \
+                .ifname = (char *) NM_CONSTCAST(char, (xifname)), \
+            },                                                    \
+    }))
+
+#define OVSDB_METHOD_PAYLOAD_SET_INTERFACE_MTU(xifname, xmtu)     \
+    (&((const OvsdbMethodPayload){                                \
+        .set_interface_mtu =                                      \
+            {                                                     \
+                .ifname = (char *) NM_CONSTCAST(char, (xifname)), \
+                .mtu    = (xmtu),                                 \
+            },                                                    \
+    }))
+
+#define OVSDB_METHOD_PAYLOAD_SET_EXTERNAL_IDS(xdevice_type,                         \
+                                              xifname,                              \
+                                              xconnection_uuid,                     \
+                                              xexid_old,                            \
+                                              xexid_new)                            \
+    (&((const OvsdbMethodPayload){                                                  \
+        .set_external_ids =                                                         \
+            {                                                                       \
+                .device_type     = xdevice_type,                                    \
+                .ifname          = (char *) NM_CONSTCAST(char, (xifname)),          \
+                .connection_uuid = (char *) NM_CONSTCAST(char, (xconnection_uuid)), \
+                .exid_old        = (xexid_old),                                     \
+                .exid_new        = (xexid_new),                                     \
+            },                                                                      \
+    }))
+
+/*****************************************************************************/
+
+static NM_UTILS_LOOKUP_STR_DEFINE(_device_type_to_table,
+                                  NMDeviceType,
+                                  NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(NULL),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_BRIDGE, "Bridge"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_PORT, "Port"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_TYPE_OVS_INTERFACE,
+                                                           "Interface"),
+                                  NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), );
+
+/*****************************************************************************/
 
 static void
-_LOGT_call_do(const char *comment, OvsdbMethodCall *call, json_t *msg)
+_call_complete(OvsdbMethodCall *call, json_t *response, GError *error)
 {
-    gs_free char *str = NULL;
+    if (response) {
+        gs_free char *str = NULL;
+
+        str = json_dumps(response, 0);
+        if (error)
+            _LOGT_call(call, "completed: %s ; error: %s", str, error->message);
+        else
+            _LOGT_call(call, "completed: %s", str);
+    } else {
+        nm_assert(error);
+        _LOGT_call(call, "completed: error: %s", error->message);
+    }
+
+    c_list_unlink_stale(&call->calls_lst);
 
-    if (msg)
-        str = json_dumps(msg, 0);
+    if (call->callback)
+        call->callback(call->self, response, error, call->user_data);
 
     switch (call->command) {
     case OVSDB_MONITOR:
-        _LOGT("%s: monitor%s%s", comment, msg ? ": " : "", msg ? str : "");
         break;
     case OVSDB_ADD_INTERFACE:
-        _LOGT("%s: add-iface bridge=%s port=%s interface=%s%s%s",
-              comment,
-              nm_connection_get_interface_name(call->bridge),
-              nm_connection_get_interface_name(call->port),
-              nm_connection_get_interface_name(call->interface),
-              msg ? ": " : "",
-              msg ? str : "");
+        g_clear_object(&call->payload.add_interface.bridge);
+        g_clear_object(&call->payload.add_interface.port);
+        g_clear_object(&call->payload.add_interface.interface);
+        g_clear_object(&call->payload.add_interface.bridge_device);
+        g_clear_object(&call->payload.add_interface.interface_device);
         break;
     case OVSDB_DEL_INTERFACE:
-        _LOGT("%s: del-iface interface=%s%s%s",
-              comment,
-              call->ifname,
-              msg ? ": " : "",
-              msg ? str : "");
+        nm_clear_g_free(&call->payload.del_interface.ifname);
         break;
     case OVSDB_SET_INTERFACE_MTU:
-        _LOGT("%s: set-iface-mtu interface=%s%s%s mtu=%u",
-              comment,
-              call->ifname,
-              msg ? ": " : "",
-              msg ? str : "",
-              call->mtu);
+        nm_clear_g_free(&call->payload.set_interface_mtu.ifname);
+        break;
+    case OVSDB_SET_EXTERNAL_IDS:
+        nm_clear_g_free(&call->payload.set_external_ids.ifname);
+        nm_clear_g_free(&call->payload.set_external_ids.connection_uuid);
+        nm_clear_pointer(&call->payload.set_external_ids.exid_old, g_hash_table_destroy);
+        nm_clear_pointer(&call->payload.set_external_ids.exid_new, g_hash_table_destroy);
         break;
     }
+
+    nm_g_slice_free(call);
+}
+
+/*****************************************************************************/
+
+static void
+_free_bridge(OpenvswitchBridge *ovs_bridge)
+{
+    g_free(ovs_bridge->bridge_uuid);
+    g_free(ovs_bridge->name);
+    g_free(ovs_bridge->connection_uuid);
+    g_ptr_array_free(ovs_bridge->ports, TRUE);
+    nm_g_array_unref(ovs_bridge->external_ids);
+    nm_g_slice_free(ovs_bridge);
 }
 
-#define _LOGT_call(comment, call, message)               \
-    G_STMT_START                                         \
-    {                                                    \
-        if (_LOGT_ENABLED())                             \
-            _LOGT_call_do((comment), (call), (message)); \
-    }                                                    \
-    G_STMT_END
+static void
+_free_port(OpenvswitchPort *ovs_port)
+{
+    g_free(ovs_port->port_uuid);
+    g_free(ovs_port->name);
+    g_free(ovs_port->connection_uuid);
+    g_ptr_array_free(ovs_port->interfaces, TRUE);
+    nm_g_array_unref(ovs_port->external_ids);
+    nm_g_slice_free(ovs_port);
+}
+
+static void
+_free_interface(OpenvswitchInterface *ovs_interface)
+{
+    g_free(ovs_interface->interface_uuid);
+    g_free(ovs_interface->name);
+    g_free(ovs_interface->connection_uuid);
+    g_free(ovs_interface->type);
+    nm_g_array_unref(ovs_interface->external_ids);
+    nm_g_slice_free(ovs_interface);
+}
+
+/*****************************************************************************/
+
+static void
+_signal_emit_device_added(NMOvsdb *    self,
+                          const char * name,
+                          NMDeviceType device_type,
+                          const char * device_subtype)
+{
+    g_signal_emit(self, signals[DEVICE_ADDED], 0, name, (guint) device_type, device_subtype);
+}
+
+static void
+_signal_emit_device_removed(NMOvsdb *    self,
+                            const char * name,
+                            NMDeviceType device_type,
+                            const char * device_subtype)
+{
+    g_signal_emit(self, signals[DEVICE_REMOVED], 0, name, (guint) device_type, device_subtype);
+}
+
+static void
+_signal_emit_interface_failed(NMOvsdb *   self,
+                              const char *name,
+                              const char *connection_uuid,
+                              const char *error)
+{
+    g_signal_emit(self, signals[INTERFACE_FAILED], 0, name, connection_uuid, error);
+}
+
+/*****************************************************************************/
 
 /**
  * ovsdb_call_method:
@@ -181,18 +363,12 @@ _LOGT_call_do(const char *comment, OvsdbMethodCall *call, json_t *msg)
  * there's no command pending completion.
  */
 static void
-ovsdb_call_method(NMOvsdb *           self,
-                  OvsdbCommand        command,
-                  const char *        ifname,
-                  NMConnection *      bridge,
-                  NMConnection *      port,
-                  NMConnection *      interface,
-                  NMDevice *          bridge_device,
-                  NMDevice *          interface_device,
-                  guint32             mtu,
-                  OvsdbMethodCallback callback,
-                  gpointer            user_data,
-                  gboolean            add_first)
+ovsdb_call_method(NMOvsdb *                 self,
+                  OvsdbMethodCallback       callback,
+                  gpointer                  user_data,
+                  gboolean                  add_first,
+                  OvsdbCommand              command,
+                  const OvsdbMethodPayload *payload)
 {
     NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self);
     OvsdbMethodCall *call;
@@ -200,39 +376,76 @@ ovsdb_call_method(NMOvsdb *           self,
     /* Ensure we're not unsynchronized before we queue the method call. */
     ovsdb_try_connect(self);
 
-    if (add_first) {
-        g_array_prepend_val(priv->calls, (OvsdbMethodCall){});
-        call = &g_array_index(priv->calls, OvsdbMethodCall, 0);
-    } else {
-        g_array_set_size(priv->calls, priv->calls->len + 1);
-        call = &g_array_index(priv->calls, OvsdbMethodCall, priv->calls->len - 1);
-    }
-    call->id        = COMMAND_PENDING;
-    call->command   = command;
-    call->callback  = callback;
-    call->user_data = user_data;
+    call  = g_slice_new(OvsdbMethodCall);
+    *call = (OvsdbMethodCall){
+        .self      = self,
+        .call_id   = CALL_ID_UNSPEC,
+        .command   = command,
+        .callback  = callback,
+        .user_data = user_data,
+    };
 
-    switch (call->command) {
+    if (add_first)
+        c_list_link_front(&priv->calls_lst_head, &call->calls_lst);
+    else
+        c_list_link_tail(&priv->calls_lst_head, &call->calls_lst);
+
+    /* Migrate the arguments from @payload to @call->payload. Technically,
+     * this is not a plain copy, because
+     * - call->payload is not initialized (thus no need to free the previous data).
+     * - payload does not own the data. It is merely initialized using the
+     *   OVSDB_METHOD_PAYLOAD_*() macros. */
+    switch (command) {
     case OVSDB_MONITOR:
+        _LOGT_call(call, "new: monitor");
         break;
     case OVSDB_ADD_INTERFACE:
-        call->bridge           = nm_simple_connection_new_clone(bridge);
-        call->port             = nm_simple_connection_new_clone(port);
-        call->interface        = nm_simple_connection_new_clone(interface);
-        call->bridge_device    = g_object_ref(bridge_device);
-        call->interface_device = g_object_ref(interface_device);
+        /* FIXME(applied-connection-immutable): we should not modify the applied
+         *   connection, consequently there is no need to clone the connections. */
+        call->payload.add_interface.bridge =
+            nm_simple_connection_new_clone(payload->add_interface.bridge);
+        call->payload.add_interface.port =
+            nm_simple_connection_new_clone(payload->add_interface.port);
+        call->payload.add_interface.interface =
+            nm_simple_connection_new_clone(payload->add_interface.interface);
+        call->payload.add_interface.bridge_device =
+            g_object_ref(payload->add_interface.bridge_device);
+        call->payload.add_interface.interface_device =
+            g_object_ref(payload->add_interface.interface_device);
+        _LOGT_call(call,
+                   "new: add-interface bridge=%s port=%s interface=%s",
+                   nm_connection_get_interface_name(call->payload.add_interface.bridge),
+                   nm_connection_get_interface_name(call->payload.add_interface.port),
+                   nm_connection_get_interface_name(call->payload.add_interface.interface));
         break;
     case OVSDB_DEL_INTERFACE:
-        call->ifname = g_strdup(ifname);
+        call->payload.del_interface.ifname = g_strdup(payload->del_interface.ifname);
+        _LOGT_call(call, "new: del-interface interface=%s", call->payload.del_interface.ifname);
         break;
     case OVSDB_SET_INTERFACE_MTU:
-        call->ifname = g_strdup(ifname);
-        call->mtu    = mtu;
+        call->payload.set_interface_mtu.ifname = g_strdup(payload->set_interface_mtu.ifname);
+        call->payload.set_interface_mtu.mtu    = payload->set_interface_mtu.mtu;
+        _LOGT_call(call,
+                   "new: set-interface-mtu interface=%s mtu=%u",
+                   call->payload.set_interface_mtu.ifname,
+                   call->payload.set_interface_mtu.mtu);
+        break;
+    case OVSDB_SET_EXTERNAL_IDS:
+        call->payload.set_external_ids.device_type = payload->set_external_ids.device_type;
+        call->payload.set_external_ids.ifname      = g_strdup(payload->set_external_ids.ifname);
+        call->payload.set_external_ids.connection_uuid =
+            g_strdup(payload->set_external_ids.connection_uuid);
+        call->payload.set_external_ids.exid_old =
+            nm_g_hash_table_ref(payload->set_external_ids.exid_old);
+        call->payload.set_external_ids.exid_new =
+            nm_g_hash_table_ref(payload->set_external_ids.exid_new);
+        _LOGT_call(call,
+                   "new: set-external-ids con-uuid=%s, interface=%s",
+                   call->payload.set_external_ids.connection_uuid,
+                   call->payload.set_external_ids.ifname);
         break;
     }
 
-    _LOGT_call("enqueue", call, NULL);
-
     ovsdb_next_command(self);
 }
 
@@ -437,6 +650,94 @@ _set_port_interfaces(json_t *params, const char *ifname, json_t *new_interfaces)
                                     ifname));
 }
 
+static json_t *
+_j_create_external_ids_array_new(NMConnection *connection)
+{
+    json_t *                 array;
+    const char *const *      external_ids   = NULL;
+    guint                    n_external_ids = 0;
+    guint                    i;
+    const char *             uuid;
+    NMSettingOvsExternalIDs *s_exid;
+
+    nm_assert(NM_IS_CONNECTION(connection));
+
+    array = json_array();
+
+    uuid = nm_connection_get_uuid(connection);
+    nm_assert(uuid);
+    json_array_append_new(array, json_pack("[s, s]", NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID, uuid));
+
+    s_exid = _nm_connection_get_setting(connection, NM_TYPE_SETTING_OVS_EXTERNAL_IDS);
+    if (s_exid)
+        external_ids = nm_setting_ovs_external_ids_get_data_keys(s_exid, &n_external_ids);
+    for (i = 0; i < n_external_ids; i++) {
+        const char *k = external_ids[i];
+
+        json_array_append_new(
+            array,
+            json_pack("[s, s]", k, nm_setting_ovs_external_ids_get_data(s_exid, k)));
+    }
+
+    return json_pack("[s, o]", "map", array);
+}
+
+static json_t *
+_j_create_external_ids_array_update(const char *connection_uuid,
+                                    GHashTable *exid_old,
+                                    GHashTable *exid_new)
+{
+    GHashTableIter iter;
+    json_t *       mutations;
+    json_t *       array;
+    const char *   key;
+    const char *   val;
+
+    nm_assert(connection_uuid);
+
+    mutations = json_array();
+
+    if (exid_old) {
+        array = NULL;
+        g_hash_table_iter_init(&iter, exid_old);
+        while (g_hash_table_iter_next(&iter, (gpointer *) &key, NULL)) {
+            if (nm_g_hash_table_contains(exid_new, key))
+                continue;
+            if (NM_STR_HAS_PREFIX(key, NM_OVS_EXTERNAL_ID_NM_PREFIX))
+                continue;
+
+            if (!array)
+                array = json_array();
+
+            json_array_append_new(array, json_string(key));
+        }
+        if (array) {
+            json_array_append_new(
+                mutations,
+                json_pack("[s, s, [s, o]]", "external_ids", "delete", "set", array));
+        }
+    }
+
+    array = json_array();
+
+    json_array_append_new(
+        array,
+        json_pack("[s, s]", NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID, connection_uuid));
+
+    if (exid_new) {
+        g_hash_table_iter_init(&iter, exid_new);
+        while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) {
+            if (NM_STR_HAS_PREFIX(key, NM_OVS_EXTERNAL_ID_NM_PREFIX))
+                continue;
+            json_array_append_new(array, json_pack("[s, s]", key, val));
+        }
+    }
+
+    json_array_append_new(mutations,
+                          json_pack("[s, s, [s, o]]", "external_ids", "insert", "map", array));
+    return mutations;
+}
+
 /**
  * _insert_interface:
  *
@@ -487,7 +788,7 @@ _insert_interface(json_t *      params,
         json_array_append_new(options, json_array());
     }
 
-    row = json_pack("{s:s, s:s, s:o, s:[s, [[s, s]]]}",
+    row = json_pack("{s:s, s:s, s:o, s:o}",
                     "name",
                     nm_connection_get_interface_name(interface),
                     "type",
@@ -495,9 +796,7 @@ _insert_interface(json_t *      params,
                     "options",
                     options,
                     "external_ids",
-                    "map",
-                    "NM.connection.uuid",
-                    nm_connection_get_uuid(interface));
+                    _j_create_external_ids_array_new(interface));
 
     if (cloned_mac)
         json_object_set_new(row, "mac", json_string(cloned_mac));
@@ -562,10 +861,7 @@ _insert_port(json_t *params, NMConnection *port, json_t *new_interfaces)
 
     json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(port)));
     json_object_set_new(row, "interfaces", json_pack("[s, O]", "set", new_interfaces));
-    json_object_set_new(
-        row,
-        "external_ids",
-        json_pack("[s, [[s, s]]]", "map", "NM.connection.uuid", nm_connection_get_uuid(port)));
+    json_object_set_new(row, "external_ids", _j_create_external_ids_array_new(port));
 
     /* Create a new one. */
     json_array_append_new(params,
@@ -625,10 +921,7 @@ _insert_bridge(json_t *      params,
 
     json_object_set_new(row, "name", json_string(nm_connection_get_interface_name(bridge)));
     json_object_set_new(row, "ports", json_pack("[s, O]", "set", new_ports));
-    json_object_set_new(
-        row,
-        "external_ids",
-        json_pack("[s, [[s, s]]]", "map", "NM.connection.uuid", nm_connection_get_uuid(bridge)));
+    json_object_set_new(row, "external_ids", _j_create_external_ids_array_new(bridge));
 
     if (cloned_mac) {
         json_object_set_new(row,
@@ -691,7 +984,6 @@ _add_interface(NMOvsdb *     self,
 {
     NMOvsdbPrivate *      priv = NM_OVSDB_GET_PRIVATE(self);
     GHashTableIter        iter;
-    const char *          bridge_uuid;
     const char *          port_uuid;
     const char *          interface_uuid;
     const char *          bridge_name;
@@ -777,43 +1069,43 @@ _add_interface(NMOvsdb *     self,
     }
 
     g_hash_table_iter_init(&iter, priv->bridges);
-    while (g_hash_table_iter_next(&iter, (gpointer) &bridge_uuid, (gpointer) &ovs_bridge)) {
-        json_array_append_new(bridges, json_pack("[s, s]", "uuid", bridge_uuid));
+    while (g_hash_table_iter_next(&iter, (gpointer) &ovs_bridge, NULL)) {
+        json_array_append_new(bridges, json_pack("[s, s]", "uuid", ovs_bridge->bridge_uuid));
 
-        if (g_strcmp0(ovs_bridge->name, bridge_name) != 0
-            || g_strcmp0(ovs_bridge->connection_uuid, nm_connection_get_uuid(bridge)) != 0)
+        if (!nm_streq0(ovs_bridge->name, bridge_name)
+            || !nm_streq0(ovs_bridge->connection_uuid, nm_connection_get_uuid(bridge)))
             continue;
 
         for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
             port_uuid = g_ptr_array_index(ovs_bridge->ports, pi);
-            ovs_port  = g_hash_table_lookup(priv->ports, port_uuid);
+            ovs_port  = g_hash_table_lookup(priv->ports, &port_uuid);
 
             json_array_append_new(ports, json_pack("[s, s]", "uuid", port_uuid));
 
             if (!ovs_port) {
                 /* This would be a violation of ovsdb's reference integrity (a bug). */
-                _LOGW("Unknown port '%s' in bridge '%s'", port_uuid, bridge_uuid);
-                continue;
-            } else if (strcmp(ovs_port->name, port_name) != 0
-                       || g_strcmp0(ovs_port->connection_uuid, nm_connection_get_uuid(port)) != 0) {
+                _LOGW("Unknown port '%s' in bridge '%s'", port_uuid, ovs_bridge->bridge_uuid);
                 continue;
             }
 
+            if (!nm_streq(ovs_port->name, port_name)
+                || !nm_streq0(ovs_port->connection_uuid, nm_connection_get_uuid(port)))
+                continue;
+
             for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
                 interface_uuid = g_ptr_array_index(ovs_port->interfaces, ii);
-                ovs_interface  = g_hash_table_lookup(priv->interfaces, interface_uuid);
+                ovs_interface  = g_hash_table_lookup(priv->interfaces, &interface_uuid);
 
                 json_array_append_new(interfaces, json_pack("[s, s]", "uuid", interface_uuid));
 
                 if (!ovs_interface) {
                     /* This would be a violation of ovsdb's reference integrity (a bug). */
                     _LOGW("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid);
-                } else if (strcmp(ovs_interface->name, interface_name) == 0
-                           && g_strcmp0(ovs_interface->connection_uuid,
-                                        nm_connection_get_uuid(interface))
-                                  == 0) {
-                    has_interface = TRUE;
+                    continue;
                 }
+                if (nm_streq(ovs_interface->name, interface_name)
+                    && nm_streq0(ovs_interface->connection_uuid, nm_connection_get_uuid(interface)))
+                    has_interface = TRUE;
             }
 
             break;
@@ -869,7 +1161,6 @@ _delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
 {
     NMOvsdbPrivate *      priv = NM_OVSDB_GET_PRIVATE(self);
     GHashTableIter        iter;
-    char *                bridge_uuid;
     char *                port_uuid;
     char *                interface_uuid;
     OpenvswitchBridge *   ovs_bridge;
@@ -888,7 +1179,7 @@ _delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
     bridges_changed = FALSE;
 
     g_hash_table_iter_init(&iter, priv->bridges);
-    while (g_hash_table_iter_next(&iter, (gpointer) &bridge_uuid, (gpointer) &ovs_bridge)) {
+    while (g_hash_table_iter_next(&iter, (gpointer) &ovs_bridge, NULL)) {
         nm_auto_decref_json json_t *ports     = NULL;
         nm_auto_decref_json json_t *new_ports = NULL;
 
@@ -896,7 +1187,7 @@ _delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
         new_ports     = json_array();
         ports_changed = FALSE;
 
-        json_array_append_new(bridges, json_pack("[s,s]", "uuid", bridge_uuid));
+        json_array_append_new(bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
 
         for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
             nm_auto_decref_json json_t *interfaces     = NULL;
@@ -905,7 +1196,7 @@ _delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
             interfaces     = json_array();
             new_interfaces = json_array();
             port_uuid      = g_ptr_array_index(ovs_bridge->ports, pi);
-            ovs_port       = g_hash_table_lookup(priv->ports, port_uuid);
+            ovs_port       = g_hash_table_lookup(priv->ports, &port_uuid);
 
             json_array_append_new(ports, json_pack("[s,s]", "uuid", port_uuid));
 
@@ -913,18 +1204,18 @@ _delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
 
             if (!ovs_port) {
                 /* This would be a violation of ovsdb's reference integrity (a bug). */
-                _LOGW("Unknown port '%s' in bridge '%s'", port_uuid, bridge_uuid);
+                _LOGW("Unknown port '%s' in bridge '%s'", port_uuid, ovs_bridge->bridge_uuid);
                 continue;
             }
 
             for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
                 interface_uuid = g_ptr_array_index(ovs_port->interfaces, ii);
-                ovs_interface  = g_hash_table_lookup(priv->interfaces, interface_uuid);
+                ovs_interface  = g_hash_table_lookup(priv->interfaces, &interface_uuid);
 
                 json_array_append_new(interfaces, json_pack("[s,s]", "uuid", interface_uuid));
 
                 if (ovs_interface) {
-                    if (strcmp(ovs_interface->name, ifname) == 0) {
+                    if (nm_streq(ovs_interface->name, ifname)) {
                         /* skip the interface */
                         interfaces_changed = TRUE;
                         continue;
@@ -955,7 +1246,7 @@ _delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
                 _expect_bridge_ports(params, ovs_bridge->name, ports);
                 _set_bridge_ports(params, ovs_bridge->name, new_ports);
             }
-            json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", bridge_uuid));
+            json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
         }
     }
 
@@ -979,30 +1270,32 @@ static void
 ovsdb_next_command(NMOvsdb *self)
 {
     NMOvsdbPrivate *    priv = NM_OVSDB_GET_PRIVATE(self);
-    OvsdbMethodCall *   call = NULL;
+    OvsdbMethodCall *   call;
     char *              cmd;
     nm_auto_decref_json json_t *msg = NULL;
-    json_t *                    params;
 
     if (!priv->conn)
         return;
-    if (!priv->calls->len)
+
+    if (c_list_is_empty(&priv->calls_lst_head))
         return;
-    call = &g_array_index(priv->calls, OvsdbMethodCall, 0);
-    if (call->id != COMMAND_PENDING)
+
+    call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst);
+    if (call->call_id != CALL_ID_UNSPEC)
         return;
-    call->id = priv->seq++;
+
+    call->call_id = ++priv->call_id_counter;
 
     switch (call->command) {
     case OVSDB_MONITOR:
-        msg = json_pack("{s:i, s:s, s:[s, n, {"
+        msg = json_pack("{s:I, s:s, s:[s, n, {"
                         "  s:[{s:[s, s, s]}],"
                         "  s:[{s:[s, s, s]}],"
                         "  s:[{s:[s, s, s, s]}],"
                         "  s:[{s:[]}]"
                         "}]}",
                         "id",
-                        call->id,
+                        (json_int_t) call->call_id,
                         "method",
                         "monitor",
                         "params",
@@ -1026,57 +1319,81 @@ ovsdb_next_command(NMOvsdb *self)
                         "Open_vSwitch",
                         "columns");
         break;
-    case OVSDB_ADD_INTERFACE:
-        params = json_array();
-        json_array_append_new(params, json_string("Open_vSwitch"));
-        json_array_append_new(params, _inc_next_cfg(priv->db_uuid));
-
-        _add_interface(self,
-                       params,
-                       call->bridge,
-                       call->port,
-                       call->interface,
-                       call->bridge_device,
-                       call->interface_device);
+    default:
+    {
+        json_t *params = NULL;
 
-        msg = json_pack("{s:i, s:s, s:o}", "id", call->id, "method", "transact", "params", params);
-        break;
-    case OVSDB_DEL_INTERFACE:
         params = json_array();
         json_array_append_new(params, json_string("Open_vSwitch"));
         json_array_append_new(params, _inc_next_cfg(priv->db_uuid));
 
-        _delete_interface(self, params, call->ifname);
+        switch (call->command) {
+        case OVSDB_ADD_INTERFACE:
+            _add_interface(self,
+                           params,
+                           call->payload.add_interface.bridge,
+                           call->payload.add_interface.port,
+                           call->payload.add_interface.interface,
+                           call->payload.add_interface.bridge_device,
+                           call->payload.add_interface.interface_device);
+            break;
+        case OVSDB_DEL_INTERFACE:
+            _delete_interface(self, params, call->payload.del_interface.ifname);
+            break;
+        case OVSDB_SET_INTERFACE_MTU:
+            json_array_append_new(params,
+                                  json_pack("{s:s, s:s, s:{s: I}, s:[[s, s, s]]}",
+                                            "op",
+                                            "update",
+                                            "table",
+                                            "Interface",
+                                            "row",
+                                            "mtu_request",
+                                            (json_int_t) call->payload.set_interface_mtu.mtu,
+                                            "where",
+                                            "name",
+                                            "==",
+                                            call->payload.set_interface_mtu.ifname));
+            break;
+        case OVSDB_SET_EXTERNAL_IDS:
+            json_array_append_new(
+                params,
+                json_pack("{s:s, s:s, s:o, s:[[s, s, s]]}",
+                          "op",
+                          "mutate",
+                          "table",
+                          _device_type_to_table(call->payload.set_external_ids.device_type),
+                          "mutations",
+                          _j_create_external_ids_array_update(
+                              call->payload.set_external_ids.connection_uuid,
+                              call->payload.set_external_ids.exid_old,
+                              call->payload.set_external_ids.exid_new),
+                          "where",
+                          "name",
+                          "==",
+                          call->payload.set_external_ids.ifname));
+            break;
 
-        msg = json_pack("{s:i, s:s, s:o}", "id", call->id, "method", "transact", "params", params);
-        break;
-    case OVSDB_SET_INTERFACE_MTU:
-        params = json_array();
-        json_array_append_new(params, json_string("Open_vSwitch"));
-        json_array_append_new(params, _inc_next_cfg(priv->db_uuid));
+        default:
+            nm_assert_not_reached();
+            break;
+        }
 
-        json_array_append_new(params,
-                              json_pack("{s:s, s:s, s:{s: i}, s:[[s, s, s]]}",
-                                        "op",
-                                        "update",
-                                        "table",
-                                        "Interface",
-                                        "row",
-                                        "mtu_request",
-                                        call->mtu,
-                                        "where",
-                                        "name",
-                                        "==",
-                                        call->ifname));
-
-        msg = json_pack("{s:i, s:s, s:o}", "id", call->id, "method", "transact", "params", params);
+        msg = json_pack("{s:I, s:s, s:o}",
+                        "id",
+                        (json_int_t) call->call_id,
+                        "method",
+                        "transact",
+                        "params",
+                        params);
         break;
     }
+    }
 
     g_return_if_fail(msg);
-    _LOGT_call("send", call, msg);
-    cmd = json_dumps(msg, 0);
 
+    cmd = json_dumps(msg, 0);
+    _LOGT_call(call, "send: call-id=%" G_GUINT64_FORMAT ", %s", call->call_id, cmd);
     g_string_append(priv->output, cmd);
     free(cmd);
 
@@ -1097,7 +1414,7 @@ ovsdb_next_command(NMOvsdb *self)
  *              [ "uuid", "185c93f6-0b39-424e-8587-77d074aa7ce0" ], ... ] ]
  */
 static void
-_uuids_to_array(GPtrArray *array, const json_t *items)
+_uuids_to_array_inplace(GPtrArray *array, const json_t *items)
 {
     const char *key;
     json_t *    value;
@@ -1111,38 +1428,123 @@ _uuids_to_array(GPtrArray *array, const json_t *items)
         value = json_array_get(items, index);
         index++;
 
-        if (!value)
+        if (!value || !key)
             return;
 
-        if (g_strcmp0(key, "uuid") == 0 && json_is_string(value)) {
-            g_ptr_array_add(array, g_strdup(json_string_value(value)));
-        } else if (g_strcmp0(key, "set") == 0 && json_is_array(value)) {
-            json_array_foreach(value, set_index, set_value)
-            {
-                _uuids_to_array(array, set_value);
+        if (nm_streq(key, "uuid")) {
+            if (json_is_string(value))
+                g_ptr_array_add(array, g_strdup(json_string_value(value)));
+            continue;
+        }
+        if (nm_streq(key, "set")) {
+            if (json_is_array(value)) {
+                json_array_foreach (value, set_index, set_value)
+                    _uuids_to_array_inplace(array, set_value);
             }
+            continue;
         }
     }
 }
 
-static char *
-_connection_uuid_from_external_ids(json_t *external_ids)
+static GPtrArray *
+_uuids_to_array(const json_t *items)
 {
+    GPtrArray *array;
+
+    array = g_ptr_array_new_with_free_func(g_free);
+    _uuids_to_array_inplace(array, items);
+    return array;
+}
+
+static void
+_external_ids_extract(json_t *external_ids, GArray **out_array, const char **out_connection_uuid)
+{
+    json_t *array;
     json_t *value;
-    size_t  index;
+    gsize   index;
 
-    if (g_strcmp0("map", json_string_value(json_array_get(external_ids, 0))) != 0)
-        return NULL;
+    nm_assert(out_array && !*out_array);
+    nm_assert(!out_connection_uuid || !*out_connection_uuid);
 
-    json_array_foreach(json_array_get(external_ids, 1), index, value)
-    {
-        if (g_strcmp0("NM.connection.uuid", json_string_value(json_array_get(value, 0))) == 0)
-            return g_strdup(json_string_value(json_array_get(value, 1)));
+    if (!nm_streq0("map", json_string_value(json_array_get(external_ids, 0))))
+        return;
+
+    array = json_array_get(external_ids, 1);
+
+    json_array_foreach (array, index, value) {
+        const char *       key = json_string_value(json_array_get(value, 0));
+        const char *       val = json_string_value(json_array_get(value, 1));
+        NMUtilsNamedValue *v;
+
+        if (!key || !val)
+            continue;
+
+        if (!*out_array) {
+            *out_array = g_array_new(FALSE, FALSE, sizeof(NMUtilsNamedValue));
+            g_array_set_clear_func(*out_array,
+                                   (GDestroyNotify) nm_utils_named_value_clear_with_g_free);
+        }
+
+        v  = nm_g_array_append_new(*out_array, NMUtilsNamedValue);
+        *v = (NMUtilsNamedValue){
+            .name      = g_strdup(key),
+            .value_str = g_strdup(val),
+        };
+
+        if (out_connection_uuid && nm_streq(v->name, NM_OVS_EXTERNAL_ID_NM_CONNECTION_UUID)) {
+            *out_connection_uuid = v->value_str;
+            out_connection_uuid  = NULL;
+        }
     }
+}
+
+static gboolean
+_external_ids_equal(const GArray *arr1, const GArray *arr2)
+{
+    guint n;
+    guint i;
 
-    return NULL;
+    n = nm_g_array_len(arr1);
+
+    if (n != nm_g_array_len(arr2))
+        return FALSE;
+    for (i = 0; i < n; i++) {
+        const NMUtilsNamedValue *n1 = &g_array_index(arr1, NMUtilsNamedValue, i);
+        const NMUtilsNamedValue *n2 = &g_array_index(arr2, NMUtilsNamedValue, i);
+
+        if (!nm_streq0(n1->name, n2->name))
+            return FALSE;
+        if (!nm_streq0(n1->value_str, n2->value_str))
+            return FALSE;
+    }
+    return TRUE;
 }
 
+static char *
+_external_ids_to_string(const GArray *arr)
+{
+    NMStrBuf strbuf;
+    guint    i;
+
+    if (!arr)
+        return g_strdup("empty");
+
+    nm_str_buf_init(&strbuf, NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE);
+    nm_str_buf_append(&strbuf, "[");
+    for (i = 0; i < arr->len; i++) {
+        const NMUtilsNamedValue *n = &g_array_index(arr, NMUtilsNamedValue, i);
+
+        if (i > 0)
+            nm_str_buf_append_c(&strbuf, ',');
+        nm_str_buf_append_printf(&strbuf, " \"%s\" = \"%s\"]", n->name, n->value_str);
+    }
+    nm_str_buf_append(&strbuf, " ]");
+
+    return nm_str_buf_finalize(&strbuf, NULL);
+}
+
+/*****************************************************************************/
+
 /**
  * ovsdb_got_update:
  *
@@ -1163,14 +1565,11 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
     json_error_t    json_error = {
         0,
     };
-    void *                iter;
-    const char *          name;
-    const char *          key;
-    const char *          type;
-    json_t *              value;
-    OpenvswitchBridge *   ovs_bridge;
-    OpenvswitchPort *     ovs_port;
-    OpenvswitchInterface *ovs_interface;
+    void *      iter;
+    const char *name;
+    const char *key;
+    const char *type;
+    json_t *    value;
 
     if (json_unpack_ex(msg,
                        &json_error,
@@ -1199,17 +1598,14 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
             nm_utils_strdup_reset(&priv->db_uuid, s);
     }
 
-    /* Interfaces */
-    json_object_foreach(interface, key, value)
-    {
-        json_t * error = NULL;
-        gboolean old   = FALSE;
-        gboolean new   = FALSE;
-
-        if (json_unpack(value, "{s:{}}", "old") == 0)
-            old = TRUE;
+    json_object_foreach (interface, key, value) {
+        OpenvswitchInterface *ovs_interface;
+        gs_unref_array GArray *external_ids_arr = NULL;
+        const char *           connection_uuid  = NULL;
+        json_t *               error            = NULL;
+        int                    r;
 
-        if (json_unpack(value,
+        r = json_unpack(value,
                         "{s:{s:s, s:s, s?:o, s:o}}",
                         "new",
                         "name",
@@ -1219,85 +1615,120 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
                         "error",
                         &error,
                         "external_ids",
-                        &external_ids)
-            == 0)
-            new = TRUE;
-
-        if (old) {
-            ovs_interface = g_hash_table_lookup(priv->interfaces, key);
-            if (!ovs_interface) {
-                _LOGW("Interface '%s' was not seen", key);
-            } else if (!new || strcmp(ovs_interface->name, name) != 0) {
-                old = FALSE;
-                _LOGT("removed an '%s' interface: %s%s%s",
-                      ovs_interface->type,
-                      ovs_interface->name,
-                      ovs_interface->connection_uuid ? ", " : "",
-                      ovs_interface->connection_uuid ?: "");
-                if (g_strcmp0(ovs_interface->type, "internal") == 0) {
-                    /* Currently, the factory only creates NMDevices for
-                     * internal interfaces. Ignore the rest. */
-                    g_signal_emit(self,
-                                  signals[DEVICE_REMOVED],
-                                  0,
-                                  ovs_interface->name,
-                                  NM_DEVICE_TYPE_OVS_INTERFACE);
-                }
-            }
-            g_hash_table_remove(priv->interfaces, key);
+                        &external_ids);
+        if (r != 0) {
+            gpointer unused;
+
+            r = json_unpack(value, "{s:{}}", "old");
+            if (r != 0)
+                continue;
+
+            if (!g_hash_table_steal_extended(priv->interfaces,
+                                             &key,
+                                             (gpointer *) &ovs_interface,
+                                             &unused))
+                continue;
+
+            _LOGT("obj[iface:%s]: removed an '%s' interface: %s%s%s",
+                  key,
+                  ovs_interface->type,
+                  ovs_interface->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid,
+                                       ", ",
+                                       ovs_interface->connection_uuid,
+                                       ""));
+            _signal_emit_device_removed(self,
+                                        ovs_interface->name,
+                                        NM_DEVICE_TYPE_OVS_INTERFACE,
+                                        ovs_interface->type);
+            _free_interface(ovs_interface);
+            continue;
         }
 
-        if (new) {
-            ovs_interface                  = g_slice_new(OpenvswitchInterface);
-            ovs_interface->name            = g_strdup(name);
-            ovs_interface->type            = g_strdup(type);
-            ovs_interface->connection_uuid = _connection_uuid_from_external_ids(external_ids);
-            g_hash_table_insert(priv->interfaces, g_strdup(key), ovs_interface);
-            if (old) {
-                _LOGT("changed an '%s' interface: %s%s%s",
+        ovs_interface = g_hash_table_lookup(priv->interfaces, &key);
+
+        if (ovs_interface
+            && (!nm_streq0(ovs_interface->name, name) || !nm_streq0(ovs_interface->type, type))) {
+            if (!g_hash_table_steal(priv->interfaces, ovs_interface))
+                nm_assert_not_reached();
+            _signal_emit_device_removed(self,
+                                        ovs_interface->name,
+                                        NM_DEVICE_TYPE_OVS_INTERFACE,
+                                        ovs_interface->type);
+            nm_clear_pointer(&ovs_interface, _free_interface);
+        }
+
+        _external_ids_extract(external_ids, &external_ids_arr, &connection_uuid);
+
+        if (ovs_interface) {
+            gboolean changed = FALSE;
+
+            nm_assert(nm_streq0(ovs_interface->name, name));
+
+            changed |= nm_utils_strdup_reset(&ovs_interface->type, type);
+            changed |= nm_utils_strdup_reset(&ovs_interface->connection_uuid, connection_uuid);
+            if (!_external_ids_equal(ovs_interface->external_ids, external_ids_arr)) {
+                NM_SWAP(&ovs_interface->external_ids, &external_ids_arr);
+                changed = TRUE;
+            }
+            if (changed) {
+                gs_free char *strtmp = NULL;
+
+                _LOGT("obj[iface:%s]: changed an '%s' interface: %s%s%s, external-ids=%s",
+                      key,
                       type,
                       ovs_interface->name,
-                      ovs_interface->connection_uuid ? ", " : "",
-                      ovs_interface->connection_uuid ?: "");
-            } else {
-                _LOGT("added an '%s' interface: %s%s%s",
-                      ovs_interface->type,
-                      ovs_interface->name,
-                      ovs_interface->connection_uuid ? ", " : "",
-                      ovs_interface->connection_uuid ?: "");
-                if (g_strcmp0(ovs_interface->type, "internal") == 0) {
-                    /* Currently, the factory only creates NMDevices for
-                     * internal interfaces. Ignore the rest. */
-                    g_signal_emit(self,
-                                  signals[DEVICE_ADDED],
-                                  0,
-                                  ovs_interface->name,
-                                  NM_DEVICE_TYPE_OVS_INTERFACE);
-                }
-            }
-            /* The error is a string. No error is indicated by an empty set,
-             * because why the fuck not: [ "set": [] ] */
-            if (error && json_is_string(error)) {
-                g_signal_emit(self,
-                              signals[INTERFACE_FAILED],
-                              0,
-                              ovs_interface->name,
-                              ovs_interface->connection_uuid,
-                              json_string_value(error));
+                      NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid,
+                                           ", ",
+                                           ovs_interface->connection_uuid,
+                                           ""),
+                      (strtmp = _external_ids_to_string(ovs_interface->external_ids)));
             }
+        } else {
+            gs_free char *strtmp = NULL;
+
+            ovs_interface  = g_slice_new(OpenvswitchInterface);
+            *ovs_interface = (OpenvswitchInterface){
+                .interface_uuid  = g_strdup(key),
+                .name            = g_strdup(name),
+                .type            = g_strdup(type),
+                .connection_uuid = g_strdup(connection_uuid),
+                .external_ids    = g_steal_pointer(&external_ids_arr),
+            };
+            g_hash_table_add(priv->interfaces, ovs_interface);
+            _LOGT("obj[iface:%s]: added an '%s' interface: %s%s%s, external-ids=%s",
+                  key,
+                  ovs_interface->type,
+                  ovs_interface->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_interface->connection_uuid,
+                                       ", ",
+                                       ovs_interface->connection_uuid,
+                                       ""),
+                  (strtmp = _external_ids_to_string(ovs_interface->external_ids)));
+            _signal_emit_device_added(self,
+                                      ovs_interface->name,
+                                      NM_DEVICE_TYPE_OVS_INTERFACE,
+                                      ovs_interface->type);
         }
-    }
 
-    /* Ports */
-    json_object_foreach(port, key, value)
-    {
-        gboolean old = FALSE;
-        gboolean new = FALSE;
+        /* The error is a string. No error is indicated by an empty set,
+         * Why not: [ "set": [] ] ? */
+        if (error && json_is_string(error)) {
+            _signal_emit_interface_failed(self,
+                                          ovs_interface->name,
+                                          ovs_interface->connection_uuid,
+                                          json_string_value(error));
+        }
+    }
 
-        if (json_unpack(value, "{s:{}}", "old") == 0)
-            old = TRUE;
+    json_object_foreach (port, key, value) {
+        gs_unref_ptrarray GPtrArray *interfaces = NULL;
+        OpenvswitchPort *            ovs_port;
+        gs_unref_array GArray *external_ids_arr = NULL;
+        const char *           connection_uuid  = NULL;
+        int                    r;
 
-        if (json_unpack(value,
+        r = json_unpack(value,
                         "{s:{s:s, s:o, s:o}}",
                         "new",
                         "name",
@@ -1305,63 +1736,100 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
                         "external_ids",
                         &external_ids,
                         "interfaces",
-                        &items)
-            == 0)
-            new = TRUE;
-
-        if (old) {
-            ovs_port = g_hash_table_lookup(priv->ports, key);
-            if (!new || g_strcmp0(ovs_port->name, name) != 0) {
-                old = FALSE;
-                _LOGT("removed a port: %s%s%s",
-                      ovs_port->name,
-                      ovs_port->connection_uuid ? ", " : "",
-                      ovs_port->connection_uuid ?: "");
-                g_signal_emit(self,
-                              signals[DEVICE_REMOVED],
-                              0,
-                              ovs_port->name,
-                              NM_DEVICE_TYPE_OVS_PORT);
-            }
-            g_hash_table_remove(priv->ports, key);
+                        &items);
+        if (r != 0) {
+            gpointer unused;
+
+            r = json_unpack(value, "{s:{}}", "old");
+            if (r != 0)
+                continue;
+
+            if (!g_hash_table_steal_extended(priv->ports, &key, (gpointer *) &ovs_port, &unused))
+                continue;
+
+            _LOGT("obj[port:%s]: removed a port: %s%s%s",
+                  key,
+                  ovs_port->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid,
+                                       ", ",
+                                       ovs_port->connection_uuid,
+                                       ""));
+            _signal_emit_device_removed(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL);
+            _free_port(ovs_port);
+            continue;
         }
 
-        if (new) {
-            ovs_port                  = g_slice_new(OpenvswitchPort);
-            ovs_port->name            = g_strdup(name);
-            ovs_port->connection_uuid = _connection_uuid_from_external_ids(external_ids);
-            ovs_port->interfaces      = g_ptr_array_new_with_free_func(g_free);
-            _uuids_to_array(ovs_port->interfaces, items);
-            g_hash_table_insert(priv->ports, g_strdup(key), ovs_port);
-            if (old) {
-                _LOGT("changed a port: %s%s%s",
-                      ovs_port->name,
-                      ovs_port->connection_uuid ? ", " : "",
-                      ovs_port->connection_uuid ?: "");
-            } else {
-                _LOGT("added a port: %s%s%s",
+        ovs_port = g_hash_table_lookup(priv->ports, &key);
+
+        if (ovs_port && !nm_streq0(ovs_port->name, name)) {
+            if (!g_hash_table_steal(priv->ports, ovs_port))
+                nm_assert_not_reached();
+            _signal_emit_device_removed(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL);
+            nm_clear_pointer(&ovs_port, _free_port);
+        }
+
+        _external_ids_extract(external_ids, &external_ids_arr, &connection_uuid);
+        interfaces = _uuids_to_array(items);
+
+        if (ovs_port) {
+            gboolean changed = FALSE;
+
+            nm_assert(nm_streq0(ovs_port->name, name));
+
+            changed |= nm_utils_strdup_reset(&ovs_port->name, name);
+            changed |= nm_utils_strdup_reset(&ovs_port->connection_uuid, connection_uuid);
+            if (nm_strv_ptrarray_cmp(ovs_port->interfaces, interfaces) != 0) {
+                NM_SWAP(&ovs_port->interfaces, &interfaces);
+                changed = TRUE;
+            }
+            if (!_external_ids_equal(ovs_port->external_ids, external_ids_arr)) {
+                NM_SWAP(&ovs_port->external_ids, &external_ids_arr);
+                changed = TRUE;
+            }
+            if (changed) {
+                gs_free char *strtmp = NULL;
+
+                _LOGT("obj[port:%s]: changed a port: %s%s%s, external-ids=%s",
+                      key,
                       ovs_port->name,
-                      ovs_port->connection_uuid ? ", " : "",
-                      ovs_port->connection_uuid ?: "");
-                g_signal_emit(self,
-                              signals[DEVICE_ADDED],
-                              0,
-                              ovs_port->name,
-                              NM_DEVICE_TYPE_OVS_PORT);
+                      NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid,
+                                           ", ",
+                                           ovs_port->connection_uuid,
+                                           ""),
+                      (strtmp = _external_ids_to_string(ovs_port->external_ids)));
             }
+        } else {
+            gs_free char *strtmp = NULL;
+
+            ovs_port  = g_slice_new(OpenvswitchPort);
+            *ovs_port = (OpenvswitchPort){
+                .port_uuid       = g_strdup(key),
+                .name            = g_strdup(name),
+                .connection_uuid = g_strdup(connection_uuid),
+                .interfaces      = g_steal_pointer(&interfaces),
+                .external_ids    = g_steal_pointer(&external_ids_arr),
+            };
+            g_hash_table_add(priv->ports, ovs_port);
+            _LOGT("obj[port:%s]: added a port: %s%s%s, external-ids=%s",
+                  key,
+                  ovs_port->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_port->connection_uuid,
+                                       ", ",
+                                       ovs_port->connection_uuid,
+                                       ""),
+                  (strtmp = _external_ids_to_string(ovs_port->external_ids)));
+            _signal_emit_device_added(self, ovs_port->name, NM_DEVICE_TYPE_OVS_PORT, NULL);
         }
     }
 
-    /* Bridges */
-    json_object_foreach(bridge, key, value)
-    {
-        gboolean old = FALSE;
-        gboolean new = FALSE;
-
-        if (json_unpack(value, "{s:{}}", "old") == 0)
-            old = TRUE;
+    json_object_foreach (bridge, key, value) {
+        gs_unref_ptrarray GPtrArray *ports = NULL;
+        OpenvswitchBridge *          ovs_bridge;
+        gs_unref_array GArray *external_ids_arr = NULL;
+        const char *           connection_uuid  = NULL;
+        int                    r;
 
-        if (json_unpack(value,
+        r = json_unpack(value,
                         "{s:{s:s, s:o, s:o}}",
                         "new",
                         "name",
@@ -1369,50 +1837,93 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
                         "external_ids",
                         &external_ids,
                         "ports",
-                        &items)
-            == 0)
-            new = TRUE;
-
-        if (old) {
-            ovs_bridge = g_hash_table_lookup(priv->bridges, key);
-            if (!new || g_strcmp0(ovs_bridge->name, name) != 0) {
-                old = FALSE;
-                _LOGT("removed a bridge: %s%s%s",
-                      ovs_bridge->name,
-                      ovs_bridge->connection_uuid ? ", " : "",
-                      ovs_bridge->connection_uuid ?: "");
-                g_signal_emit(self,
-                              signals[DEVICE_REMOVED],
-                              0,
-                              ovs_bridge->name,
-                              NM_DEVICE_TYPE_OVS_BRIDGE);
-            }
-            g_hash_table_remove(priv->bridges, key);
+                        &items);
+
+        if (r != 0) {
+            gpointer unused;
+
+            r = json_unpack(value, "{s:{}}", "old");
+            if (r != 0)
+                continue;
+
+            if (!g_hash_table_steal_extended(priv->bridges,
+                                             &key,
+                                             (gpointer *) &ovs_bridge,
+                                             &unused))
+                continue;
+
+            _LOGT("obj[bridge:%s]: removed a bridge: %s%s%s",
+                  key,
+                  ovs_bridge->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid,
+                                       ", ",
+                                       ovs_bridge->connection_uuid,
+                                       ""));
+            _signal_emit_device_removed(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL);
+            _free_bridge(ovs_bridge);
+            continue;
         }
 
-        if (new) {
-            ovs_bridge                  = g_slice_new(OpenvswitchBridge);
-            ovs_bridge->name            = g_strdup(name);
-            ovs_bridge->connection_uuid = _connection_uuid_from_external_ids(external_ids);
-            ovs_bridge->ports           = g_ptr_array_new_with_free_func(g_free);
-            _uuids_to_array(ovs_bridge->ports, items);
-            g_hash_table_insert(priv->bridges, g_strdup(key), ovs_bridge);
-            if (old) {
-                _LOGT("changed a bridge: %s%s%s",
-                      ovs_bridge->name,
-                      ovs_bridge->connection_uuid ? ", " : "",
-                      ovs_bridge->connection_uuid ?: "");
-            } else {
-                _LOGT("added a bridge: %s%s%s",
+        ovs_bridge = g_hash_table_lookup(priv->bridges, &key);
+
+        if (ovs_bridge && !nm_streq0(ovs_bridge->name, name)) {
+            if (!g_hash_table_steal(priv->bridges, ovs_bridge))
+                nm_assert_not_reached();
+            _signal_emit_device_removed(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL);
+            nm_clear_pointer(&ovs_bridge, _free_bridge);
+        }
+
+        _external_ids_extract(external_ids, &external_ids_arr, &connection_uuid);
+        ports = _uuids_to_array(items);
+
+        if (ovs_bridge) {
+            gboolean changed = FALSE;
+
+            nm_assert(nm_streq0(ovs_bridge->name, name));
+
+            changed = nm_utils_strdup_reset(&ovs_bridge->name, name);
+            changed = nm_utils_strdup_reset(&ovs_bridge->connection_uuid, connection_uuid);
+            if (nm_strv_ptrarray_cmp(ovs_bridge->ports, ports) != 0) {
+                NM_SWAP(&ovs_bridge->ports, &ports);
+                changed = TRUE;
+            }
+            if (!_external_ids_equal(ovs_bridge->external_ids, external_ids_arr)) {
+                NM_SWAP(&ovs_bridge->external_ids, &external_ids_arr);
+                changed = TRUE;
+            }
+            if (changed) {
+                gs_free char *strtmp = NULL;
+
+                _LOGT("obj[bridge:%s]: changed a bridge: %s%s%s, external-ids=%s",
+                      key,
                       ovs_bridge->name,
-                      ovs_bridge->connection_uuid ? ", " : "",
-                      ovs_bridge->connection_uuid ?: "");
-                g_signal_emit(self,
-                              signals[DEVICE_ADDED],
-                              0,
-                              ovs_bridge->name,
-                              NM_DEVICE_TYPE_OVS_BRIDGE);
+                      NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid,
+                                           ", ",
+                                           ovs_bridge->connection_uuid,
+                                           ""),
+                      (strtmp = _external_ids_to_string(ovs_bridge->external_ids)));
             }
+        } else {
+            gs_free char *strtmp = NULL;
+
+            ovs_bridge  = g_slice_new(OpenvswitchBridge);
+            *ovs_bridge = (OpenvswitchBridge){
+                .bridge_uuid     = g_strdup(key),
+                .name            = g_strdup(name),
+                .connection_uuid = g_strdup(connection_uuid),
+                .ports           = g_steal_pointer(&ports),
+                .external_ids    = g_steal_pointer(&external_ids_arr),
+            };
+            g_hash_table_add(priv->bridges, ovs_bridge);
+            _LOGT("obj[bridge:%s]: added a bridge: %s%s%s, external-ids=%s",
+                  key,
+                  ovs_bridge->name,
+                  NM_PRINT_FMT_QUOTED2(ovs_bridge->connection_uuid,
+                                       ", ",
+                                       ovs_bridge->connection_uuid,
+                                       ""),
+                  (strtmp = _external_ids_to_string(ovs_bridge->external_ids)));
+            _signal_emit_device_added(self, ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE, NULL);
         }
     }
 }
@@ -1455,16 +1966,12 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg)
     json_error_t    json_error = {
         0,
     };
-    json_t *            json_id = NULL;
-    gint64              id      = -1;
-    const char *        method  = NULL;
-    json_t *            params  = NULL;
-    json_t *            result  = NULL;
-    json_t *            error   = NULL;
-    OvsdbMethodCall *   call    = NULL;
-    OvsdbMethodCallback callback;
-    gpointer            user_data;
-    gs_free_error GError *local = NULL;
+    json_t *    json_id = NULL;
+    json_int_t  id      = (json_int_t) -1;
+    const char *method  = NULL;
+    json_t *    params  = NULL;
+    json_t *    result  = NULL;
+    json_t *    error   = NULL;
 
     if (json_unpack_ex(msg,
                        &json_error,
@@ -1497,10 +2004,10 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg)
             return;
         }
 
-        if (g_strcmp0(method, "update") == 0) {
+        if (nm_streq0(method, "update")) {
             /* This is a update method call. */
             ovsdb_got_update(self, json_array_get(params, 1));
-        } else if (g_strcmp0(method, "echo") == 0) {
+        } else if (nm_streq0(method, "echo")) {
             /* This is an echo request. */
             ovsdb_got_echo(self, id, params);
         } else {
@@ -1509,24 +2016,28 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg)
         return;
     }
 
-    if (id > -1) {
+    if (id >= 0) {
+        OvsdbMethodCall *call;
+        gs_free_error GError *local      = NULL;
+        gs_free char *        msg_as_str = NULL;
+
         /* This is a response to a method call. */
-        if (!priv->calls->len) {
-            _LOGE("there are no queued calls expecting response %" G_GUINT64_FORMAT, id);
+        if (c_list_is_empty(&priv->calls_lst_head)) {
+            _LOGE("there are no queued calls expecting response %" G_GUINT64_FORMAT, (guint64) id);
             ovsdb_disconnect(self, FALSE, FALSE);
             return;
         }
-        call = &g_array_index(priv->calls, OvsdbMethodCall, 0);
-        if (call->id != id) {
+        call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst);
+        if (call->call_id != id) {
             _LOGE("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT,
-                  call->id,
-                  id);
+                  call->call_id,
+                  (guint64) id);
             ovsdb_disconnect(self, FALSE, FALSE);
             return;
         }
         /* Cool, we found a corresponding call. Finish it. */
 
-        _LOGT_call("response", call, msg);
+        _LOGT_call(call, "response: %s", (msg_as_str = json_dumps(msg, 0)));
 
         if (!json_is_null(error)) {
             /* The response contains an error. */
@@ -1537,10 +2048,8 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg)
                         json_string_value(error));
         }
 
-        callback  = call->callback;
-        user_data = call->user_data;
-        g_array_remove_index(priv->calls, 0);
-        callback(self, result, local, user_data);
+        _call_complete(call, result, local);
+
         priv->num_failures = 0;
 
         /* Don't progress further commands in case the callback hit an error
@@ -1709,11 +2218,8 @@ ovsdb_write(NMOvsdb *self)
 static void
 ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing)
 {
-    NMOvsdbPrivate *    priv = NM_OVSDB_GET_PRIVATE(self);
-    OvsdbMethodCall *   call;
-    OvsdbMethodCallback callback;
-    gpointer            user_data;
-    gs_free_error GError *error = NULL;
+    NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self);
+    OvsdbMethodCall *call;
 
     nm_assert(!retry || !is_disposing);
 
@@ -1722,19 +2228,23 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing)
 
     _LOGD("disconnecting from ovsdb, retry %d", retry);
 
+    /* FIXME(shutdown): NMOvsdb should process the pending calls before
+     * shutting down, and cancel the remaining calls after the timeout. */
+
     if (retry) {
-        if (priv->calls->len != 0)
-            g_array_index(priv->calls, OvsdbMethodCall, 0).id = COMMAND_PENDING;
-    } else {
-        nm_utils_error_set_cancelled(&error, is_disposing, "NMOvsdb");
-
-        while (priv->calls->len) {
-            call      = &g_array_index(priv->calls, OvsdbMethodCall, priv->calls->len - 1);
-            callback  = call->callback;
-            user_data = call->user_data;
-            g_array_remove_index(priv->calls, priv->calls->len - 1);
-            callback(self, NULL, error, user_data);
+        if (!c_list_is_empty(&priv->calls_lst_head)) {
+            call          = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst);
+            call->call_id = CALL_ID_UNSPEC;
         }
+    } else {
+        gs_free_error GError *error = NULL;
+
+        if (is_disposing)
+            nm_utils_error_set_cancelled(&error, is_disposing, "NMOvsdb");
+        else
+            nm_utils_error_set(&error, NM_UTILS_ERROR_NOT_READY, "disconnected from ovsdb");
+        while ((call = c_list_last_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst)))
+            _call_complete(call, NULL, error);
     }
 
     priv->bufp = 0;
@@ -1750,6 +2260,76 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing)
 }
 
 static void
+_check_ready(NMOvsdb *self)
+{
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+
+    nm_assert(!priv->ready);
+
+    if (priv->num_pending_deletions == 0) {
+        priv->ready = TRUE;
+        g_signal_emit(self, signals[READY], 0);
+        nm_manager_unblock_failed_ovs_interfaces(nm_manager_get());
+    }
+}
+
+static void
+_del_initial_iface_cb(GError *error, gpointer user_data)
+{
+    NMOvsdb *       self;
+    gs_free char *  ifname = NULL;
+    NMOvsdbPrivate *priv;
+
+    nm_utils_user_data_unpack(user_data, &self, &ifname);
+
+    if (nm_utils_error_is_cancelled_or_disposing(error))
+        return;
+
+    priv = NM_OVSDB_GET_PRIVATE(self);
+    nm_assert(priv->num_pending_deletions > 0);
+    priv->num_pending_deletions--;
+
+    _LOGD("delete initial interface '%s': %s %s%s%s, pending %u",
+          ifname,
+          error ? "error" : "success",
+          error ? "(" : "",
+          error ? error->message : "",
+          error ? ")" : "",
+          priv->num_pending_deletions);
+
+    _check_ready(self);
+}
+
+static void
+ovsdb_cleanup_initial_interfaces(NMOvsdb *self)
+{
+    NMOvsdbPrivate *            priv = NM_OVSDB_GET_PRIVATE(self);
+    const OpenvswitchInterface *interface;
+    NMUtilsUserData *           data;
+    GHashTableIter              iter;
+
+    if (priv->ready || priv->num_pending_deletions != 0)
+        return;
+
+    /* Delete OVS interfaces added by NM. Bridges and ports and
+     * not considered because they are deleted automatically
+     * when no interface is present. */
+    g_hash_table_iter_init(&iter, self->_priv.interfaces);
+    while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &interface)) {
+        if (interface->connection_uuid) {
+            priv->num_pending_deletions++;
+            _LOGD("deleting initial interface '%s' (pending: %u)",
+                  interface->name,
+                  priv->num_pending_deletions);
+            data = nm_utils_user_data_pack(self, g_strdup(interface->name));
+            nm_ovsdb_del_interface(self, interface->name, _del_initial_iface_cb, data);
+        }
+    }
+
+    _check_ready(self);
+}
+
+static void
 _monitor_bridges_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
 {
     if (error) {
@@ -1763,6 +2343,8 @@ _monitor_bridges_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_
     /* Treat the first response the same as the subsequent "update"
      * messages we eventually get. */
     ovsdb_got_update(self, result);
+
+    ovsdb_cleanup_initial_interfaces(self);
 }
 
 static void
@@ -1823,17 +2405,11 @@ ovsdb_try_connect(NMOvsdb *self)
     /* Queue a monitor call before any other command, ensuring that we have an up
      * to date view of existing bridged that we need for add and remove ops. */
     ovsdb_call_method(self,
-                      OVSDB_MONITOR,
-                      NULL,
-                      NULL,
-                      NULL,
-                      NULL,
-                      NULL,
-                      NULL,
-                      0,
                       _monitor_bridges_cb,
                       NULL,
-                      TRUE);
+                      TRUE,
+                      OVSDB_MONITOR,
+                      OVSDB_METHOD_PAYLOAD_MONITOR());
 }
 
 /*****************************************************************************/
@@ -1858,8 +2434,7 @@ _transact_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
     if (error)
         goto out;
 
-    json_array_foreach(result, index, value)
-    {
+    json_array_foreach (result, index, value) {
         if (json_unpack(value, "{s:s, s:s}", "error", &err, "details", &err_details) == 0) {
             g_set_error(&error,
                         G_IO_ERROR,
@@ -1873,7 +2448,7 @@ _transact_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
 
 out:
     call->callback(error, call->user_data);
-    g_slice_free(OvsdbCall, call);
+    nm_g_slice_free(call);
 }
 
 static OvsdbCall *
@@ -1881,13 +2456,22 @@ ovsdb_call_new(NMOvsdbCallback callback, gpointer user_data)
 {
     OvsdbCall *call;
 
-    call            = g_slice_new(OvsdbCall);
-    call->callback  = callback;
-    call->user_data = user_data;
-
+    call  = g_slice_new(OvsdbCall);
+    *call = (OvsdbCall){
+        .callback  = callback,
+        .user_data = user_data,
+    };
     return call;
 }
 
+gboolean
+nm_ovsdb_is_ready(NMOvsdb *self)
+{
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+
+    return priv->ready;
+}
+
 void
 nm_ovsdb_add_interface(NMOvsdb *       self,
                        NMConnection *  bridge,
@@ -1899,17 +2483,15 @@ nm_ovsdb_add_interface(NMOvsdb *       self,
                        gpointer        user_data)
 {
     ovsdb_call_method(self,
-                      OVSDB_ADD_INTERFACE,
-                      NULL,
-                      bridge,
-                      port,
-                      interface,
-                      bridge_device,
-                      interface_device,
-                      0,
                       _transact_cb,
                       ovsdb_call_new(callback, user_data),
-                      FALSE);
+                      FALSE,
+                      OVSDB_ADD_INTERFACE,
+                      OVSDB_METHOD_PAYLOAD_ADD_INTERFACE(bridge,
+                                                         port,
+                                                         interface,
+                                                         bridge_device,
+                                                         interface_device));
 }
 
 void
@@ -1919,17 +2501,11 @@ nm_ovsdb_del_interface(NMOvsdb *       self,
                        gpointer        user_data)
 {
     ovsdb_call_method(self,
-                      OVSDB_DEL_INTERFACE,
-                      ifname,
-                      NULL,
-                      NULL,
-                      NULL,
-                      NULL,
-                      NULL,
-                      0,
                       _transact_cb,
                       ovsdb_call_new(callback, user_data),
-                      FALSE);
+                      FALSE,
+                      OVSDB_DEL_INTERFACE,
+                      OVSDB_METHOD_PAYLOAD_DEL_INTERFACE(ifname));
 }
 
 void
@@ -1940,88 +2516,60 @@ nm_ovsdb_set_interface_mtu(NMOvsdb *       self,
                            gpointer        user_data)
 {
     ovsdb_call_method(self,
-                      OVSDB_SET_INTERFACE_MTU,
-                      ifname,
-                      NULL,
-                      NULL,
-                      NULL,
-                      NULL,
-                      NULL,
-                      mtu,
                       _transact_cb,
                       ovsdb_call_new(callback, user_data),
-                      FALSE);
+                      FALSE,
+                      OVSDB_SET_INTERFACE_MTU,
+                      OVSDB_METHOD_PAYLOAD_SET_INTERFACE_MTU(ifname, mtu));
 }
 
-/*****************************************************************************/
-
-static void
-_clear_call(gpointer data)
+void
+nm_ovsdb_set_external_ids(NMOvsdb *                self,
+                          NMDeviceType             device_type,
+                          const char *             ifname,
+                          const char *             connection_uuid,
+                          NMSettingOvsExternalIDs *s_exid_old,
+                          NMSettingOvsExternalIDs *s_exid_new)
 {
-    OvsdbMethodCall *call = data;
-
-    switch (call->command) {
-    case OVSDB_MONITOR:
-        break;
-    case OVSDB_ADD_INTERFACE:
-        g_clear_object(&call->bridge);
-        g_clear_object(&call->port);
-        g_clear_object(&call->interface);
-        g_clear_object(&call->bridge_device);
-        g_clear_object(&call->interface_device);
-        break;
-    case OVSDB_DEL_INTERFACE:
-    case OVSDB_SET_INTERFACE_MTU:
-        nm_clear_g_free(&call->ifname);
-        break;
-    }
-}
+    gs_unref_hashtable GHashTable *exid_old = NULL;
+    gs_unref_hashtable GHashTable *exid_new = NULL;
 
-static void
-_free_bridge(gpointer data)
-{
-    OpenvswitchBridge *ovs_bridge = data;
+    exid_old = s_exid_old
+                   ? nm_utils_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_exid_old))
+                   : NULL;
+    exid_new = s_exid_new
+                   ? nm_utils_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_exid_new))
+                   : NULL;
 
-    g_free(ovs_bridge->name);
-    g_free(ovs_bridge->connection_uuid);
-    g_ptr_array_free(ovs_bridge->ports, TRUE);
-    g_slice_free(OpenvswitchBridge, ovs_bridge);
-}
-
-static void
-_free_port(gpointer data)
-{
-    OpenvswitchPort *ovs_port = data;
-
-    g_free(ovs_port->name);
-    g_free(ovs_port->connection_uuid);
-    g_ptr_array_free(ovs_port->interfaces, TRUE);
-    g_slice_free(OpenvswitchPort, ovs_port);
+    ovsdb_call_method(self,
+                      NULL,
+                      NULL,
+                      FALSE,
+                      OVSDB_SET_EXTERNAL_IDS,
+                      OVSDB_METHOD_PAYLOAD_SET_EXTERNAL_IDS(device_type,
+                                                            ifname,
+                                                            connection_uuid,
+                                                            exid_old,
+                                                            exid_new));
 }
 
-static void
-_free_interface(gpointer data)
-{
-    OpenvswitchInterface *ovs_interface = data;
-
-    g_free(ovs_interface->name);
-    g_free(ovs_interface->connection_uuid);
-    g_free(ovs_interface->type);
-    g_slice_free(OpenvswitchInterface, ovs_interface);
-}
+/*****************************************************************************/
 
 static void
 nm_ovsdb_init(NMOvsdb *self)
 {
     NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
 
-    priv->calls = g_array_new(FALSE, TRUE, sizeof(OvsdbMethodCall));
-    g_array_set_clear_func(priv->calls, _clear_call);
-    priv->input      = g_string_new(NULL);
-    priv->output     = g_string_new(NULL);
-    priv->bridges    = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, _free_bridge);
-    priv->ports      = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, _free_port);
-    priv->interfaces = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, _free_interface);
+    c_list_init(&priv->calls_lst_head);
+
+    priv->input  = g_string_new(NULL);
+    priv->output = g_string_new(NULL);
+    priv->bridges =
+        g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_bridge, NULL);
+    priv->ports =
+        g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_port, NULL);
+    priv->interfaces =
+        g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_interface, NULL);
 
     ovsdb_try_connect(self);
 }
@@ -2034,6 +2582,8 @@ dispose(GObject *object)
 
     ovsdb_disconnect(self, FALSE, TRUE);
 
+    nm_assert(c_list_is_empty(&priv->calls_lst_head));
+
     if (priv->input) {
         g_string_free(priv->input, TRUE);
         priv->input = NULL;
@@ -2042,10 +2592,6 @@ dispose(GObject *object)
         g_string_free(priv->output, TRUE);
         priv->output = NULL;
     }
-    if (priv->calls) {
-        g_array_free(priv->calls, TRUE);
-        priv->calls = NULL;
-    }
 
     nm_clear_pointer(&priv->bridges, g_hash_table_destroy);
     nm_clear_pointer(&priv->ports, g_hash_table_destroy);
@@ -2069,9 +2615,10 @@ nm_ovsdb_class_init(NMOvsdbClass *klass)
                                          NULL,
                                          NULL,
                                          G_TYPE_NONE,
-                                         2,
+                                         3,
                                          G_TYPE_STRING,
-                                         G_TYPE_UINT);
+                                         G_TYPE_UINT,
+                                         G_TYPE_STRING);
 
     signals[DEVICE_REMOVED] = g_signal_new(NM_OVSDB_DEVICE_REMOVED,
                                            G_OBJECT_CLASS_TYPE(object_class),
@@ -2081,9 +2628,10 @@ nm_ovsdb_class_init(NMOvsdbClass *klass)
                                            NULL,
                                            NULL,
                                            G_TYPE_NONE,
-                                           2,
+                                           3,
                                            G_TYPE_STRING,
-                                           G_TYPE_UINT);
+                                           G_TYPE_UINT,
+                                           G_TYPE_STRING);
 
     signals[INTERFACE_FAILED] = g_signal_new(NM_OVSDB_INTERFACE_FAILED,
                                              G_OBJECT_CLASS_TYPE(object_class),
@@ -2097,4 +2645,14 @@ nm_ovsdb_class_init(NMOvsdbClass *klass)
                                              G_TYPE_STRING,
                                              G_TYPE_STRING,
                                              G_TYPE_STRING);
+
+    signals[READY] = g_signal_new(NM_OVSDB_READY,
+                                  G_OBJECT_CLASS_TYPE(object_class),
+                                  G_SIGNAL_RUN_LAST,
+                                  0,
+                                  NULL,
+                                  NULL,
+                                  NULL,
+                                  G_TYPE_NONE,
+                                  0);
 }
diff --git a/src/devices/ovs/nm-ovsdb.h b/src/core/devices/ovs/nm-ovsdb.h
index c9eee19c..d0b4d19d 100644
--- a/src/devices/ovs/nm-ovsdb.h
+++ b/src/core/devices/ovs/nm-ovsdb.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
@@ -16,6 +16,7 @@
 #define NM_OVSDB_DEVICE_ADDED     "device-added"
 #define NM_OVSDB_DEVICE_REMOVED   "device-removed"
 #define NM_OVSDB_INTERFACE_FAILED "interface-failed"
+#define NM_OVSDB_READY            "ready"
 
 typedef struct _NMOvsdb      NMOvsdb;
 typedef struct _NMOvsdbClass NMOvsdbClass;
@@ -46,4 +47,15 @@ void nm_ovsdb_set_interface_mtu(NMOvsdb *       self,
                                 NMOvsdbCallback callback,
                                 gpointer        user_data);
 
+struct _NMSettingOvsExternalIDs;
+
+void nm_ovsdb_set_external_ids(NMOvsdb *                        self,
+                               NMDeviceType                     device_type,
+                               const char *                     ifname,
+                               const char *                     connection_uuid,
+                               struct _NMSettingOvsExternalIDs *s_exid_old,
+                               struct _NMSettingOvsExternalIDs *s_exid_new);
+
+gboolean nm_ovsdb_is_ready(NMOvsdb *self);
+
 #endif /* __NETWORKMANAGER_OVSDB_H__ */
diff --git a/src/devices/team/meson.build b/src/core/devices/team/meson.build
index c2b77cb9..d0ff4caa 100644
--- a/src/devices/team/meson.build
+++ b/src/core/devices/team/meson.build
@@ -1,20 +1,16 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-sources = files(
-  'nm-device-team.c',
-  'nm-team-factory.c',
-)
-
-deps = [
-  daemon_nm_default_dep,
-  jansson_dep,
-  libteamdctl_dep,
-]
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 libnm_device_plugin_team = shared_module(
   'nm-device-plugin-team',
-  sources: sources,
-  dependencies: deps,
+  sources: files(
+    'nm-device-team.c',
+    'nm-team-factory.c',
+  ),
+  dependencies: [
+    core_plugin_dep,
+    jansson_dep,
+    libteamdctl_dep,
+  ],
   c_args: daemon_c_flags,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
@@ -27,5 +23,8 @@ core_plugins += libnm_device_plugin_team
 test(
   'check-local-devices-team',
   check_exports,
-  args: [libnm_device_plugin_team.full_path(), linker_script_devices],
+  args: [
+    libnm_device_plugin_team.full_path(),
+    linker_script_devices,
+  ],
 )
diff --git a/src/devices/team/nm-device-team.c b/src/core/devices/team/nm-device-team.c
index 27aa3004..d2d71729 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/core/devices/team/nm-device-team.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Jiri Pirko <jiri@resnulli.us>
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-team.h"
 
@@ -25,8 +25,8 @@
 #include "nm-ip4-config.h"
 #include "nm-std-aux/nm-dbus-compat.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceTeam
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceTeam);
 
 /*****************************************************************************/
 
@@ -1003,18 +1003,18 @@ constructed(GObject *object)
 NMDevice *
 nm_device_team_new(const char *iface)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_TEAM,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_DRIVER,
-                                     "team",
-                                     NM_DEVICE_TYPE_DESC,
-                                     "Team",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_TEAM,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_TEAM,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_TEAM,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_DRIVER,
+                        "team",
+                        NM_DEVICE_TYPE_DESC,
+                        "Team",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_TEAM,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_TEAM,
+                        NULL);
 }
 
 static void
diff --git a/src/devices/team/nm-device-team.h b/src/core/devices/team/nm-device-team.h
index eabc63e6..6f5cff93 100644
--- a/src/devices/team/nm-device-team.h
+++ b/src/core/devices/team/nm-device-team.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Jiri Pirko <jiri@resnulli.us>
  */
diff --git a/src/devices/team/nm-team-factory.c b/src/core/devices/team/nm-team-factory.c
index 644bef1c..57b51bf8 100644
--- a/src/devices/team/nm-team-factory.c
+++ b/src/core/devices/team/nm-team-factory.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <gmodule.h>
 
@@ -46,7 +46,7 @@ G_MODULE_EXPORT NMDeviceFactory *
                 nm_device_factory_create(GError **error)
 {
     nm_manager_set_capability(NM_MANAGER_GET, NM_CAPABILITY_TEAM);
-    return (NMDeviceFactory *) g_object_new(NM_TYPE_TEAM_FACTORY, NULL);
+    return g_object_new(NM_TYPE_TEAM_FACTORY, NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/tests/meson.build b/src/core/devices/tests/meson.build
index 1ddd4f85..1bc88370 100644
--- a/src/devices/tests/meson.build
+++ b/src/core/devices/tests/meson.build
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 test_units = [
   'test-acd',
@@ -9,7 +9,7 @@ foreach test_unit: test_units
   exe = executable(
     test_unit,
     test_unit + '.c',
-    dependencies: libnetwork_manager_test_dep,
+    dependencies: libNetworkManagerTest_dep,
     c_args: test_c_flags,
   )
 
diff --git a/src/devices/tests/test-acd.c b/src/core/devices/tests/test-acd.c
index da7b3c3d..b0af59db 100644
--- a/src/devices/tests/test-acd.c
+++ b/src/core/devices/tests/test-acd.c
@@ -1,12 +1,14 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "n-acd/src/n-acd.h"
 
+#include <linux/if_ether.h>
+
 #include "devices/nm-acd-manager.h"
 #include "platform/tests/test-common.h"
 
diff --git a/src/devices/tests/test-lldp.c b/src/core/devices/tests/test-lldp.c
index 5c912b7d..ef0b1549 100644
--- a/src/devices/tests/test-lldp.c
+++ b/src/core/devices/tests/test-lldp.c
@@ -1,11 +1,12 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <fcntl.h>
+#include <netinet/if_ether.h>
 #include <linux/if_tun.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
@@ -811,17 +812,36 @@ _test_recv_fixture_setup(TestRecvFixture *fixture, gconstpointer user_data)
         link = nmtstp_assert_wait_for_link(NM_PLATFORM_GET, TEST_IFNAME, NM_LINK_TYPE_TUN, 0);
     } else {
         int          s;
-        struct ifreq ifr = {};
+        struct ifreq ifr;
+        int          r;
+        int          try_cnt = 0;
 
+again:
+        memset(&ifr, 0, sizeof(ifr));
         ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
         nm_utils_ifname_cpy(ifr.ifr_name, TEST_IFNAME);
-        g_assert(ioctl(fd, TUNSETIFF, &ifr) >= 0);
+
+        r = ioctl(fd, TUNSETIFF, &ifr);
+        if (r != 0) {
+            if (errno == EPERM && try_cnt++ < 10) {
+                g_usleep(2);
+                goto again;
+            }
+            g_assert_cmpint(errno, ==, 0);
+            g_assert_cmpint(r, ==, 0);
+        }
 
         /* Bring the interface up */
         s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
         g_assert(s >= 0);
+
         ifr.ifr_flags |= IFF_UP;
-        g_assert(ioctl(s, SIOCSIFFLAGS, &ifr) >= 0);
+        r = ioctl(s, SIOCSIFFLAGS, &ifr);
+        if (r != 0) {
+            g_assert_cmpint(errno, ==, 0);
+            g_assert_cmpint(r, ==, 0);
+        }
+
         nm_close(s);
 
         link = nmtstp_assert_wait_for_link(NM_PLATFORM_GET, TEST_IFNAME, NM_LINK_TYPE_TUN, 100);
diff --git a/src/devices/wifi/meson.build b/src/core/devices/wifi/meson.build
index 00404c71..743937db 100644
--- a/src/devices/wifi/meson.build
+++ b/src/core/devices/wifi/meson.build
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 iwd_sources = files()
 if enable_iwd
@@ -8,8 +8,8 @@ if enable_iwd
   )
 endif
 
-libnm_wifi_base = static_library(
-  'nm-wifi-base',
+libnm_device_plugin_wifi_static = static_library(
+  'nm-device-plugin-wifi-static',
   sources: files(
     'nm-device-olpc-mesh.c',
     'nm-device-wifi-p2p.c',
@@ -19,12 +19,14 @@ libnm_wifi_base = static_library(
     'nm-wifi-p2p-peer.c',
     'nm-wifi-utils.c',
   ) + iwd_sources,
-  dependencies: daemon_nm_default_dep,
+  dependencies: [
+    core_plugin_dep,
+  ],
   c_args: daemon_c_flags,
 )
 
-libnm_wifi_base_dep = declare_dependency(
-  link_with: libnm_wifi_base,
+libnm_device_plugin_wifi_static_dep = declare_dependency(
+  link_with: libnm_device_plugin_wifi_static,
 )
 
 libnm_device_plugin_wifi = shared_module(
@@ -32,7 +34,10 @@ libnm_device_plugin_wifi = shared_module(
   sources: files(
     'nm-wifi-factory.c',
   ),
-  dependencies: [ daemon_nm_default_dep, libnm_wifi_base_dep ],
+  dependencies: [
+    core_plugin_dep,
+    libnm_device_plugin_wifi_static_dep
+  ],
   c_args: daemon_c_flags,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
@@ -54,7 +59,10 @@ if enable_tests
   exe = executable(
     test_unit,
     'tests/' + test_unit + '.c',
-    dependencies: [ libnetwork_manager_test_dep, libnm_wifi_base_dep ],
+    dependencies: [
+      libNetworkManagerTest_dep,
+      libnm_device_plugin_wifi_static_dep,
+    ],
     c_args: test_c_flags,
   )
 
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c
index 64ac3de7..f0de90d3 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/core/devices/wifi/nm-device-iwd.c
@@ -1,12 +1,14 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Intel Corporation
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-iwd.h"
 
+#include <linux/if_ether.h>
+
 #include "devices/nm-device-private.h"
 #include "devices/nm-device.h"
 #include "nm-act-request.h"
@@ -27,9 +29,11 @@
 #include "settings/nm-settings-connection.h"
 #include "settings/nm-settings.h"
 #include "supplicant/nm-supplicant-types.h"
+#include "nm-auth-utils.h"
+#include "nm-manager.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceIwd
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceIwd);
 
 /*****************************************************************************/
 
@@ -54,13 +58,24 @@ typedef struct {
     NMDeviceWifiCapabilities      capabilities;
     NMActRequestGetSecretsCallId *wifi_secrets_id;
     guint                         periodic_scan_id;
+    guint                         periodic_update_id;
     bool                          enabled : 1;
     bool                          can_scan : 1;
-    bool                          can_connect : 1;
+    bool                          nm_autoconnect : 1;
+    bool                          iwd_autoconnect : 1;
     bool                          scanning : 1;
     bool                          scan_requested : 1;
     bool                          act_mode_switch : 1;
+    bool                          secrets_failed : 1;
+    bool                          networks_requested : 1;
+    bool                          networks_changed : 1;
     gint64                        last_scan;
+    uint32_t                      ap_id;
+    guint32                       rate;
+    NMEtherAddr                   current_ap_bssid;
+    GDBusMethodInvocation *       pending_agent_request;
+    NMActiveConnection *          assumed_ac;
+    guint                         assumed_ac_timeout;
 } NMDeviceIwdPrivate;
 
 struct _NMDeviceIwd {
@@ -129,7 +144,9 @@ ap_add_remove(NMDeviceIwd *self,
         nm_dbus_object_clear_and_unexport(&ap);
     }
 
-    nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+    if (priv->enabled && !priv->iwd_autoconnect)
+        nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+
     if (recheck_available_connections)
         nm_device_recheck_available_connections(NM_DEVICE(self));
 }
@@ -159,6 +176,7 @@ set_current_ap(NMDeviceIwd *self, NMWifiAP *new_ap, gboolean recheck_available_c
         g_object_unref(old_ap);
     }
 
+    memset(&priv->current_ap_bssid, 0, ETH_ALEN);
     _notify(self, PROP_ACTIVE_ACCESS_POINT);
     _notify(self, PROP_MODE);
 }
@@ -172,12 +190,12 @@ remove_all_aps(NMDeviceIwd *self)
     if (c_list_is_empty(&priv->aps_lst_head))
         return;
 
-    set_current_ap(self, NULL, FALSE);
-
     c_list_for_each_entry_safe (ap, ap_safe, &priv->aps_lst_head, aps_lst)
         ap_add_remove(self, FALSE, ap, FALSE);
 
-    nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+    if (!priv->iwd_autoconnect)
+        nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+
     nm_device_recheck_available_connections(NM_DEVICE(self));
 }
 
@@ -198,49 +216,37 @@ ap_security_flags_from_network_type(const char *type)
     return flags;
 }
 
-static void
-insert_ap_from_network(NMDeviceIwd *self,
-                       GHashTable * aps,
-                       const char * path,
-                       gint64       last_seen_msec,
-                       int16_t      signal,
-                       uint32_t     ap_id)
+static NMWifiAP *
+ap_from_network(NMDeviceIwd *self,
+                GDBusProxy * network,
+                NMRefString *bss_path,
+                gint64       last_seen_msec,
+                int16_t      signal)
 {
-    gs_unref_object GDBusProxy *network_proxy = NULL;
-    gs_unref_variant GVariant *name_value     = NULL;
-    gs_unref_variant GVariant *type_value     = NULL;
-    nm_auto_ref_string NMRefString *bss_path  = NULL;
-    const char *                    name;
-    const char *                    type;
-    NMSupplicantBssInfo             bss_info;
-    uint8_t                         bssid[6];
-    NMWifiAP *                      ap;
+    NMDeviceIwdPrivate *priv              = NM_DEVICE_IWD_GET_PRIVATE(self);
+    gs_unref_variant GVariant *name_value = NULL;
+    gs_unref_variant GVariant *type_value = NULL;
+    const char *               name;
+    const char *               type;
+    uint32_t                   ap_id;
     gs_unref_bytes GBytes *ssid = NULL;
+    NMWifiAP *             ap;
+    NMSupplicantBssInfo    bss_info;
 
-    bss_path = nm_ref_string_new(path);
-
-    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);
-    if (!network_proxy)
-        return;
+    g_return_val_if_fail(network, NULL);
 
-    name_value = g_dbus_proxy_get_cached_property(network_proxy, "Name");
-    type_value = g_dbus_proxy_get_cached_property(network_proxy, "Type");
+    name_value = g_dbus_proxy_get_cached_property(network, "Name");
+    type_value = g_dbus_proxy_get_cached_property(network, "Type");
     if (!name_value || !g_variant_is_of_type(name_value, G_VARIANT_TYPE_STRING) || !type_value
         || !g_variant_is_of_type(type_value, G_VARIANT_TYPE_STRING))
-        return;
+        return NULL;
 
     name = g_variant_get_string(name_value, NULL);
     type = g_variant_get_string(type_value, NULL);
 
     if (nm_streq(type, "wep")) {
         /* WEP not supported */
-        return;
+        return NULL;
     }
 
     /* What we get from IWD are networks, or ESSs, that may contain
@@ -251,12 +257,7 @@ insert_ap_from_network(NMDeviceIwd *self,
      * already does that.  We fake the BSSIDs as they don't play any
      * role either.
      */
-    bssid[0] = 0x00;
-    bssid[1] = 0x01;
-    bssid[2] = 0x02;
-    bssid[3] = ap_id >> 16;
-    bssid[4] = ap_id >> 8;
-    bssid[5] = ap_id;
+    ap_id = priv->ap_id++;
 
     ssid = g_bytes_new(name, NM_MIN(32u, strlen(name)));
 
@@ -270,13 +271,39 @@ insert_ap_from_network(NMDeviceIwd *self,
         .signal_percent = nm_wifi_utils_level_to_quality(signal / 100),
         .frequency      = 2417,
         .max_rate       = 65000,
+        .bssid          = NM_ETHER_ADDR_INIT(0x00, 0x01, 0x02, ap_id >> 16, ap_id >> 8, ap_id),
     };
-    memcpy(bss_info.bssid, bssid, sizeof(bssid));
 
     ap = nm_wifi_ap_new_from_properties(&bss_info);
 
     nm_assert(bss_path == nm_wifi_ap_get_supplicant_path(ap));
 
+    return ap;
+}
+
+static void
+insert_ap_from_network(NMDeviceIwd *self,
+                       GHashTable * aps,
+                       const char * path,
+                       gint64       last_seen_msec,
+                       int16_t      signal)
+{
+    gs_unref_object GDBusProxy *network_proxy = NULL;
+    nm_auto_ref_string NMRefString *bss_path  = nm_ref_string_new(path);
+    NMWifiAP *                      ap;
+
+    if (g_hash_table_lookup(aps, bss_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);
+
+    ap = ap_from_network(self, network_proxy, bss_path, last_seen_msec, signal);
+    if (!ap)
+        return;
+
     g_hash_table_insert(aps, bss_path, ap);
 }
 
@@ -288,55 +315,45 @@ get_ordered_networks_cb(GObject *source, GAsyncResult *res, gpointer user_data)
     gs_free_error GError *error        = NULL;
     gs_unref_variant GVariant *variant = NULL;
     GVariantIter *             networks;
-    const char *               path, *name, *type;
+    const char *               path;
     int16_t                    signal;
     NMWifiAP *                 ap, *ap_safe, *new_ap;
-    gboolean                   changed = FALSE;
+    gboolean                   changed;
     GHashTableIter             ap_iter;
     gs_unref_hashtable GHashTable *new_aps = NULL;
-    gboolean                       compat;
-    const char *                   return_sig;
-    static uint32_t                ap_id = 0;
     gint64                         last_seen_msec;
 
     variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
+    if (!variant && nm_utils_error_is_cancelled(error))
+        return;
+
+    priv                     = NM_DEVICE_IWD_GET_PRIVATE(self);
+    priv->networks_requested = FALSE;
+
     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))) {
+    if (!g_variant_is_of_type(variant, G_VARIANT_TYPE("(a(on))"))) {
         _LOGE(LOGD_WIFI,
-              "Station.GetOrderedNetworks returned type %s instead of %s",
-              g_variant_get_type_string(variant),
-              return_sig);
+              "Station.GetOrderedNetworks returned type %s instead of (a(on))",
+              g_variant_get_type_string(variant));
         return;
     }
 
     new_aps = g_hash_table_new_full(nm_direct_hash, NULL, NULL, g_object_unref);
-
-    g_variant_get(variant, return_sig, &networks);
+    g_variant_get(variant, "(a(on))", &networks);
 
     last_seen_msec = nm_utils_get_monotonic_timestamp_msec();
-    if (compat) {
-        while (g_variant_iter_next(networks, "(&o&sn&s)", &path, &name, &signal, &type))
-            insert_ap_from_network(self, new_aps, path, last_seen_msec, signal, ap_id++);
-    } else {
-        while (g_variant_iter_next(networks, "(&on)", &path, &signal))
-            insert_ap_from_network(self, new_aps, path, last_seen_msec, signal, ap_id++);
-    }
+    while (g_variant_iter_next(networks, "(&on)", &path, &signal))
+        insert_ap_from_network(self, new_aps, path, last_seen_msec, signal);
 
     g_variant_iter_free(networks);
 
+    changed                = priv->networks_changed;
+    priv->networks_changed = FALSE;
+
     c_list_for_each_entry_safe (ap, ap_safe, &priv->aps_lst_head, aps_lst) {
         new_ap = g_hash_table_lookup(new_aps, nm_wifi_ap_get_supplicant_path(ap));
         if (new_ap) {
@@ -368,7 +385,9 @@ get_ordered_networks_cb(GObject *source, GAsyncResult *res, gpointer user_data)
     }
 
     if (changed) {
-        nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+        if (!priv->iwd_autoconnect)
+            nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+
         nm_device_recheck_available_connections(NM_DEVICE(self));
     }
 }
@@ -389,6 +408,61 @@ update_aps(NMDeviceIwd *self)
                       priv->cancellable,
                       get_ordered_networks_cb,
                       self);
+    priv->networks_requested = TRUE;
+}
+
+static void
+periodic_update(NMDeviceIwd *self)
+{
+    NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    int                 ifindex;
+    guint32             new_rate;
+    int                 percent;
+    NMEtherAddr         bssid;
+    gboolean            ap_changed = FALSE;
+    NMPlatform *        platform;
+
+    ifindex = nm_device_get_ifindex(NM_DEVICE(self));
+    if (ifindex <= 0)
+        g_return_if_reached();
+
+    platform = nm_device_get_platform(NM_DEVICE(self));
+
+    /* TODO: obtain quality through the net.connman.iwd.SignalLevelAgent API.
+     * For now we're waking up for the rate/BSSID updates anyway.
+     */
+    if (!nm_platform_wifi_get_station(platform, ifindex, &bssid, &percent, &new_rate)) {
+        _LOGD(LOGD_WIFI, "BSSID / quality / rate platform query failed");
+        return;
+    }
+
+    if (nm_wifi_ap_set_strength(priv->current_ap, (gint8) percent)) {
+#if NM_MORE_LOGGING
+        ap_changed = TRUE;
+#endif
+    }
+
+    if (new_rate != priv->rate) {
+        priv->rate = new_rate;
+        _notify(self, PROP_BITRATE);
+    }
+
+    if (nm_ether_addr_is_valid(&bssid) && !nm_ether_addr_equal(&bssid, &priv->current_ap_bssid)) {
+        priv->current_ap_bssid = bssid;
+        ap_changed |= nm_wifi_ap_set_address_bin(priv->current_ap, &bssid);
+        ap_changed |= nm_wifi_ap_set_freq(priv->current_ap,
+                                          nm_platform_wifi_get_frequency(platform, ifindex));
+    }
+
+    if (ap_changed)
+        _ap_dump(self, LOGL_DEBUG, priv->current_ap, "updated");
+}
+
+static gboolean
+periodic_update_cb(gpointer user_data)
+{
+    periodic_update(user_data);
+    return TRUE;
 }
 
 static void
@@ -414,6 +488,26 @@ wifi_secrets_cancel(NMDeviceIwd *self)
     if (priv->wifi_secrets_id)
         nm_act_request_cancel_secrets(NULL, priv->wifi_secrets_id);
     nm_assert(!priv->wifi_secrets_id);
+
+    if (priv->pending_agent_request) {
+        g_dbus_method_invocation_return_error_literal(priv->pending_agent_request,
+                                                      NM_DEVICE_ERROR,
+                                                      NM_DEVICE_ERROR_INVALID_CONNECTION,
+                                                      "NM secrets request cancelled");
+        g_clear_object(&priv->pending_agent_request);
+    }
+}
+
+static void
+cleanup_assumed_connect(NMDeviceIwd *self)
+{
+    NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+
+    if (!priv->assumed_ac)
+        return;
+
+    g_signal_handlers_disconnect_by_data(priv->assumed_ac, self);
+    g_clear_object(&priv->assumed_ac);
 }
 
 static void
@@ -421,9 +515,12 @@ cleanup_association_attempt(NMDeviceIwd *self, gboolean disconnect)
 {
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
 
+    cleanup_assumed_connect(self);
     wifi_secrets_cancel(self);
 
     set_current_ap(self, NULL, TRUE);
+    nm_clear_g_source(&priv->periodic_update_id);
+    nm_clear_g_source(&priv->assumed_ac_timeout);
 
     if (disconnect && priv->dbus_station_proxy)
         send_disconnect(self);
@@ -448,6 +545,32 @@ reset_mode(NMDeviceIwd *       self,
         user_data);
 }
 
+static gboolean
+get_variant_boolean(GVariant *v, const char *property)
+{
+    if (!v || !g_variant_is_of_type(v, G_VARIANT_TYPE_BOOLEAN)) {
+        nm_log_warn(LOGD_DEVICE | LOGD_WIFI,
+                    "Property %s not cached or not boolean type",
+                    property);
+
+        return FALSE;
+    }
+
+    return g_variant_get_boolean(v);
+}
+
+static const char *
+get_variant_state(GVariant *v)
+{
+    if (!v || !g_variant_is_of_type(v, G_VARIANT_TYPE_STRING)) {
+        nm_log_warn(LOGD_DEVICE | LOGD_WIFI, "State property not cached or not a string");
+
+        return "unknown";
+    }
+
+    return g_variant_get_string(v, NULL);
+}
+
 static void
 deactivate(NMDevice *device)
 {
@@ -457,6 +580,14 @@ deactivate(NMDevice *device)
     if (!priv->dbus_obj)
         return;
 
+    if (priv->dbus_station_proxy) {
+        gs_unref_variant GVariant *value =
+            g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
+
+        if (NM_IN_STRSET(get_variant_state(value), "disconnecting", "disconnected"))
+            return;
+    }
+
     cleanup_association_attempt(self, TRUE);
     priv->act_mode_switch = FALSE;
 
@@ -532,27 +663,13 @@ deactivate_async(NMDevice *                 device,
 static gboolean
 is_connection_known_network(NMConnection *connection)
 {
-    NMSettingWireless *  s_wireless;
     NMIwdNetworkSecurity security;
-    gboolean             security_ok;
-    GBytes *             ssid;
-    gs_free char *       ssid_utf8 = NULL;
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    if (!s_wireless)
-        return FALSE;
+    gs_free char *       ssid = NULL;
 
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
-    if (!ssid)
+    if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, &ssid, &security))
         return FALSE;
 
-    ssid_utf8 = _nm_utils_ssid_to_utf8(ssid);
-
-    security = nm_wifi_connection_get_iwd_security(connection, &security_ok);
-    if (!security_ok)
-        return FALSE;
-
-    return nm_iwd_manager_is_known_network(nm_iwd_manager_get(), ssid_utf8, security);
+    return nm_iwd_manager_is_known_network(nm_iwd_manager_get(), ssid, security);
 }
 
 static gboolean
@@ -584,7 +701,9 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
     const char *         perm_hw_addr;
     const char *         mode;
     NMIwdNetworkSecurity security;
-    gboolean             mapped;
+    GBytes *             ssid;
+    const guint8 *       ssid_bytes;
+    gsize                ssid_len;
 
     if (!NM_DEVICE_CLASS(nm_device_iwd_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -592,12 +711,29 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 
     s_wireless = nm_connection_get_setting_wireless(connection);
 
+    /* complete_connection would be called (if at all) before this function
+     * so an SSID should always be set.  IWD doesn't support non-UTF8 SSIDs
+     * (ignores BSSes with such SSIDs and has no way to represent them on
+     * DBus) so we can cut it short for connections with a non-UTF8 SSID.
+     */
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    if (!ssid)
+        return FALSE;
+
+    ssid_bytes = g_bytes_get_data(ssid, &ssid_len);
+    if (!g_utf8_validate((const char *) ssid_bytes, ssid_len, NULL)) {
+        nm_utils_error_set_literal(error,
+                                   NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
+                                   "non-UTF-8 connection SSID not supported by IWD backend");
+        return FALSE;
+    }
+
     perm_hw_addr = nm_device_get_permanent_hw_address(device);
     mac          = nm_setting_wireless_get_mac_address(s_wireless);
     if (perm_hw_addr) {
         if (mac && !nm_utils_hwaddr_matches(mac, -1, perm_hw_addr, -1)) {
             nm_utils_error_set_literal(error,
-                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
                                        "device MAC address does not match the profile");
             return FALSE;
         }
@@ -621,10 +757,10 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
         return FALSE;
     }
 
-    security = nm_wifi_connection_get_iwd_security(connection, &mapped);
-    if (!mapped) {
+    if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security)
+        || security == NM_IWD_NETWORK_SECURITY_WEP) {
         nm_utils_error_set_literal(error,
-                                   NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                   NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
                                    "connection authentication type not supported by IWD backend");
         return FALSE;
     }
@@ -636,7 +772,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
         && !NM_IN_STRSET(mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
         nm_utils_error_set_literal(
             error,
-            NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+            NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
             "non-infrastructure hidden networks not supported by the IWD backend");
         return FALSE;
     }
@@ -648,52 +784,63 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
         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,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
                                            "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_OPEN,
                               NM_IWD_NETWORK_SECURITY_PSK)) {
             nm_utils_error_set_literal(error,
-                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
                                        "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)) {
+        NMSettingWirelessSecurity *s_wireless_sec =
+            nm_connection_get_setting_wireless_security(connection);
+
         if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) {
             nm_utils_error_set_literal(error,
-                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
                                        "device does not support Access Point mode");
             return FALSE;
         }
 
-        if (!NM_IN_SET(security, NM_IWD_NETWORK_SECURITY_PSK)) {
+        if (!NM_IN_SET(security, NM_IWD_NETWORK_SECURITY_PSK) || !s_wireless_sec
+            || !nm_streq0(nm_setting_wireless_security_get_key_mgmt(s_wireless_sec), "wpa-psk")) {
             nm_utils_error_set_literal(error,
-                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
                                        "IWD backend only supports PSK authentication in AP mode");
             return FALSE;
         }
     } else if (nm_streq(mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+        NMSettingWirelessSecurity *s_wireless_sec =
+            nm_connection_get_setting_wireless_security(connection);
+
         if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) {
             nm_utils_error_set_literal(error,
-                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
                                        "device does not support Ad-Hoc mode");
             return FALSE;
         }
 
-        if (!NM_IN_SET(security, NM_IWD_NETWORK_SECURITY_NONE, NM_IWD_NETWORK_SECURITY_PSK)) {
+        if (!NM_IN_SET(security, NM_IWD_NETWORK_SECURITY_OPEN, NM_IWD_NETWORK_SECURITY_PSK)
+            || (s_wireless_sec
+                && !nm_streq0(nm_setting_wireless_security_get_key_mgmt(s_wireless_sec),
+                              "wpa-psk"))) {
             nm_utils_error_set_literal(
                 error,
-                NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
                 "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");
+        nm_utils_error_set(error,
+                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
+                           "'%s' type profiles not supported by IWD backend",
+                           mode);
         return FALSE;
     }
 
@@ -707,11 +854,12 @@ check_connection_available(NMDevice *                     device,
                            const char *                   specific_object,
                            GError **                      error)
 {
-    NMDeviceIwd *       self = NM_DEVICE_IWD(device);
-    NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMSettingWireless * s_wifi;
-    const char *        mode;
-    NMWifiAP *          ap = NULL;
+    NMDeviceIwd *        self = NM_DEVICE_IWD(device);
+    NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMSettingWireless *  s_wifi;
+    const char *         mode;
+    NMWifiAP *           ap = NULL;
+    NMIwdNetworkSecurity security;
 
     s_wifi = nm_connection_get_setting_wireless(connection);
     g_return_val_if_fail(s_wifi, FALSE);
@@ -740,7 +888,16 @@ check_connection_available(NMDevice *                     device,
     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))
+    /* Hidden SSIDs obviously don't always appear in the scan list either.
+     *
+     * 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
+     * availability.
+     */
+    if (nm_setting_wireless_get_hidden(s_wifi)
+        || NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP))
         return TRUE;
 
     if (!ap)
@@ -756,7 +913,8 @@ check_connection_available(NMDevice *                     device,
     /* 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 (nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security)
+        && security == NM_IWD_NETWORK_SECURITY_8021X) {
         if (!is_ap_known_network(ap)) {
             nm_utils_error_set_literal(
                 error,
@@ -769,6 +927,18 @@ check_connection_available(NMDevice *                     device,
     return TRUE;
 }
 
+/* To be used where the SSID has been validated before */
+static char *
+iwd_ssid_to_str(const GBytes *ssid)
+{
+    const guint8 *ssid_bytes;
+    gsize         ssid_len;
+
+    ssid_bytes = g_bytes_get_data((GBytes *) ssid, &ssid_len);
+    nm_assert(ssid && g_utf8_validate((const char *) ssid_bytes, ssid_len, NULL));
+    return g_strndup((const char *) ssid_bytes, ssid_len);
+}
+
 static gboolean
 complete_connection(NMDevice *           device,
                     NMConnection *       connection,
@@ -781,20 +951,18 @@ complete_connection(NMDevice *           device,
     NMSettingWireless * s_wifi;
     gs_free char *      ssid_utf8 = NULL;
     NMWifiAP *          ap;
-    GBytes *            ssid;
-    GBytes *            setting_ssid = NULL;
-    gboolean            hidden       = FALSE;
+    GBytes *            ssid   = NULL;
+    gboolean            hidden = FALSE;
     const char *        mode;
 
     s_wifi = nm_connection_get_setting_wireless(connection);
 
     mode = s_wifi ? nm_setting_wireless_get_mode(s_wifi) : NULL;
 
-    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 (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP) || !specific_object) {
+        const guint8 *ssid_bytes;
+        gsize         ssid_len;
+
         /* If not given a specific object, we need at minimum an SSID */
         if (!s_wifi) {
             g_set_error_literal(error,
@@ -804,16 +972,24 @@ complete_connection(NMDevice *           device,
             return FALSE;
         }
 
-        setting_ssid = nm_setting_wireless_get_ssid(s_wifi);
-        if (!setting_ssid || g_bytes_get_size(setting_ssid) == 0) {
-            g_set_error_literal(
-                error,
-                NM_DEVICE_ERROR,
-                NM_DEVICE_ERROR_INVALID_CONNECTION,
-                "A 'wireless' setting with a valid SSID is required if no AP path was given.");
+        ssid       = nm_setting_wireless_get_ssid(s_wifi);
+        ssid_bytes = g_bytes_get_data(ssid, &ssid_len);
+
+        if (!ssid || ssid_len == 0 || !g_utf8_validate((const char *) ssid_bytes, ssid_len, NULL)) {
+            g_set_error_literal(error,
+                                NM_DEVICE_ERROR,
+                                NM_DEVICE_ERROR_INVALID_CONNECTION,
+                                "A 'wireless' setting with a valid UTF-8 SSID is required if no AP "
+                                "path was given.");
             return FALSE;
         }
+    }
 
+    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) {
         /* Find a compatible AP in the scan list */
         ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection);
         if (!ap) {
@@ -824,6 +1000,12 @@ complete_connection(NMDevice *           device,
             if (!nm_setting_verify(NM_SETTING(s_wifi), connection, error))
                 return FALSE;
 
+            /* We could either require the profile to be marked as hidden by the
+             * client or at least check that a hidden AP with a matching security
+             * type is in range using Station.GetHiddenAccessPoints().  For now
+             * assume it is hidden even though that will reveal the SSID on the
+             * air.
+             */
             hidden = TRUE;
         }
     } else {
@@ -836,35 +1018,22 @@ complete_connection(NMDevice *           device,
                         specific_object);
             return FALSE;
         }
-    }
 
-    /* Add a wifi setting if one doesn't exist yet */
-    if (!s_wifi) {
-        s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-    }
-
-    ssid = nm_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,
-                            NM_DEVICE_ERROR_INVALID_CONNECTION,
-                            "A 'wireless' setting with a valid SSID is required.");
-        return FALSE;
+        /* Add a wifi setting if one doesn't exist yet */
+        if (!s_wifi) {
+            s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+            nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+        }
     }
 
     if (ap) {
-        if (!nm_wifi_ap_complete_connection(ap,
-                                            connection,
-                                            nm_wifi_utils_is_manf_default_ssid(ssid),
-                                            error))
+        if (!nm_wifi_ap_complete_connection(ap, connection, FALSE, error))
             return FALSE;
     }
 
-    ssid_utf8 = _nm_utils_ssid_to_utf8(ssid);
+    ssid_utf8 = iwd_ssid_to_str(ssid);
     nm_utils_complete_generic(
         nm_device_get_platform(device),
         connection,
@@ -883,32 +1052,6 @@ complete_connection(NMDevice *           device,
 }
 
 static gboolean
-get_variant_boolean(GVariant *v, const char *property)
-{
-    if (!v || !g_variant_is_of_type(v, G_VARIANT_TYPE_BOOLEAN)) {
-        nm_log_warn(LOGD_DEVICE | LOGD_WIFI,
-                    "Property %s not cached or not boolean type",
-                    property);
-
-        return FALSE;
-    }
-
-    return g_variant_get_boolean(v);
-}
-
-static const char *
-get_variant_state(GVariant *v)
-{
-    if (!v || !g_variant_is_of_type(v, G_VARIANT_TYPE_STRING)) {
-        nm_log_warn(LOGD_DEVICE | LOGD_WIFI, "State property not cached or not a string");
-
-        return "unknown";
-    }
-
-    return g_variant_get_string(v, NULL);
-}
-
-static gboolean
 is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
     NMDeviceIwd *       self  = NM_DEVICE_IWD(device);
@@ -934,7 +1077,7 @@ get_autoconnect_allowed(NMDevice *device)
 {
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(NM_DEVICE_IWD(device));
 
-    return priv->can_connect;
+    return priv->nm_autoconnect;
 }
 
 static gboolean
@@ -1114,7 +1257,6 @@ check_scanning_prohibited(NMDeviceIwd *self, gboolean periodic)
     case NM_DEVICE_STATE_UNAVAILABLE:
     case NM_DEVICE_STATE_PREPARE:
     case NM_DEVICE_STATE_CONFIG:
-    case NM_DEVICE_STATE_NEED_AUTH:
     case NM_DEVICE_STATE_IP_CONFIG:
     case NM_DEVICE_STATE_IP_CHECK:
     case NM_DEVICE_STATE_SECONDARIES:
@@ -1124,6 +1266,7 @@ check_scanning_prohibited(NMDeviceIwd *self, gboolean periodic)
     case NM_DEVICE_STATE_DISCONNECTED:
     case NM_DEVICE_STATE_FAILED:
     case NM_DEVICE_STATE_ACTIVATED:
+    case NM_DEVICE_STATE_NEED_AUTH:
         break;
     }
 
@@ -1131,6 +1274,27 @@ check_scanning_prohibited(NMDeviceIwd *self, gboolean periodic)
     return !priv->can_scan;
 }
 
+static const char *
+get_agent_request_network_path(GDBusMethodInvocation *invocation)
+{
+    const char *method_name  = g_dbus_method_invocation_get_method_name(invocation);
+    GVariant *  params       = g_dbus_method_invocation_get_parameters(invocation);
+    const char *network_path = NULL;
+
+    if (nm_streq(method_name, "RequestPassphrase"))
+        g_variant_get(params, "(o)", &network_path);
+    else if (nm_streq(method_name, "RequestPrivateKeyPassphrase"))
+        g_variant_get(params, "(o)", &network_path);
+    else if (nm_streq(method_name, "RequestUserNameAndPassword"))
+        g_variant_get(params, "(o)", &network_path);
+    else if (nm_streq(method_name, "RequestUserPassword")) {
+        const char *user;
+        g_variant_get(params, "(os)", &network_path, &user);
+    }
+
+    return network_path;
+}
+
 /*
  * try_reply_agent_request
  *
@@ -1238,6 +1402,25 @@ try_reply_agent_request(NMDeviceIwd *          self,
         return FALSE;
 }
 
+static gboolean
+assumed_ac_timeout_cb(gpointer user_data)
+{
+    NMDeviceIwd *       self = user_data;
+    NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+
+    nm_assert(priv->assumed_ac);
+
+    priv->assumed_ac_timeout = 0;
+    nm_device_state_changed(NM_DEVICE(self),
+                            NM_DEVICE_STATE_FAILED,
+                            NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
+    /* NMDevice's state change -> NMActRequests/NMActiveConnection's state
+     * change -> assumed_connection_state_changed_before_managed() ->
+     * cleanup_association_attempt() so no need to call it explicitly.
+     */
+    return G_SOURCE_REMOVE;
+}
+
 static void wifi_secrets_get_one(NMDeviceIwd *                self,
                                  const char *                 setting_name,
                                  NMSecretAgentGetSecretsFlags flags,
@@ -1273,6 +1456,7 @@ wifi_secrets_cb(NMActRequest *                req,
     priv->wifi_secrets_id = NULL;
 
     if (nm_utils_error_is_cancelled(error)) {
+        priv->secrets_failed = TRUE;
         g_dbus_method_invocation_return_error_literal(invocation,
                                                       NM_DEVICE_ERROR,
                                                       NM_DEVICE_ERROR_INVALID_CONNECTION,
@@ -1300,6 +1484,20 @@ wifi_secrets_cb(NMActRequest *                req,
         goto secrets_error;
 
     if (replied) {
+        /* If we replied to the secrets request from IWD in the "disconnected"
+         * state and IWD doesn't move to a new state within 1 second, assume
+         * something went wrong (shouldn't happen).  If a state change arrives
+         * after that nothing is lost, state_changed() will try to assume the
+         * connection again.
+         */
+        if (priv->assumed_ac) {
+            gs_unref_variant GVariant *value =
+                g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
+
+            if (nm_streq(get_variant_state(value), "disconnected"))
+                priv->assumed_ac_timeout = g_timeout_add_seconds(1, assumed_ac_timeout_cb, self);
+        }
+
         /* Change state back to what it was before NEED_AUTH */
         nm_device_state_changed(device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
         return;
@@ -1317,7 +1515,17 @@ secrets_error:
                                                   NM_DEVICE_ERROR,
                                                   NM_DEVICE_ERROR_INVALID_CONNECTION,
                                                   "NM secrets request failed");
-    /* Now wait for the Connect callback to update device state */
+
+    if (priv->assumed_ac) {
+        nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS);
+        /* NMDevice's state change -> NMActRequests/NMActiveConnection's state
+         * change -> assumed_connection_state_changed_before_managed() ->
+         * cleanup_association_attempt() so no need to call it explicitly.
+         */
+    } else {
+        priv->secrets_failed = TRUE;
+        /* Now wait for the Connect callback to update device state */
+    }
 }
 
 static void
@@ -1353,11 +1561,13 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
     gs_unref_variant GVariant *variant = NULL;
     gs_free_error GError *error        = NULL;
     NMConnection *        connection;
-    NMSettingWireless *   s_wifi;
-    GBytes *              ssid;
-    gs_free char *        ssid_utf8 = NULL;
-    NMDeviceStateReason   reason    = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED;
+    gs_free char *        ssid   = NULL;
+    NMDeviceStateReason   reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED;
     GVariant *            value;
+    gboolean              disconnect;
+
+    disconnect = !priv->iwd_autoconnect
+                 || nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL);
 
     variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
     if (!variant) {
@@ -1391,7 +1601,7 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 
             /* If secrets were wrong, we'd be getting a net.connman.iwd.Failed */
             reason = NM_DEVICE_STATE_REASON_NO_SECRETS;
-        } else if (nm_streq0(dbus_error, "net.connman.iwd.Aborted")) {
+        } else if (nm_streq0(dbus_error, "net.connman.iwd.Aborted") && priv->secrets_failed) {
             /* If agent call was cancelled we'd be getting a net.connman.iwd.Aborted */
             reason = NM_DEVICE_STATE_REASON_NO_SECRETS;
         }
@@ -1401,37 +1611,36 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 
     nm_assert(nm_device_get_state(device) == NM_DEVICE_STATE_CONFIG);
 
+    disconnect = TRUE;
+
     connection = nm_device_get_applied_connection(device);
     if (!connection)
         goto failed;
 
-    s_wifi = nm_connection_get_setting_wireless(connection);
-    if (!s_wifi)
-        goto failed;
-
-    ssid = nm_setting_wireless_get_ssid(s_wifi);
-    if (!ssid)
+    if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, &ssid, NULL))
         goto failed;
 
-    ssid_utf8 = _nm_utils_ssid_to_utf8(ssid);
-
     _LOGI(LOGD_DEVICE | LOGD_WIFI,
           "Activation: (wifi) Stage 2 of 5 (Device Configure) successful.  Connected to '%s'.",
-          ssid_utf8);
+          ssid);
     nm_device_activate_schedule_stage3_ip_config_start(device);
 
     return;
 
 failed:
-    /* Call Disconnect to make sure IWD's autoconnect is disabled */
-    cleanup_association_attempt(self, TRUE);
+    /* If necessary call Disconnect to make sure IWD's autoconnect is disabled */
+    cleanup_association_attempt(self, disconnect);
 
-    nm_device_queue_state(device, NM_DEVICE_STATE_FAILED, reason);
+    nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, reason);
 
     value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
-    if (!priv->can_connect && nm_streq0(get_variant_state(value), "disconnected")) {
-        priv->can_connect = true;
-        nm_device_emit_recheck_auto_activate(device);
+    if (!priv->iwd_autoconnect && nm_streq(get_variant_state(value), "disconnected")) {
+        schedule_periodic_scan(self, TRUE);
+
+        if (!priv->nm_autoconnect) {
+            priv->nm_autoconnect = true;
+            nm_device_emit_recheck_auto_activate(device);
+        }
     }
     g_variant_unref(value);
 }
@@ -1466,14 +1675,12 @@ act_start_cb(GObject *source, GAsyncResult *res, gpointer user_data)
     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;
+    gs_free char *        ssid         = 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",
+              "Activation: (wifi) {AccessPoint,AdHoc}.Start() failed: %s",
               error->message);
 
         if (nm_utils_error_is_cancelled(error))
@@ -1487,21 +1694,16 @@ act_start_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 
     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)
+    if (!nm_wifi_connection_get_iwd_ssid_and_security(nm_device_get_applied_connection(device),
+                                                      &ssid,
+                                                      NULL))
         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);
-
+          ssid);
     nm_device_activate_schedule_stage3_ip_config_start(device);
+
     return;
 
 error:
@@ -1515,14 +1717,13 @@ error:
 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;
+    NMDeviceIwdPrivate * priv   = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMDevice *           device = NM_DEVICE(self);
+    NMSettingWireless *  s_wireless;
+    GDBusProxy *         proxy = NULL;
+    gs_free char *       ssid  = NULL;
+    const char *         mode;
+    NMIwdNetworkSecurity security;
 
     if (!priv->act_mode_switch)
         return;
@@ -1544,44 +1745,46 @@ act_check_interface(NMDeviceIwd *self)
     if (!NM_IN_SET(nm_device_get_state(device), NM_DEVICE_STATE_CONFIG))
         return;
 
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
-    if (!ssid)
+    if (!nm_wifi_connection_get_iwd_ssid_and_security(nm_device_get_applied_connection(device),
+                                                      &ssid,
+                                                      &security))
         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) {
+    if (security == NM_IWD_NETWORK_SECURITY_OPEN) {
         g_dbus_proxy_call(proxy,
                           "StartOpen",
-                          g_variant_new("(s)", ssid_utf8),
+                          g_variant_new("(s)", ssid),
                           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);
+    } else if (security == NM_IWD_NETWORK_SECURITY_PSK) {
+        NMSettingWirelessSecurity *s_wireless_sec;
+        const char *               psk;
+
+        s_wireless_sec = (NMSettingWirelessSecurity *) nm_device_get_applied_setting(
+            device,
+            NM_TYPE_SETTING_WIRELESS_SECURITY);
+        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);
+            _LOGE(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) No PSK for '%s'.", ssid);
             goto failed;
         }
 
         g_dbus_proxy_call(proxy,
                           "Start",
-                          g_variant_new("(ss)", ssid_utf8, psk),
+                          g_variant_new("(ss)", ssid, psk),
                           G_DBUS_CALL_FLAGS_NONE,
                           G_MAXINT,
                           priv->cancellable,
                           act_start_cb,
                           self);
-    }
+    } else
+        goto failed;
 
-    _LOGD(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Called Start('%s').", ssid_utf8);
+    _LOGD(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Called Start('%s').", ssid);
     return;
 
 failed:
@@ -1718,6 +1921,237 @@ set_powered(NMDeviceIwd *self, gboolean powered)
 
 /*****************************************************************************/
 
+static NMWifiAP *
+find_ap_by_supplicant_path(NMDeviceIwd *self, const NMRefString *path)
+{
+    NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMWifiAP *          tmp;
+
+    c_list_for_each_entry (tmp, &priv->aps_lst_head, aps_lst)
+        if (nm_wifi_ap_get_supplicant_path(tmp) == path)
+            return tmp;
+
+    return NULL;
+}
+
+static void
+assumed_connection_state_changed(NMActiveConnection *active, GParamSpec *pspec, NMDeviceIwd *self)
+{
+    NMSettingsConnection *  sett_conn = nm_active_connection_get_settings_connection(active);
+    NMActiveConnectionState state     = nm_active_connection_get_state(active);
+
+    /* Delete the temporary connection created for an external IWD connection
+     * (triggered by somebody outside of NM, be it IWD autoconnect or a
+     * parallel client), unless it's been referenced by a Known Network
+     * object since, which would remove the EXTERNAL flag.
+     *
+     * Note we can't do this too early, e.g. at the same time that we're
+     * setting the device state to FAILED or DISCONNECTING because the
+     * connection shouldn't disappear while it's still being used.  We do
+     * this on the connection's transition to DEACTIVATED same as as
+     * NMManager does for external activations.
+     */
+    if (state != NM_ACTIVE_CONNECTION_STATE_DEACTIVATED)
+        return;
+
+    g_signal_handlers_disconnect_by_func(active, assumed_connection_state_changed, NULL);
+
+    if (sett_conn
+        && NM_FLAGS_HAS(nm_settings_connection_get_flags(sett_conn),
+                        NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
+        nm_settings_connection_delete(sett_conn, FALSE);
+}
+
+static void
+assumed_connection_state_changed_before_managed(NMActiveConnection *active,
+                                                GParamSpec *        pspec,
+                                                NMDeviceIwd *       self)
+{
+    NMDeviceIwdPrivate *    priv  = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMActiveConnectionState state = nm_active_connection_get_state(active);
+    gboolean                disconnect;
+
+    if (state != NM_ACTIVE_CONNECTION_STATE_DEACTIVATED)
+        return;
+
+    /* When an assumed connection fails we always get called, even if the
+     * activation hasn't reached PREPARE or CONFIG, e.g. because of a policy
+     * or authorization problem in NMManager.  .deactivate would only be
+     * called starting at some stage so we can't rely on that.
+     *
+     * If the error happened before PREPARE (where we set a non-NULL
+     * priv->current_ap) that will mean NM is somehow blocking autoconnect
+     * so we want to call IWD's Station.Disconnect() to block its
+     * autoconnect.  If this happens during or after PREPARE, we just
+     * clean up and wait for a new attempt by IWD.
+     *
+     * cleanup_association_attempt will clear priv->assumed_ac, disconnect
+     * this callback from the signal and also send a Disconnect to IWD if
+     * needed.
+     *
+     * Note this function won't be called after IWD transitions to
+     * "connected" (and NMDevice to IP_CONFIG) as we disconnect from the
+     * signal at that point, cleanup_association_attempt() will be
+     * triggered by an IWD state change instead.
+     */
+    disconnect = !priv->current_ap;
+    cleanup_association_attempt(self, disconnect);
+}
+
+static void
+assume_connection(NMDeviceIwd *self, NMWifiAP *ap)
+{
+    NMDeviceIwdPrivate *  priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMSettingsConnection *sett_conn;
+    gs_unref_object NMAuthSubject *subject = NULL;
+    NMActiveConnection *           ac;
+    gs_free_error GError *error = NULL;
+
+    /* We can use the .update_connection / nm_device_emit_recheck_assume
+     * API but we can also pass an assumed/external activation type
+     * directly to nm_manager_activate_connection() and skip the
+     * complicated process of creating a matching connection, taking
+     * advantage of the Known Networks pointing directly to a mirror
+     * connection.  The only downside seems to be
+     * nm_manager_activate_connection() goes through the extra
+     * authorization.
+     *
+     * However for now we implement a similar behaviour using a normal
+     * "managed" activation.  For one, assumed/external
+     * connection state is not reflected in nm_manager_get_state() until
+     * fully activated.  Secondly setting the device state to FAILED
+     * is treated as ACTIVATED so we'd have to find another way to signal
+     * that stage2 is failing asynchronously.  Thirdly the connection
+     * becomes "managed" only when ACTIVATED but for IWD it's really
+     * managed when IP_CONFIG starts.
+     */
+    sett_conn = nm_iwd_manager_get_ap_mirror_connection(nm_iwd_manager_get(), ap);
+    if (!sett_conn)
+        goto error;
+
+    subject = nm_auth_subject_new_internal();
+    ac      = nm_manager_activate_connection(
+        NM_MANAGER_GET,
+        sett_conn,
+        NULL,
+        nm_dbus_object_get_path(NM_DBUS_OBJECT(ap)),
+        NM_DEVICE(self),
+        subject,
+        NM_ACTIVATION_TYPE_MANAGED,
+        NM_ACTIVATION_REASON_ASSUME,
+        NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
+        &error);
+
+    if (!ac) {
+        _LOGW(LOGD_WIFI, "Activation: (wifi) assume error: %s", error->message);
+        goto error;
+    }
+
+    /* If no Known Network existed for this AP, we generated a temporary
+     * NMSettingsConnection with the EXTERNAL flag.  It is not referenced by
+     * any Known Network objects at this time so we want to delete it if the
+     * IWD connection ends up failing or a later part of the activation fails
+     * before IWD created a Known Network.
+     * Setting the activation type to EXTERNAL would do this by causing
+     * NM_ACTIVATION_STATE_FLAG_EXTERNAL to be set on the NMActiveConnection
+     * but we don't want the connection to be marked EXTERNAL because we
+     * will be assuming the ownership of it in IP_CONFIG or thereabouts.
+     *
+     * This callback stays connected forever while the second one gets
+     * disconnected when we reset the activation type to managed.
+     */
+    g_signal_connect(ac,
+                     "notify::" NM_ACTIVE_CONNECTION_STATE,
+                     G_CALLBACK(assumed_connection_state_changed),
+                     NULL);
+    g_signal_connect(ac,
+                     "notify::" NM_ACTIVE_CONNECTION_STATE,
+                     G_CALLBACK(assumed_connection_state_changed_before_managed),
+                     self);
+    priv->assumed_ac = g_object_ref(ac);
+
+    return;
+
+error:
+    send_disconnect(self);
+
+    if (sett_conn
+        && NM_FLAGS_HAS(nm_settings_connection_get_flags(sett_conn),
+                        NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
+        nm_settings_connection_delete(sett_conn, FALSE);
+}
+
+static void
+assumed_connection_progress_to_ip_config(NMDeviceIwd *self, gboolean was_postponed)
+{
+    NMDeviceIwdPrivate *priv      = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMDevice *          device    = NM_DEVICE(self);
+    NMDeviceState       dev_state = nm_device_get_state(device);
+
+    wifi_secrets_cancel(self);
+    nm_clear_g_source(&priv->assumed_ac_timeout);
+
+    /* NM takes over the activation from this point on so clear the assumed
+     * activation state and if we were using NM_ACTIVATION_TYPE_ASSUMED or
+     * _EXTERNAL we'd need to reset the activation type to _MANAGED at this
+     * point instead of waiting for the ACTIVATED state (as done in
+     * nm_active_connection_set_state).
+     */
+    cleanup_assumed_connect(self);
+
+    if (dev_state == NM_DEVICE_STATE_NEED_AUTH)
+        nm_device_state_changed(NM_DEVICE(self),
+                                NM_DEVICE_STATE_CONFIG,
+                                NM_DEVICE_STATE_REASON_NONE);
+
+    /* If stage2 had returned NM_ACT_STAGE_RETURN_POSTPONE, we tell NMDevice
+     * that stage2 is done.
+     */
+    if (was_postponed)
+        nm_device_activate_schedule_stage3_ip_config_start(NM_DEVICE(self));
+}
+
+static void
+initial_check_assume(NMDeviceIwd *self)
+{
+    NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    const char *        network_path_str;
+    nm_auto_ref_string NMRefString *network_path = NULL;
+    NMWifiAP *                      ap           = NULL;
+    gs_unref_variant GVariant *state_value =
+        g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
+    gs_unref_variant GVariant *cn_value =
+        g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "ConnectedNetwork");
+
+    if (!NM_IN_STRSET(get_variant_state(state_value), "connecting", "connected", "roaming"))
+        return;
+
+    if (!priv->iwd_autoconnect) {
+        send_disconnect(self);
+        return;
+    }
+
+    if (!cn_value || !g_variant_is_of_type(cn_value, G_VARIANT_TYPE_OBJECT_PATH)) {
+        _LOGW(LOGD_DEVICE | LOGD_WIFI,
+              "ConnectedNetwork property not cached or not an object path");
+        return;
+    }
+
+    network_path_str = g_variant_get_string(cn_value, NULL);
+    network_path     = nm_ref_string_new(network_path_str);
+    ap               = find_ap_by_supplicant_path(self, network_path);
+
+    if (!ap) {
+        _LOGW(LOGD_DEVICE | LOGD_WIFI,
+              "ConnectedNetwork points to an unknown Network %s",
+              network_path_str);
+        return;
+    }
+
+    _LOGD(LOGD_DEVICE | LOGD_WIFI, "assuming connection in initial_check_assume");
+    assume_connection(self, ap);
+}
+
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
@@ -1740,9 +2174,9 @@ 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 */
+    /* AP, Ad-Hoc modes never use a specific object or existing scanned AP */
     mode = nm_setting_wireless_get_mode(s_wireless);
-    if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP))
+    if (NM_IN_STRSET(mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC))
         goto add_new;
 
     ap_path = nm_active_connection_get_specific_object(NM_ACTIVE_CONNECTION(req));
@@ -1760,14 +2194,16 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         return NM_ACT_STAGE_RETURN_SUCCESS;
     }
 
-    if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_INFRA)) {
-        /* Hidden networks not supported at this time */
+    /* In infrastructure mode the specific object should be set by now except
+     * for a first-time connection to a hidden network.  If a hidden network is
+     * a Known Network it should still have been in the AP list.
+     */
+    if (!nm_setting_wireless_get_hidden(s_wireless) || is_connection_known_network(connection))
         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
+     * (hidden network or something) or starting a Hotspot, create a 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).
@@ -1808,12 +2244,49 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     if (NM_IN_STRSET(mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
         gs_unref_object GDBusProxy *network_proxy = NULL;
         NMWifiAP *                  ap            = priv->current_ap;
+        NMSettingWirelessSecurity * s_wireless_sec;
 
         if (!ap) {
             NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
             goto out_fail;
         }
 
+        /* With priv->iwd_autoconnect, if we're assuming a connection because
+         * of a state change to "connecting", signal stage 2 is still running.
+         * If "connected" or "roaming", we can go right to the IP_CONFIG state
+         * and there's nothing left to do in CONFIG.
+         * If we're assuming the connection because of an agent request we
+         * switch to NEED_AUTH and actually send the request now that we
+         * have an activation request.
+         *
+         * This all assumes ConnectedNetwork hasn't changed.
+         */
+        if (priv->assumed_ac) {
+            gboolean result;
+
+            if (!priv->pending_agent_request) {
+                gs_unref_variant GVariant *value =
+                    g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
+
+                if (nm_streq(get_variant_state(value), "connecting")) {
+                    return NM_ACT_STAGE_RETURN_POSTPONE;
+                } else {
+                    /* This basically forgets that the connection was "assumed"
+                     * as we can treat it like any connection triggered by a
+                     * Network.Connect() call from now on.
+                     */
+                    assumed_connection_progress_to_ip_config(self, FALSE);
+                    return NM_ACT_STAGE_RETURN_SUCCESS;
+                }
+            }
+
+            result = nm_device_iwd_agent_query(self, priv->pending_agent_request);
+            g_clear_object(&priv->pending_agent_request);
+            nm_assert(result);
+
+            return NM_ACT_STAGE_RETURN_POSTPONE;
+        }
+
         /* 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.
@@ -1828,15 +2301,28 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
             goto out_fail;
         }
 
-        if (!is_connection_known_network(connection)
-            && nm_setting_wireless_get_hidden(s_wireless)) {
-            gs_free char *ssid_str = NULL;
+        priv->secrets_failed = FALSE;
+
+        if (nm_wifi_ap_get_fake(ap)) {
+            gs_free char *ssid = NULL;
+
+            if (!nm_setting_wireless_get_hidden(s_wireless)) {
+                _LOGW(LOGD_DEVICE | LOGD_WIFI,
+                      "Activation: (wifi) target network not known to IWD but is not "
+                      "marked hidden");
+                NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+                goto out_fail;
+            }
+
+            if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, &ssid, NULL)) {
+                NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+                goto out_fail;
+            }
 
             /* Use Station.ConnectHiddenNetwork method instead of Network proxy. */
-            ssid_str = _nm_utils_ssid_to_utf8(nm_setting_wireless_get_ssid(s_wireless));
             g_dbus_proxy_call(priv->dbus_station_proxy,
                               "ConnectHiddenNetwork",
-                              g_variant_new("(s)", ssid_str),
+                              g_variant_new("(s)", ssid),
                               G_DBUS_CALL_FLAGS_NONE,
                               G_MAXINT,
                               priv->cancellable,
@@ -1845,14 +2331,6 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
             return NM_ACT_STAGE_RETURN_POSTPONE;
         }
 
-        if (!nm_wifi_ap_get_supplicant_path(ap)) {
-            _LOGW(LOGD_DEVICE | LOGD_WIFI,
-                  "Activation: (wifi) network is provisioned but dbus supplicant path for AP "
-                  "unknown");
-            NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-            goto out_fail;
-        }
-
         network_proxy = nm_iwd_manager_get_dbus_interface(
             nm_iwd_manager_get(),
             nm_ref_string_get_str(nm_wifi_ap_get_supplicant_path(ap)),
@@ -1868,6 +2346,15 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         if (!priv->cancellable)
             priv->cancellable = g_cancellable_new();
 
+        s_wireless_sec = nm_connection_get_setting_wireless_security(connection);
+        if (s_wireless_sec
+            && nm_streq0(nm_setting_wireless_security_get_key_mgmt(s_wireless_sec), "owe")) {
+            _LOGI(LOGD_WIFI,
+                  "An OWE connection is requested but IWD may connect to either an OWE "
+                  "or unsecured network and there won't be any indication of whether "
+                  "encryption is in use -- proceed at your own risk!");
+        }
+
         /* Call Network.Connect.  No timeout because IWD already handles
          * timeouts.
          */
@@ -1951,31 +2438,26 @@ static void
 schedule_periodic_scan(NMDeviceIwd *self, gboolean initial_scan)
 {
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    GVariant *          value;
-    gboolean            disconnected = TRUE;
     guint               interval;
 
-    if (priv->can_scan) {
-        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 10 seconds.  When connected, update
-     * AP list mainly on UI requests.
+    /* Automatically start a scan after a disconnect, mode change or device UP,
+     * otherwise scan periodically every 10 seconds if needed for NM's
+     * autoconnect.  There's no need to scan When using IWD's autoconnect or
+     * when connected, we update the AP list on UI requests.
      *
-     * (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 then send it a Disconnect() causing IWD to
-     * exit autoconnect and interrupt the ongoing scan, meaning that
-     * we still want a new scan ASAP.
+     * (initial_scan && disconnected && !priv->iwd_autoconnect) 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 then send it a Disconnect() causing IWD to exit
+     * autoconnect and interrupt the ongoing scan, meaning that we still want
+     * a new scan ASAP.
      */
-    if (!priv->can_scan || !disconnected || priv->scan_requested || priv->scanning)
+    if (!priv->can_scan || priv->scan_requested || priv->current_ap || priv->iwd_autoconnect)
         interval = -1;
-    else if (initial_scan)
+    else if (initial_scan && priv->scanning)
         interval = 0;
+    else if (priv->scanning)
+        interval = -1;
     else if (!priv->periodic_scan_id)
         interval = 10;
     else
@@ -1997,7 +2479,8 @@ set_can_scan(NMDeviceIwd *self, gboolean can_scan)
 
     priv->can_scan = can_scan;
 
-    schedule_periodic_scan(self, TRUE);
+    if (!priv->iwd_autoconnect)
+        schedule_periodic_scan(self, TRUE);
 }
 
 static void
@@ -2008,6 +2491,8 @@ device_state_changed(NMDevice *          device,
 {
     NMDeviceIwd *       self = NM_DEVICE_IWD(device);
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMSettingWireless * s_wireless;
+    const char *        mode;
 
     switch (new_state) {
     case NM_DEVICE_STATE_UNMANAGED:
@@ -2024,15 +2509,22 @@ device_state_changed(NMDevice *          device,
                                               NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
         }
         break;
-    case NM_DEVICE_STATE_NEED_AUTH:
-        break;
-    case NM_DEVICE_STATE_IP_CHECK:
-        break;
-    case NM_DEVICE_STATE_ACTIVATED:
-        break;
-    case NM_DEVICE_STATE_FAILED:
-        break;
     case NM_DEVICE_STATE_DISCONNECTED:
+        if (old_state == NM_DEVICE_STATE_UNAVAILABLE)
+            initial_check_assume(self);
+        break;
+    case NM_DEVICE_STATE_IP_CONFIG:
+        s_wireless =
+            (NMSettingWireless *) nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS);
+        mode = nm_setting_wireless_get_mode(s_wireless);
+        if (!priv->periodic_update_id
+            && NM_IN_STRSET(mode,
+                            NULL,
+                            NM_SETTING_WIRELESS_MODE_INFRA,
+                            NM_SETTING_WIRELESS_MODE_ADHOC)) {
+            priv->periodic_update_id = g_timeout_add_seconds(6, periodic_update_cb, self);
+            periodic_update(self);
+        }
         break;
     default:
         break;
@@ -2128,7 +2620,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 
         break;
     case PROP_BITRATE:
-        g_value_set_uint(value, 65000);
+        g_value_set_uint(value, priv->rate);
         break;
     case PROP_CAPABILITIES:
         g_value_set_uint(value, priv->capabilities);
@@ -2164,51 +2656,149 @@ state_changed(NMDeviceIwd *self, const char *new_state)
     NMDeviceIwdPrivate *priv           = NM_DEVICE_IWD_GET_PRIVATE(self);
     NMDevice *          device         = NM_DEVICE(self);
     NMDeviceState       dev_state      = nm_device_get_state(device);
+    gboolean            nm_connection  = priv->current_ap || priv->assumed_ac;
     gboolean            iwd_connection = FALSE;
-    gboolean            can_connect    = priv->can_connect;
+    NMWifiAP *          ap             = NULL;
+    gboolean            can_connect    = priv->nm_autoconnect;
 
     _LOGI(LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state);
 
-    if (dev_state >= NM_DEVICE_STATE_CONFIG && dev_state <= NM_DEVICE_STATE_ACTIVATED)
-        iwd_connection = TRUE;
+    if (NM_IN_STRSET(new_state, "connecting", "connected", "roaming")) {
+        gs_unref_variant GVariant *value = NULL;
+        const char *               network_path_str;
+        nm_auto_ref_string NMRefString *network_path = NULL;
+
+        value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "ConnectedNetwork");
+        if (!value || !g_variant_is_of_type(value, G_VARIANT_TYPE_OBJECT_PATH)) {
+            _LOGW(LOGD_DEVICE | LOGD_WIFI,
+                  "ConnectedNetwork property not cached or not an object path");
+            return;
+        }
+
+        iwd_connection   = TRUE;
+        network_path_str = g_variant_get_string(value, NULL);
+        network_path     = nm_ref_string_new(network_path_str);
+        ap               = find_ap_by_supplicant_path(self, network_path);
+
+        if (!ap) {
+            _LOGW(LOGD_DEVICE | LOGD_WIFI,
+                  "ConnectedNetwork points to an unknown Network %s",
+                  network_path_str);
+            return;
+        }
+    }
 
     /* Don't allow scanning while connecting, disconnecting or roaming */
     set_can_scan(self, NM_IN_STRSET(new_state, "connected", "disconnected"));
 
-    priv->can_connect = FALSE;
+    priv->nm_autoconnect = FALSE;
 
-    if (NM_IN_STRSET(new_state, "connecting", "connected", "roaming")) {
+    if (nm_connection && iwd_connection && priv->current_ap && ap != priv->current_ap) {
+        gboolean switch_ap = priv->iwd_autoconnect && priv->assumed_ac;
+
+        _LOGW(LOGD_DEVICE | LOGD_WIFI,
+              "IWD is connecting to the wrong AP, %s activation",
+              switch_ap ? "replacing" : "aborting");
+        cleanup_association_attempt(self, !switch_ap);
+        nm_device_state_changed(device,
+                                NM_DEVICE_STATE_FAILED,
+                                NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+
+        if (switch_ap)
+            assume_connection(self, ap);
+        return;
+    }
+
+    if (priv->iwd_autoconnect && iwd_connection) {
+        if (dev_state < NM_DEVICE_STATE_DISCONNECTED)
+            return;
+
+        /* If IWD is in any state other than disconnected and the NMDevice is
+         * in DISCONNECTED then someone else, possibly IWD's autoconnect, has
+         * commanded an action and we need to update our NMDevice's state to
+         * match, including finding the NMSettingsConnection and NMWifiAP
+         * matching the network pointed to by Station.ConnectedNetwork.
+         *
+         * If IWD is in the connected state and we're in CONFIG, we only have
+         * to signal that the existing connection request has advanced to a new
+         * state.  If the connection request came from NM, we must have used
+         * Network.Connect() so that method call's callback will update the
+         * connection request, otherwise we do it here.
+         *
+         * If IWD is disconnecting or just disconnected, the common code below
+         * (independent from priv->iwd_autoconnect) will handle this case.
+         * If IWD is disconnecting but we never saw a connection request in the
+         * first place (maybe because we're only startig up) we won't be
+         * setting up an NMActiveConnection just to put the NMDevice in the
+         * DEACTIVATING state and we ignore this case.
+         *
+         * If IWD was in the disconnected state and transitioned to
+         * "connecting" but we were already in NEED_AUTH because we handled an
+         * agent query -- IWD normally stays in "disconnected" until it has all
+         * the secrets -- we record this fact and remain in NEED_AUTH.
+         */
+        if (!nm_connection) {
+            _LOGD(LOGD_DEVICE | LOGD_WIFI, "This is a new connection, 'assuming' it");
+            assume_connection(self, ap);
+            return;
+        }
+
+        if (priv->assumed_ac && dev_state >= NM_DEVICE_STATE_PREPARE
+            && dev_state < NM_DEVICE_STATE_IP_CONFIG
+            && NM_IN_STRSET(new_state, "connected", "roaming")) {
+            _LOGD(LOGD_DEVICE | LOGD_WIFI, "Updating assumed activation state");
+            assumed_connection_progress_to_ip_config(self, TRUE);
+            return;
+        }
+
+        if (priv->assumed_ac) {
+            _LOGD(LOGD_DEVICE | LOGD_WIFI, "Clearing assumed activation timeout");
+            nm_clear_g_source(&priv->assumed_ac_timeout);
+            return;
+        }
+    } else if (!priv->iwd_autoconnect && iwd_connection) {
         /* If we were connecting, do nothing, the confirmation of
          * a connection success is handled in the Device.Connect
          * method return callback.  Otherwise, IWD must have connected
          * without Network Manager's will so for simplicity force a
          * disconnect.
          */
-        if (iwd_connection)
+        if (nm_connection)
             return;
 
-        _LOGW(LOGD_DEVICE | LOGD_WIFI, "Unsolicited connection success, asking IWD to disconnect");
+        _LOGW(LOGD_DEVICE | LOGD_WIFI, "Unsolicited connection, asking IWD to disconnect");
         send_disconnect(self);
     } else if (NM_IN_STRSET(new_state, "disconnecting", "disconnected")) {
-        /* Call Disconnect on the IWD device object to make sure it
-         * disables its own autoconnect.
+        /* If necessary, call Disconnect on the IWD device object to make sure
+         * it disables its autoconnect.
          */
-        send_disconnect(self);
+        if ((!priv->iwd_autoconnect
+             || nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL))
+            && !priv->wifi_secrets_id && !priv->pending_agent_request)
+            send_disconnect(self);
 
         /*
          * If IWD is still handling the Connect call, let our Connect
          * callback for the dbus method handle the failure.  The main
-         * reason we can't handle the failure here is because the method
-         * callback will have more information on the specific failure
-         * reason.
+         * reason we don't want to handle the failure here is because the
+         * method callback will have more information on the specific
+         * failure reason.
+         *
+         * If IWD is handling an autoconnect agent call, let the agent's
+         * Cancel() handler take care of this.
          */
-        if (NM_IN_SET(dev_state, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH))
+        if (NM_IN_SET(dev_state, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH)
+            && !priv->assumed_ac)
+            return;
+        if (NM_IN_SET(dev_state, NM_DEVICE_STATE_NEED_AUTH) && priv->assumed_ac)
             return;
 
-        if (iwd_connection)
+        if (nm_connection) {
+            cleanup_association_attempt(self, FALSE);
             nm_device_state_changed(device,
                                     NM_DEVICE_STATE_FAILED,
                                     NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+        }
     } else if (!nm_streq(new_state, "unknown")) {
         _LOGE(LOGD_WIFI, "State %s unknown", new_state);
         return;
@@ -2217,8 +2807,8 @@ state_changed(NMDeviceIwd *self, const char *new_state)
     /* Don't allow new connection until iwd exits disconnecting and no
      * Connect callback is pending.
      */
-    if (NM_IN_STRSET(new_state, "disconnected")) {
-        priv->can_connect = TRUE;
+    if (!priv->iwd_autoconnect && NM_IN_STRSET(new_state, "disconnected")) {
+        priv->nm_autoconnect = TRUE;
         if (!can_connect)
             nm_device_emit_recheck_auto_activate(device);
     }
@@ -2239,7 +2829,7 @@ scanning_changed(NMDeviceIwd *self, gboolean new_scanning)
     if (!priv->scanning) {
         update_aps(self);
 
-        if (!priv->scan_requested)
+        if (!priv->scan_requested && !priv->iwd_autoconnect)
             schedule_periodic_scan(self, FALSE);
     }
 }
@@ -2281,7 +2871,6 @@ 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,
@@ -2340,24 +2929,11 @@ powered_changed(NMDeviceIwd *self, gboolean new_powered)
     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
-             * "State" property is present on the Device interface, that
-             * would mean we're dealing with an IWD version from before the
-             * Device/Station split (0.7 or earlier) and we can easily
-             * handle that by making priv->dbus_device_proxy and
-             * priv->dbus_station_proxy both point at the Device interface.
-             */
-            value = g_dbus_proxy_get_cached_property(priv->dbus_device_proxy, "State");
-            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));
-                interface = NULL;
-            }
+            _LOGE(LOGD_WIFI,
+                  "Interface %s not found on obj %s",
+                  NM_IWD_STATION_INTERFACE,
+                  g_dbus_object_get_object_path(priv->dbus_obj));
+            interface = NULL;
         }
     } else
         interface = NULL;
@@ -2370,6 +2946,8 @@ powered_changed(NMDeviceIwd *self, gboolean new_powered)
     }
 
     if (interface) {
+        GVariant *value;
+
         priv->dbus_station_proxy = G_DBUS_PROXY(interface);
         g_signal_connect(priv->dbus_station_proxy,
                          "g-properties-changed",
@@ -2385,11 +2963,21 @@ powered_changed(NMDeviceIwd *self, gboolean new_powered)
         g_variant_unref(value);
 
         update_aps(self);
+
+        /* When a device is brought UP in station mode, including after a mode
+         * switch, IWD re-enables autoconnect.  This is unlike NM's autoconnect
+         * where a mode change doesn't interfere with the
+         * BLOCKED_MANUAL_DISCONNECT flag.
+         */
+        if (priv->iwd_autoconnect) {
+            nm_device_autoconnect_blocked_unset(NM_DEVICE(self),
+                                                NM_DEVICE_AUTOCONNECT_BLOCKED_INTERNAL);
+        }
     } else {
         set_can_scan(self, FALSE);
         priv->scanning       = FALSE;
         priv->scan_requested = FALSE;
-        priv->can_connect    = FALSE;
+        priv->nm_autoconnect = FALSE;
         cleanup_association_attempt(self, FALSE);
         remove_all_aps(self);
     }
@@ -2408,6 +2996,36 @@ device_properties_changed(GDBusProxy *proxy,
         powered_changed(self, new_bool);
 }
 
+static void
+config_changed(NMConfig *          config,
+               NMConfigData *      config_data,
+               NMConfigChangeFlags changes,
+               NMConfigData *      old_data,
+               NMDeviceIwd *       self)
+{
+    NMDeviceIwdPrivate *priv       = NM_DEVICE_IWD_GET_PRIVATE(self);
+    gboolean            old_iwd_ac = priv->iwd_autoconnect;
+
+    priv->iwd_autoconnect =
+        nm_config_data_get_device_config_boolean(config_data,
+                                                 NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT,
+                                                 NM_DEVICE(self),
+                                                 TRUE,
+                                                 TRUE);
+
+    if (old_iwd_ac != priv->iwd_autoconnect && priv->dbus_station_proxy && !priv->current_ap) {
+        gs_unref_variant GVariant *value = NULL;
+
+        if (!priv->iwd_autoconnect
+            && !nm_device_autoconnect_blocked_get(NM_DEVICE(self),
+                                                  NM_DEVICE_AUTOCONNECT_BLOCKED_ALL))
+            send_disconnect(self);
+
+        value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
+        state_changed(self, get_variant_state(value));
+    }
+}
+
 void
 nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object)
 {
@@ -2432,6 +3050,8 @@ nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object)
         powered_changed(self, FALSE);
 
         priv->act_mode_switch = FALSE;
+
+        g_signal_handlers_disconnect_by_func(nm_config_get(), config_changed, self);
     }
 
     if (!object)
@@ -2494,6 +3114,13 @@ nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object)
         _notify(self, PROP_CAPABILITIES);
     }
 
+    /* Update iwd_autoconnect before any state_changed call */
+    g_signal_connect(nm_config_get(),
+                     NM_CONFIG_SIGNAL_CONFIG_CHANGED,
+                     G_CALLBACK(config_changed),
+                     self);
+    config_changed(NULL, NM_CONFIG_GET_DATA, 0, NULL, self);
+
     g_variant_unref(value);
     value   = g_dbus_proxy_get_cached_property(priv->dbus_device_proxy, "Powered");
     powered = get_variant_boolean(value, "Powered");
@@ -2513,24 +3140,127 @@ error:
 gboolean
 nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation)
 {
-    NMActRequest *               req;
+    NMDevice *                   device = NM_DEVICE(self);
+    NMDeviceIwdPrivate *         priv   = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMDeviceState                state  = nm_device_get_state(device);
     const char *                 setting_name;
     const char *                 setting_key;
     gboolean                     replied;
+    NMWifiAP *                   ap;
     NMSecretAgentGetSecretsFlags get_secret_flags =
         NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
+    nm_auto_ref_string NMRefString *network_path = NULL;
 
-    req = nm_device_get_act_request(NM_DEVICE(self));
-    if (!req)
+    if (!invocation) {
+        gs_unref_variant GVariant *value =
+            g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
+        gboolean disconnect;
+
+        if (!priv->wifi_secrets_id && !priv->pending_agent_request)
+            return FALSE;
+
+        _LOGI(LOGD_WIFI, "IWD agent request is being cancelled");
+        wifi_secrets_cancel(self);
+
+        if (state == NM_DEVICE_STATE_NEED_AUTH)
+            nm_device_state_changed(device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+
+        /* The secrets request is being cancelled.  If we don't have an assumed
+         * connection than we've probably called Network.Connect and that method
+         * call's callback is going to handle the failure.  And if the state was
+         * not "disconnected" then let the state change handler process the
+         * failure.
+         */
+        if (!priv->assumed_ac)
+            return TRUE;
+
+        if (!nm_streq(get_variant_state(value), "disconnected"))
+            return TRUE;
+
+        disconnect = nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL);
+        cleanup_association_attempt(self, disconnect);
+        nm_device_state_changed(device,
+                                NM_DEVICE_STATE_FAILED,
+                                NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+        return TRUE;
+    }
+
+    if (state > NM_DEVICE_STATE_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) {
+        _LOGW(LOGD_WIFI, "Can't handle the IWD agent request in current device state");
         return FALSE;
+    }
+
+    if (priv->wifi_secrets_id || priv->pending_agent_request) {
+        _LOGW(LOGD_WIFI, "There's already a pending agent request for this device");
+        return FALSE;
+    }
+
+    network_path = nm_ref_string_new(get_agent_request_network_path(invocation));
+    ap           = find_ap_by_supplicant_path(self, network_path);
+    if (!ap) {
+        _LOGW(LOGD_WIFI, "IWD Network object not found for the agent request");
+        return FALSE;
+    }
+
+    if (priv->assumed_ac) {
+        const char *ac_ap_path = nm_active_connection_get_specific_object(priv->assumed_ac);
+
+        if (!nm_streq(ac_ap_path, nm_dbus_object_get_path(NM_DBUS_OBJECT(ap)))) {
+            _LOGW(LOGD_WIFI,
+                  "Dropping an existing assumed connection to create a new one based on the IWD "
+                  "agent request network parameter");
+
+            if (priv->current_ap)
+                nm_device_state_changed(device,
+                                        NM_DEVICE_STATE_FAILED,
+                                        NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+
+            cleanup_association_attempt(self, FALSE);
+            priv->pending_agent_request = g_object_ref(invocation);
+            assume_connection(self, ap);
+            return TRUE;
+        }
+
+        if (state != NM_DEVICE_STATE_CONFIG) {
+            _LOGI(LOGD_WIFI, "IWD agent request deferred until in CONFIG");
+            priv->pending_agent_request = g_object_ref(invocation);
+            return TRUE;
+        }
+
+        /* Otherwise handle as usual */
+    } else if (!priv->current_ap) {
+        _LOGI(LOGD_WIFI, "IWD is asking for secrets without explicit connect request");
+
+        if (priv->iwd_autoconnect) {
+            priv->pending_agent_request = g_object_ref(invocation);
+            assume_connection(self, ap);
+            return TRUE;
+        }
+
+        send_disconnect(self);
+        return FALSE;
+    } else if (priv->current_ap) {
+        if (priv->current_ap != ap) {
+            _LOGW(LOGD_WIFI, "IWD agent request for a wrong network object");
+            cleanup_association_attempt(self, TRUE);
+            nm_device_state_changed(device,
+                                    NM_DEVICE_STATE_FAILED,
+                                    NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+            return FALSE;
+        }
+
+        /* Otherwise handle as usual */
+    }
 
     if (!try_reply_agent_request(self,
-                                 nm_act_request_get_applied_connection(req),
+                                 nm_device_get_applied_connection(device),
                                  invocation,
                                  &setting_name,
                                  &setting_key,
-                                 &replied))
+                                 &replied)) {
+        priv->secrets_failed = TRUE;
         return FALSE;
+    }
 
     if (replied)
         return TRUE;
@@ -2543,17 +3273,83 @@ nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation)
      * Connection timestamp is set after activation or after first
      * activation failure (to 0).
      */
-    if (nm_settings_connection_get_timestamp(nm_act_request_get_settings_connection(req), NULL))
+    if (nm_settings_connection_get_timestamp(nm_device_get_settings_connection(device), NULL))
         get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
 
-    nm_device_state_changed(NM_DEVICE(self),
-                            NM_DEVICE_STATE_NEED_AUTH,
-                            NM_DEVICE_STATE_REASON_NO_SECRETS);
+    nm_device_state_changed(device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NO_SECRETS);
     wifi_secrets_get_one(self, setting_name, get_secret_flags, setting_key, invocation);
 
     return TRUE;
 }
 
+void
+nm_device_iwd_network_add_remove(NMDeviceIwd *self, GDBusProxy *network, bool add)
+{
+    NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMWifiAP *          ap   = NULL;
+    bool                recheck;
+    nm_auto_ref_string NMRefString *bss_path = NULL;
+
+    bss_path = nm_ref_string_new(g_dbus_proxy_get_object_path(network));
+    ap       = find_ap_by_supplicant_path(self, bss_path);
+
+    /* We could schedule an update_aps(self) idle call here but up to IWD 1.9
+     * when a hidden network connection is attempted, that network is initially
+     * only added as a Network object but not shown in GetOrderedNetworks()
+     * return values, and for some corner case scenarios it's beneficial to
+     * have that Network reflected in our ap list so that we don't attempt
+     * calling ConnectHiddenNetwork() on it, as that will fail in 1.9.  But we
+     * can skip recheck-available if we're currently scanning or in the middle
+     * of a GetOrderedNetworks() call as that will trigger the recheck too.
+     */
+    recheck = priv->enabled && !priv->scanning && !priv->networks_requested;
+
+    if (!add) {
+        if (ap) {
+            ap_add_remove(self, FALSE, ap, recheck);
+            priv->networks_changed |= !recheck;
+        }
+
+        return;
+    }
+
+    if (!ap) {
+        ap = ap_from_network(self,
+                             network,
+                             bss_path,
+                             nm_utils_get_monotonic_timestamp_msec(),
+                             -10000);
+        if (!ap)
+            return;
+
+        ap_add_remove(self, TRUE, ap, recheck);
+        g_object_unref(ap);
+        priv->networks_changed |= !recheck;
+        return;
+    }
+}
+
+static void
+autoconnect_changed(NMDevice *device, GParamSpec *pspec, NMDeviceIwd *self)
+{
+    NMDeviceIwdPrivate *priv         = NM_DEVICE_IWD_GET_PRIVATE(self);
+    gs_unref_variant GVariant *value = NULL;
+
+    /* Note IWD normally remains in "disconnected" during a secret request
+     * and we don't want to interrupt it by calling Station.Disconnect().
+     */
+    if (!priv->dbus_station_proxy || !priv->iwd_autoconnect
+        || !nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL)
+        || priv->wifi_secrets_id || priv->pending_agent_request)
+        return;
+
+    value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
+    if (!nm_streq(get_variant_state(value), "disconnected"))
+        return;
+
+    send_disconnect(self);
+}
+
 /*****************************************************************************/
 
 static const char *
@@ -2573,6 +3369,8 @@ nm_device_iwd_init(NMDeviceIwd *self)
 
     c_list_init(&priv->aps_lst_head);
 
+    g_signal_connect(self, "notify::" NM_DEVICE_AUTOCONNECT, G_CALLBACK(autoconnect_changed), self);
+
     /* Make sure the manager is running */
     (void) nm_iwd_manager_get();
 }
@@ -2602,6 +3400,7 @@ dispose(GObject *object)
 
     nm_clear_g_cancellable(&priv->cancellable);
 
+    g_signal_handlers_disconnect_by_func(self, autoconnect_changed, self);
     nm_device_iwd_set_dbus_object(self, NULL);
 
     G_OBJECT_CLASS(nm_device_iwd_parent_class)->dispose(object);
@@ -2643,6 +3442,13 @@ nm_device_iwd_class_init(NMDeviceIwdClass *klass)
     device_class->deactivate_async   = deactivate_async;
     device_class->can_reapply_change = can_reapply_change;
 
+    /* Stage 1 needed only for the set_current_ap() call.  Stage 2 is
+     * needed if we're assuming a connection still in the "connecting"
+     * state or on an agent request.
+     */
+    device_class->act_stage1_prepare_also_for_external_or_assume = TRUE;
+    device_class->act_stage2_config_also_for_external_or_assume  = TRUE;
+
     device_class->state_changed = device_state_changed;
 
     obj_properties[PROP_MODE] = g_param_spec_uint(NM_DEVICE_IWD_MODE,
diff --git a/src/devices/wifi/nm-device-iwd.h b/src/core/devices/wifi/nm-device-iwd.h
index e364beaf..ce94c9ea 100644
--- a/src/devices/wifi/nm-device-iwd.h
+++ b/src/core/devices/wifi/nm-device-iwd.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Intel Corporation
  */
@@ -44,4 +44,6 @@ void _nm_device_iwd_request_scan(NMDeviceIwd *          self,
                                  GVariant *             options,
                                  GDBusMethodInvocation *invocation);
 
+void nm_device_iwd_network_add_remove(NMDeviceIwd *device, GDBusProxy *network, bool add);
+
 #endif /* __NETWORKMANAGER_DEVICE_IWD_H__ */
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/core/devices/wifi/nm-device-olpc-mesh.c
index 22e94a6d..af83c4a3 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/core/devices/wifi/nm-device-olpc-mesh.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Dan Williams <dcbw@redhat.com>
  * Sjoerd Simons <sjoerd.simons@collabora.co.uk>
@@ -8,7 +8,7 @@
  * Copyright (C) 2009 One Laptop per Child
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-olpc-mesh.h"
 
@@ -30,8 +30,8 @@
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceOlpcMesh
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceOlpcMesh);
 
 /*****************************************************************************/
 
@@ -460,16 +460,16 @@ constructed(GObject *object)
 NMDevice *
 nm_device_olpc_mesh_new(const char *iface)
 {
-    return (NMDevice *) g_object_new(NM_TYPE_DEVICE_OLPC_MESH,
-                                     NM_DEVICE_IFACE,
-                                     iface,
-                                     NM_DEVICE_TYPE_DESC,
-                                     "802.11 OLPC Mesh",
-                                     NM_DEVICE_DEVICE_TYPE,
-                                     NM_DEVICE_TYPE_OLPC_MESH,
-                                     NM_DEVICE_LINK_TYPE,
-                                     NM_LINK_TYPE_OLPC_MESH,
-                                     NULL);
+    return g_object_new(NM_TYPE_DEVICE_OLPC_MESH,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "802.11 OLPC Mesh",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_OLPC_MESH,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_OLPC_MESH,
+                        NULL);
 }
 
 static void
diff --git a/src/devices/wifi/nm-device-olpc-mesh.h b/src/core/devices/wifi/nm-device-olpc-mesh.h
index d31c86fb..79b7fd5d 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.h
+++ b/src/core/devices/wifi/nm-device-olpc-mesh.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Dan Williams <dcbw@redhat.com>
  * Sjoerd Simons <sjoerd.simons@collabora.co.uk>
diff --git a/src/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c
index 2e5317fd..fb987600 100644
--- a/src/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/core/devices/wifi/nm-device-wifi-p2p.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-wifi-p2p.h"
 
@@ -27,8 +27,8 @@
 #include "platform/nmp-object.h"
 #include "settings/nm-settings.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceWifiP2P
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceWifiP2P);
 
 /*****************************************************************************/
 
diff --git a/src/devices/wifi/nm-device-wifi-p2p.h b/src/core/devices/wifi/nm-device-wifi-p2p.h
index 03e49311..d1aadd8e 100644
--- a/src/devices/wifi/nm-device-wifi-p2p.h
+++ b/src/core/devices/wifi/nm-device-wifi-p2p.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index 980916dd..042d4887 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -1,15 +1,16 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-wifi.h"
 
 #include <netinet/in.h>
 #include <unistd.h>
+#include <linux/if_ether.h>
 
 #include "nm-glib-aux/nm-ref-string.h"
 #include "nm-glib-aux/nm-c-list.h"
@@ -41,8 +42,8 @@
 #include "nm-core-internal.h"
 #include "nm-config.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceWifi
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceWifi);
 
 #define SCAN_INTERVAL_SEC_MIN  3
 #define SCAN_INTERVAL_SEC_STEP 20
@@ -728,6 +729,7 @@ periodic_update(NMDeviceWifi *self)
     NMDeviceWifiPrivate *priv;
     int                  ifindex;
     guint32              new_rate;
+    int                  percent;
 
     if (nm_device_get_state(NM_DEVICE(self)) != NM_DEVICE_STATE_ACTIVATED) {
         /* BSSID and signal strength have meaningful values only if the device
@@ -757,23 +759,22 @@ periodic_update(NMDeviceWifi *self)
     if (ifindex <= 0)
         g_return_if_reached();
 
-    if (priv->current_ap) {
-        int percent;
-
-        percent = nm_platform_wifi_get_quality(nm_device_get_platform(NM_DEVICE(self)), ifindex);
-        if (percent >= 0 && percent <= 100) {
-            if (nm_wifi_ap_set_strength(priv->current_ap, (gint8) percent)) {
+    if (priv->current_ap
+        && nm_platform_wifi_get_station(nm_device_get_platform(NM_DEVICE(self)),
+                                        ifindex,
+                                        NULL,
+                                        &percent,
+                                        &new_rate)) {
+        if (nm_wifi_ap_set_strength(priv->current_ap, (gint8) percent)) {
 #if NM_MORE_LOGGING
-                _ap_dump(self, LOGL_TRACE, priv->current_ap, "updated", 0);
+            _ap_dump(self, LOGL_TRACE, priv->current_ap, "updated", 0);
 #endif
-            }
         }
-    }
 
-    new_rate = nm_platform_wifi_get_rate(nm_device_get_platform(NM_DEVICE(self)), ifindex);
-    if (new_rate != priv->rate) {
-        priv->rate = new_rate;
-        _notify(self, PROP_BITRATE);
+        if (new_rate != priv->rate) {
+            priv->rate = new_rate;
+            _notify(self, PROP_BITRATE);
+        }
     }
 }
 
@@ -1744,6 +1745,23 @@ _scan_kickoff(NMDeviceWifi *self)
         return;
     }
 
+    if (priv->scan_last_complete_msec + 200 > now_msec) {
+        gint32 timeout_msec = priv->scan_last_complete_msec + 200 - now_msec;
+
+        /* after a scan just completed, it is ratelimited for another 200 msec. This is in
+         * addition to our rate limiting above (where scanning can take longer than our rate limit
+         * duration).
+         *
+         * This gives the device a chance to autoconnect. Also, if a scanning just completed,
+         * we want to back off a bit before starting again. */
+        _LOGT_scan("kickoff: don't scan (rate limited for another %d.%03d sec after previous scan)",
+                   timeout_msec / 1000,
+                   timeout_msec % 1000);
+        nm_clear_g_source(&priv->scan_kickoff_timeout_id);
+        priv->scan_kickoff_timeout_id = g_timeout_add(timeout_msec, _scan_kickoff_timeout_cb, self);
+        return;
+    }
+
     if (priv->scan_explicit_requested) {
         if (!priv->scan_explicit_allowed) {
             _LOGT_scan("kickoff: don't scan (explicit scan requested but not allowed)");
@@ -3136,6 +3154,9 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     NMSettingWireless *                 s_wireless;
     GError *                            error = NULL;
     guint                               timeout;
+    NMActRequest *                      request;
+    NMActiveConnection *                master_ac;
+    NMDevice *                          master;
 
     nm_clear_g_source(&priv->sup_timeout_id);
     nm_clear_g_source(&priv->link_timeout_id);
@@ -3209,6 +3230,15 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         goto out_fail;
     }
 
+    /* Tell the supplicant in which bridge the interface is */
+    if ((request = nm_device_get_act_request(device))
+        && (master_ac = nm_active_connection_get_master(NM_ACTIVE_CONNECTION(request)))
+        && (master = nm_active_connection_get_device(master_ac))
+        && nm_device_get_device_type(master) == NM_DEVICE_TYPE_BRIDGE) {
+        nm_supplicant_interface_set_bridge(priv->sup_iface, nm_device_get_iface(master));
+    } else
+        nm_supplicant_interface_set_bridge(priv->sup_iface, NULL);
+
     nm_supplicant_interface_assoc(priv->sup_iface, config, supplicant_iface_assoc_cb, self);
 
     /* Set up a timeout on the association attempt */
@@ -3357,31 +3387,33 @@ activation_success_handler(NMDevice *device)
     g_warn_if_fail(priv->current_ap);
     if (priv->current_ap) {
         if (nm_wifi_ap_get_fake(priv->current_ap)) {
-            gboolean ap_changed = FALSE;
+            gboolean    ap_changed   = FALSE;
+            gboolean    update_bssid = !nm_wifi_ap_get_address(priv->current_ap);
+            gboolean    update_rate  = !nm_wifi_ap_get_max_bitrate(priv->current_ap);
+            NMEtherAddr bssid;
+            guint32     rate;
 
             /* If the activation AP hasn't been seen by the supplicant in a scan
              * yet, it will be "fake".  This usually happens for Ad-Hoc and
              * AP-mode connections.  Fill in the details from the device itself
              * until the supplicant sends the scan result.
              */
-            if (!nm_wifi_ap_get_address(priv->current_ap)) {
-                guint8        bssid[ETH_ALEN] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
-                gs_free char *bssid_str       = NULL;
-
-                if (nm_platform_wifi_get_bssid(nm_device_get_platform(device), ifindex, bssid)
-                    && nm_ethernet_address_is_valid(bssid, ETH_ALEN)) {
-                    bssid_str = nm_utils_hwaddr_ntoa(bssid, ETH_ALEN);
-                    ap_changed |= nm_wifi_ap_set_address(priv->current_ap, bssid_str);
-                }
-            }
             if (!nm_wifi_ap_get_freq(priv->current_ap))
                 ap_changed |= nm_wifi_ap_set_freq(
                     priv->current_ap,
                     nm_platform_wifi_get_frequency(nm_device_get_platform(device), ifindex));
-            if (!nm_wifi_ap_get_max_bitrate(priv->current_ap))
-                ap_changed |= nm_wifi_ap_set_max_bitrate(
-                    priv->current_ap,
-                    nm_platform_wifi_get_rate(nm_device_get_platform(device), ifindex));
+
+            if ((update_bssid || update_rate)
+                && nm_platform_wifi_get_station(nm_device_get_platform(device),
+                                                ifindex,
+                                                update_bssid ? &bssid : NULL,
+                                                NULL,
+                                                update_rate ? &rate : NULL)) {
+                if (update_bssid && nm_ether_addr_is_valid(&bssid))
+                    ap_changed |= nm_wifi_ap_set_address_bin(priv->current_ap, &bssid);
+                if (update_rate)
+                    ap_changed |= nm_wifi_ap_set_max_bitrate(priv->current_ap, rate);
+            }
 
             if (ap_changed)
                 _ap_dump(self, LOGL_DEBUG, priv->current_ap, "updated", 0);
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/core/devices/wifi/nm-device-wifi.h
index 713c1480..d9e9038c 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/core/devices/wifi/nm-device-wifi.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2016 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c
index c7e99e0b..b4b019d3 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/core/devices/wifi/nm-iwd-manager.c
@@ -1,21 +1,21 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Intel Corporation
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-iwd-manager.h"
 
 #include <net/if.h>
 
-#include "nm-logging.h"
 #include "nm-core-internal.h"
 #include "nm-manager.h"
 #include "nm-device-iwd.h"
 #include "nm-wifi-utils.h"
 #include "nm-glib-aux/nm-random-utils.h"
 #include "settings/nm-settings.h"
+#include "nm-std-aux/nm-dbus-compat.h"
 
 /*****************************************************************************/
 
@@ -39,6 +39,7 @@ typedef struct {
     guint               agent_id;
     char *              agent_path;
     GHashTable *        known_networks;
+    NMDeviceIwd *       last_agent_call_device;
 } NMIwdManagerPrivate;
 
 struct _NMIwdManager {
@@ -87,7 +88,8 @@ G_DEFINE_TYPE(NMIwdManager, nm_iwd_manager, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
-static void mirror_8021x_connection_take_and_delete(NMSettingsConnection *sett_conn);
+static void mirror_connection_take_and_delete(NMSettingsConnection *sett_conn,
+                                              KnownNetworkData *    data);
 
 /*****************************************************************************/
 
@@ -117,6 +119,59 @@ get_property_string_or_null(GDBusProxy *proxy, const char *property)
     return get_variant_string_or_null(value);
 }
 
+static gboolean
+get_property_bool(GDBusProxy *proxy, const char *property, gboolean default_val)
+{
+    gs_unref_variant GVariant *value = NULL;
+
+    if (!proxy || !property)
+        return default_val;
+
+    value = g_dbus_proxy_get_cached_property(proxy, property);
+    if (!value || !g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN))
+        return default_val;
+
+    return g_variant_get_boolean(value);
+}
+
+static NMDeviceIwd *
+get_device_from_network(NMIwdManager *self, GDBusProxy *network)
+{
+    NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+    const char *         ifname;
+    const char *         device_path;
+    NMDevice *           device;
+    gs_unref_object GDBusInterface *device_obj = NULL;
+
+    /* Try not to rely on the path of the Device being a prefix of the
+     * Network's object path.
+     */
+
+    device_path = get_property_string_or_null(network, "Device");
+    if (!device_path) {
+        _LOGD("Device not cached for network at %s", g_dbus_proxy_get_object_path(network));
+        return NULL;
+    }
+
+    device_obj = g_dbus_object_manager_get_interface(priv->object_manager,
+                                                     device_path,
+                                                     NM_IWD_DEVICE_INTERFACE);
+
+    ifname = get_property_string_or_null(G_DBUS_PROXY(device_obj), "Name");
+    if (!ifname) {
+        _LOGD("Name not cached for device at %s", device_path);
+        return NULL;
+    }
+
+    device = nm_manager_get_device(priv->manager, ifname, NM_DEVICE_TYPE_WIFI);
+    if (!device || !NM_IS_DEVICE_IWD(device)) {
+        _LOGD("NM device %s is not an IWD-managed device", ifname);
+        return NULL;
+    }
+
+    return NM_DEVICE_IWD(device);
+}
+
 static void
 agent_dbus_method_cb(GDBusConnection *      connection,
                      const char *           sender,
@@ -129,12 +184,10 @@ agent_dbus_method_cb(GDBusConnection *      connection,
 {
     NMIwdManager *       self = user_data;
     NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    const char *         network_path, *device_path, *ifname;
-    gs_unref_object GDBusInterface *network = NULL, *device_obj = NULL;
-    int                             ifindex;
-    NMDevice *                      device;
-    gs_free char *                  name_owner = NULL;
-    int                             errsv;
+    const char *         network_path;
+    NMDeviceIwd *        device;
+    gs_free char *       name_owner         = NULL;
+    gs_unref_object GDBusInterface *network = NULL;
 
     /* Be paranoid and check the sender address */
     name_owner = g_dbus_object_manager_client_get_name_owner(
@@ -142,6 +195,25 @@ agent_dbus_method_cb(GDBusConnection *      connection,
     if (!nm_streq0(name_owner, sender))
         goto return_error;
 
+    if (!strcmp(method_name, "Cancel")) {
+        const char *reason = NULL;
+
+        g_variant_get(parameters, "(&s)", &reason);
+        _LOGD("agent-request: Cancel reason: %s", reason);
+
+        if (!priv->last_agent_call_device)
+            goto return_error;
+
+        if (nm_device_iwd_agent_query(priv->last_agent_call_device, NULL)) {
+            priv->last_agent_call_device = NULL;
+            g_dbus_method_invocation_return_value(invocation, NULL);
+            return;
+        }
+
+        priv->last_agent_call_device = NULL;
+        goto return_error;
+    }
+
     if (!strcmp(method_name, "RequestUserPassword"))
         g_variant_get(parameters, "(&os)", &network_path, NULL);
     else
@@ -151,47 +223,23 @@ agent_dbus_method_cb(GDBusConnection *      connection,
                                                   network_path,
                                                   NM_IWD_NETWORK_INTERFACE);
     if (!network) {
-        _LOGE("unable to find the network object");
-        return;
-    }
-
-    device_path = get_property_string_or_null(G_DBUS_PROXY(network), "Device");
-    if (!device_path) {
-        _LOGD("agent-request: device not cached for network %s in IWD Agent request", network_path);
+        _LOGE("agent-request: unable to find the network object");
         goto return_error;
     }
 
-    device_obj = g_dbus_object_manager_get_interface(priv->object_manager,
-                                                     device_path,
-                                                     NM_IWD_DEVICE_INTERFACE);
-
-    ifname = get_property_string_or_null(G_DBUS_PROXY(device_obj), "Name");
-    if (!ifname) {
-        _LOGD("agent-request: name not cached for device %s in IWD Agent request", device_path);
-        goto return_error;
-    }
-
-    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,
-              errsv);
-        goto return_error;
-    }
-
-    device = nm_manager_get_device_by_ifindex(priv->manager, ifindex);
-    if (!NM_IS_DEVICE_IWD(device)) {
-        _LOGD("agent-request: IWD device named %s is not a Wifi device in IWD Agent request",
-              ifname);
+    device = get_device_from_network(self, G_DBUS_PROXY(network));
+    if (!device) {
+        _LOGD("agent-request: device not found in IWD Agent request");
         goto return_error;
     }
 
-    if (nm_device_iwd_agent_query(NM_DEVICE_IWD(device), invocation))
+    if (nm_device_iwd_agent_query(device, invocation)) {
+        priv->last_agent_call_device = device;
         return;
+    }
 
-    _LOGD("agent-request: device %s did not handle the IWD Agent request", ifname);
+    _LOGD("agent-request: device %s did not handle the IWD Agent request",
+          nm_device_get_iface(NM_DEVICE(device)));
 
 return_error:
     /* IWD doesn't look at the specific error */
@@ -219,10 +267,12 @@ static const GDBusInterfaceInfo iwd_agent_iface_info = NM_DEFINE_GDBUS_INTERFACE
                                                   NM_DEFINE_GDBUS_ARG_INFO("password", "s"), ), ),
         NM_DEFINE_GDBUS_METHOD_INFO(
             "RequestUserPassword",
-            .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("network", "o"),
+            .in_args  = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("network", "o"),
                                                  NM_DEFINE_GDBUS_ARG_INFO("user", "s"), ),
-            .out_args =
-                NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("password", "s"), ), ), ), );
+            .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("password", "s"), ), ),
+        NM_DEFINE_GDBUS_METHOD_INFO("Cancel",
+                                    .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
+                                        NM_DEFINE_GDBUS_ARG_INFO("reason", "s"), ), ), ), );
 
 static guint
 iwd_agent_export(GDBusConnection *connection, gpointer user_data, char **agent_path, GError **error)
@@ -259,17 +309,8 @@ register_agent(NMIwdManager *self)
     GDBusInterface *     agent_manager;
 
     agent_manager = g_dbus_object_manager_get_interface(priv->object_manager,
-                                                        "/net/connman/iwd",
+                                                        "/net/connman/iwd", /* IWD 1.0+ */
                                                         NM_IWD_AGENT_MANAGER_INTERFACE);
-
-    if (!agent_manager) {
-        /* IWD prior to 1.0 dated 30 October, 2019 has the agent manager on a
-         * different path. */
-        agent_manager = g_dbus_object_manager_get_interface(priv->object_manager,
-                                                            "/",
-                                                            NM_IWD_AGENT_MANAGER_INTERFACE);
-    }
-
     if (!agent_manager) {
         _LOGE("unable to register the IWD Agent: PSK/8021x Wi-Fi networks may not work");
         return;
@@ -328,7 +369,7 @@ known_network_data_free(KnownNetworkData *network)
         return;
 
     g_object_unref(network->known_network);
-    mirror_8021x_connection_take_and_delete(network->mirror_connection);
+    mirror_connection_take_and_delete(network->mirror_connection, network);
     g_slice_free(KnownNetworkData, network);
 }
 
@@ -369,14 +410,72 @@ set_device_dbus_object(NMIwdManager *self, GDBusProxy *proxy, GDBusObject *objec
     nm_device_iwd_set_dbus_object(NM_DEVICE_IWD(device), object);
 }
 
-/* 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 void
+known_network_update_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+    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_log_warn(LOGD_WIFI,
+                    "Updating %s on IWD known network %s failed: %s",
+                    (const char *) user_data,
+                    g_dbus_proxy_get_object_path(G_DBUS_PROXY(source)),
+                    error->message);
+    }
+}
+
+static void
+sett_conn_changed(NMSettingsConnection *sett_conn, guint update_reason, KnownNetworkData *data)
+{
+    NMSettingsConnectionIntFlags flags;
+    NMConnection *               conn   = nm_settings_connection_get_connection(sett_conn);
+    NMSettingConnection *        s_conn = nm_connection_get_setting_connection(conn);
+    gboolean                     nm_autoconnectable = nm_setting_connection_get_autoconnect(s_conn);
+    gboolean iwd_autoconnectable = get_property_bool(data->known_network, "AutoConnect", TRUE);
+
+    nm_assert(sett_conn == data->mirror_connection);
+
+    if (iwd_autoconnectable == nm_autoconnectable)
+        return;
+
+    /* If this is a generated connection it may be ourselves updating it */
+    flags = nm_settings_connection_get_flags(data->mirror_connection);
+    if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
+        return;
+
+    nm_log_dbg(LOGD_WIFI,
+               "Updating AutoConnect on known network at %s based on connection %s",
+               g_dbus_proxy_get_object_path(data->known_network),
+               nm_settings_connection_get_id(data->mirror_connection));
+    g_dbus_proxy_call(data->known_network,
+                      DBUS_INTERFACE_PROPERTIES ".Set",
+                      g_variant_new("(ssv)",
+                                    NM_IWD_KNOWN_NETWORK_INTERFACE,
+                                    "AutoConnect",
+                                    g_variant_new_boolean(nm_autoconnectable)),
+                      G_DBUS_CALL_FLAGS_NONE,
+                      -1,
+                      NULL,
+                      known_network_update_cb,
+                      "AutoConnect");
+}
+
+/* Look up an existing NMSettingsConnection for a network that has been
+ * preprovisioned with an IWD config file or has been connected to before,
+ * or create a new in-memory NMSettingsConnection object.  This will let
+ * users control the few supported properties (mainly make it
+ * IWD-autoconnectable or not), remove/forget the network, or, for a
+ * WPA2-Enterprise type network it will inform the NM autoconnect mechanism
+ * and the clients that this networks needs no additional EAP configuration
+ * from the user.
  */
 static NMSettingsConnection *
-mirror_8021x_connection(NMIwdManager *self, const char *name, gboolean create_new)
+mirror_connection(NMIwdManager *        self,
+                  const KnownNetworkId *id,
+                  gboolean              create_new,
+                  GDBusProxy *          known_network)
 {
     NMIwdManagerPrivate *        priv = NM_IWD_MANAGER_GET_PRIVATE(self);
     NMSettingsConnection *const *iter;
@@ -384,43 +483,126 @@ mirror_8021x_connection(NMIwdManager *self, const char *name, gboolean create_ne
     NMSettingsConnection *        settings_connection = NULL;
     char                          uuid[37];
     NMSetting *                   setting;
-    GError *                      error = NULL;
-    gs_unref_bytes GBytes *new_ssid     = NULL;
+    gs_free_error GError *error            = NULL;
+    gs_unref_bytes GBytes *new_ssid        = NULL;
+    gsize                  ssid_len        = strlen(id->name);
+    gboolean               autoconnectable = TRUE;
+    gboolean               hidden          = FALSE;
+    gboolean               exact_match     = TRUE;
+    const char *           key_mgmt        = NULL;
+
+    if (known_network) {
+        autoconnectable = get_property_bool(known_network, "AutoConnect", TRUE);
+        hidden          = get_property_bool(known_network, "Hidden", FALSE);
+    }
 
     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;
+        const guint8 *        ssid_bytes;
+        gsize                 ssid_len2;
+
+        if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &security))
+            continue;
 
-        security = nm_wifi_connection_get_iwd_security(conn, NULL);
-        if (security != NM_IWD_NETWORK_SECURITY_8021X)
+        if (security != id->security)
             continue;
 
         s_wifi = nm_connection_get_setting_wireless(conn);
         if (!s_wifi)
             continue;
 
-        ssid_name = _nm_utils_ssid_to_utf8(nm_setting_wireless_get_ssid(s_wifi));
-
-        if (!nm_streq(ssid_name, name))
+        /* The SSID must be UTF-8 if it matches since id->name is known to be
+         * valid UTF-8, so just memcmp them.
+         */
+        ssid_bytes = g_bytes_get_data(nm_setting_wireless_get_ssid(s_wifi), &ssid_len2);
+        if (!ssid_bytes || ssid_len2 != ssid_len || memcmp(ssid_bytes, id->name, ssid_len))
             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;
+        exact_match = TRUE;
+
+        if (known_network) {
+            NMSettingConnection *s_conn = nm_connection_get_setting_connection(conn);
+
+            if (nm_setting_connection_get_autoconnect(s_conn) != autoconnectable
+                || nm_setting_wireless_get_hidden(s_wifi) != hidden)
+                exact_match = FALSE;
+        }
+
+        switch (id->security) {
+        case NM_IWD_NETWORK_SECURITY_WEP:
+        case NM_IWD_NETWORK_SECURITY_OPEN:
+        case NM_IWD_NETWORK_SECURITY_PSK:
+            break;
+        case NM_IWD_NETWORK_SECURITY_8021X:
+        {
+            NMSetting8021x *s_8021x  = nm_connection_get_setting_802_1x(conn);
+            gboolean        external = FALSE;
+            guint           i;
+
+            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 with EAP method "external" */
+            if (!external)
+                exact_match = FALSE;
+        }
         }
 
-        /* Prefer returning connections for EAP method "external" */
-        if (!settings_connection || external)
+        if (!settings_connection || exact_match)
             settings_connection = sett_conn;
+
+        if (exact_match)
+            break;
+    }
+
+    if (settings_connection && known_network && !exact_match) {
+        NMSettingsConnectionIntFlags flags = nm_settings_connection_get_flags(settings_connection);
+
+        /* If we found a connection and it's generated (likely by ourselves)
+         * it may have been created on a request by
+         * nm_iwd_manager_get_ap_mirror_connection() when no Known Network
+         * was available so we didn't have access to its properties other
+         * than Name and Security.  Copy their values to the generated
+         * NMConnection.
+         * TODO: avoid notify signals triggering our own watch.
+         *
+         * If on the other hand this is a user-created NMConnection we
+         * should try to copy the properties from it to IWD's Known Network
+         * using the Properties DBus interface in case the user created an
+         * NM connection before IWD appeared on the bus, or before IWD
+         * created its Known Network object.
+         */
+        if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+            NMConnection *tmp_conn = nm_settings_connection_get_connection(settings_connection);
+            NMSettingConnection *s_conn = nm_connection_get_setting_connection(tmp_conn);
+            NMSettingWireless *  s_wifi = nm_connection_get_setting_wireless(tmp_conn);
+
+            g_object_set(G_OBJECT(s_conn),
+                         NM_SETTING_CONNECTION_AUTOCONNECT,
+                         autoconnectable,
+                         NULL);
+            g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_HIDDEN, hidden, NULL);
+        } else {
+            KnownNetworkData data = {known_network, settings_connection};
+            sett_conn_changed(settings_connection, 0, &data);
+        }
+    }
+
+    if (settings_connection && known_network) {
+        /* Reset NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL now that the
+         * connection is going to be referenced by a known network, we don't
+         * want it to be deleted when activation fails anymore.
+         */
+        nm_settings_connection_set_flags_full(settings_connection,
+                                              NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,
+                                              0);
     }
 
     /* If we already have an NMSettingsConnection matching this
@@ -433,62 +615,86 @@ mirror_8021x_connection(NMIwdManager *self, const char *name, gboolean create_ne
 
     connection = nm_simple_connection_new();
 
-    setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_CONNECTION,
-                                      NM_SETTING_CONNECTION_TYPE,
-                                      NM_SETTING_WIRELESS_SETTING_NAME,
-                                      NM_SETTING_CONNECTION_ID,
-                                      name,
-                                      NM_SETTING_CONNECTION_UUID,
-                                      nm_utils_uuid_generate_buf(uuid),
-                                      NULL));
+    setting = g_object_new(NM_TYPE_SETTING_CONNECTION,
+                           NM_SETTING_CONNECTION_TYPE,
+                           NM_SETTING_WIRELESS_SETTING_NAME,
+                           NM_SETTING_CONNECTION_ID,
+                           id->name,
+                           NM_SETTING_CONNECTION_UUID,
+                           nm_utils_uuid_generate_buf(uuid),
+                           NM_SETTING_CONNECTION_AUTOCONNECT,
+                           autoconnectable,
+                           NULL);
     nm_connection_add_setting(connection, setting);
 
-    new_ssid = g_bytes_new(name, strlen(name));
-    setting  = NM_SETTING(g_object_new(NM_TYPE_SETTING_WIRELESS,
-                                      NM_SETTING_WIRELESS_SSID,
-                                      new_ssid,
-                                      NM_SETTING_WIRELESS_MODE,
-                                      NM_SETTING_WIRELESS_MODE_INFRA,
-                                      NULL));
+    new_ssid = g_bytes_new(id->name, ssid_len);
+    setting  = g_object_new(NM_TYPE_SETTING_WIRELESS,
+                           NM_SETTING_WIRELESS_SSID,
+                           new_ssid,
+                           NM_SETTING_WIRELESS_MODE,
+                           NM_SETTING_WIRELESS_MODE_INFRA,
+                           NM_SETTING_WIRELESS_HIDDEN,
+                           hidden,
+                           NULL);
     nm_connection_add_setting(connection, setting);
 
-    setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_WIRELESS_SECURITY,
-                                      NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
-                                      "open",
-                                      NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
-                                      "wpa-eap",
-                                      NULL));
-    nm_connection_add_setting(connection, setting);
+    switch (id->security) {
+    case NM_IWD_NETWORK_SECURITY_WEP:
+        key_mgmt = "none";
+        break;
+    case NM_IWD_NETWORK_SECURITY_OPEN:
+        key_mgmt = NULL;
+        break;
+    case NM_IWD_NETWORK_SECURITY_PSK:
+        key_mgmt = "wpa-psk";
+        break;
+    case NM_IWD_NETWORK_SECURITY_8021X:
+        key_mgmt = "wpa-eap";
+        break;
+    }
 
-    /* "password" and "private-key-password" may be requested by the IWD agent
-     * from NM and IWD will implement a specific secret cache policy so by
-     * default respect that policy and don't save copies of those secrets in
-     * NM settings.  The saved values can not be used anyway because of our
-     * use of NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW.
-     */
-    setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_802_1X,
-                                      NM_SETTING_802_1X_PASSWORD_FLAGS,
-                                      NM_SETTING_SECRET_FLAG_NOT_SAVED,
-                                      NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS,
-                                      NM_SETTING_SECRET_FLAG_NOT_SAVED,
-                                      NULL));
-    nm_setting_802_1x_add_eap_method(NM_SETTING_802_1X(setting), "external");
-    nm_connection_add_setting(connection, setting);
+    if (key_mgmt) {
+        setting = g_object_new(NM_TYPE_SETTING_WIRELESS_SECURITY,
+                               NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                               "open",
+                               NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                               key_mgmt,
+                               NULL);
+        nm_connection_add_setting(connection, setting);
+    }
+
+    if (id->security == NM_IWD_NETWORK_SECURITY_8021X) {
+        /* "password" and "private-key-password" may be requested by the IWD agent
+         * from NM and IWD will implement a specific secret cache policy so by
+         * default respect that policy and don't save copies of those secrets in
+         * NM settings.  The saved values can not be used anyway because of our
+         * use of NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW.
+         */
+        setting = g_object_new(NM_TYPE_SETTING_802_1X,
+                               NM_SETTING_802_1X_PASSWORD_FLAGS,
+                               NM_SETTING_SECRET_FLAG_NOT_SAVED,
+                               NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS,
+                               NM_SETTING_SECRET_FLAG_NOT_SAVED,
+                               NULL);
+        nm_setting_802_1x_add_eap_method(NM_SETTING_802_1X(setting), "external");
+        nm_connection_add_setting(connection, setting);
+    }
 
     if (!nm_connection_normalize(connection, NULL, NULL, NULL))
         return NULL;
 
-    if (!nm_settings_add_connection(priv->settings,
-                                    connection,
-                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
-                                    NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
-                                    NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
-                                    &settings_connection,
-                                    &error)) {
+    if (!nm_settings_add_connection(
+            priv->settings,
+            connection,
+            NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+            NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+                | (known_network ? 0 : NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL),
+            &settings_connection,
+            &error)) {
         _LOGW("failed to add a mirror NMConnection for IWD's Known Network '%s': %s",
-              name,
+              id->name,
               error->message);
-        g_error_free(error);
         return NULL;
     }
 
@@ -496,7 +702,7 @@ mirror_8021x_connection(NMIwdManager *self, const char *name, gboolean create_ne
 }
 
 static void
-mirror_8021x_connection_take_and_delete(NMSettingsConnection *sett_conn)
+mirror_connection_take_and_delete(NMSettingsConnection *sett_conn, KnownNetworkData *data)
 {
     NMSettingsConnectionIntFlags flags;
 
@@ -510,6 +716,7 @@ mirror_8021x_connection_take_and_delete(NMSettingsConnection *sett_conn)
     if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
         nm_settings_connection_delete(sett_conn, FALSE);
 
+    g_signal_handlers_disconnect_by_data(sett_conn, data);
     g_object_unref(sett_conn);
 }
 
@@ -539,6 +746,7 @@ interface_added(GDBusObjectManager *object_manager,
 
     if (nm_streq(iface_name, NM_IWD_KNOWN_NETWORK_INTERFACE)) {
         KnownNetworkId *      id;
+        KnownNetworkId *      orig_id;
         KnownNetworkData *    data;
         NMIwdNetworkSecurity  security;
         const char *          type_str, *name;
@@ -550,7 +758,7 @@ interface_added(GDBusObjectManager *object_manager,
             return;
 
         if (nm_streq(type_str, "open"))
-            security = NM_IWD_NETWORK_SECURITY_NONE;
+            security = NM_IWD_NETWORK_SECURITY_OPEN;
         else if (nm_streq(type_str, "psk"))
             security = NM_IWD_NETWORK_SECURITY_PSK;
         else if (nm_streq(type_str, "8021x"))
@@ -560,28 +768,42 @@ interface_added(GDBusObjectManager *object_manager,
 
         id = known_network_id_new(name, security);
 
-        data = g_hash_table_lookup(priv->known_networks, id);
-        if (data) {
+        if (g_hash_table_lookup_extended(priv->known_networks,
+                                         id,
+                                         (void **) &orig_id,
+                                         (void **) &data)) {
             _LOGW("DBus error: KnownNetwork already exists ('%s', %s)", name, type_str);
-            g_free(id);
             nm_g_object_ref_set(&data->known_network, proxy);
+            g_free(id);
+            id = orig_id;
         } else {
             data                = g_slice_new0(KnownNetworkData);
             data->known_network = g_object_ref(proxy);
             g_hash_table_insert(priv->known_networks, id, data);
         }
 
-        if (security == NM_IWD_NETWORK_SECURITY_8021X) {
-            sett_conn = mirror_8021x_connection(self, name, TRUE);
+        sett_conn = mirror_connection(self, id, TRUE, proxy);
 
-            if (sett_conn && sett_conn != data->mirror_connection) {
-                NMSettingsConnection *sett_conn_old = data->mirror_connection;
+        if (sett_conn && sett_conn != data->mirror_connection) {
+            NMSettingsConnection *sett_conn_old = data->mirror_connection;
 
-                data->mirror_connection = nm_g_object_ref(sett_conn);
-                mirror_8021x_connection_take_and_delete(sett_conn_old);
-            }
-        } else
-            mirror_8021x_connection_take_and_delete(g_steal_pointer(&data->mirror_connection));
+            data->mirror_connection = nm_g_object_ref(sett_conn);
+            mirror_connection_take_and_delete(sett_conn_old, data);
+
+            g_signal_connect(sett_conn,
+                             NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
+                             G_CALLBACK(sett_conn_changed),
+                             data);
+        }
+
+        return;
+    }
+
+    if (nm_streq(iface_name, NM_IWD_NETWORK_INTERFACE)) {
+        NMDeviceIwd *device = get_device_from_network(self, proxy);
+
+        if (device)
+            nm_device_iwd_network_add_remove(device, proxy, TRUE);
 
         return;
     }
@@ -618,7 +840,7 @@ interface_removed(GDBusObjectManager *object_manager,
             return;
 
         if (nm_streq(type_str, "open"))
-            id.security = NM_IWD_NETWORK_SECURITY_NONE;
+            id.security = NM_IWD_NETWORK_SECURITY_OPEN;
         else if (nm_streq(type_str, "psk"))
             id.security = NM_IWD_NETWORK_SECURITY_PSK;
         else if (nm_streq(type_str, "8021x"))
@@ -629,6 +851,15 @@ interface_removed(GDBusObjectManager *object_manager,
         g_hash_table_remove(priv->known_networks, &id);
         return;
     }
+
+    if (nm_streq(iface_name, NM_IWD_NETWORK_INTERFACE)) {
+        NMDeviceIwd *device = get_device_from_network(self, proxy);
+
+        if (device)
+            nm_device_iwd_network_add_remove(device, proxy, FALSE);
+
+        return;
+    }
 }
 
 static void
@@ -666,42 +897,47 @@ object_removed(GDBusObjectManager *object_manager, GDBusObject *object, gpointer
 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;
-    gs_free char *       ssid_str = NULL;
-
-    id.security = nm_wifi_connection_get_iwd_security(conn, &mapped);
-    if (!mapped)
+    NMIwdManager *        self = user_data;
+    NMIwdManagerPrivate * priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+    NMConnection *        conn = nm_settings_connection_get_connection(sett_conn);
+    NMSettingWireless *   s_wireless;
+    KnownNetworkData *    data;
+    KnownNetworkId        id;
+    char                  ssid_buf[33];
+    const guint8 *        ssid_bytes;
+    gsize                 ssid_len;
+    NMSettingsConnection *new_mirror_conn;
+
+    if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &id.security))
         return;
 
     s_wireless = nm_connection_get_setting_wireless(conn);
-    ssid_str   = _nm_utils_ssid_to_utf8(nm_setting_wireless_get_ssid(s_wireless));
-    id.name    = ssid_str;
-    data       = g_hash_table_lookup(priv->known_networks, &id);
-    if (!data)
+    if (!s_wireless)
         return;
 
-    if (id.security == NM_IWD_NETWORK_SECURITY_8021X) {
-        NMSettingsConnection *new_mirror_conn;
+    ssid_bytes = g_bytes_get_data(nm_setting_wireless_get_ssid(s_wireless), &ssid_len);
+    if (!ssid_bytes || ssid_len > 32 || memchr(ssid_bytes, 0, ssid_len))
+        return;
 
-        if (data->mirror_connection != sett_conn)
-            return;
+    memcpy(ssid_buf, ssid_bytes, ssid_len);
+    ssid_buf[ssid_len] = '\0';
+    id.name            = ssid_buf;
+    data               = g_hash_table_lookup(priv->known_networks, &id);
+    if (!data)
+        return;
 
-        g_clear_object(&data->mirror_connection);
+    if (data->mirror_connection != sett_conn)
+        return;
 
-        /* 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;
-        }
+    g_clear_object(&data->mirror_connection);
+
+    /* Don't call Forget on the Known Network until there's no longer *any*
+     * matching NMSettingsConnection (debatable)
+     */
+    new_mirror_conn = mirror_connection(self, &id, FALSE, NULL);
+    if (new_mirror_conn) {
+        data->mirror_connection = g_object_ref(new_mirror_conn);
+        return;
     }
 
     if (!priv->running)
@@ -801,7 +1037,28 @@ device_added(NMManager *manager, NMDevice *device, gpointer user_data)
     if (!priv->running)
         return;
 
+    /* Here we handle a potential scenario where IWD's DBus objects for the
+     * new device popped up before the NMDevice.  The
+     * interface_added/object_added signals have been received already and
+     * the handlers couldn't do much because the NMDevice wasn't there yet
+     * so now we go over the Network and Device interfaces again.  In this
+     * exact order for "object path" property consistency -- see reasoning
+     * in object_compare_interfaces.
+     */
     objects = g_dbus_object_manager_get_objects(priv->object_manager);
+
+    for (iter = objects; iter; iter = iter->next) {
+        GDBusObject *   object                    = G_DBUS_OBJECT(iter->data);
+        gs_unref_object GDBusInterface *interface = NULL;
+
+        interface = g_dbus_object_get_interface(object, NM_IWD_NETWORK_INTERFACE);
+        if (!interface)
+            continue;
+
+        if (NM_DEVICE_IWD(device) == get_device_from_network(self, (GDBusProxy *) interface))
+            nm_device_iwd_network_add_remove(NM_DEVICE_IWD(device), (GDBusProxy *) interface, TRUE);
+    }
+
     for (iter = objects; iter; iter = iter->next) {
         GDBusObject *   object                    = G_DBUS_OBJECT(iter->data);
         gs_unref_object GDBusInterface *interface = NULL;
@@ -821,6 +1078,73 @@ device_added(NMManager *manager, NMDevice *device, gpointer user_data)
 }
 
 static void
+device_removed(NMManager *manager, NMDevice *device, gpointer user_data)
+{
+    NMIwdManager *       self = user_data;
+    NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+
+    if (!NM_IS_DEVICE_IWD(device))
+        return;
+
+    if (priv->last_agent_call_device == NM_DEVICE_IWD(device))
+        priv->last_agent_call_device = NULL;
+}
+
+/* This is used to sort the list of objects returned by GetManagedObjects()
+ * based on the DBus interfaces available on these objects in such a way that
+ * the interface_added calls happen in the right order.  The order is defined
+ * by how some DBus interfaces point to interfaces on other objects using
+ * DBus properties of the type "object path" ("o" signature).  This creates
+ * "dependencies" between objects.
+ *
+ * When NM and IWD are running, the InterfacesAdded signals should come in
+ * an order that ensures consistency of those object paths.  For example
+ * when a Network interface is added with a KnownNetwork property, or that
+ * property is assigned a new value, the KnownNetwork object pointed to by
+ * it will have been added in an earlier InterfacesAdded signal.  Similarly
+ * Station.ConnectedNetwork and Station.GetOrdereNetworks() only point to
+ * existing Network objects.  (There may be circular dependencies but during
+ * initialization we only need a subset of those properties that doesn't
+ * have this problem.)
+ *
+ * But GetManagedObjects doesn't guarantee this kind of consistency so we
+ * order the returned object list ourselves to simplify the job of
+ * interface_added().  Objects that don't have any interfaces listed in
+ * interface_order are moved to the end of the list.
+ */
+static int
+object_compare_interfaces(gconstpointer a, gconstpointer b)
+{
+    static const char *interface_order[] = {
+        NM_IWD_KNOWN_NETWORK_INTERFACE,
+        NM_IWD_NETWORK_INTERFACE,
+        NM_IWD_DEVICE_INTERFACE,
+    };
+    int   rank_a = G_N_ELEMENTS(interface_order);
+    int   rank_b = G_N_ELEMENTS(interface_order);
+    guint pos;
+
+    for (pos = 0; interface_order[pos]; pos++) {
+        GDBusInterface *iface_a;
+        GDBusInterface *iface_b;
+
+        if (rank_a == G_N_ELEMENTS(interface_order)
+            && (iface_a = g_dbus_object_get_interface(G_DBUS_OBJECT(a), interface_order[pos]))) {
+            rank_a = pos;
+            g_object_unref(iface_a);
+        }
+
+        if (rank_b == G_N_ELEMENTS(interface_order)
+            && (iface_b = g_dbus_object_get_interface(G_DBUS_OBJECT(b), interface_order[pos]))) {
+            rank_b = pos;
+            g_object_unref(iface_b);
+        }
+    }
+
+    return rank_a - rank_b;
+}
+
+static void
 got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data)
 {
     NMIwdManager *       self  = user_data;
@@ -875,6 +1199,7 @@ got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data)
         g_hash_table_remove_all(priv->known_networks);
 
         objects = g_dbus_object_manager_get_objects(object_manager);
+        objects = g_list_sort(objects, object_compare_interfaces);
         for (iter = objects; iter; iter = iter->next)
             object_added(NULL, G_DBUS_OBJECT(iter->data), self);
 
@@ -911,6 +1236,47 @@ nm_iwd_manager_is_known_network(NMIwdManager *self, const char *name, NMIwdNetwo
     return g_hash_table_contains(priv->known_networks, &kn_id);
 }
 
+NMSettingsConnection *
+nm_iwd_manager_get_ap_mirror_connection(NMIwdManager *self, NMWifiAP *ap)
+{
+    NMIwdManagerPrivate *  priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+    KnownNetworkData *     data;
+    char                   name_buf[33];
+    KnownNetworkId         kn_id = {name_buf, NM_IWD_NETWORK_SECURITY_OPEN};
+    const guint8 *         ssid_bytes;
+    gsize                  ssid_len;
+    NM80211ApFlags         flags     = nm_wifi_ap_get_flags(ap);
+    NM80211ApSecurityFlags sec_flags = nm_wifi_ap_get_wpa_flags(ap) | nm_wifi_ap_get_rsn_flags(ap);
+
+    ssid_bytes = g_bytes_get_data(nm_wifi_ap_get_ssid(ap), &ssid_len);
+    ssid_len   = MIN(ssid_len, 32);
+    memcpy(name_buf, ssid_bytes, ssid_len);
+    name_buf[ssid_len] = '\0';
+
+    if (flags & NM_802_11_AP_FLAGS_PRIVACY)
+        kn_id.security = NM_IWD_NETWORK_SECURITY_WEP;
+
+    if (sec_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
+        kn_id.security = NM_IWD_NETWORK_SECURITY_PSK;
+    else if (sec_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)
+        kn_id.security = NM_IWD_NETWORK_SECURITY_8021X;
+
+    /* Right now it's easier for us to do a name+security lookup than to use
+     * the Network.KnownNetwork property to look up by path.
+     */
+    data = g_hash_table_lookup(priv->known_networks, &kn_id);
+    if (data)
+        return data->mirror_connection;
+
+    /* We have no KnownNetwork for this AP, we're probably connecting to it for
+     * the first time.  This is not a usual/supported scenario so we don't need
+     * to bother too much about creating a great mirror connection, we don't
+     * even have any more information than the Name & Type properties on the
+     * Network interface.  This *should* never happen for an 8021x type network.
+     */
+    return mirror_connection(self, &kn_id, TRUE, NULL);
+}
+
 GDBusProxy *
 nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const char *name)
 {
@@ -936,6 +1302,7 @@ nm_iwd_manager_init(NMIwdManager *self)
 
     priv->manager = g_object_ref(NM_MANAGER_GET);
     g_signal_connect(priv->manager, NM_MANAGER_DEVICE_ADDED, G_CALLBACK(device_added), self);
+    g_signal_connect(priv->manager, NM_MANAGER_DEVICE_REMOVED, G_CALLBACK(device_removed), self);
 
     priv->settings = g_object_ref(NM_SETTINGS_GET);
     g_signal_connect(priv->settings,
@@ -978,6 +1345,8 @@ dispose(GObject *object)
         g_clear_object(&priv->manager);
     }
 
+    priv->last_agent_call_device = NULL;
+
     G_OBJECT_CLASS(nm_iwd_manager_parent_class)->dispose(object);
 }
 
diff --git a/src/devices/wifi/nm-iwd-manager.h b/src/core/devices/wifi/nm-iwd-manager.h
index 00f0c304..466f67c7 100644
--- a/src/devices/wifi/nm-iwd-manager.h
+++ b/src/core/devices/wifi/nm-iwd-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Intel Corporation
  */
@@ -8,6 +8,7 @@
 
 #include "devices/nm-device.h"
 #include "nm-wifi-utils.h"
+#include "nm-wifi-ap.h"
 
 #define NM_IWD_BUS_TYPE G_BUS_TYPE_SYSTEM
 #define NM_IWD_SERVICE  "net.connman.iwd"
@@ -44,6 +45,8 @@ gboolean nm_iwd_manager_is_known_network(NMIwdManager *       self,
                                          const char *         name,
                                          NMIwdNetworkSecurity security);
 
+NMSettingsConnection *nm_iwd_manager_get_ap_mirror_connection(NMIwdManager *self, NMWifiAP *ap);
+
 GDBusProxy *
 nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const char *name);
 
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/core/devices/wifi/nm-wifi-ap.c
index 55438a53..08fa10ec 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/core/devices/wifi/nm-wifi-ap.c
@@ -1,14 +1,15 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-wifi-ap.h"
 
 #include <stdlib.h>
+#include <linux/if_ether.h>
 
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device.h"
@@ -83,29 +84,6 @@ nm_wifi_ap_get_ssid(const NMWifiAP *ap)
 }
 
 gboolean
-nm_wifi_ap_set_ssid_arr(NMWifiAP *ap, const guint8 *ssid, gsize ssid_len)
-{
-    NMWifiAPPrivate *priv;
-
-    g_return_val_if_fail(NM_IS_WIFI_AP(ap), FALSE);
-
-    if (ssid_len > 32)
-        g_return_val_if_reached(FALSE);
-
-    priv = NM_WIFI_AP_GET_PRIVATE(ap);
-
-    if (nm_utils_gbytes_equal_mem(priv->ssid, ssid, ssid_len))
-        return FALSE;
-
-    nm_clear_pointer(&priv->ssid, g_bytes_unref);
-    if (ssid_len > 0)
-        priv->ssid = g_bytes_new(ssid, ssid_len);
-
-    _notify(ap, PROP_SSID);
-    return TRUE;
-}
-
-gboolean
 nm_wifi_ap_set_ssid(NMWifiAP *ap, GBytes *ssid)
 {
     NMWifiAPPrivate *priv;
@@ -185,11 +163,13 @@ nm_wifi_ap_get_address(const NMWifiAP *ap)
     return NM_WIFI_AP_GET_PRIVATE(ap)->address;
 }
 
-static gboolean
-nm_wifi_ap_set_address_bin(NMWifiAP *ap, const guint8 addr[static 6 /* ETH_ALEN */])
+gboolean
+nm_wifi_ap_set_address_bin(NMWifiAP *ap, const NMEtherAddr *addr)
 {
     NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(ap);
 
+    nm_assert(addr);
+
     if (!priv->address || !nm_utils_hwaddr_matches(addr, ETH_ALEN, priv->address, -1)) {
         g_free(priv->address);
         priv->address = nm_utils_hwaddr_ntoa(addr, ETH_ALEN);
@@ -202,13 +182,13 @@ nm_wifi_ap_set_address_bin(NMWifiAP *ap, const guint8 addr[static 6 /* ETH_ALEN
 gboolean
 nm_wifi_ap_set_address(NMWifiAP *ap, const char *addr)
 {
-    guint8 addr_buf[ETH_ALEN];
+    NMEtherAddr addr_buf;
 
     g_return_val_if_fail(NM_IS_WIFI_AP(ap), FALSE);
-    if (!addr || !nm_utils_hwaddr_aton(addr, addr_buf, sizeof(addr_buf)))
+    if (!addr || !nm_utils_hwaddr_aton(addr, &addr_buf, sizeof(addr_buf)))
         g_return_val_if_reached(FALSE);
 
-    return nm_wifi_ap_set_address_bin(ap, addr_buf);
+    return nm_wifi_ap_set_address_bin(ap, &addr_buf);
 }
 
 NM80211Mode
@@ -365,6 +345,18 @@ nm_wifi_ap_get_metered(const NMWifiAP *self)
     return NM_WIFI_AP_GET_PRIVATE(self)->metered;
 }
 
+NM80211ApSecurityFlags
+nm_wifi_ap_get_wpa_flags(const NMWifiAP *self)
+{
+    return NM_WIFI_AP_GET_PRIVATE(self)->wpa_flags;
+}
+
+NM80211ApSecurityFlags
+nm_wifi_ap_get_rsn_flags(const NMWifiAP *self)
+{
+    return NM_WIFI_AP_GET_PRIVATE(self)->rsn_flags;
+}
+
 /*****************************************************************************/
 
 gboolean
@@ -395,7 +387,7 @@ nm_wifi_ap_update_from_properties(NMWifiAP *ap, const NMSupplicantBssInfo *bss_i
     changed |= nm_wifi_ap_set_ssid(ap, bss_info->ssid);
 
     if (bss_info->bssid_valid)
-        changed |= nm_wifi_ap_set_address_bin(ap, bss_info->bssid);
+        changed |= nm_wifi_ap_set_address_bin(ap, &bss_info->bssid);
     else {
         /* we don't actually clear the value. */
     }
@@ -523,7 +515,7 @@ nm_wifi_ap_to_string(const NMWifiAP *self, char *str_buf, gulong buf_len, gint64
 
     g_snprintf(str_buf,
                buf_len,
-               "%17s %-35s [ %c %3u %3u%% %c%c W:%04X R:%04X ] %s sup:%s [nm:%s]",
+               "%17s %-35s [ %c %3u %3u%% %c%c %c%c W:%04X R:%04X ] %s sup:%s [nm:%s]",
                priv->address ?: "(none)",
                (ssid_to_free = _nm_utils_ssid_to_string(priv->ssid)),
                (priv->mode == NM_802_11_MODE_ADHOC
@@ -535,6 +527,10 @@ nm_wifi_ap_to_string(const NMWifiAP *self, char *str_buf, gulong buf_len, gint64
                priv->strength,
                priv->flags & NM_802_11_AP_FLAGS_PRIVACY ? 'P' : '_',
                priv->metered ? 'M' : '_',
+               priv->flags & NM_802_11_AP_FLAGS_WPS ? 'W' : '_',
+               priv->flags & NM_802_11_AP_FLAGS_WPS_PIN
+                   ? 'p'
+                   : (priv->flags & NM_802_11_AP_FLAGS_WPS_PBC ? '#' : '_'),
                priv->wpa_flags & 0xFFFF,
                priv->rsn_flags & 0xFFFF,
                priv->last_seen_msec != G_MININT64
@@ -751,7 +747,7 @@ nm_wifi_ap_new_fake_from_connection(NMConnection *connection)
     s_wireless = nm_connection_get_setting_wireless(connection);
     g_return_val_if_fail(s_wireless != NULL, NULL);
 
-    ap         = (NMWifiAP *) g_object_new(NM_TYPE_WIFI_AP, NULL);
+    ap         = g_object_new(NM_TYPE_WIFI_AP, NULL);
     priv       = NM_WIFI_AP_GET_PRIVATE(ap);
     priv->fake = TRUE;
 
@@ -803,8 +799,8 @@ nm_wifi_ap_new_fake_from_connection(NMConnection *connection)
     if (!strcmp(key_mgmt, "none") || !strcmp(key_mgmt, "ieee8021x"))
         goto done;
 
-    psk = !strcmp(key_mgmt, "wpa-psk");
-    eap = !strcmp(key_mgmt, "wpa-eap");
+    psk = nm_streq(key_mgmt, "wpa-psk");
+    eap = nm_streq(key_mgmt, "wpa-eap") || nm_streq(key_mgmt, "wpa-eap-suite-b-192");
     if (!adhoc && (psk || eap)) {
         if (has_proto(s_wireless_sec, PROTO_WPA)) {
             flags = priv->wpa_flags
@@ -897,7 +893,7 @@ nm_wifi_ap_class_init(NMWifiAPClass *ap_class)
      | NM_802_11_AP_SEC_GROUP_WEP104 | 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_SAE | NM_802_11_AP_SEC_KEY_MGMT_OWE                             \
-     | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)
+     | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM | NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192)
 
     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-ap.h b/src/core/devices/wifi/nm-wifi-ap.h
index 4608c6ff..bdd72415 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/core/devices/wifi/nm-wifi-ap.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
@@ -64,23 +64,25 @@ nm_wifi_ap_get_supplicant_path(NMWifiAP *ap)
     return ap->_supplicant_path;
 }
 
-GBytes *       nm_wifi_ap_get_ssid(const NMWifiAP *ap);
-gboolean       nm_wifi_ap_set_ssid_arr(NMWifiAP *ap, const guint8 *ssid, gsize ssid_len);
-gboolean       nm_wifi_ap_set_ssid(NMWifiAP *ap, GBytes *ssid);
-const char *   nm_wifi_ap_get_address(const NMWifiAP *ap);
-gboolean       nm_wifi_ap_set_address(NMWifiAP *ap, const char *addr);
-NM80211Mode    nm_wifi_ap_get_mode(NMWifiAP *ap);
-gboolean       nm_wifi_ap_is_hotspot(NMWifiAP *ap);
-gint8          nm_wifi_ap_get_strength(NMWifiAP *ap);
-gboolean       nm_wifi_ap_set_strength(NMWifiAP *ap, gint8 strength);
-guint32        nm_wifi_ap_get_freq(NMWifiAP *ap);
-gboolean       nm_wifi_ap_set_freq(NMWifiAP *ap, guint32 freq);
-guint32        nm_wifi_ap_get_max_bitrate(NMWifiAP *ap);
-gboolean       nm_wifi_ap_set_max_bitrate(NMWifiAP *ap, guint32 bitrate);
-gboolean       nm_wifi_ap_get_fake(const NMWifiAP *ap);
-gboolean       nm_wifi_ap_set_fake(NMWifiAP *ap, gboolean fake);
-NM80211ApFlags nm_wifi_ap_get_flags(const NMWifiAP *self);
-gboolean       nm_wifi_ap_get_metered(const NMWifiAP *self);
+GBytes *               nm_wifi_ap_get_ssid(const NMWifiAP *ap);
+gboolean               nm_wifi_ap_set_ssid(NMWifiAP *ap, GBytes *ssid);
+const char *           nm_wifi_ap_get_address(const NMWifiAP *ap);
+gboolean               nm_wifi_ap_set_address(NMWifiAP *ap, const char *addr);
+gboolean               nm_wifi_ap_set_address_bin(NMWifiAP *ap, const NMEtherAddr *addr);
+NM80211Mode            nm_wifi_ap_get_mode(NMWifiAP *ap);
+gboolean               nm_wifi_ap_is_hotspot(NMWifiAP *ap);
+gint8                  nm_wifi_ap_get_strength(NMWifiAP *ap);
+gboolean               nm_wifi_ap_set_strength(NMWifiAP *ap, gint8 strength);
+guint32                nm_wifi_ap_get_freq(NMWifiAP *ap);
+gboolean               nm_wifi_ap_set_freq(NMWifiAP *ap, guint32 freq);
+guint32                nm_wifi_ap_get_max_bitrate(NMWifiAP *ap);
+gboolean               nm_wifi_ap_set_max_bitrate(NMWifiAP *ap, guint32 bitrate);
+gboolean               nm_wifi_ap_get_fake(const NMWifiAP *ap);
+gboolean               nm_wifi_ap_set_fake(NMWifiAP *ap, gboolean fake);
+NM80211ApFlags         nm_wifi_ap_get_flags(const NMWifiAP *self);
+gboolean               nm_wifi_ap_get_metered(const NMWifiAP *self);
+NM80211ApSecurityFlags nm_wifi_ap_get_wpa_flags(const NMWifiAP *self);
+NM80211ApSecurityFlags nm_wifi_ap_get_rsn_flags(const NMWifiAP *self);
 
 const char *
 nm_wifi_ap_to_string(const NMWifiAP *self, char *str_buf, gulong buf_len, gint64 now_msec);
diff --git a/src/devices/wifi/nm-wifi-common.c b/src/core/devices/wifi/nm-wifi-common.c
index e2ef4186..c715c07c 100644
--- a/src/devices/wifi/nm-wifi-common.c
+++ b/src/core/devices/wifi/nm-wifi-common.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-wifi-common.h"
 
diff --git a/src/devices/wifi/nm-wifi-common.h b/src/core/devices/wifi/nm-wifi-common.h
index 203f2a53..fd6f47ed 100644
--- a/src/devices/wifi/nm-wifi-common.h
+++ b/src/core/devices/wifi/nm-wifi-common.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/core/devices/wifi/nm-wifi-factory.c
index 1d140759..40375e1c 100644
--- a/src/devices/wifi/nm-wifi-factory.c
+++ b/src/core/devices/wifi/nm-wifi-factory.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 - 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <gmodule.h>
 
@@ -52,7 +52,7 @@ NM_DEVICE_FACTORY_DECLARE_TYPES(
 G_MODULE_EXPORT NMDeviceFactory *
                 nm_device_factory_create(GError **error)
 {
-    return (NMDeviceFactory *) g_object_new(NM_TYPE_WIFI_FACTORY, NULL);
+    return g_object_new(NM_TYPE_WIFI_FACTORY, NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.c b/src/core/devices/wifi/nm-wifi-p2p-peer.c
index a194f168..8488f32d 100644
--- a/src/devices/wifi/nm-wifi-p2p-peer.c
+++ b/src/core/devices/wifi/nm-wifi-p2p-peer.c
@@ -1,13 +1,14 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-wifi-p2p-peer.h"
 
 #include <stdlib.h>
+#include <linux/if_ether.h>
 
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device.h"
@@ -407,7 +408,7 @@ nm_wifi_p2p_peer_update_from_properties(NMWifiP2PPeer *peer, const NMSupplicantP
 
     /* We currently only use the groups information internally to check if
      * the peer is still joined. */
-    if (!_nm_utils_strv_equal((char **) priv->groups, (char **) peer_info->groups)) {
+    if (!nm_utils_strv_equal(priv->groups, peer_info->groups)) {
         g_free(priv->groups);
         priv->groups = nm_utils_strv_dup_packed(peer_info->groups, -1);
         changed |= TRUE;
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.h b/src/core/devices/wifi/nm-wifi-p2p-peer.h
index e19cf3d8..ee4bfb53 100644
--- a/src/devices/wifi/nm-wifi-p2p-peer.h
+++ b/src/core/devices/wifi/nm-wifi-p2p-peer.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c
index e371d197..aed236cc 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/core/devices/wifi/nm-wifi-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-wifi-utils.h"
 
@@ -407,7 +407,7 @@ verify_wpa_eap(NMSettingWirelessSecurity *s_wsec,
     auth_alg = nm_setting_wireless_security_get_auth_alg(s_wsec);
 
     if (key_mgmt) {
-        if (!strcmp(key_mgmt, "wpa-eap")) {
+        if (NM_IN_STRSET(key_mgmt, "wpa-eap", "wpa-eap-suite-b-192")) {
             if (!s_8021x) {
                 g_set_error_literal(error,
                                     NM_CONNECTION_ERROR,
@@ -444,7 +444,8 @@ verify_wpa_eap(NMSettingWirelessSecurity *s_wsec,
     if (is_wpa_eap || s_8021x) {
         /* Make sure the AP's capabilities support WPA-EAP */
         if (!(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)
-            && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) {
+            && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)
+            && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192)) {
             g_set_error_literal(error,
                                 NM_CONNECTION_ERROR,
                                 NM_CONNECTION_ERROR_INVALID_SETTING,
@@ -838,6 +839,14 @@ nm_wifi_utils_complete_connection(GBytes *      ap_ssid,
         /* Leave proto/pairwise/group as client set them; if they are unset the
          * supplicant will figure out the best combination at connect time.
          */
+    } else if ((key_mgmt && !strcmp(key_mgmt, "wpa-eap-suite-b-192"))
+               || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192)) {
+        g_object_set(s_wsec,
+                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                     "wpa-eap-suite-b-192",
+                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                     "open",
+                     NULL);
     } else {
         g_set_error_literal(error,
                             NM_CONNECTION_ERROR,
@@ -886,34 +895,51 @@ nm_wifi_utils_is_manf_default_ssid(GBytes *ssid)
     return FALSE;
 }
 
-NMIwdNetworkSecurity
-nm_wifi_connection_get_iwd_security(NMConnection *connection, gboolean *mapped)
+/* To be used for connections where the SSID has been validated before */
+gboolean
+nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *        connection,
+                                             char **               ssid,
+                                             NMIwdNetworkSecurity *security)
 {
+    NMSettingWireless *        s_wireless;
     NMSettingWirelessSecurity *s_wireless_sec;
     const char *               key_mgmt = NULL;
 
-    if (!nm_connection_get_setting_wireless(connection))
-        goto error;
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    if (!s_wireless)
+        return FALSE;
+
+    if (ssid) {
+        GBytes *    bytes = nm_setting_wireless_get_ssid(s_wireless);
+        gsize       ssid_len;
+        const char *ssid_str = (const char *) g_bytes_get_data(bytes, &ssid_len);
 
-    NM_SET_OUT(mapped, TRUE);
+        nm_assert(bytes && g_utf8_validate(ssid_str, ssid_len, NULL));
+        NM_SET_OUT(ssid, g_strndup(ssid_str, ssid_len));
+    }
+
+    if (!security)
+        return TRUE;
 
     s_wireless_sec = nm_connection_get_setting_wireless_security(connection);
-    if (!s_wireless_sec)
-        return NM_IWD_NETWORK_SECURITY_NONE;
+    if (!s_wireless_sec) {
+        NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_OPEN);
+        return TRUE;
+    }
 
     key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wireless_sec);
     nm_assert(key_mgmt);
 
     if (NM_IN_STRSET(key_mgmt, "none", "ieee8021x"))
-        return NM_IWD_NETWORK_SECURITY_WEP;
-
-    if (nm_streq(key_mgmt, "wpa-psk"))
-        return NM_IWD_NETWORK_SECURITY_PSK;
-
-    if (nm_streq(key_mgmt, "wpa-eap"))
-        return NM_IWD_NETWORK_SECURITY_8021X;
+        NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_WEP);
+    else if (nm_streq(key_mgmt, "owe"))
+        NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_OPEN);
+    else if (NM_IN_STRSET(key_mgmt, "wpa-psk", "sae"))
+        NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_PSK);
+    else if (nm_streq(key_mgmt, "wpa-eap"))
+        NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_8021X);
+    else
+        return FALSE;
 
-error:
-    NM_SET_OUT(mapped, FALSE);
-    return NM_IWD_NETWORK_SECURITY_NONE;
+    return TRUE;
 }
diff --git a/src/devices/wifi/nm-wifi-utils.h b/src/core/devices/wifi/nm-wifi-utils.h
index 678b353b..474bea41 100644
--- a/src/devices/wifi/nm-wifi-utils.h
+++ b/src/core/devices/wifi/nm-wifi-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  */
@@ -13,7 +13,7 @@
 #include "nm-setting-8021x.h"
 
 typedef enum {
-    NM_IWD_NETWORK_SECURITY_NONE,
+    NM_IWD_NETWORK_SECURITY_OPEN,
     NM_IWD_NETWORK_SECURITY_WEP,
     NM_IWD_NETWORK_SECURITY_PSK,
     NM_IWD_NETWORK_SECURITY_8021X,
@@ -32,7 +32,8 @@ gboolean nm_wifi_utils_complete_connection(GBytes *      ssid,
 
 gboolean nm_wifi_utils_is_manf_default_ssid(GBytes *ssid);
 
-NMIwdNetworkSecurity nm_wifi_connection_get_iwd_security(NMConnection *connection,
-                                                         gboolean *    mapped);
+gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *        connection,
+                                                      char **               ssid,
+                                                      NMIwdNetworkSecurity *security);
 
 #endif /* __NM_WIFI_UTILS_H__ */
diff --git a/src/devices/wifi/tests/test-devices-wifi.c b/src/core/devices/wifi/tests/test-devices-wifi.c
index 7be55860..bc0ba126 100644
--- a/src/devices/wifi/tests/test-devices-wifi.c
+++ b/src/core/devices/wifi/tests/test-devices-wifi.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "devices/wifi/nm-wifi-utils.h"
 #include "devices/wifi/nm-device-wifi.h"
diff --git a/src/devices/wwan/libnm-wwan.ver b/src/core/devices/wwan/libnm-wwan.ver
index c368a590..c368a590 100644
--- a/src/devices/wwan/libnm-wwan.ver
+++ b/src/core/devices/wwan/libnm-wwan.ver
diff --git a/src/devices/wwan/meson.build b/src/core/devices/wwan/meson.build
index 97252edc..87af0429 100644
--- a/src/devices/wwan/meson.build
+++ b/src/core/devices/wwan/meson.build
@@ -1,29 +1,22 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
-nm_service_providers_source = files('nm-service-providers.c')
-
-sources = nm_service_providers_source + files(
-  'nm-modem-broadband.c',
-  'nm-modem.c',
-  'nm-modem-manager.c',
-)
-
-deps = [
-  daemon_nm_default_dep,
-  libsystemd_dep,
-  mm_glib_dep,
-]
-
-if enable_ofono
-  sources += files('nm-modem-ofono.c')
-endif
+wwan_inc = include_directories('.')
 
 linker_script = join_paths(meson.current_source_dir(), 'libnm-wwan.ver')
 
 libnm_wwan = shared_module(
   'nm-wwan',
-  sources: sources,
-  dependencies: deps,
+  sources: files(
+  'nm-service-providers.c',
+  'nm-modem-broadband.c',
+  'nm-modem.c',
+  'nm-modem-manager.c',
+  ) + (enable_ofono ? files('nm-modem-ofono.c') : files()),
+  dependencies: [
+    core_plugin_dep,
+    libsystemd_dep,
+    mm_glib_dep,
+  ],
   c_args: daemon_c_flags,
   link_args: '-Wl,--version-script,@0@'.format(linker_script),
   link_depends: linker_script,
@@ -31,8 +24,6 @@ libnm_wwan = shared_module(
   install_dir: nm_plugindir,
 )
 
-wwan_inc = include_directories('.')
-
 libnm_wwan_dep = declare_dependency(
   include_directories: wwan_inc,
   link_with: libnm_wwan,
@@ -43,18 +34,23 @@ core_plugins += libnm_wwan
 test(
   'check-wwan',
   check_exports,
-  args: [libnm_wwan.full_path(), linker_script],
-)
-
-sources = files(
-  'nm-device-modem.c',
-  'nm-wwan-factory.c',
+  args: [
+    libnm_wwan.full_path(),
+    linker_script,
+  ],
 )
 
 libnm_device_plugin_wwan = shared_module(
   'nm-device-plugin-wwan',
-  sources: sources,
-  dependencies: deps,
+  sources: files(
+    'nm-device-modem.c',
+    'nm-wwan-factory.c',
+  ),
+  dependencies: [
+    core_plugin_dep,
+    libsystemd_dep,
+    mm_glib_dep,
+  ],
   c_args: daemon_c_flags,
   link_with: libnm_wwan,
   link_args: ldflags_linker_script_devices,
@@ -73,18 +69,18 @@ run_target(
 )
 
 if enable_tests
-  test_unit = 'test-service-providers'
-
   exe = executable(
-    test_unit,
-    ['tests/' + test_unit + '.c'] + nm_service_providers_source,
+    'test-service-providers',
+    files(
+      'tests/test-service-providers.c',
+      'nm-service-providers.c',
+    ),
     include_directories: wwan_inc,
-    dependencies: libnetwork_manager_test_dep,
+    dependencies: libNetworkManagerTest_dep,
     c_args: test_c_flags,
   )
-
   test(
-    'wwan/' + test_unit,
+    'wwan/test-service-providers',
     test_script,
     timeout: default_test_timeout,
     args: test_args + [exe.full_path()],
diff --git a/src/devices/wwan/nm-device-modem.c b/src/core/devices/wwan/nm-device-modem.c
index f77de85a..3ea89d2c 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/core/devices/wwan/nm-device-modem.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 - 2019 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-device-modem.h"
 
@@ -16,8 +16,8 @@
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
 
+#define _NMLOG_DEVICE_TYPE NMDeviceModem
 #include "devices/nm-device-logging.h"
-_LOG_DECLARE_SELF(NMDeviceModem);
 
 /*****************************************************************************/
 
diff --git a/src/devices/wwan/nm-device-modem.h b/src/core/devices/wwan/nm-device-modem.h
index caabeb6c..f171d76f 100644
--- a/src/devices/wwan/nm-device-modem.h
+++ b/src/core/devices/wwan/nm-device-modem.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  */
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c
index 7b3a13a3..ca028804 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/core/devices/wwan/nm-modem-broadband.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-modem-broadband.h"
 #include "nm-service-providers.h"
diff --git a/src/devices/wwan/nm-modem-broadband.h b/src/core/devices/wwan/nm-modem-broadband.h
index 5a822f6f..627fe25a 100644
--- a/src/devices/wwan/nm-modem-broadband.h
+++ b/src/core/devices/wwan/nm-modem-broadband.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 - Aleksander Morgado <aleksander@gnu.org>
  */
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/core/devices/wwan/nm-modem-manager.c
index 65f7c9f9..598c6898 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/core/devices/wwan/nm-modem-manager.c
@@ -1,11 +1,11 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 - 2014 Red Hat, Inc.
  * Copyright (C) 2009 Novell, Inc.
  * Copyright (C) 2009 - 2013 Canonical Ltd.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-modem-manager.h"
 
diff --git a/src/devices/wwan/nm-modem-manager.h b/src/core/devices/wwan/nm-modem-manager.h
index 895d4ced..e89a032b 100644
--- a/src/devices/wwan/nm-modem-manager.h
+++ b/src/core/devices/wwan/nm-modem-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 - 2014 Red Hat, Inc.
  * Copyright (C) 2009 Novell, Inc.
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c
index d8d0893b..21734cee 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/core/devices/wwan/nm-modem-ofono.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 - 2016 Canonical Ltd.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-modem-ofono.h"
 
@@ -1160,20 +1160,20 @@ nm_modem_ofono_new(const char *path)
      */
     basename = g_path_get_basename(path);
 
-    return (NMModem *) g_object_new(NM_TYPE_MODEM_OFONO,
-                                    NM_MODEM_PATH,
-                                    path,
-                                    NM_MODEM_UID,
-                                    basename,
-                                    NM_MODEM_DEVICE_ID,
-                                    basename,
-                                    NM_MODEM_CONTROL_PORT,
-                                    "ofono", /* mandatory */
-                                    NM_MODEM_DRIVER,
-                                    "ofono",
-                                    NM_MODEM_STATE,
-                                    (int) NM_MODEM_STATE_INITIALIZING,
-                                    NULL);
+    return g_object_new(NM_TYPE_MODEM_OFONO,
+                        NM_MODEM_PATH,
+                        path,
+                        NM_MODEM_UID,
+                        basename,
+                        NM_MODEM_DEVICE_ID,
+                        basename,
+                        NM_MODEM_CONTROL_PORT,
+                        "ofono", /* mandatory */
+                        NM_MODEM_DRIVER,
+                        "ofono",
+                        NM_MODEM_STATE,
+                        (int) NM_MODEM_STATE_INITIALIZING,
+                        NULL);
 }
 
 static void
diff --git a/src/devices/wwan/nm-modem-ofono.h b/src/core/devices/wwan/nm-modem-ofono.h
index 10b4a385..260e3954 100644
--- a/src/devices/wwan/nm-modem-ofono.h
+++ b/src/core/devices/wwan/nm-modem-ofono.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 - Canonical Ltd.
  */
diff --git a/src/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c
index 9e16420e..0d334fa4 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/core/devices/wwan/nm-modem.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 - 2014 Red Hat, Inc.
  * Copyright (C) 2009 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-modem.h"
 
diff --git a/src/devices/wwan/nm-modem.h b/src/core/devices/wwan/nm-modem.h
index 4b275cf7..87162cfc 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/core/devices/wwan/nm-modem.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 - 2011 Red Hat, Inc.
  * Copyright (C) 2009 Novell, Inc.
diff --git a/src/devices/wwan/nm-service-providers.c b/src/core/devices/wwan/nm-service-providers.c
index f7c6b5b8..89add6c5 100644
--- a/src/devices/wwan/nm-service-providers.c
+++ b/src/core/devices/wwan/nm-service-providers.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2009 Novell, Inc.
  * Author: Tambet Ingo (tambet@gmail.com).
@@ -6,7 +6,7 @@
  * Copyright (C) 2012 Lanedo GmbH
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-service-providers.h"
 
diff --git a/src/devices/wwan/nm-service-providers.h b/src/core/devices/wwan/nm-service-providers.h
index 093bcd5b..959f660a 100644
--- a/src/devices/wwan/nm-service-providers.h
+++ b/src/core/devices/wwan/nm-service-providers.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/core/devices/wwan/nm-wwan-factory.c
index 1c3406da..5d2ce2b3 100644
--- a/src/devices/wwan/nm-wwan-factory.c
+++ b/src/core/devices/wwan/nm-wwan-factory.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <gmodule.h>
 
@@ -54,7 +54,7 @@ NM_DEVICE_FACTORY_DECLARE_TYPES(NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(
 G_MODULE_EXPORT NMDeviceFactory *
                 nm_device_factory_create(GError **error)
 {
-    return (NMDeviceFactory *) g_object_new(NM_TYPE_WWAN_FACTORY, NULL);
+    return g_object_new(NM_TYPE_WWAN_FACTORY, NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/wwan/tests/test-service-providers.c b/src/core/devices/wwan/tests/test-service-providers.c
index ec19d588..f95cccf8 100644
--- a/src/devices/wwan/tests/test-service-providers.c
+++ b/src/core/devices/wwan/tests/test-service-providers.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-service-providers.h"
 
@@ -43,7 +43,7 @@ test_positive(void)
     GMainLoop *loop = g_main_loop_new(NULL, FALSE);
 
     nm_service_providers_find_gsm_apn(NM_BUILD_SRCDIR
-                                      "/src/devices/wwan/tests/test-service-providers.xml",
+                                      "/src/core/devices/wwan/tests/test-service-providers.xml",
                                       "13337",
                                       NULL,
                                       test_positive_cb,
@@ -76,7 +76,7 @@ test_negative(void)
     GMainLoop *loop = g_main_loop_new(NULL, FALSE);
 
     nm_service_providers_find_gsm_apn(NM_BUILD_SRCDIR
-                                      "/src/devices/wwan/tests/test-service-providers.xml",
+                                      "/src/core/devices/wwan/tests/test-service-providers.xml",
                                       "78130",
                                       NULL,
                                       test_negative_cb,
diff --git a/src/devices/wwan/tests/test-service-providers.xml b/src/core/devices/wwan/tests/test-service-providers.xml
index f0ca2deb..f0ca2deb 100644
--- a/src/devices/wwan/tests/test-service-providers.xml
+++ b/src/core/devices/wwan/tests/test-service-providers.xml
diff --git a/src/core/dhcp/meson.build b/src/core/dhcp/meson.build
new file mode 100644
index 00000000..1bb004cd
--- /dev/null
+++ b/src/core/dhcp/meson.build
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+executable(
+  'nm-dhcp-helper',
+  'nm-dhcp-helper.c',
+  dependencies: glib_nm_default_dep,
+  c_args: [
+    '-DG_LOG_DOMAIN="nm-dhcp-helper"',
+  ],
+  link_args: ldflags_linker_script_binary,
+  link_depends: linker_script_binary,
+  install: true,
+  install_dir: nm_libexecdir,
+)
+
+if enable_tests
+  subdir('tests')
+endif
diff --git a/src/dhcp/nm-dhcp-client-logging.h b/src/core/dhcp/nm-dhcp-client-logging.h
index ecbf5210..d69b3ebd 100644
--- a/src/dhcp/nm-dhcp-client-logging.h
+++ b/src/core/dhcp/nm-dhcp-client-logging.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
diff --git a/src/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c
index 46ab4895..c38c814e 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/core/dhcp/nm-dhcp-client.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2010 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dhcp-client.h"
 
@@ -20,6 +20,7 @@
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
 #include "nm-dhcp-utils.h"
+#include "nm-dhcp-options.h"
 #include "platform/nm-platform.h"
 
 #include "nm-dhcp-client-logging.h"
@@ -367,10 +368,13 @@ reason_to_state(NMDhcpClient *self, const char *iface, const char *reason)
     else if (g_ascii_strcasecmp(reason, "nak") == 0 || g_ascii_strcasecmp(reason, "expire") == 0
              || g_ascii_strcasecmp(reason, "expire6") == 0)
         return NM_DHCP_STATE_EXPIRE;
-    else if (g_ascii_strcasecmp(reason, "end") == 0)
+    else if (g_ascii_strcasecmp(reason, "end") == 0 || g_ascii_strcasecmp(reason, "stop") == 0
+             || g_ascii_strcasecmp(reason, "stopped") == 0)
         return NM_DHCP_STATE_DONE;
     else if (g_ascii_strcasecmp(reason, "fail") == 0 || g_ascii_strcasecmp(reason, "abend") == 0)
         return NM_DHCP_STATE_FAIL;
+    else if (g_ascii_strcasecmp(reason, "preinit") == 0)
+        return NM_DHCP_STATE_NOOP;
 
     _LOGD("unmapped DHCP state '%s'", reason);
     return NM_DHCP_STATE_UNKNOWN;
@@ -434,8 +438,7 @@ nm_dhcp_client_set_state(NMDhcpClient *self,
                          NMIPConfig *  ip_config,
                          GHashTable *  options)
 {
-    NMDhcpClientPrivate *priv     = NM_DHCP_CLIENT_GET_PRIVATE(self);
-    gs_free char *       event_id = NULL;
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
 
     if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) {
         g_return_if_fail(NM_IS_IP_CONFIG_ADDR_FAMILY(ip_config, priv->addr_family));
@@ -459,23 +462,36 @@ nm_dhcp_client_set_state(NMDhcpClient *self,
         && !NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED))
         return;
 
-    if (_LOGI_ENABLED()) {
+    if (_LOGD_ENABLED()) {
         gs_free const char **keys = NULL;
         guint                i, nkeys;
 
         keys = nm_utils_strdict_get_keys(options, TRUE, &nkeys);
         for (i = 0; i < nkeys; i++) {
-            _LOGI("option %-20s => '%s'", keys[i], (char *) g_hash_table_lookup(options, keys[i]));
+            _LOGD("option %-20s => '%s'", keys[i], (char *) g_hash_table_lookup(options, keys[i]));
         }
     }
 
-    if (priv->addr_family == AF_INET6)
+    if (_LOGT_ENABLED() && priv->addr_family == AF_INET6) {
+        gs_free char *event_id = NULL;
+
         event_id = nm_dhcp_utils_get_dhcp6_event_id(options);
+        if (event_id)
+            _LOGT("event-id: \"%s\"", event_id);
+    }
 
-    _LOGI("state changed %s -> %s%s%s%s",
-          state_to_string(priv->state),
-          state_to_string(new_state),
-          NM_PRINT_FMT_QUOTED(event_id, ", event ID=\"", event_id, "\"", ""));
+    if (_LOGI_ENABLED()) {
+        const char *req_str =
+            NM_IS_IPv4(priv->addr_family)
+                ? nm_dhcp_option_request_string(AF_INET, NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS)
+                : nm_dhcp_option_request_string(AF_INET6, NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS);
+        const char *addr = nm_g_hash_table_lookup(options, req_str);
+
+        _LOGI("state changed %s -> %s%s%s%s",
+              state_to_string(priv->state),
+              state_to_string(new_state),
+              NM_PRINT_FMT_QUOTED(addr, ", address=", addr, "", ""));
+    }
 
     priv->state = new_state;
     g_signal_emit(G_OBJECT(self), signals[SIGNAL_STATE_CHANGED], 0, new_state, ip_config, options);
@@ -547,6 +563,18 @@ nm_dhcp_client_watch_child(NMDhcpClient *self, pid_t pid)
     priv->watch_id = g_child_watch_add(pid, daemon_watch_cb, self);
 }
 
+void
+nm_dhcp_client_stop_watch_child(NMDhcpClient *self, pid_t pid)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    g_return_if_fail(priv->pid == pid);
+    priv->pid = -1;
+
+    watch_cleanup(self);
+    timeout_cleanup(self);
+}
+
 gboolean
 nm_dhcp_client_start_ip4(NMDhcpClient *self,
                          GBytes *      client_id,
@@ -874,6 +902,9 @@ nm_dhcp_client_handle_event(gpointer      unused,
           state_to_string(new_state),
           reason);
 
+    if (new_state == NM_DHCP_STATE_NOOP)
+        return TRUE;
+
     if (NM_IN_SET(new_state, NM_DHCP_STATE_BOUND, NM_DHCP_STATE_EXTENDED)) {
         GVariantIter iter;
         const char * name;
diff --git a/src/dhcp/nm-dhcp-client.h b/src/core/dhcp/nm-dhcp-client.h
index 05faa9ea..72ab477d 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/core/dhcp/nm-dhcp-client.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2010 Red Hat, Inc.
  */
@@ -55,6 +55,7 @@ typedef enum {
     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_NOOP,       /* state is a non operation for NetworkManager */
     __NM_DHCP_STATE_MAX,
     NM_DHCP_STATE_MAX = __NM_DHCP_STATE_MAX - 1,
 } NMDhcpState;
@@ -183,6 +184,8 @@ void nm_dhcp_client_start_timeout(NMDhcpClient *self);
 
 void nm_dhcp_client_watch_child(NMDhcpClient *self, pid_t pid);
 
+void nm_dhcp_client_stop_watch_child(NMDhcpClient *self, pid_t pid);
+
 void nm_dhcp_client_set_state(NMDhcpClient *self,
                               NMDhcpState   new_state,
                               NMIPConfig *  ip_config,
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/core/dhcp/nm-dhcp-dhclient-utils.c
index f0eed6f5..ad1e097f 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/core/dhcp/nm-dhcp-dhclient-utils.c
@@ -1,15 +1,16 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dhcp-dhclient-utils.h"
 
 #include <ctype.h>
 #include <arpa/inet.h>
 #include <net/if.h>
+#include <linux/if_ether.h>
 
 #include "nm-glib-aux/nm-dedup-multi.h"
 
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.h b/src/core/dhcp/nm-dhcp-dhclient-utils.h
index 3e524ee0..ed7c1c7c 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/core/dhcp/nm-dhcp-dhclient-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 Red Hat, Inc.
  */
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/core/dhcp/nm-dhcp-dhclient.c
index 260f400e..c42a0ba5 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/core/dhcp/nm-dhcp-dhclient.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2012 Red Hat, Inc.
  */
@@ -10,7 +10,7 @@
 #include <time.h>
 #undef _XOPEN_SOURCE
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #if WITH_DHCLIENT
 
@@ -437,6 +437,12 @@ dhclient_start(NMDhcpClient *client,
 
     if (addr_family == AF_INET6) {
         g_ptr_array_add(argv, (gpointer) "-6");
+
+        if (prefixes > 0 && nm_streq0(mode_opt, "-S")) {
+            /* -S is incompatible with -P, only use the latter */
+            mode_opt = NULL;
+        }
+
         if (mode_opt)
             g_ptr_array_add(argv, (gpointer) mode_opt);
         while (prefixes--)
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/core/dhcp/nm-dhcp-dhcpcanon.c
index 8e61b6e5..3504a048 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/core/dhcp/nm-dhcp-dhcpcanon.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 juga <juga at riseup dot net>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #if WITH_DHCPCANON
 
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/core/dhcp/nm-dhcp-dhcpcd.c
index b2b5d28b..cf9fe5c2 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/core/dhcp/nm-dhcp-dhcpcd.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
- * Copyright (C) 2008 Roy Marples
+ * Copyright (C) 2008,2020 Roy Marples <roy@marples.name>
  * Copyright (C) 2010 Dan Williams <dcbw@redhat.com>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #if WITH_DHCPCD
 
@@ -40,7 +40,6 @@ static GType nm_dhcp_dhcpcd_get_type(void);
 /*****************************************************************************/
 
 typedef struct {
-    char *          pid_file;
     NMDhcpListener *dhcp_listener;
 } NMDhcpDhcpcdPrivate;
 
@@ -71,39 +70,37 @@ ip4_start(NMDhcpClient *client,
           const char *  last_ip4_address,
           GError **     error)
 {
-    NMDhcpDhcpcd *       self                = NM_DHCP_DHCPCD(client);
-    NMDhcpDhcpcdPrivate *priv                = NM_DHCP_DHCPCD_GET_PRIVATE(self);
-    gs_unref_ptrarray GPtrArray *argv        = NULL;
-    pid_t                        pid         = -1;
-    GError *                     local       = NULL;
-    gs_free char *               cmd_str     = NULL;
-    gs_free char *               binary_name = NULL;
+    NMDhcpDhcpcd *    self            = NM_DHCP_DHCPCD(client);
+    gs_unref_ptrarray GPtrArray *argv = NULL;
+    pid_t                        pid;
+    GError *                     local;
+    gs_free char *               cmd_str = NULL;
     const char *                 iface;
     const char *                 dhcpcd_path;
     const char *                 hostname;
 
-    g_return_val_if_fail(priv->pid_file == NULL, FALSE);
+    pid = nm_dhcp_client_get_pid(client);
+    g_return_val_if_fail(pid == -1, FALSE);
 
     iface = nm_dhcp_client_get_iface(client);
 
-    /* dhcpcd does not allow custom pidfiles; the pidfile is always
-     * RUNSTATEDIR "dhcpcd-<ifname>.pid".
-     */
-    priv->pid_file = g_strdup_printf(RUNSTATEDIR "/dhcpcd-%s.pid", iface);
-
     dhcpcd_path = nm_dhcp_dhcpcd_get_path();
     if (!dhcpcd_path) {
         nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "dhcpcd binary not found");
         return FALSE;
     }
 
-    /* Kill any existing dhcpcd from the pidfile */
-    binary_name = g_path_get_basename(dhcpcd_path);
-    nm_dhcp_client_stop_existing(priv->pid_file, binary_name);
-
     argv = g_ptr_array_new();
     g_ptr_array_add(argv, (gpointer) dhcpcd_path);
 
+    /* Don't configure anything, we will do that instead.
+     * This requires dhcpcd-9.3.3 or newer.
+     * Older versions only had an option not to install a default route,
+     * dhcpcd still added addresses and other routes so we no longer support that
+     * as it doesn't fit how NetworkManager wants to work.
+     */
+    g_ptr_array_add(argv, (gpointer) "--noconfigure");
+
     g_ptr_array_add(argv, (gpointer) "-B"); /* Don't background on lease (disable fork()) */
 
     g_ptr_array_add(argv, (gpointer) "-K"); /* Disable built-in carrier detection */
@@ -113,8 +110,6 @@ ip4_start(NMDhcpClient *client,
     /* --noarp. Don't request or claim the address by ARP; this also disables IPv4LL. */
     g_ptr_array_add(argv, (gpointer) "-A");
 
-    g_ptr_array_add(argv, (gpointer) "-G"); /* Let NM handle routing */
-
     g_ptr_array_add(argv, (gpointer) "-c"); /* Set script file */
     g_ptr_array_add(argv, (gpointer) nm_dhcp_helper_path);
 
@@ -146,8 +141,8 @@ ip4_start(NMDhcpClient *client,
     if (!g_spawn_async(NULL,
                        (char **) argv->pdata,
                        NULL,
-                       G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL
-                           | G_SPAWN_STDERR_TO_DEV_NULL,
+                       G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL
+                           | G_SPAWN_DO_NOT_REAP_CHILD,
                        nm_utils_setpgid,
                        NULL,
                        &pid,
@@ -169,23 +164,32 @@ ip4_start(NMDhcpClient *client,
 static void
 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) {
-            errsv = errno;
-            _LOGD("could not remove dhcp pid file \"%s\": %d (%s)",
-                  priv->pid_file,
-                  errsv,
-                  nm_strerror_native(errsv));
-        }
+    NMDhcpDhcpcd *self = NM_DHCP_DHCPCD(client);
+    pid_t         pid;
+    int           sig, errsv;
+
+    pid = nm_dhcp_client_get_pid(client);
+    sig = release ? SIGALRM : SIGTERM;
+    _LOGD("sending %s to dhcpcd pid %d", sig == SIGALRM ? "SIGALRM" : "SIGTERM", pid);
+
+    /* dhcpcd-9.x features privilege separation.
+     * It's not our job to track all these processes so we rely on dhcpcd
+     * to always cleanup after itself.
+     * Because it also re-parents itself to PID 1, the process cannot be
+     * reaped or waited for.
+     * As such, just send the correct signal.
+     */
+    if (kill(pid, sig) == -1) {
+        errsv = errno;
+        _LOGE("failed to kill dhcpcd %d:%s", errsv, strerror(errsv));
     }
 
-    /* FIXME: implement release... */
+    /* When this function exits NM expects the PID to be -1.
+     * This means we also need to stop watching the pid.
+     * If we need to know the exit status then we need to refactor NM
+     * to allow a non -1 to mean we're waiting to exit still.
+     */
+    nm_dhcp_client_stop_watch_child(client, pid);
 }
 
 /*****************************************************************************/
@@ -214,8 +218,6 @@ dispose(GObject *object)
         g_clear_object(&priv->dhcp_listener);
     }
 
-    nm_clear_g_free(&priv->pid_file);
-
     G_OBJECT_CLASS(nm_dhcp_dhcpcd_parent_class)->dispose(object);
 }
 
diff --git a/src/dhcp/nm-dhcp-helper-api.h b/src/core/dhcp/nm-dhcp-helper-api.h
index aa1d203a..ffbf3f72 100644
--- a/src/dhcp/nm-dhcp-helper-api.h
+++ b/src/core/dhcp/nm-dhcp-helper-api.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/core/dhcp/nm-dhcp-helper.c
index 84fa859d..0f98add1 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/core/dhcp/nm-dhcp-helper.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2007 - 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib.h"
 
 #include <unistd.h>
 #include <stdlib.h>
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/core/dhcp/nm-dhcp-listener.c
index c90590d5..1673bbb7 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/core/dhcp/nm-dhcp-listener.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 - 2016 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dhcp-listener.h"
 
diff --git a/src/dhcp/nm-dhcp-listener.h b/src/core/dhcp/nm-dhcp-listener.h
index 57d3b0fc..5f3c952c 100644
--- a/src/dhcp/nm-dhcp-listener.h
+++ b/src/core/dhcp/nm-dhcp-listener.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/core/dhcp/nm-dhcp-manager.c
index 665944d7..bc114ad8 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/core/dhcp/nm-dhcp-manager.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2013 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dhcp-manager.h"
 
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/core/dhcp/nm-dhcp-manager.h
index 729d6d4e..f7aba8a0 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/core/dhcp/nm-dhcp-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2010 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
diff --git a/src/dhcp/nm-dhcp-nettools.c b/src/core/dhcp/nm-dhcp-nettools.c
index 7b4e4605..116e1bdb 100644
--- a/src/dhcp/nm-dhcp-nettools.c
+++ b/src/core/dhcp/nm-dhcp-nettools.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014 - 2019 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <stdlib.h>
 #include <unistd.h>
@@ -13,10 +13,9 @@
 #include <ctype.h>
 #include <net/if_arp.h>
 
-#include "nm-sd-adapt-shared.h"
-
 #include "nm-glib-aux/nm-dedup-multi.h"
 #include "nm-std-aux/unaligned.h"
+#include "nm-glib-aux/nm-str-buf.h"
 
 #include "nm-utils.h"
 #include "nm-config.h"
@@ -83,66 +82,39 @@ set_error_nettools(GError **error, int r, const char *message)
         nm_utils_error_set(error, r, "%s (code %d)", message, r);
 }
 
-/*****************************************************************************/
-
-#define DHCP_MAX_FQDN_LENGTH 255
-
-enum {
-    NM_IN_ADDR_CLASS_A,
-    NM_IN_ADDR_CLASS_B,
-    NM_IN_ADDR_CLASS_C,
-    NM_IN_ADDR_CLASS_INVALID,
-};
-
-static int
-in_addr_class(struct in_addr addr)
+static inline int
+_client_lease_query(NDhcp4ClientLease *lease,
+                    uint8_t            option,
+                    const uint8_t **   datap,
+                    size_t *           n_datap)
 {
-    switch (ntohl(addr.s_addr) >> 24) {
-    case 0 ... 127:
-        return NM_IN_ADDR_CLASS_A;
-    case 128 ... 191:
-        return NM_IN_ADDR_CLASS_B;
-    case 192 ... 223:
-        return NM_IN_ADDR_CLASS_C;
-    default:
-        return NM_IN_ADDR_CLASS_INVALID;
-    }
+    return n_dhcp4_client_lease_query(lease, option, (guint8 **) datap, n_datap);
 }
 
-static gboolean
-lease_option_consume(void *out, size_t n_out, uint8_t **datap, size_t *n_datap)
-{
-    if (*n_datap < n_out)
-        return FALSE;
+/*****************************************************************************/
 
-    memcpy(out, *datap, n_out);
-    *datap += n_out;
-    *n_datap -= n_out;
-    return TRUE;
-}
+#define DHCP_MAX_FQDN_LENGTH 255
 
-static gboolean
-lease_option_next_in_addr(struct in_addr *addrp, uint8_t **datap, size_t *n_datap)
-{
-    return lease_option_consume(addrp, sizeof(struct in_addr), datap, n_datap);
-}
+/*****************************************************************************/
 
 static gboolean
-lease_option_next_route(struct in_addr *destp,
-                        uint8_t *       plenp,
-                        struct in_addr *gatewayp,
-                        gboolean        classless,
-                        uint8_t **      datap,
-                        size_t *        n_datap)
+lease_option_consume_route(const uint8_t **datap,
+                           size_t *        n_datap,
+                           gboolean        classless,
+                           in_addr_t *     destp,
+                           uint8_t *       plenp,
+                           in_addr_t *     gatewayp)
 {
-    struct in_addr dest   = {}, gateway;
-    uint8_t *      data   = *datap;
+    in_addr_t      dest;
+    in_addr_t      gateway;
+    const uint8_t *data   = *datap;
     size_t         n_data = *n_datap;
     uint8_t        plen;
-    uint8_t        bytes;
 
     if (classless) {
-        if (!lease_option_consume(&plen, sizeof(plen), &data, &n_data))
+        uint8_t bytes;
+
+        if (!nm_dhcp_lease_data_consume(&data, &n_data, &plen, sizeof(plen)))
             return FALSE;
 
         if (plen > 32)
@@ -150,30 +122,21 @@ lease_option_next_route(struct in_addr *destp,
 
         bytes = plen == 0 ? 0 : ((plen - 1) / 8) + 1;
 
-        if (!lease_option_consume(&dest, bytes, &data, &n_data))
+        dest = 0;
+        if (!nm_dhcp_lease_data_consume(&data, &n_data, &dest, bytes))
             return FALSE;
     } else {
-        if (!lease_option_next_in_addr(&dest, &data, &n_data))
+        if (!nm_dhcp_lease_data_consume_in_addr(&data, &n_data, &dest))
             return FALSE;
 
-        switch (in_addr_class(dest)) {
-        case NM_IN_ADDR_CLASS_A:
-            plen = 8;
-            break;
-        case NM_IN_ADDR_CLASS_B:
-            plen = 16;
-            break;
-        case NM_IN_ADDR_CLASS_C:
-            plen = 24;
-            break;
-        case NM_IN_ADDR_CLASS_INVALID:
+        plen = _nm_utils_ip4_get_default_prefix0(dest);
+        if (plen == 0)
             return FALSE;
-        }
     }
 
-    dest.s_addr = nm_utils_ip4_address_clear_host_address(dest.s_addr, plen);
+    dest = nm_utils_ip4_address_clear_host_address(dest, plen);
 
-    if (!lease_option_next_in_addr(&gateway, &data, &n_data))
+    if (!nm_dhcp_lease_data_consume_in_addr(&data, &n_data, &gateway))
         return FALSE;
 
     *destp    = dest;
@@ -184,170 +147,7 @@ lease_option_next_route(struct in_addr *destp,
     return TRUE;
 }
 
-static gboolean
-lease_option_print_label(GString *str, size_t n_label, uint8_t **datap, size_t *n_datap)
-{
-    for (size_t i = 0; i < n_label; ++i) {
-        uint8_t c;
-
-        if (!lease_option_consume(&c, sizeof(c), datap, n_datap))
-            return FALSE;
-
-        switch (c) {
-        case 'a' ... 'z':
-        case 'A' ... 'Z':
-        case '0' ... '9':
-        case '-':
-        case '_':
-            g_string_append_c(str, c);
-            break;
-        case '.':
-        case '\\':
-            g_string_append_printf(str, "\\%c", c);
-            break;
-        default:
-            g_string_append_printf(str, "\\%3d", c);
-        }
-    }
-
-    return TRUE;
-}
-
-static gboolean
-lease_option_print_domain_name(GString * str,
-                               uint8_t * cache,
-                               size_t *  n_cachep,
-                               uint8_t **datap,
-                               size_t *  n_datap)
-{
-    uint8_t * domain;
-    size_t    n_domain, n_cache = *n_cachep;
-    uint8_t **domainp   = datap;
-    size_t *  n_domainp = n_datap;
-    gboolean  first     = TRUE;
-    uint8_t   c;
-
-    /*
-     * We are given two adjacent memory regions. The @cache contains alreday parsed
-     * domain names, and the @datap contains the remaining data to parse.
-     *
-     * A domain name is formed from a sequence of labels. Each label start with
-     * a length byte, where the two most significant bits are unset. A zero-length
-     * label indicates the end of the domain name.
-     *
-     * Alternatively, a label can be followed by an offset (indicated by the two
-     * most significant bits being set in the next byte that is read). The offset
-     * is an offset into the cache, where the next label of the domain name can
-     * be found.
-     *
-     * Note, that each time a jump to an offset is performed, the size of the
-     * cache shrinks, so this is guaranteed to terminate.
-     */
-    if (cache + n_cache != *datap)
-        return FALSE;
-
-    for (;;) {
-        if (!lease_option_consume(&c, sizeof(c), domainp, n_domainp))
-            return FALSE;
-
-        switch (c & 0xC0) {
-        case 0x00: /* label length */
-        {
-            size_t n_label = c;
-
-            if (n_label == 0) {
-                /*
-                 * We reached the final label of the domain name. Adjust
-                 * the cache to include the consumed data, and return.
-                 */
-                *n_cachep = *datap - cache;
-                return TRUE;
-            }
-
-            if (!first)
-                g_string_append_c(str, '.');
-            else
-                first = FALSE;
-
-            if (!lease_option_print_label(str, n_label, domainp, n_domainp))
-                return FALSE;
-
-            break;
-        }
-        case 0xC0: /* back pointer */
-        {
-            size_t offset = (c & 0x3F) << 16;
-
-            /*
-             * The offset is given as two bytes (in big endian), where the
-             * two high bits are masked out.
-             */
-
-            if (!lease_option_consume(&c, sizeof(c), domainp, n_domainp))
-                return FALSE;
-
-            offset += c;
-
-            if (offset >= n_cache)
-                return FALSE;
-
-            domain   = cache + offset;
-            n_domain = n_cache - offset;
-            n_cache  = offset;
-
-            domainp   = &domain;
-            n_domainp = &n_domain;
-
-            break;
-        }
-        default:
-            return FALSE;
-        }
-    }
-}
-
-static gboolean
-lease_get_in_addr(NDhcp4ClientLease *lease, guint8 option, struct in_addr *addrp)
-{
-    struct in_addr addr;
-    uint8_t *      data;
-    size_t         n_data;
-    int            r;
-
-    r = n_dhcp4_client_lease_query(lease, option, &data, &n_data);
-    if (r)
-        return FALSE;
-
-    if (!lease_option_next_in_addr(&addr, &data, &n_data))
-        return FALSE;
-
-    if (n_data != 0)
-        return FALSE;
-
-    *addrp = addr;
-    return TRUE;
-}
-
-static gboolean
-lease_get_u16(NDhcp4ClientLease *lease, uint8_t option, uint16_t *u16p)
-{
-    uint8_t *data;
-    size_t   n_data;
-    uint16_t be16;
-    int      r;
-
-    r = n_dhcp4_client_lease_query(lease, option, &data, &n_data);
-    if (r)
-        return FALSE;
-
-    if (n_data != sizeof(be16))
-        return FALSE;
-
-    memcpy(&be16, data, sizeof(be16));
-
-    *u16p = ntohs(be16);
-    return TRUE;
-}
+/*****************************************************************************/
 
 static gboolean
 lease_parse_address(NDhcp4ClientLease *lease,
@@ -355,15 +155,17 @@ lease_parse_address(NDhcp4ClientLease *lease,
                     GHashTable *       options,
                     GError **          error)
 {
-    char           addr_str[NM_UTILS_INET_ADDRSTRLEN];
     struct in_addr a_address;
-    struct in_addr a_netmask;
+    in_addr_t      a_netmask;
     struct in_addr a_next_server;
     guint32        a_plen;
     guint64        nettools_lifetime;
     guint32        a_lifetime;
     guint32        a_timestamp;
     guint64        a_expiry;
+    const guint8 * l_data;
+    gsize          l_data_len;
+    int            r;
 
     n_dhcp4_client_lease_get_yiaddr(lease, &a_address);
     if (a_address.s_addr == INADDR_ANY) {
@@ -418,44 +220,40 @@ lease_parse_address(NDhcp4ClientLease *lease,
                       / NM_UTILS_NSEC_PER_SEC);
     }
 
-    if (!lease_get_in_addr(lease, NM_DHCP_OPTION_DHCP4_SUBNET_MASK, &a_netmask)) {
+    r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_SUBNET_MASK, &l_data, &l_data_len);
+    if (r != 0 || !nm_dhcp_lease_data_parse_in_addr(l_data, l_data_len, &a_netmask)) {
         nm_utils_error_set_literal(error,
                                    NM_UTILS_ERROR_UNKNOWN,
                                    "could not get netmask from lease");
         return FALSE;
     }
 
-    _nm_utils_inet4_ntop(a_address.s_addr, addr_str);
-    a_plen = nm_utils_ip4_netmask_to_prefix(a_netmask.s_addr);
+    a_plen = nm_utils_ip4_netmask_to_prefix(a_netmask);
 
-    nm_dhcp_option_add_option(options,
-                              _nm_dhcp_option_dhcp4_options,
-                              NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS,
-                              addr_str);
-    nm_dhcp_option_add_option(options,
-                              _nm_dhcp_option_dhcp4_options,
-                              NM_DHCP_OPTION_DHCP4_SUBNET_MASK,
-                              _nm_utils_inet4_ntop(a_netmask.s_addr, addr_str));
+    nm_dhcp_option_add_option_in_addr(options,
+                                      AF_INET,
+                                      NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS,
+                                      a_address.s_addr);
+    nm_dhcp_option_add_option_in_addr(options,
+                                      AF_INET,
+                                      NM_DHCP_OPTION_DHCP4_SUBNET_MASK,
+                                      a_netmask);
 
     nm_dhcp_option_add_option_u64(options,
-                                  _nm_dhcp_option_dhcp4_options,
+                                  AF_INET,
                                   NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME,
                                   (guint64) a_lifetime);
 
     if (a_expiry != G_MAXUINT64) {
-        nm_dhcp_option_add_option_u64(options,
-                                      _nm_dhcp_option_dhcp4_options,
-                                      NM_DHCP_OPTION_DHCP4_NM_EXPIRY,
-                                      a_expiry);
+        nm_dhcp_option_add_option_u64(options, AF_INET, NM_DHCP_OPTION_DHCP4_NM_EXPIRY, a_expiry);
     }
 
     n_dhcp4_client_lease_get_siaddr(lease, &a_next_server);
     if (a_next_server.s_addr != INADDR_ANY) {
-        _nm_utils_inet4_ntop(a_next_server.s_addr, addr_str);
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
-                                  NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER,
-                                  addr_str);
+        nm_dhcp_option_add_option_in_addr(options,
+                                          AF_INET,
+                                          NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER,
+                                          a_next_server.s_addr);
     }
 
     nm_ip4_config_add_address(ip4_config,
@@ -476,46 +274,52 @@ static void
 lease_parse_address_list(NDhcp4ClientLease *      lease,
                          NMIP4Config *            ip4_config,
                          NMDhcpOptionDhcp4Options option,
-                         GHashTable *             options)
+                         GHashTable *             options,
+                         NMStrBuf *               sbuf)
 {
-    nm_auto_free_gstring GString *str = NULL;
-    char                          addr_str[NM_UTILS_INET_ADDRSTRLEN];
-    struct in_addr                addr;
-    uint8_t *                     data;
-    size_t                        n_data;
-    int                           r;
-
-    r = n_dhcp4_client_lease_query(lease, option, &data, &n_data);
-    if (r)
+    const guint8 *l_data;
+    gsize         l_data_len;
+    int           r;
+
+    r = _client_lease_query(lease, option, &l_data, &l_data_len);
+    if (r != 0)
+        return;
+
+    if (l_data_len == 0 || l_data_len % 4 != 0)
         return;
 
-    nm_gstring_prepare(&str);
+    nm_str_buf_reset(sbuf);
 
-    while (lease_option_next_in_addr(&addr, &data, &n_data)) {
-        _nm_utils_inet4_ntop(addr.s_addr, addr_str);
-        g_string_append(nm_gstring_add_space_delimiter(str), addr_str);
+    for (; l_data_len > 0; l_data_len -= 4, l_data += 4) {
+        char            addr_str[NM_UTILS_INET_ADDRSTRLEN];
+        const in_addr_t addr = unaligned_read_ne32(l_data);
+
+        nm_str_buf_append_required_delimiter(sbuf, ' ');
+        nm_str_buf_append(sbuf, _nm_utils_inet4_ntop(addr, addr_str));
 
         switch (option) {
         case NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER:
-            if (addr.s_addr == 0 || nm_ip4_addr_is_localhost(addr.s_addr)) {
+            if (addr == 0 || nm_ip4_addr_is_localhost(addr)) {
                 /* Skip localhost addresses, like also networkd does.
                  * See https://github.com/systemd/systemd/issues/4524. */
                 continue;
             }
-            nm_ip4_config_add_nameserver(ip4_config, addr.s_addr);
+            nm_ip4_config_add_nameserver(ip4_config, addr);
             break;
         case NM_DHCP_OPTION_DHCP4_NIS_SERVERS:
-            nm_ip4_config_add_nis_server(ip4_config, addr.s_addr);
+            nm_ip4_config_add_nis_server(ip4_config, addr);
             break;
         case NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER:
-            nm_ip4_config_add_wins(ip4_config, addr.s_addr);
+            nm_ip4_config_add_wins(ip4_config, addr);
+            break;
+        case NM_DHCP_OPTION_DHCP4_NTP_SERVER:
             break;
         default:
             nm_assert_not_reached();
         }
     }
 
-    nm_dhcp_option_add_option(options, _nm_dhcp_option_dhcp4_options, option, str->str);
+    nm_dhcp_option_add_option(options, AF_INET, option, nm_str_buf_get_str(sbuf));
 }
 
 static void
@@ -523,39 +327,37 @@ lease_parse_routes(NDhcp4ClientLease *lease,
                    NMIP4Config *      ip4_config,
                    GHashTable *       options,
                    guint32            route_table,
-                   guint32            route_metric)
+                   guint32            route_metric,
+                   NMStrBuf *         sbuf)
 {
-    nm_auto_free_gstring GString *str = NULL;
-    char                          dest_str[NM_UTILS_INET_ADDRSTRLEN];
-    char                          gateway_str[NM_UTILS_INET_ADDRSTRLEN];
-    const char *                  s;
-    struct in_addr                dest, gateway;
-    uint8_t                       plen;
-    guint32                       m;
-    gboolean                      has_router_from_classless = FALSE, has_classless = FALSE;
-    guint32                       default_route_metric = route_metric;
-    uint8_t *                     data;
-    size_t                        n_data;
-    int                           r;
-
-    r = n_dhcp4_client_lease_query(lease,
-                                   NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
-                                   &data,
-                                   &n_data);
-    if (!r) {
-        nm_gstring_prepare(&str);
+    char          dest_str[NM_UTILS_INET_ADDRSTRLEN];
+    char          gateway_str[NM_UTILS_INET_ADDRSTRLEN];
+    in_addr_t     dest;
+    in_addr_t     gateway;
+    uint8_t       plen;
+    guint32       m;
+    gboolean      has_router_from_classless = FALSE;
+    gboolean      has_classless             = FALSE;
+    guint32       default_route_metric      = route_metric;
+    const guint8 *l_data;
+    gsize         l_data_len;
+    int           r;
+
+    r = _client_lease_query(lease,
+                            NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
+                            &l_data,
+                            &l_data_len);
+    if (r == 0) {
+        nm_str_buf_reset(sbuf);
 
         has_classless = TRUE;
 
-        while (lease_option_next_route(&dest, &plen, &gateway, TRUE, &data, &n_data)) {
-            _nm_utils_inet4_ntop(dest.s_addr, dest_str);
-            _nm_utils_inet4_ntop(gateway.s_addr, gateway_str);
+        while (lease_option_consume_route(&l_data, &l_data_len, TRUE, &dest, &plen, &gateway)) {
+            _nm_utils_inet4_ntop(dest, dest_str);
+            _nm_utils_inet4_ntop(gateway, gateway_str);
 
-            g_string_append_printf(nm_gstring_add_space_delimiter(str),
-                                   "%s/%d %s",
-                                   dest_str,
-                                   (int) plen,
-                                   gateway_str);
+            nm_str_buf_append_required_delimiter(sbuf, ' ');
+            nm_str_buf_append_printf(sbuf, "%s/%d %s", dest_str, (int) plen, gateway_str);
 
             if (plen == 0) {
                 /* if there are multiple default routes, we add them with differing
@@ -572,34 +374,32 @@ lease_parse_routes(NDhcp4ClientLease *lease,
             nm_ip4_config_add_route(
                 ip4_config,
                 &((const NMPlatformIP4Route){
-                    .network       = dest.s_addr,
+                    .network       = dest,
                     .plen          = plen,
-                    .gateway       = gateway.s_addr,
+                    .gateway       = gateway,
                     .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
                     .metric        = m,
                     .table_coerced = nm_platform_route_table_coerce(route_table),
                 }),
                 NULL);
         }
+
         nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
+                                  AF_INET,
                                   NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
-                                  str->str);
+                                  nm_str_buf_get_str(sbuf));
     }
 
-    r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, &data, &n_data);
-    if (!r) {
-        nm_gstring_prepare(&str);
+    r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, &l_data, &l_data_len);
+    if (r == 0) {
+        nm_str_buf_reset(sbuf);
 
-        while (lease_option_next_route(&dest, &plen, &gateway, FALSE, &data, &n_data)) {
-            _nm_utils_inet4_ntop(dest.s_addr, dest_str);
-            _nm_utils_inet4_ntop(gateway.s_addr, gateway_str);
+        while (lease_option_consume_route(&l_data, &l_data_len, FALSE, &dest, &plen, &gateway)) {
+            _nm_utils_inet4_ntop(dest, dest_str);
+            _nm_utils_inet4_ntop(gateway, gateway_str);
 
-            g_string_append_printf(nm_gstring_add_space_delimiter(str),
-                                   "%s/%d %s",
-                                   dest_str,
-                                   (int) plen,
-                                   gateway_str);
+            nm_str_buf_append_required_delimiter(sbuf, ' ');
+            nm_str_buf_append_printf(sbuf, "%s/%d %s", dest_str, (int) plen, gateway_str);
 
             if (has_classless) {
                 /* RFC 3443: if the DHCP server returns both a Classless Static Routes
@@ -619,30 +419,31 @@ lease_parse_routes(NDhcp4ClientLease *lease,
             nm_ip4_config_add_route(
                 ip4_config,
                 &((const NMPlatformIP4Route){
-                    .network       = dest.s_addr,
+                    .network       = dest,
                     .plen          = plen,
-                    .gateway       = gateway.s_addr,
+                    .gateway       = gateway,
                     .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
                     .metric        = route_metric,
                     .table_coerced = nm_platform_route_table_coerce(route_table),
                 }),
                 NULL);
         }
+
         nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
+                                  AF_INET,
                                   NM_DHCP_OPTION_DHCP4_STATIC_ROUTE,
-                                  str->str);
+                                  nm_str_buf_get_str(sbuf));
     }
 
-    r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_ROUTER, &data, &n_data);
-    if (!r) {
-        nm_gstring_prepare(&str);
+    r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_ROUTER, &l_data, &l_data_len);
+    if (r == 0) {
+        nm_str_buf_reset(sbuf);
 
-        while (lease_option_next_in_addr(&gateway, &data, &n_data)) {
-            s = _nm_utils_inet4_ntop(gateway.s_addr, gateway_str);
-            g_string_append(nm_gstring_add_space_delimiter(str), s);
+        while (nm_dhcp_lease_data_consume_in_addr(&l_data, &l_data_len, &gateway)) {
+            nm_str_buf_append_required_delimiter(sbuf, ' ');
+            nm_str_buf_append(sbuf, _nm_utils_inet4_ntop(gateway, gateway_str));
 
-            if (gateway.s_addr == 0) {
+            if (gateway == 0) {
                 /* silently skip 0.0.0.0 */
                 continue;
             }
@@ -667,262 +468,45 @@ lease_parse_routes(NDhcp4ClientLease *lease,
                 ip4_config,
                 &((const NMPlatformIP4Route){
                     .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
-                    .gateway       = gateway.s_addr,
+                    .gateway       = gateway,
                     .table_coerced = nm_platform_route_table_coerce(route_table),
                     .metric        = m,
                 }),
                 NULL);
         }
+
         nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
+                                  AF_INET,
                                   NM_DHCP_OPTION_DHCP4_ROUTER,
-                                  str->str);
-    }
-}
-
-static void
-lease_parse_mtu(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options)
-{
-    uint16_t mtu;
-
-    if (!lease_get_u16(lease, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, &mtu))
-        return;
-
-    if (mtu < 68)
-        return;
-
-    nm_dhcp_option_add_option_u64(options,
-                                  _nm_dhcp_option_dhcp4_options,
-                                  NM_DHCP_OPTION_DHCP4_INTERFACE_MTU,
-                                  mtu);
-    nm_ip4_config_set_mtu(ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
-}
-
-static void
-lease_parse_metered(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options)
-{
-    gboolean metered = FALSE;
-    uint8_t *data;
-    size_t   n_data;
-    int      r;
-
-    r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, &data, &n_data);
-    if (r)
-        metered = FALSE;
-    else
-        metered = !!memmem(data, n_data, "ANDROID_METERED", NM_STRLEN("ANDROID_METERED"));
-
-    /* TODO: expose the vendor specific option when present */
-    nm_ip4_config_set_metered(ip4_config, metered);
-}
-
-static void
-lease_parse_ntps(NDhcp4ClientLease *lease, GHashTable *options)
-{
-    nm_auto_free_gstring GString *str = NULL;
-    char                          addr_str[NM_UTILS_INET_ADDRSTRLEN];
-    struct in_addr                addr;
-    uint8_t *                     data;
-    size_t                        n_data;
-    int                           r;
-
-    r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_NTP_SERVER, &data, &n_data);
-    if (r)
-        return;
-
-    nm_gstring_prepare(&str);
-
-    while (lease_option_next_in_addr(&addr, &data, &n_data)) {
-        _nm_utils_inet4_ntop(addr.s_addr, addr_str);
-        g_string_append(nm_gstring_add_space_delimiter(str), addr_str);
+                                  nm_str_buf_get_str(sbuf));
     }
-
-    nm_dhcp_option_add_option(options,
-                              _nm_dhcp_option_dhcp4_options,
-                              NM_DHCP_OPTION_DHCP4_NTP_SERVER,
-                              str->str);
-}
-
-static void
-lease_parse_hostname(NDhcp4ClientLease *lease, GHashTable *options)
-{
-    nm_auto_free_gstring GString *str = NULL;
-    uint8_t *                     data;
-    size_t                        n_data;
-    int                           r;
-
-    r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_HOST_NAME, &data, &n_data);
-    if (r)
-        return;
-
-    str = g_string_new_len((char *) data, n_data);
-
-    if (nm_utils_is_localhost(str->str))
-        return;
-
-    nm_dhcp_option_add_option(options,
-                              _nm_dhcp_option_dhcp4_options,
-                              NM_DHCP_OPTION_DHCP4_HOST_NAME,
-                              str->str);
-}
-
-static void
-lease_parse_domainname(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options)
-{
-    nm_auto_free_gstring GString *str     = NULL;
-    gs_strfreev char **           domains = NULL;
-    uint8_t *                     data;
-    size_t                        n_data;
-    int                           r;
-
-    r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, &data, &n_data);
-    if (r)
-        return;
-
-    str = g_string_new_len((char *) data, n_data);
-
-    /* Multiple domains sometimes stuffed into option 15 "Domain Name". */
-    domains = g_strsplit(str->str, " ", 0);
-    nm_gstring_prepare(&str);
-
-    for (char **d = domains; *d; d++) {
-        if (nm_utils_is_localhost(*d))
-            return;
-
-        g_string_append(nm_gstring_add_space_delimiter(str), *d);
-        nm_ip4_config_add_domain(ip4_config, *d);
-    }
-    nm_dhcp_option_add_option(options,
-                              _nm_dhcp_option_dhcp4_options,
-                              NM_DHCP_OPTION_DHCP4_DOMAIN_NAME,
-                              str->str);
-}
-
-char **
-nm_dhcp_parse_search_list(guint8 *data, size_t n_data)
-{
-    GPtrArray *array   = NULL;
-    guint8 *   cache   = data;
-    size_t     n_cache = 0;
-
-    for (;;) {
-        nm_auto_free_gstring GString *domain = NULL;
-
-        nm_gstring_prepare(&domain);
-
-        if (!lease_option_print_domain_name(domain, cache, &n_cache, &data, &n_data))
-            break;
-
-        if (!array)
-            array = g_ptr_array_new();
-
-        g_ptr_array_add(array, g_string_free(domain, FALSE));
-        domain = NULL;
-    }
-
-    if (array) {
-        g_ptr_array_add(array, NULL);
-        return (char **) g_ptr_array_free(array, FALSE);
-    } else
-        return NULL;
 }
 
 static void
 lease_parse_search_domains(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options)
 {
-    nm_auto_free_gstring GString *str = NULL;
-    uint8_t *                     data;
-    size_t                        n_data;
-    gs_strfreev char **           domains = NULL;
-    guint                         i;
-    int                           r;
-
-    r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, &data, &n_data);
-    if (r)
-        return;
-
-    domains = nm_dhcp_parse_search_list(data, n_data);
-    nm_gstring_prepare(&str);
-
-    for (i = 0; domains && domains[i]; i++) {
-        g_string_append(nm_gstring_add_space_delimiter(str), domains[i]);
-        nm_ip4_config_add_search(ip4_config, domains[i]);
-    }
-    nm_dhcp_option_add_option(options,
-                              _nm_dhcp_option_dhcp4_options,
-                              NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST,
-                              str->str);
-}
-
-static void
-lease_parse_root_path(NDhcp4ClientLease *lease, GHashTable *options)
-{
-    nm_auto_free_gstring GString *str = NULL;
-    uint8_t *                     data;
-    size_t                        n_data;
-    int                           r;
-
-    r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_ROOT_PATH, &data, &n_data);
-    if (r)
+    gs_strfreev char **domains = NULL;
+    const guint8 *     l_data;
+    gsize              l_data_len;
+    guint              i;
+    int                r;
+
+    r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, &l_data, &l_data_len);
+    if (r != 0)
         return;
 
-    str = g_string_new_len((char *) data, n_data);
-    nm_dhcp_option_add_option(options,
-                              _nm_dhcp_option_dhcp4_options,
-                              NM_DHCP_OPTION_DHCP4_ROOT_PATH,
-                              str->str);
-}
-
-static void
-lease_parse_wpad(NDhcp4ClientLease *lease, GHashTable *options)
-{
-    gs_free char *wpad = NULL;
-    uint8_t *     data;
-    size_t        n_data;
-    int           r;
-
-    r = n_dhcp4_client_lease_query(lease,
-                                   NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY,
-                                   &data,
-                                   &n_data);
-    if (r)
-        return;
-
-    nm_utils_buf_utf8safe_escape((char *) data, n_data, 0, &wpad);
-    if (wpad == NULL)
-        wpad = g_strndup((char *) data, n_data);
-
-    nm_dhcp_option_add_option(options,
-                              _nm_dhcp_option_dhcp4_options,
-                              NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY,
-                              wpad);
-}
-
-static void
-lease_parse_nis_domain(NDhcp4ClientLease *lease, NMIP4Config *ip4_config, GHashTable *options)
-{
-    gs_free char *str_free = NULL;
-    const char *  str;
-    uint8_t *     data;
-    size_t        n_data;
-    guint         i;
-    int           r;
+    domains = nm_dhcp_lease_data_parse_search_list(l_data, l_data_len);
 
-    r = n_dhcp4_client_lease_query(lease, NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, &data, &n_data);
-    if (r)
+    if (!domains || !domains[0])
         return;
 
-    for (i = 0; i < n_data; i++) {
-        if (!nm_is_ascii((char) data[i]))
-            return;
-    }
+    for (i = 0; domains[i]; i++)
+        nm_ip4_config_add_search(ip4_config, domains[i]);
 
-    str = nm_strndup_a(300, (const char *) data, n_data, &str_free);
-    nm_dhcp_option_add_option(options,
-                              _nm_dhcp_option_dhcp4_options,
-                              NM_DHCP_OPTION_DHCP4_NIS_DOMAIN,
-                              str);
-    nm_ip4_config_set_nis_domain(ip4_config, str);
+    nm_dhcp_option_take_option(options,
+                               AF_INET,
+                               NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST,
+                               g_strjoinv(" ", domains));
 }
 
 static void
@@ -932,8 +516,8 @@ lease_parse_private_options(NDhcp4ClientLease *lease, GHashTable *options)
 
     for (i = NM_DHCP_OPTION_DHCP4_PRIVATE_224; i <= NM_DHCP_OPTION_DHCP4_PRIVATE_254; i++) {
         gs_free char *option_string = NULL;
-        guint8 *      data;
-        gsize         n_data;
+        const guint8 *l_data;
+        gsize         l_data_len;
         int           r;
 
         /* We manage private options 249 (private classless static route) and 252 (wpad) in a special
@@ -943,15 +527,12 @@ lease_parse_private_options(NDhcp4ClientLease *lease, GHashTable *options)
                       NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY))
             continue;
 
-        r = n_dhcp4_client_lease_query(lease, i, &data, &n_data);
+        r = _client_lease_query(lease, i, &l_data, &l_data_len);
         if (r)
             continue;
 
-        option_string = nm_utils_bin2hexstr_full(data, n_data, ':', FALSE, NULL);
-        nm_dhcp_option_take_option(options,
-                                   _nm_dhcp_option_dhcp4_options,
-                                   i,
-                                   g_steal_pointer(&option_string));
+        option_string = nm_utils_bin2hexstr_full(l_data, l_data_len, ':', FALSE, NULL);
+        nm_dhcp_option_take_option(options, AF_INET, i, g_steal_pointer(&option_string));
     }
 }
 
@@ -965,8 +546,17 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
                     GHashTable **      out_options,
                     GError **          error)
 {
+    nm_auto_str_buf NMStrBuf sbuf           = NM_STR_BUF_INIT(0, FALSE);
     gs_unref_object NMIP4Config *ip4_config = NULL;
     gs_unref_hashtable GHashTable *options  = NULL;
+    const guint8 *                 l_data;
+    gsize                          l_data_len;
+    const char *                   v_str;
+    guint16                        v_u16;
+    gboolean                       v_bool;
+    in_addr_t                      v_inaddr;
+    struct in_addr                 v_inaddr_s;
+    int                            r;
 
     g_return_val_if_fail(lease != NULL, NULL);
 
@@ -976,20 +566,145 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
     if (!lease_parse_address(lease, ip4_config, options, error))
         return NULL;
 
-    lease_parse_routes(lease, ip4_config, options, route_table, route_metric);
-    lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER, options);
-    lease_parse_domainname(lease, ip4_config, options);
+    r = n_dhcp4_client_lease_get_server_identifier(lease, &v_inaddr_s);
+    if (r == 0) {
+        nm_dhcp_option_add_option_in_addr(options,
+                                          AF_INET,
+                                          NM_DHCP_OPTION_DHCP4_SERVER_ID,
+                                          v_inaddr_s.s_addr);
+    }
+
+    r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_BROADCAST, &l_data, &l_data_len);
+    if (r == 0 && nm_dhcp_lease_data_parse_in_addr(l_data, l_data_len, &v_inaddr)) {
+        nm_dhcp_option_add_option_in_addr(options,
+                                          AF_INET,
+                                          NM_DHCP_OPTION_DHCP4_BROADCAST,
+                                          v_inaddr);
+    }
+
+    lease_parse_routes(lease, ip4_config, options, route_table, route_metric, &sbuf);
+
+    lease_parse_address_list(lease,
+                             ip4_config,
+                             NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER,
+                             options,
+                             &sbuf);
+
+    r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, &l_data, &l_data_len);
+    if (r == 0 && nm_dhcp_lease_data_parse_cstr(l_data, l_data_len, &l_data_len)) {
+        gs_free const char **domains = NULL;
+
+        nm_str_buf_reset(&sbuf);
+        nm_str_buf_append_len0(&sbuf, (const char *) l_data, l_data_len);
+
+        /* Multiple domains sometimes stuffed into option 15 "Domain Name". */
+        domains = nm_utils_strsplit_set(nm_str_buf_get_str(&sbuf), " ");
+
+        nm_str_buf_reset(&sbuf);
+        if (domains) {
+            gsize i;
+
+            for (i = 0; domains[i]; i++) {
+                gs_free char *s = NULL;
+
+                s = nm_dhcp_lease_data_parse_domain_validate(domains[i]);
+                if (!s)
+                    continue;
+
+                nm_str_buf_append_required_delimiter(&sbuf, ' ');
+                nm_str_buf_append(&sbuf, s);
+                nm_ip4_config_add_domain(ip4_config, s);
+            }
+        }
+
+        if (sbuf.len > 0) {
+            nm_dhcp_option_add_option(options,
+                                      AF_INET,
+                                      NM_DHCP_OPTION_DHCP4_DOMAIN_NAME,
+                                      nm_str_buf_get_str(&sbuf));
+        }
+    }
+
     lease_parse_search_domains(lease, ip4_config, options);
-    lease_parse_mtu(lease, ip4_config, options);
-    lease_parse_metered(lease, ip4_config, options);
-
-    lease_parse_hostname(lease, options);
-    lease_parse_ntps(lease, options);
-    lease_parse_root_path(lease, options);
-    lease_parse_wpad(lease, options);
-    lease_parse_nis_domain(lease, ip4_config, options);
-    lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_NIS_SERVERS, options);
-    lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER, options);
+
+    r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, &l_data, &l_data_len);
+    if (r == 0 && nm_dhcp_lease_data_parse_mtu(l_data, l_data_len, &v_u16)) {
+        nm_dhcp_option_add_option_u64(options, AF_INET, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, v_u16);
+        nm_ip4_config_set_mtu(ip4_config, v_u16, NM_IP_CONFIG_SOURCE_DHCP);
+    }
+
+    r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, &l_data, &l_data_len);
+    v_bool =
+        (r == 0) && memmem(l_data, l_data_len, "ANDROID_METERED", NM_STRLEN("ANDROID_METERED"));
+    nm_ip4_config_set_metered(ip4_config, v_bool);
+
+    r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_HOST_NAME, &l_data, &l_data_len);
+    if (r == 0) {
+        gs_free char *s = NULL;
+
+        if (nm_dhcp_lease_data_parse_domain(l_data, l_data_len, &s)) {
+            nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_HOST_NAME, s);
+        }
+    }
+
+    lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_NTP_SERVER, options, &sbuf);
+
+    r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_ROOT_PATH, &l_data, &l_data_len);
+    if (r == 0 && nm_dhcp_lease_data_parse_cstr(l_data, l_data_len, &l_data_len)) {
+        /* https://tools.ietf.org/html/rfc2132#section-3.19
+         *
+         *   The path is formatted as a character string consisting of
+         *   characters from the NVT ASCII character set.
+         *
+         * We still accept any character set and backslash escape it! */
+        if (l_data_len == 0) {
+            /* "Its minimum length is 1." */
+        } else {
+            nm_dhcp_option_add_option_utf8safe_escape(options,
+                                                      AF_INET,
+                                                      NM_DHCP_OPTION_DHCP4_ROOT_PATH,
+                                                      l_data,
+                                                      l_data_len);
+        }
+    }
+
+    r = _client_lease_query(lease,
+                            NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY,
+                            &l_data,
+                            &l_data_len);
+    if (r == 0 && nm_dhcp_lease_data_parse_cstr(l_data, l_data_len, &l_data_len)) {
+        /* https://tools.ietf.org/html/draft-ietf-wrec-wpad-01#section-4.4.1
+         *
+         * We reject NUL characters inside the string (except trailing NULs).
+         * Otherwise, we allow any encoding and backslash-escape the result to
+         * UTF-8. */
+        nm_dhcp_option_add_option_utf8safe_escape(options,
+                                                  AF_INET,
+                                                  NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY,
+                                                  l_data,
+                                                  l_data_len);
+    }
+
+    r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, &l_data, &l_data_len);
+    if (r == 0 && nm_dhcp_lease_data_parse_cstr(l_data, l_data_len, &l_data_len)) {
+        gs_free char *to_free = NULL;
+
+        /* https://tools.ietf.org/html/rfc2132#section-8.1 */
+
+        v_str = nm_utils_buf_utf8safe_escape((char *) l_data, l_data_len, 0, &to_free);
+
+        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NIS_DOMAIN, v_str);
+        nm_ip4_config_set_nis_domain(ip4_config, v_str);
+    }
+
+    lease_parse_address_list(lease, ip4_config, NM_DHCP_OPTION_DHCP4_NIS_SERVERS, options, &sbuf);
+
+    lease_parse_address_list(lease,
+                             ip4_config,
+                             NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER,
+                             options,
+                             &sbuf);
+
     lease_parse_private_options(lease, options);
 
     NM_SET_OUT(out_options, g_steal_pointer(&options));
@@ -1001,25 +716,24 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
 static void
 lease_save(NMDhcpNettools *self, NDhcp4ClientLease *lease, const char *lease_file)
 {
-    struct in_addr       a_address;
-    nm_auto_free_gstring GString *new_contents = NULL;
-    char                          sbuf[NM_UTILS_INET_ADDRSTRLEN];
+    struct in_addr           a_address;
+    nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE);
+    char                     addr_str[NM_UTILS_INET_ADDRSTRLEN];
     gs_free_error GError *error = NULL;
 
     nm_assert(lease);
     nm_assert(lease_file);
 
-    new_contents = g_string_new("# This is private data. Do not parse.\n");
-
     n_dhcp4_client_lease_get_yiaddr(lease, &a_address);
     if (a_address.s_addr == INADDR_ANY)
         return;
 
-    g_string_append_printf(new_contents,
-                           "ADDRESS=%s\n",
-                           _nm_utils_inet4_ntop(a_address.s_addr, sbuf));
+    nm_str_buf_append(&sbuf, "# This is private data. Do not parse.\n");
+    nm_str_buf_append_printf(&sbuf,
+                             "ADDRESS=%s\n",
+                             _nm_utils_inet4_ntop(a_address.s_addr, addr_str));
 
-    if (!g_file_set_contents(lease_file, new_contents->str, -1, &error))
+    if (!g_file_set_contents(lease_file, nm_str_buf_get_str_unsafe(&sbuf), sbuf.len, &error))
         _LOGW("error saving lease to %s: %s", lease_file, error->message);
 }
 
@@ -1049,7 +763,7 @@ bound4_handle(NMDhcpNettools *self, NDhcp4ClientLease *lease, gboolean extended)
         return;
     }
 
-    nm_dhcp_option_add_requests_to_options(options, _nm_dhcp_option_dhcp4_options);
+    nm_dhcp_option_add_requests_to_options(options, AF_INET);
     lease_save(self, lease, priv->lease_file);
 
     nm_dhcp_client_set_state(NM_DHCP_CLIENT(self),
@@ -1127,9 +841,9 @@ dhcp4_event_handle(NMDhcpNettools *self, NDhcp4ClientEvent *event)
 }
 
 static gboolean
-dhcp4_event_cb(int fd, GIOCondition condition, gpointer data)
+dhcp4_event_cb(int fd, GIOCondition condition, gpointer user_data)
 {
-    NMDhcpNettools *       self = data;
+    NMDhcpNettools *       self = user_data;
     NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE(self);
     NDhcp4ClientEvent *    event;
     int                    r;
@@ -1375,7 +1089,7 @@ ip4_start(NMDhcpClient *client,
     }
 
     /* Add requested options */
-    for (i = 0; _nm_dhcp_option_dhcp4_options[i].name; i++) {
+    for (i = 0; i < (int) G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options); i++) {
         if (_nm_dhcp_option_dhcp4_options[i].include) {
             nm_assert(_nm_dhcp_option_dhcp4_options[i].option_num <= 255);
             n_dhcp4_client_probe_config_request_option(config,
diff --git a/src/dhcp/nm-dhcp-options.c b/src/core/dhcp/nm-dhcp-options.c
index 58d38201..3537cd14 100644
--- a/src/dhcp/nm-dhcp-options.c
+++ b/src/core/dhcp/nm-dhcp-options.c
@@ -1,17 +1,19 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dhcp-options.h"
 
-#define REQPREFIX "requested_"
+#include "nm-glib-aux/nm-str-buf.h"
 
-#define REQ(_num, _name, _include)                                          \
-    {                                                                       \
-        .name = REQPREFIX ""_name, .option_num = _num, .include = _include, \
+/*****************************************************************************/
+
+#define REQ(_num, _name, _include)                                                         \
+    {                                                                                      \
+        .name = NM_DHCP_OPTION_REQPREFIX ""_name, .option_num = _num, .include = _include, \
     }
 
 const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = {
@@ -167,8 +169,24 @@ const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = {
     REQ(NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, "ip_address", FALSE),
     REQ(NM_DHCP_OPTION_DHCP4_NM_EXPIRY, "expiry", FALSE),
     REQ(NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER, "next_server", FALSE),
+};
 
-    {0}};
+static const NMDhcpOption *const _sorted_options_4[G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options)] = {
+#define A(idx) (&_nm_dhcp_option_dhcp4_options[(idx)])
+    A(0),   A(1),   A(8),   A(18),  A(19),  A(2),   A(20),  A(21),  A(22),  A(23),  A(24),  A(3),
+    A(25),  A(26),  A(4),   A(27),  A(17),  A(28),  A(29),  A(30),  A(31),  A(32),  A(33),  A(34),
+    A(35),  A(5),   A(36),  A(6),   A(37),  A(38),  A(39),  A(40),  A(9),   A(41),  A(42),  A(43),
+    A(44),  A(45),  A(46),  A(10),  A(11),  A(12),  A(47),  A(48),  A(49),  A(50),  A(51),  A(52),
+    A(13),  A(53),  A(54),  A(55),  A(57),  A(58),  A(59),  A(60),  A(61),  A(62),  A(63),  A(64),
+    A(65),  A(66),  A(67),  A(68),  A(69),  A(70),  A(71),  A(72),  A(73),  A(74),  A(75),  A(76),
+    A(77),  A(78),  A(79),  A(80),  A(81),  A(82),  A(83),  A(84),  A(85),  A(86),  A(87),  A(56),
+    A(88),  A(89),  A(90),  A(91),  A(92),  A(93),  A(14),  A(7),   A(94),  A(95),  A(96),  A(97),
+    A(98),  A(99),  A(100), A(101), A(102), A(103), A(104), A(105), A(106), A(107), A(108), A(109),
+    A(110), A(111), A(112), A(113), A(114), A(115), A(116), A(117), A(118), A(119), A(120), A(121),
+    A(122), A(123), A(124), A(125), A(126), A(127), A(128), A(129), A(130), A(131), A(132), A(133),
+    A(134), A(15),  A(135), A(136), A(16),  A(137), A(138), A(139), A(140), A(141),
+#undef A
+};
 
 const NMDhcpOption _nm_dhcp_option_dhcp6_options[] = {
     REQ(NM_DHCP_OPTION_DHCP6_CLIENTID, "dhcp6_client_id", FALSE),
@@ -194,73 +212,235 @@ const NMDhcpOption _nm_dhcp_option_dhcp6_options[] = {
     REQ(NM_DHCP_OPTION_DHCP6_NM_RENEW, "renew", FALSE),
     REQ(NM_DHCP_OPTION_DHCP6_NM_REBIND, "rebind", FALSE),
     REQ(NM_DHCP_OPTION_DHCP6_NM_IAID, "iaid", FALSE),
+};
+
+#undef REQ
+
+static const NMDhcpOption *const _sorted_options_6[G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options)] = {
+#define A(idx) (&_nm_dhcp_option_dhcp6_options[(idx)])
+    A(0),
+    A(1),
+    A(2),
+    A(3),
+    A(4),
+    A(5),
+    A(6),
+    A(7),
+    A(8),
+    A(9),
+    A(10),
+    A(11),
+    A(12),
+    A(13),
+    A(14),
+    A(15),
+#undef A
+};
+
+/*****************************************************************************/
+
+static int
+_sorted_options_generate_sort(gconstpointer pa, gconstpointer pb, gpointer user_data)
+{
+    const NMDhcpOption *const *a = pa;
+    const NMDhcpOption *const *b = pb;
+
+    NM_CMP_DIRECT((*a)->option_num, (*b)->option_num);
+    return nm_assert_unreachable_val(0);
+}
+
+static char *
+_sorted_options_generate(const NMDhcpOption *base, const NMDhcpOption *const *sorted, guint n)
+{
+    gs_free const NMDhcpOption **sort2 = NULL;
+    NMStrBuf                     sbuf  = NM_STR_BUF_INIT(0, FALSE);
+    guint                        i;
+
+    sort2 = nm_memdup(sorted, n * sizeof(sorted[0]));
+
+    g_qsort_with_data(sort2, n, sizeof(sort2[0]), _sorted_options_generate_sort, NULL);
+
+    for (i = 0; i < n; i++) {
+        if (i > 0)
+            nm_str_buf_append(&sbuf, ", ");
+        nm_str_buf_append_printf(&sbuf, "A(%d)", (int) (sort2[i] - base));
+    }
+
+    return nm_str_buf_finalize(&sbuf, NULL);
+}
+
+_nm_unused static void
+_ASSERT_sorted(int IS_IPv4, const NMDhcpOption *const *const sorted, int n)
+
+{
+    const NMDhcpOption *const options =
+        IS_IPv4 ? _nm_dhcp_option_dhcp4_options : _nm_dhcp_option_dhcp6_options;
+    int           i;
+    int           j;
+    gs_free char *sorted_msg = NULL;
+
+    for (i = 0; i < n; i++) {
+        const NMDhcpOption *opt = sorted[i];
+
+        g_assert(opt);
+        g_assert(opt >= options);
+        g_assert(opt < &options[n]);
+
+        for (j = 0; j < i; j++) {
+            const NMDhcpOption *opt2 = sorted[j];
+
+            if (opt == opt2) {
+                g_error("%s:%d: the _sorted_options_%c at [%d] (opt=%u, %s) is duplicated at "
+                        "[%d] (SORT: %s)",
+                        __FILE__,
+                        __LINE__,
+                        IS_IPv4 ? '4' : '6',
+                        i,
+                        opt->option_num,
+                        opt->name,
+                        j,
+                        (sorted_msg = _sorted_options_generate(options, sorted, n)));
+            }
+        }
 
-    {0}};
+        if (i > 0) {
+            const NMDhcpOption *opt2 = sorted[i - 1];
 
-const char *
-nm_dhcp_option_request_string(const NMDhcpOption *requests, guint option)
+            if (opt2->option_num >= opt->option_num) {
+                g_error("%s:%d: the _sorted_options_%c at [%d] (opt=%u, %s) should come before "
+                        "[%d] (opt=%u, %s) (SORT: %s)",
+                        __FILE__,
+                        __LINE__,
+                        IS_IPv4 ? '4' : '6',
+                        i,
+                        opt->option_num,
+                        opt->name,
+                        i - 1,
+                        opt2->option_num,
+                        opt2->name,
+                        (sorted_msg = _sorted_options_generate(options, sorted, n)));
+            }
+        }
+    }
+}
+
+/*****************************************************************************/
+
+const NMDhcpOption *
+nm_dhcp_option_find(int addr_family, guint option)
 {
-    guint i = 0;
+    const int                        IS_IPv4 = NM_IS_IPv4(addr_family);
+    const NMDhcpOption *const *const sorted  = IS_IPv4 ? _sorted_options_4 : _sorted_options_6;
+    const int                        n       = IS_IPv4 ? G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options)
+                                                       : G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options);
+    int                              imin    = 0;
+    int                              imax    = n - 1;
+    int                              imid    = (n - 1) / 2;
+
+#if NM_MORE_ASSERTS > 10
+    nm_assert(n < G_MAXINT / 2);
+    if (IS_IPv4 && !NM_MORE_ASSERT_ONCE(10)) {
+        /* already checked */
+    } else if (!IS_IPv4 && !NM_MORE_ASSERT_ONCE(10)) {
+        /* already checked */
+    } else
+        _ASSERT_sorted(IS_IPv4, sorted, n);
+#endif
+
+    for (;;) {
+        const guint o = sorted[imid]->option_num;
 
-    while (requests[i].name) {
-        if (requests[i].option_num == option)
-            return requests[i].name + NM_STRLEN(REQPREFIX);
-        i++;
+        if (G_UNLIKELY(o == option))
+            return sorted[imid];
+
+        if (o < option)
+            imin = imid + 1;
+        else
+            imax = imid - 1;
+
+        if (G_UNLIKELY(imin > imax))
+            break;
+
+        imid = (imin + imax) / 2;
     }
 
     /* Option should always be found */
-    nm_assert_not_reached();
-    return NULL;
+    return nm_assert_unreachable_val(NULL);
 }
 
+/*****************************************************************************/
+
 void
-nm_dhcp_option_take_option(GHashTable *        options,
-                           const NMDhcpOption *requests,
-                           guint               option,
-                           char *              value)
+nm_dhcp_option_take_option(GHashTable *options, int addr_family, guint option, char *value)
 {
-    nm_assert(options);
-    nm_assert(requests);
+    nm_assert_addr_family(addr_family);
     nm_assert(value);
     nm_assert(g_utf8_validate(value, -1, NULL));
 
-    g_hash_table_insert(options, (gpointer) nm_dhcp_option_request_string(requests, option), value);
+    if (!options) {
+        nm_assert_not_reached();
+        g_free(value);
+        return;
+    }
+
+    g_hash_table_insert(options,
+                        (gpointer) nm_dhcp_option_request_string(addr_family, option),
+                        value);
 }
 
 void
-nm_dhcp_option_add_option(GHashTable *        options,
-                          const NMDhcpOption *requests,
-                          guint               option,
-                          const char *        value)
+nm_dhcp_option_add_option(GHashTable *options, int addr_family, guint option, const char *value)
 {
-    if (options)
-        nm_dhcp_option_take_option(options, requests, option, g_strdup(value));
+    nm_dhcp_option_take_option(options, addr_family, option, g_strdup(value));
 }
 
 void
-nm_dhcp_option_add_option_u64(GHashTable *        options,
-                              const NMDhcpOption *requests,
-                              guint               option,
-                              guint64             value)
+nm_dhcp_option_add_option_utf8safe_escape(GHashTable *  options,
+                                          int           addr_family,
+                                          guint         option,
+                                          const guint8 *data,
+                                          gsize         n_data)
 {
-    if (options)
-        nm_dhcp_option_take_option(options,
-                                   requests,
-                                   option,
-                                   g_strdup_printf("%" G_GUINT64_FORMAT, value));
+    gs_free char *to_free = NULL;
+    const char *  escaped;
+
+    escaped = nm_utils_buf_utf8safe_escape((char *) data, n_data, 0, &to_free);
+    nm_dhcp_option_add_option(options, addr_family, option, escaped ?: "");
 }
 
 void
-nm_dhcp_option_add_requests_to_options(GHashTable *options, const NMDhcpOption *requests)
+nm_dhcp_option_add_option_u64(GHashTable *options, int addr_family, guint option, guint64 value)
 {
-    guint i;
+    nm_dhcp_option_take_option(options,
+                               addr_family,
+                               option,
+                               g_strdup_printf("%" G_GUINT64_FORMAT, value));
+}
 
-    if (!options)
-        return;
+void
+nm_dhcp_option_add_option_in_addr(GHashTable *options,
+                                  int         addr_family,
+                                  guint       option,
+                                  in_addr_t   value)
+{
+    char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
+    nm_dhcp_option_add_option(options, addr_family, option, _nm_utils_inet4_ntop(value, sbuf));
+}
+
+void
+nm_dhcp_option_add_requests_to_options(GHashTable *options, int addr_family)
+{
+    const int                 IS_IPv4 = NM_IS_IPv4(addr_family);
+    const NMDhcpOption *const all_options =
+        IS_IPv4 ? _nm_dhcp_option_dhcp4_options : _nm_dhcp_option_dhcp6_options;
+    int n_options = IS_IPv4 ? G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options)
+                            : G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options);
+    int i;
 
-    for (i = 0; requests[i].name; i++) {
-        if (requests[i].include)
-            g_hash_table_insert(options, (gpointer) requests[i].name, g_strdup("1"));
+    for (i = 0; i < n_options; i++) {
+        if (all_options[i].include)
+            g_hash_table_insert(options, (gpointer) all_options[i].name, g_strdup("1"));
     }
 }
 
diff --git a/src/dhcp/nm-dhcp-options.h b/src/core/dhcp/nm-dhcp-options.h
index 6c867c40..585f1187 100644
--- a/src/dhcp/nm-dhcp-options.h
+++ b/src/core/dhcp/nm-dhcp-options.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
@@ -177,29 +177,50 @@ typedef enum {
 
 } NMDhcpOptionDhcp6Options;
 
+#define NM_DHCP_OPTION_REQPREFIX "requested_"
+
 typedef struct {
     const char *name;
     uint16_t    option_num;
     bool        include;
 } NMDhcpOption;
 
-extern const NMDhcpOption _nm_dhcp_option_dhcp4_options[];
-extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[];
+extern const NMDhcpOption _nm_dhcp_option_dhcp4_options[142];
+extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[16];
+
+static inline const char *
+nm_dhcp_option_get_name(const NMDhcpOption *option)
+{
+    nm_assert(option);
+    nm_assert(option->name);
+    nm_assert(NM_STR_HAS_PREFIX(option->name, NM_DHCP_OPTION_REQPREFIX));
+
+    return &option->name[NM_STRLEN(NM_DHCP_OPTION_REQPREFIX)];
+}
+
+const NMDhcpOption *nm_dhcp_option_find(int addr_family, guint option);
+
+static inline const char *
+nm_dhcp_option_request_string(int addr_family, guint option)
+{
+    return nm_dhcp_option_get_name(nm_dhcp_option_find(addr_family, option));
+}
 
-const char *nm_dhcp_option_request_string(const NMDhcpOption *requests, guint option);
-void        nm_dhcp_option_take_option(GHashTable *        options,
-                                       const NMDhcpOption *requests,
-                                       guint               option,
-                                       char *              value);
-void        nm_dhcp_option_add_option(GHashTable *        options,
-                                      const NMDhcpOption *requests,
-                                      guint               option,
-                                      const char *        value);
-void        nm_dhcp_option_add_option_u64(GHashTable *        options,
-                                          const NMDhcpOption *requests,
-                                          guint               option,
-                                          guint64             value);
-void nm_dhcp_option_add_requests_to_options(GHashTable *options, const NMDhcpOption *requests);
+void nm_dhcp_option_take_option(GHashTable *options, int addr_family, guint option, char *value);
+void
+nm_dhcp_option_add_option(GHashTable *options, int addr_family, guint option, const char *value);
+void nm_dhcp_option_add_option_utf8safe_escape(GHashTable *  options,
+                                               int           addr_family,
+                                               guint         option,
+                                               const guint8 *data,
+                                               gsize         n_data);
+void nm_dhcp_option_add_option_in_addr(GHashTable *options,
+                                       int         addr_family,
+                                       guint       option,
+                                       in_addr_t   value);
+void
+nm_dhcp_option_add_option_u64(GHashTable *options, int addr_family, guint option, guint64 value);
+void        nm_dhcp_option_add_requests_to_options(GHashTable *options, int addr_family);
 GHashTable *nm_dhcp_option_create_options_dict(void);
 
 #endif /* __NM_DHCP_OPTIONS_H__ */
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c
index f9297223..b92a9073 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/core/dhcp/nm-dhcp-systemd.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <stdlib.h>
 #include <unistd.h>
@@ -138,32 +138,26 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
     options = out_options ? nm_dhcp_option_create_options_dict() : NULL;
 
     _nm_utils_inet4_ntop(a_address.s_addr, addr_str);
-    nm_dhcp_option_add_option(options,
-                              _nm_dhcp_option_dhcp4_options,
-                              NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS,
-                              addr_str);
+    nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, addr_str);
 
     a_plen = nm_utils_ip4_netmask_to_prefix(a_netmask.s_addr);
     nm_dhcp_option_add_option(options,
-                              _nm_dhcp_option_dhcp4_options,
+                              AF_INET,
                               NM_DHCP_OPTION_DHCP4_SUBNET_MASK,
                               _nm_utils_inet4_ntop(a_netmask.s_addr, addr_str));
 
     nm_dhcp_option_add_option_u64(options,
-                                  _nm_dhcp_option_dhcp4_options,
+                                  AF_INET,
                                   NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME,
                                   a_lifetime);
     nm_dhcp_option_add_option_u64(options,
-                                  _nm_dhcp_option_dhcp4_options,
+                                  AF_INET,
                                   NM_DHCP_OPTION_DHCP4_NM_EXPIRY,
                                   (guint64)(ts_time + a_lifetime));
 
     if (sd_dhcp_lease_get_next_server(lease, &a_next_server) == 0) {
         _nm_utils_inet4_ntop(a_next_server.s_addr, addr_str);
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
-                                  NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER,
-                                  addr_str);
+        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER, addr_str);
     }
 
     nm_ip4_config_add_address(ip4_config,
@@ -179,18 +173,12 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
 
     if (sd_dhcp_lease_get_server_identifier(lease, &server_id) >= 0) {
         _nm_utils_inet4_ntop(server_id.s_addr, addr_str);
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
-                                  NM_DHCP_OPTION_DHCP4_SERVER_ID,
-                                  addr_str);
+        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_SERVER_ID, addr_str);
     }
 
     if (sd_dhcp_lease_get_broadcast(lease, &broadcast) >= 0) {
         _nm_utils_inet4_ntop(broadcast.s_addr, addr_str);
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
-                                  NM_DHCP_OPTION_DHCP4_BROADCAST,
-                                  addr_str);
+        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_BROADCAST, addr_str);
     }
 
     num = sd_dhcp_lease_get_dns(lease, &addr_list);
@@ -208,7 +196,7 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
             nm_ip4_config_add_nameserver(ip4_config, addr_list[i].s_addr);
         }
         nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
+                                  AF_INET,
                                   NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER,
                                   str->str);
     }
@@ -221,7 +209,7 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
             nm_ip4_config_add_search(ip4_config, search_domains[i]);
         }
         nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
+                                  AF_INET,
                                   NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST,
                                   str->str);
     }
@@ -230,10 +218,7 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
         gs_strfreev char **domains = NULL;
         char **            d;
 
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
-                                  NM_DHCP_OPTION_DHCP4_DOMAIN_NAME,
-                                  s);
+        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, s);
 
         /* Multiple domains sometimes stuffed into option 15 "Domain Name".
          * As systemd escapes such characters, split them at \\032. */
@@ -243,10 +228,7 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
     }
 
     if (sd_dhcp_lease_get_hostname(lease, &s) >= 0) {
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
-                                  NM_DHCP_OPTION_DHCP4_HOST_NAME,
-                                  s);
+        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_HOST_NAME, s);
     }
 
     num = sd_dhcp_lease_get_routes(lease, &routes);
@@ -348,12 +330,12 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
 
         if (str_classless && str_classless->len > 0)
             nm_dhcp_option_add_option(options,
-                                      _nm_dhcp_option_dhcp4_options,
+                                      AF_INET,
                                       NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
                                       str_classless->str);
         if (str_static && str_static->len > 0)
             nm_dhcp_option_add_option(options,
-                                      _nm_dhcp_option_dhcp4_options,
+                                      AF_INET,
                                       NM_DHCP_OPTION_DHCP4_STATIC_ROUTE,
                                       str_static->str);
     }
@@ -400,17 +382,11 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
                 }),
                 NULL);
         }
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
-                                  NM_DHCP_OPTION_DHCP4_ROUTER,
-                                  str->str);
+        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_ROUTER, str->str);
     }
 
     if (sd_dhcp_lease_get_mtu(lease, &mtu) >= 0 && mtu) {
-        nm_dhcp_option_add_option_u64(options,
-                                      _nm_dhcp_option_dhcp4_options,
-                                      NM_DHCP_OPTION_DHCP4_INTERFACE_MTU,
-                                      mtu);
+        nm_dhcp_option_add_option_u64(options, AF_INET, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, mtu);
         nm_ip4_config_set_mtu(ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
     }
 
@@ -421,38 +397,29 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
             _nm_utils_inet4_ntop(addr_list[i].s_addr, addr_str);
             g_string_append(nm_gstring_add_space_delimiter(str), addr_str);
         }
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
-                                  NM_DHCP_OPTION_DHCP4_NTP_SERVER,
-                                  str->str);
+        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NTP_SERVER, str->str);
     }
 
     if (sd_dhcp_lease_get_root_path(lease, &s) >= 0) {
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
-                                  NM_DHCP_OPTION_DHCP4_ROOT_PATH,
-                                  s);
+        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_ROOT_PATH, s);
     }
 
     if (sd_dhcp_lease_get_t1(lease, &renewal) >= 0) {
         nm_dhcp_option_add_option_u64(options,
-                                      _nm_dhcp_option_dhcp4_options,
+                                      AF_INET,
                                       NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME,
                                       renewal);
     }
 
     if (sd_dhcp_lease_get_t2(lease, &rebinding) >= 0) {
         nm_dhcp_option_add_option_u64(options,
-                                      _nm_dhcp_option_dhcp4_options,
+                                      AF_INET,
                                       NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME,
                                       rebinding);
     }
 
     if (sd_dhcp_lease_get_timezone(lease, &s) >= 0) {
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp4_options,
-                                  NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE,
-                                  s);
+        nm_dhcp_option_add_option(options, AF_INET, NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE, s);
     }
 
     if (sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len) >= 0)
@@ -473,10 +440,7 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
                 g_free(option_string);
                 continue;
             }
-            nm_dhcp_option_take_option(options,
-                                       _nm_dhcp_option_dhcp4_options,
-                                       private_options[i].code,
-                                       option_string);
+            nm_dhcp_option_take_option(options, AF_INET, private_options[i].code, option_string);
         }
     }
     NM_SET_OUT(out_options, g_steal_pointer(&options));
@@ -518,7 +482,7 @@ bound4_handle(NMDhcpSystemd *self, gboolean extended)
         return;
     }
 
-    nm_dhcp_option_add_requests_to_options(options, _nm_dhcp_option_dhcp4_options);
+    nm_dhcp_option_add_requests_to_options(options, AF_INET);
     dhcp_lease_save(lease, priv->lease_file);
 
     nm_dhcp_client_set_state(NM_DHCP_CLIENT(self),
@@ -568,6 +532,8 @@ dhcp_event_cb(sd_dhcp_client *client, int event, gpointer user_data)
             return -ENOMSG;
         }
         break;
+    case SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE:
+        break;
     default:
         _LOGW("unhandled DHCP event %d", event);
         break;
@@ -599,6 +565,9 @@ ip4_start(NMDhcpClient *client,
     const char *           hostname;
     const char *           mud_url;
     int                    r, i;
+    GBytes *               bcast_hwaddr;
+    const uint8_t *        bcast_hwaddr_arr;
+    gsize                  bcast_hwaddr_len;
 
     g_return_val_if_fail(!priv->client4, FALSE);
     g_return_val_if_fail(!priv->client6, FALSE);
@@ -623,7 +592,19 @@ ip4_start(NMDhcpClient *client,
         nm_utils_error_set_literal(error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address");
         return FALSE;
     }
-    r = sd_dhcp_client_set_mac(sd_client, hwaddr_arr, hwaddr_len, (guint16) arp_type);
+
+    bcast_hwaddr_arr = NULL;
+    if ((bcast_hwaddr = nm_dhcp_client_get_broadcast_hw_addr(NM_DHCP_CLIENT(self)))) {
+        bcast_hwaddr_arr = g_bytes_get_data(bcast_hwaddr, &bcast_hwaddr_len);
+        if (bcast_hwaddr_len != hwaddr_len)
+            bcast_hwaddr_arr = NULL;
+    }
+
+    r = sd_dhcp_client_set_mac(sd_client,
+                               hwaddr_arr,
+                               bcast_hwaddr_arr,
+                               hwaddr_len,
+                               (guint16) arp_type);
     if (r < 0) {
         nm_utils_error_set_errno(error, r, "failed to set MAC address: %s");
         return FALSE;
@@ -685,7 +666,7 @@ ip4_start(NMDhcpClient *client,
     }
 
     /* Add requested options */
-    for (i = 0; _nm_dhcp_option_dhcp4_options[i].name; i++) {
+    for (i = 0; i < (int) G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options); i++) {
         if (_nm_dhcp_option_dhcp4_options[i].include) {
             nm_assert(_nm_dhcp_option_dhcp4_options[i].option_num <= 255);
             r = sd_dhcp_client_set_request_option(sd_client,
@@ -804,10 +785,7 @@ lease_to_ip6_config(NMDedupMultiIndex *multi_idx,
         g_string_append(nm_gstring_add_space_delimiter(str), addr_str);
     };
     if (str->len)
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp6_options,
-                                  NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS,
-                                  str->str);
+        nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS, str->str);
 
     if (!info_only && nm_ip6_config_get_num_addresses(ip6_config) == 0) {
         g_set_error_literal(error,
@@ -825,10 +803,7 @@ lease_to_ip6_config(NMDedupMultiIndex *multi_idx,
             g_string_append(nm_gstring_add_space_delimiter(str), addr_str);
             nm_ip6_config_add_nameserver(ip6_config, &dns[i]);
         }
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp6_options,
-                                  NM_DHCP_OPTION_DHCP6_DNS_SERVERS,
-                                  str->str);
+        nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_DNS_SERVERS, str->str);
     }
 
     num = sd_dhcp6_lease_get_domains(lease, &domains);
@@ -838,17 +813,11 @@ lease_to_ip6_config(NMDedupMultiIndex *multi_idx,
             g_string_append(nm_gstring_add_space_delimiter(str), domains[i]);
             nm_ip6_config_add_search(ip6_config, domains[i]);
         }
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp6_options,
-                                  NM_DHCP_OPTION_DHCP6_DOMAIN_LIST,
-                                  str->str);
+        nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_DOMAIN_LIST, str->str);
     }
 
     if (sd_dhcp6_lease_get_fqdn(lease, &s) >= 0) {
-        nm_dhcp_option_add_option(options,
-                                  _nm_dhcp_option_dhcp6_options,
-                                  NM_DHCP_OPTION_DHCP6_FQDN,
-                                  s);
+        nm_dhcp_option_add_option(options, AF_INET6, NM_DHCP_OPTION_DHCP6_FQDN, s);
     }
 
     NM_SET_OUT(out_options, g_steal_pointer(&options));
@@ -969,8 +938,11 @@ ip6_start(NMDhcpClient *            client,
 
     _LOGT("dhcp-client6: set %p", sd_client);
 
-    if (nm_dhcp_client_get_info_only(client))
-        sd_dhcp6_client_set_information_request(sd_client, 1);
+    if (nm_dhcp_client_get_info_only(client)) {
+        sd_dhcp6_client_set_address_request(sd_client, 0);
+        if (needed_prefixes == 0)
+            sd_dhcp6_client_set_information_request(sd_client, 1);
+    }
 
     r = sd_dhcp6_client_set_iaid(sd_client, nm_dhcp_client_get_iaid(client));
     if (r < 0) {
@@ -1000,7 +972,7 @@ ip6_start(NMDhcpClient *            client,
     }
 
     /* Add requested options */
-    for (i = 0; _nm_dhcp_option_dhcp6_options[i].name; i++) {
+    for (i = 0; i < (int) G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options); i++) {
         if (_nm_dhcp_option_dhcp6_options[i].include) {
             r = sd_dhcp6_client_set_request_option(sd_client,
                                                    _nm_dhcp_option_dhcp6_options[i].option_num);
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/core/dhcp/nm-dhcp-utils.c
index bf10e2b5..646411e2 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/core/dhcp/nm-dhcp-utils.c
@@ -1,14 +1,17 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2010 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <unistd.h>
 #include <arpa/inet.h>
 
+#include "nm-std-aux/unaligned.h"
 #include "nm-glib-aux/nm-dedup-multi.h"
+#include "nm-glib-aux/nm-str-buf.h"
+#include "systemd/nm-sd-utils-shared.h"
 
 #include "nm-dhcp-utils.h"
 #include "nm-utils.h"
@@ -834,3 +837,285 @@ nm_dhcp_utils_get_dhcp6_event_id(GHashTable *lease)
 
     return g_strdup_printf("%s|%s", iaid, start);
 }
+
+/*****************************************************************************/
+
+gboolean
+nm_dhcp_lease_data_parse_u16(const guint8 *data, gsize n_data, uint16_t *out_val)
+{
+    if (n_data != 2)
+        return FALSE;
+
+    *out_val = unaligned_read_be16(data);
+    return TRUE;
+}
+
+gboolean
+nm_dhcp_lease_data_parse_mtu(const guint8 *data, gsize n_data, uint16_t *out_val)
+{
+    uint16_t mtu;
+
+    if (!nm_dhcp_lease_data_parse_u16(data, n_data, &mtu))
+        return FALSE;
+
+    if (mtu < 68) {
+        /* https://tools.ietf.org/html/rfc2132#section-5.1:
+         *
+         * The minimum legal value for the MTU is 68. */
+        return FALSE;
+    }
+
+    *out_val = mtu;
+    return TRUE;
+}
+
+gboolean
+nm_dhcp_lease_data_parse_cstr(const guint8 *data, gsize n_data, gsize *out_new_len)
+{
+    /* WARNING: this function only validates that the string does not contain
+     * NUL characters (and ignores trailing NULs). It does not check character
+     * encoding! */
+
+    while (n_data > 0 && data[n_data - 1] == '\0')
+        n_data--;
+
+    if (n_data > 0) {
+        if (memchr(data, n_data, '\0')) {
+            /* we accept trailing NUL, but none in between.
+             *
+             * https://tools.ietf.org/html/rfc2132#section-2
+             * https://github.com/systemd/systemd/issues/1337 */
+            return FALSE;
+        }
+    }
+
+    NM_SET_OUT(out_new_len, n_data);
+    return TRUE;
+}
+
+char *
+nm_dhcp_lease_data_parse_domain_validate(const char *str)
+{
+    gs_free char *s = NULL;
+
+    s = nm_sd_dns_name_normalize(str);
+    if (!s)
+        return NULL;
+
+    if (nm_str_is_empty(s) || (s[0] == '.' && s[1] == '\0')) {
+        /* root domains are not allowed. */
+        return NULL;
+    }
+
+    if (nm_utils_is_localhost(s))
+        return NULL;
+
+    if (!g_utf8_validate(s, -1, NULL)) {
+        /* the result must be valid UTF-8. */
+        return NULL;
+    }
+
+    return g_steal_pointer(&s);
+}
+
+gboolean
+nm_dhcp_lease_data_parse_domain(const guint8 *data, gsize n_data, char **out_val)
+{
+    gs_free char *str1_free = NULL;
+    const char *  str1;
+    gs_free char *s = NULL;
+
+    /* this is mostly the same as systemd's lease_parse_domain(). */
+
+    if (!nm_dhcp_lease_data_parse_cstr(data, n_data, &n_data))
+        return FALSE;
+
+    if (n_data == 0) {
+        /* empty domains are rejected. See
+         * https://tools.ietf.org/html/rfc2132#section-3.14
+         * https://tools.ietf.org/html/rfc2132#section-3.17
+         *
+         *   Its minimum length is 1.
+         *
+         * Note that this is *after* we potentially stripped trailing NULs.
+         */
+        return FALSE;
+    }
+
+    str1 = nm_strndup_a(300, (char *) data, n_data, &str1_free);
+
+    s = nm_dhcp_lease_data_parse_domain_validate(str1);
+    if (!s)
+        return FALSE;
+
+    *out_val = g_steal_pointer(&s);
+    return TRUE;
+}
+
+gboolean
+nm_dhcp_lease_data_parse_in_addr(const guint8 *data, gsize n_data, in_addr_t *out_val)
+{
+    /* - option 1, https://tools.ietf.org/html/rfc2132#section-3.3
+     * - option 28, https://tools.ietf.org/html/rfc2132#section-5.3
+     */
+
+    if (n_data != 4)
+        return FALSE;
+
+    *out_val = unaligned_read_ne32(data);
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+lease_option_print_label(NMStrBuf *sbuf, size_t n_label, const uint8_t **datap, size_t *n_datap)
+{
+    gsize i;
+
+    for (i = 0; i < n_label; ++i) {
+        uint8_t c = 0;
+
+        if (!nm_dhcp_lease_data_consume(datap, n_datap, &c, sizeof(c)))
+            return FALSE;
+
+        switch (c) {
+        case 'a' ... 'z':
+        case 'A' ... 'Z':
+        case '0' ... '9':
+        case '-':
+        case '_':
+            nm_str_buf_append_c(sbuf, c);
+            break;
+        case '.':
+        case '\\':
+            nm_str_buf_append_c2(sbuf, '\\', c);
+            break;
+        default:
+            nm_str_buf_append_printf(sbuf, "\\%3d", c);
+        }
+    }
+
+    return TRUE;
+}
+
+static char *
+lease_option_print_domain_name(const uint8_t * cache,
+                               size_t *        n_cachep,
+                               const uint8_t **datap,
+                               size_t *        n_datap)
+{
+    nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_40, FALSE);
+    const uint8_t *          domain;
+    size_t                   n_domain;
+    size_t                   n_cache   = *n_cachep;
+    const uint8_t **         domainp   = datap;
+    size_t *                 n_domainp = n_datap;
+    gboolean                 first     = TRUE;
+    uint8_t                  c;
+
+    /*
+     * We are given two adjacent memory regions. The @cache contains alreday parsed
+     * domain names, and the @datap contains the remaining data to parse.
+     *
+     * A domain name is formed from a sequence of labels. Each label start with
+     * a length byte, where the two most significant bits are unset. A zero-length
+     * label indicates the end of the domain name.
+     *
+     * Alternatively, a label can be followed by an offset (indicated by the two
+     * most significant bits being set in the next byte that is read). The offset
+     * is an offset into the cache, where the next label of the domain name can
+     * be found.
+     *
+     * Note, that each time a jump to an offset is performed, the size of the
+     * cache shrinks, so this is guaranteed to terminate.
+     */
+    if (cache + n_cache != *datap)
+        return NULL;
+
+    for (;;) {
+        if (!nm_dhcp_lease_data_consume(domainp, n_domainp, &c, sizeof(c)))
+            return NULL;
+
+        switch (c & 0xC0) {
+        case 0x00: /* label length */
+        {
+            size_t n_label = c;
+
+            if (n_label == 0) {
+                /*
+                 * We reached the final label of the domain name. Adjust
+                 * the cache to include the consumed data, and return.
+                 */
+                *n_cachep = *datap - cache;
+                return nm_str_buf_finalize(&sbuf, NULL);
+            }
+
+            if (!first)
+                nm_str_buf_append_c(&sbuf, '.');
+            else
+                first = FALSE;
+
+            if (!lease_option_print_label(&sbuf, n_label, domainp, n_domainp))
+                return NULL;
+
+            break;
+        }
+        case 0xC0: /* back pointer */
+        {
+            size_t offset = (c & 0x3F) << 16;
+
+            /*
+             * The offset is given as two bytes (in big endian), where the
+             * two high bits are masked out.
+             */
+
+            if (!nm_dhcp_lease_data_consume(domainp, n_domainp, &c, sizeof(c)))
+                return NULL;
+
+            offset += c;
+
+            if (offset >= n_cache)
+                return NULL;
+
+            domain   = cache + offset;
+            n_domain = n_cache - offset;
+            n_cache  = offset;
+
+            domainp   = &domain;
+            n_domainp = &n_domain;
+
+            break;
+        }
+        default:
+            return NULL;
+        }
+    }
+}
+
+char **
+nm_dhcp_lease_data_parse_search_list(const guint8 *data, gsize n_data)
+{
+    GPtrArray *   array   = NULL;
+    const guint8 *cache   = data;
+    gsize         n_cache = 0;
+
+    for (;;) {
+        gs_free char *s = NULL;
+
+        s = lease_option_print_domain_name(cache, &n_cache, &data, &n_data);
+        if (!s)
+            break;
+
+        if (!array)
+            array = g_ptr_array_new();
+
+        g_ptr_array_add(array, g_steal_pointer(&s));
+    }
+
+    if (!array)
+        return NULL;
+
+    g_ptr_array_add(array, NULL);
+    return (char **) g_ptr_array_free(array, FALSE);
+}
diff --git a/src/dhcp/nm-dhcp-utils.h b/src/core/dhcp/nm-dhcp-utils.h
index c72d9617..69715f90 100644
--- a/src/dhcp/nm-dhcp-utils.h
+++ b/src/core/dhcp/nm-dhcp-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
@@ -36,8 +36,35 @@ gboolean nm_dhcp_utils_get_leasefile_path(int         addr_family,
                                           const char *uuid,
                                           char **     out_leasefile_path);
 
-char **nm_dhcp_parse_search_list(guint8 *data, size_t n_data);
-
 char *nm_dhcp_utils_get_dhcp6_event_id(GHashTable *lease);
 
+/*****************************************************************************/
+
+static inline gboolean
+nm_dhcp_lease_data_consume(const uint8_t **datap, size_t *n_datap, void *out, size_t n_out)
+{
+    if (*n_datap < n_out)
+        return FALSE;
+
+    memcpy(out, *datap, n_out);
+    *datap += n_out;
+    *n_datap -= n_out;
+    return TRUE;
+}
+
+static inline gboolean
+nm_dhcp_lease_data_consume_in_addr(const uint8_t **datap, size_t *n_datap, in_addr_t *addrp)
+{
+    return nm_dhcp_lease_data_consume(datap, n_datap, addrp, sizeof(struct in_addr));
+}
+
+char *nm_dhcp_lease_data_parse_domain_validate(const char *str);
+
+gboolean nm_dhcp_lease_data_parse_u16(const guint8 *data, gsize n_data, guint16 *out_val);
+gboolean nm_dhcp_lease_data_parse_mtu(const guint8 *data, gsize n_data, guint16 *out_val);
+gboolean nm_dhcp_lease_data_parse_cstr(const guint8 *data, gsize n_data, gsize *out_new_len);
+gboolean nm_dhcp_lease_data_parse_domain(const guint8 *data, gsize n_data, char **out_val);
+gboolean nm_dhcp_lease_data_parse_in_addr(const guint8 *data, gsize n_data, in_addr_t *out_val);
+char **  nm_dhcp_lease_data_parse_search_list(const guint8 *data, gsize n_data);
+
 #endif /* __NETWORKMANAGER_DHCP_UTILS_H__ */
diff --git a/src/dhcp/tests/meson.build b/src/core/dhcp/tests/meson.build
index 59a647cc..e43c8cab 100644
--- a/src/dhcp/tests/meson.build
+++ b/src/core/dhcp/tests/meson.build
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 test_units = [
   'test-dhcp-dhclient',
@@ -9,7 +9,7 @@ foreach test_unit: test_units
   exe = executable(
     test_unit,
     test_unit + '.c',
-    dependencies: libnetwork_manager_test_dep,
+    dependencies: libNetworkManagerTest_dep,
     c_args: test_c_flags,
   )
 
diff --git a/src/dhcp/tests/test-dhclient-commented-duid.leases b/src/core/dhcp/tests/test-dhclient-commented-duid.leases
index 3e46ae7d..3e46ae7d 100644
--- a/src/dhcp/tests/test-dhclient-commented-duid.leases
+++ b/src/core/dhcp/tests/test-dhclient-commented-duid.leases
diff --git a/src/dhcp/tests/test-dhclient-duid.leases b/src/core/dhcp/tests/test-dhclient-duid.leases
index 229331d4..229331d4 100644
--- a/src/dhcp/tests/test-dhclient-duid.leases
+++ b/src/core/dhcp/tests/test-dhclient-duid.leases
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/core/dhcp/tests/test-dhcp-dhclient.c
index da24f3c5..77626f69 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/core/dhcp/tests/test-dhcp-dhclient.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <unistd.h>
 #include <arpa/inet.h>
@@ -20,7 +20,7 @@
 
 #include "nm-test-utils-core.h"
 
-#define TEST_DIR    NM_BUILD_SRCDIR "/src/dhcp/tests"
+#define TEST_DIR    NM_BUILD_SRCDIR "/src/core/dhcp/tests"
 #define TEST_MUDURL "https://example.com/mud.json"
 
 static void
diff --git a/src/dhcp/tests/test-dhcp-utils.c b/src/core/dhcp/tests/test-dhcp-utils.c
index 50245c79..9b54e2cd 100644
--- a/src/dhcp/tests/test-dhcp-utils.c
+++ b/src/core/dhcp/tests/test-dhcp-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <netinet/in.h>
 #include <arpa/inet.h>
@@ -13,10 +13,13 @@
 #include "nm-utils.h"
 
 #include "dhcp/nm-dhcp-utils.h"
+#include "dhcp/nm-dhcp-options.h"
 #include "platform/nm-platform.h"
 
 #include "nm-test-utils-core.h"
 
+/*****************************************************************************/
+
 static NMIP4Config *
 _ip4_config_from_options(int ifindex, const char *iface, GHashTable *options, guint32 route_metric)
 {
@@ -202,7 +205,7 @@ test_parse_search_list(void)
     char ** domains;
 
     data    = (guint8[]){0x05, 'l', 'o', 'c', 'a', 'l', 0x00};
-    domains = nm_dhcp_parse_search_list(data, 7);
+    domains = nm_dhcp_lease_data_parse_search_list(data, 7);
     g_assert(domains);
     g_assert_cmpint(g_strv_length(domains), ==, 1);
     g_assert_cmpstr(domains[0], ==, "local");
@@ -211,7 +214,7 @@ test_parse_search_list(void)
     data    = (guint8[]){0x04, 't',  'e',  's', 't', 0x07, 'e',  'x',  'a',  'm', 'p', 'l',
                       'e',  0x03, 'c',  'o', 'm', 0x00, 0xc0, 0x05, 0x03, 'a', 'b', 'c',
                       0xc0, 0x0d, 0x06, 'f', 'o', 'o',  'b',  'a',  'r',  0x00};
-    domains = nm_dhcp_parse_search_list(data, 34);
+    domains = nm_dhcp_lease_data_parse_search_list(data, 34);
     g_assert(domains);
     g_assert_cmpint(g_strv_length(domains), ==, 4);
     g_assert_cmpstr(domains[0], ==, "test.example.com");
@@ -226,7 +229,7 @@ test_parse_search_list(void)
         'a',
         'd',
     };
-    domains = nm_dhcp_parse_search_list(data, 4);
+    domains = nm_dhcp_lease_data_parse_search_list(data, 4);
     g_assert(!domains);
 
     data = (guint8[]){
@@ -241,7 +244,7 @@ test_parse_search_list(void)
         'a',
         'd',
     };
-    domains = nm_dhcp_parse_search_list(data, 10);
+    domains = nm_dhcp_lease_data_parse_search_list(data, 10);
     g_assert(domains);
     g_assert_cmpint(g_strv_length(domains), ==, 1);
     g_assert_cmpstr(domains[0], ==, "okay");
@@ -740,6 +743,46 @@ test_client_id_from_string(void)
     COMPARE_ID(endcolon, TRUE, endcolon, strlen(endcolon));
 }
 
+/*****************************************************************************/
+
+static void
+test_dhcp_opt_list(gconstpointer test_data)
+{
+    const gboolean            IS_IPv4     = (GPOINTER_TO_INT(test_data) == 0);
+    const int                 addr_family = IS_IPv4 ? AF_INET : AF_INET6;
+    const NMDhcpOption *const options =
+        IS_IPv4 ? _nm_dhcp_option_dhcp4_options : _nm_dhcp_option_dhcp6_options;
+    const guint n = (IS_IPv4 ? G_N_ELEMENTS(_nm_dhcp_option_dhcp4_options)
+                             : G_N_ELEMENTS(_nm_dhcp_option_dhcp6_options));
+    guint       i;
+    guint       j;
+
+    g_assert(options);
+    g_assert(n > 0);
+
+    for (i = 0; i < n; i++) {
+        const NMDhcpOption *const opt = &options[i];
+
+        g_assert_cmpstr(opt->name, !=, NULL);
+        g_assert(NM_STR_HAS_PREFIX(opt->name, NM_DHCP_OPTION_REQPREFIX));
+
+        for (j = 0; j < i; j++) {
+            const NMDhcpOption *const opt2 = &options[j];
+
+            g_assert_cmpstr(opt->name, !=, opt2->name);
+            g_assert_cmpint(opt->option_num, !=, opt2->option_num);
+        }
+    }
+
+    for (i = 0; i < n; i++) {
+        const NMDhcpOption *const opt = &options[i];
+
+        g_assert(opt == nm_dhcp_option_find(addr_family, opt->option_num));
+    }
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -776,6 +819,8 @@ main(int argc, char **argv)
     g_test_add_func("/dhcp/client-id-from-string", test_client_id_from_string);
     g_test_add_func("/dhcp/vendor-option-metered", test_vendor_option_metered);
     g_test_add_func("/dhcp/parse-search-list", test_parse_search_list);
+    g_test_add_data_func("/dhcp/test_dhcp_opt_list/IPv4", GINT_TO_POINTER(0), test_dhcp_opt_list);
+    g_test_add_data_func("/dhcp/test_dhcp_opt_list/IPv6", GINT_TO_POINTER(1), test_dhcp_opt_list);
 
     return g_test_run();
 }
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/core/dns/nm-dns-dnsmasq.c
index 97f1dfab..dcff98e9 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/core/dns/nm-dns-dnsmasq.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 Dan Williams <dcbw@redhat.com>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dns-dnsmasq.h"
 
@@ -804,7 +804,7 @@ add_global_config(NMDnsDnsmasq *           self,
 }
 
 static void
-add_ip_config(NMDnsDnsmasq *self, GVariantBuilder *servers, const NMDnsIPConfigData *ip_data)
+add_ip_config(NMDnsDnsmasq *self, GVariantBuilder *servers, const NMDnsConfigIPData *ip_data)
 {
     NMIPConfig *  ip_config = ip_data->ip_config;
     gconstpointer addr;
@@ -850,7 +850,7 @@ create_update_args(NMDnsDnsmasq *           self,
                    const char *             hostname)
 {
     GVariantBuilder          servers;
-    const NMDnsIPConfigData *ip_data;
+    const NMDnsConfigIPData *ip_data;
 
     g_variant_builder_init(&servers, G_VARIANT_TYPE("aas"));
 
diff --git a/src/dns/nm-dns-dnsmasq.h b/src/core/dns/nm-dns-dnsmasq.h
index d75eef55..bd6d4c60 100644
--- a/src/dns/nm-dns-dnsmasq.h
+++ b/src/core/dns/nm-dns-dnsmasq.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 Red Hat, Inc.
  */
diff --git a/src/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c
index 3fc9a396..f318b9b8 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/core/dns/nm-dns-manager.c
@@ -1,11 +1,11 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2005 Colin Walters <walters@redhat.com>
  * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2005 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <fcntl.h>
 #include <resolv.h>
@@ -80,14 +80,18 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMDnsManager, PROP_MODE, PROP_RC_MANAGER, PROP_CONF
 static guint signals[LAST_SIGNAL] = {0};
 
 typedef struct {
-    GHashTable *configs;
-    CList       ip_config_lst_head;
-    GVariant *  config_variant;
+    GHashTable *configs_dict;
+    CList       configs_lst_head;
 
-    NMDnsIPConfigData *best_ip_config_4;
-    NMDnsIPConfigData *best_ip_config_6;
+    CList     ip_configs_lst_head;
+    GVariant *config_variant;
 
-    bool ip_config_lst_need_sort : 1;
+    NMDnsConfigIPData *best_ip_config_4;
+    NMDnsConfigIPData *best_ip_config_6;
+
+    bool ip_configs_lst_need_sort : 1;
+
+    bool configs_lst_need_sort : 1;
 
     bool dns_touched : 1;
     bool is_stopped : 1;
@@ -158,7 +162,7 @@ NM_DEFINE_SINGLETON_GETTER(NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER
 /*****************************************************************************/
 
 static void
-_ip_config_dns_priority_changed(gpointer config, GParamSpec *pspec, NMDnsIPConfigData *ip_data);
+_ip_config_dns_priority_changed(gpointer config, GParamSpec *pspec, NMDnsConfigIPData *ip_data);
 
 /*****************************************************************************/
 
@@ -207,7 +211,7 @@ static NM_UTILS_LOOKUP_STR_DEFINE(
 /*****************************************************************************/
 
 static void
-_ASSERT_config_data(const NMDnsConfigData *data)
+_ASSERT_dns_config_data(const NMDnsConfigData *data)
 {
     nm_assert(data);
     nm_assert(NM_IS_DNS_MANAGER(data->self));
@@ -215,10 +219,10 @@ _ASSERT_config_data(const NMDnsConfigData *data)
 }
 
 static void
-_ASSERT_ip_config_data(const NMDnsIPConfigData *ip_data)
+_ASSERT_dns_config_ip_data(const NMDnsConfigIPData *ip_data)
 {
     nm_assert(ip_data);
-    _ASSERT_config_data(ip_data->data);
+    _ASSERT_dns_config_data(ip_data->data);
     nm_assert(NM_IS_IP_CONFIG(ip_data->ip_config));
     nm_assert(c_list_contains(&ip_data->data->data_lst_head, &ip_data->data_lst));
     nm_assert(ip_data->data->ifindex == nm_ip_config_get_ifindex(ip_data->ip_config));
@@ -244,23 +248,25 @@ _ASSERT_ip_config_data(const NMDnsIPConfigData *ip_data)
 #endif
 }
 
-static NMDnsIPConfigData *
-_ip_config_data_new(NMDnsConfigData *data, NMIPConfig *ip_config, NMDnsIPConfigType ip_config_type)
+static NMDnsConfigIPData *
+_dns_config_ip_data_new(NMDnsConfigData * data,
+                        NMIPConfig *      ip_config,
+                        NMDnsIPConfigType ip_config_type)
 {
-    NMDnsIPConfigData *ip_data;
+    NMDnsConfigIPData *ip_data;
 
-    _ASSERT_config_data(data);
+    _ASSERT_dns_config_data(data);
     nm_assert(NM_IS_IP_CONFIG(ip_config));
     nm_assert(ip_config_type != NM_DNS_IP_CONFIG_TYPE_REMOVED);
 
-    ip_data  = g_slice_new(NMDnsIPConfigData);
-    *ip_data = (NMDnsIPConfigData){
+    ip_data  = g_slice_new(NMDnsConfigIPData);
+    *ip_data = (NMDnsConfigIPData){
         .data           = data,
         .ip_config      = g_object_ref(ip_config),
         .ip_config_type = ip_config_type,
     };
     c_list_link_tail(&data->data_lst_head, &ip_data->data_lst);
-    c_list_link_tail(&NM_DNS_MANAGER_GET_PRIVATE(data->self)->ip_config_lst_head,
+    c_list_link_tail(&NM_DNS_MANAGER_GET_PRIVATE(data->self)->ip_configs_lst_head,
                      &ip_data->ip_config_lst);
 
     g_signal_connect(ip_config,
@@ -269,14 +275,14 @@ _ip_config_data_new(NMDnsConfigData *data, NMIPConfig *ip_config, NMDnsIPConfigT
                      (GCallback) _ip_config_dns_priority_changed,
                      ip_data);
 
-    _ASSERT_ip_config_data(ip_data);
+    _ASSERT_dns_config_ip_data(ip_data);
     return ip_data;
 }
 
 static void
-_ip_config_data_free(NMDnsIPConfigData *ip_data)
+_dns_config_ip_data_free(NMDnsConfigIPData *ip_data)
 {
-    _ASSERT_ip_config_data(ip_data);
+    _ASSERT_dns_config_ip_data(ip_data);
 
     c_list_unlink_stale(&ip_data->data_lst);
     c_list_unlink_stale(&ip_data->ip_config_lst);
@@ -292,15 +298,15 @@ _ip_config_data_free(NMDnsIPConfigData *ip_data)
     nm_g_slice_free(ip_data);
 }
 
-static NMDnsIPConfigData *
-_config_data_find_ip_config(NMDnsConfigData *data, NMIPConfig *ip_config)
+static NMDnsConfigIPData *
+_dns_config_data_find_ip_config(NMDnsConfigData *data, NMIPConfig *ip_config)
 {
-    NMDnsIPConfigData *ip_data;
+    NMDnsConfigIPData *ip_data;
 
-    _ASSERT_config_data(data);
+    _ASSERT_dns_config_data(data);
 
     c_list_for_each_entry (ip_data, &data->data_lst_head, data_lst) {
-        _ASSERT_ip_config_data(ip_data);
+        _ASSERT_dns_config_ip_data(ip_data);
 
         if (ip_data->ip_config == ip_config)
             return ip_data;
@@ -309,19 +315,20 @@ _config_data_find_ip_config(NMDnsConfigData *data, NMIPConfig *ip_config)
 }
 
 static void
-_config_data_free(NMDnsConfigData *data)
+_dns_config_data_free(NMDnsConfigData *data)
 {
-    _ASSERT_config_data(data);
+    _ASSERT_dns_config_data(data);
 
     nm_assert(c_list_is_empty(&data->data_lst_head));
+    c_list_unlink_stale(&data->configs_lst);
     nm_g_slice_free(data);
 }
 
 static int
-_ip_config_lst_cmp(const CList *a_lst, const CList *b_lst, const void *user_data)
+_mgr_get_ip_configs_lst_cmp(const CList *a_lst, const CList *b_lst, const void *user_data)
 {
-    const NMDnsIPConfigData *a = c_list_entry(a_lst, NMDnsIPConfigData, ip_config_lst);
-    const NMDnsIPConfigData *b = c_list_entry(b_lst, NMDnsIPConfigData, ip_config_lst);
+    const NMDnsConfigIPData *a = c_list_entry(a_lst, NMDnsConfigIPData, ip_config_lst);
+    const NMDnsConfigIPData *b = c_list_entry(b_lst, NMDnsConfigIPData, ip_config_lst);
 
     /* Configurations with lower priority value first */
     NM_CMP_DIRECT(nm_ip_config_get_dns_priority(a->ip_config),
@@ -334,16 +341,39 @@ _ip_config_lst_cmp(const CList *a_lst, const CList *b_lst, const void *user_data
 }
 
 static CList *
-_ip_config_lst_head(NMDnsManager *self)
+_mgr_get_ip_configs_lst_head(NMDnsManager *self)
 {
     NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
 
-    if (priv->ip_config_lst_need_sort) {
-        priv->ip_config_lst_need_sort = FALSE;
-        c_list_sort(&priv->ip_config_lst_head, _ip_config_lst_cmp, NULL);
+    if (G_UNLIKELY(priv->ip_configs_lst_need_sort)) {
+        priv->ip_configs_lst_need_sort = FALSE;
+        c_list_sort(&priv->ip_configs_lst_head, _mgr_get_ip_configs_lst_cmp, NULL);
     }
 
-    return &priv->ip_config_lst_head;
+    return &priv->ip_configs_lst_head;
+}
+
+static int
+_mgr_get_configs_lst_cmp(const CList *a_lst, const CList *b_lst, const void *user_data)
+{
+    const NMDnsConfigData *a = c_list_entry(a_lst, NMDnsConfigData, configs_lst);
+    const NMDnsConfigData *b = c_list_entry(b_lst, NMDnsConfigData, configs_lst);
+
+    NM_CMP_FIELD(b, a, ifindex);
+    return nm_assert_unreachable_val(0);
+}
+
+_nm_unused static CList *
+_mgr_get_configs_lst_head(NMDnsManager *self)
+{
+    NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
+
+    if (G_UNLIKELY(priv->configs_lst_need_sort)) {
+        priv->configs_lst_need_sort = FALSE;
+        c_list_sort(&priv->configs_lst_head, _mgr_get_configs_lst_cmp, NULL);
+    }
+
+    return &priv->configs_lst_head;
 }
 
 /*****************************************************************************/
@@ -1077,7 +1107,7 @@ static void
 compute_hash(NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[HASH_LEN])
 {
     nm_auto_free_checksum GChecksum *sum = NULL;
-    NMDnsIPConfigData *              ip_data;
+    NMDnsConfigIPData *              ip_data;
 
     sum = g_checksum_new(G_CHECKSUM_SHA1);
     nm_assert(HASH_LEN == g_checksum_type_get_length(G_CHECKSUM_SHA1));
@@ -1089,7 +1119,7 @@ compute_hash(NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[
 
         /* FIXME(ip-config-checksum): this relies on the fact that an IP
          * configuration without DNS parameters gives a zero checksum. */
-        head = _ip_config_lst_head(self);
+        head = _mgr_get_ip_configs_lst_head(self);
         c_list_for_each_entry (ip_data, head, ip_config_lst)
             nm_ip_config_hash(ip_data->ip_config, sum, TRUE);
     }
@@ -1196,15 +1226,15 @@ _collect_resolv_conf_data(NMDnsManager *     self,
     else {
         nm_auto_free_gstring GString *tmp_gstring = NULL;
         int                           prio, first_prio = 0;
-        const NMDnsIPConfigData *     ip_data;
+        const NMDnsConfigIPData *     ip_data;
         const CList *                 head;
         gboolean                      is_first = TRUE;
 
-        head = _ip_config_lst_head(self);
+        head = _mgr_get_ip_configs_lst_head(self);
         c_list_for_each_entry (ip_data, head, ip_config_lst) {
             gboolean skip = FALSE;
 
-            _ASSERT_ip_config_data(ip_data);
+            _ASSERT_dns_config_ip_data(ip_data);
 
             prio = nm_ip_config_get_dns_priority(ip_data->ip_config);
 
@@ -1259,41 +1289,35 @@ _collect_resolv_conf_data(NMDnsManager *     self,
     *out_nis_domain  = rc.nis_domain;
 }
 
+/*****************************************************************************/
+
 static char **
 get_ip_rdns_domains(NMIPConfig *ip_config)
 {
-    int              addr_family = nm_ip_config_get_addr_family(ip_config);
-    char **          strv;
-    GPtrArray *      domains = NULL;
-    NMDedupMultiIter ipconf_iter;
+    int                        addr_family = nm_ip_config_get_addr_family(ip_config);
+    char **                    strv;
+    GPtrArray *                domains;
+    NMDedupMultiIter           ipconf_iter;
+    const NMPlatformIPAddress *address;
+    const NMPlatformIPRoute *  route;
 
     nm_assert_addr_family(addr_family);
 
     domains = g_ptr_array_sized_new(5);
 
-    if (addr_family == AF_INET) {
-        NMIP4Config *               ip4 = (gpointer) ip_config;
-        const NMPlatformIP4Address *address;
-        const NMPlatformIP4Route *  route;
-
-        nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &address)
-            nm_utils_get_reverse_dns_domains_ip4(address->address, address->plen, domains);
-
-        nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route) {
-            if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route))
-                nm_utils_get_reverse_dns_domains_ip4(route->network, route->plen, domains);
-        }
-    } else {
-        NMIP6Config *               ip6 = (gpointer) ip_config;
-        const NMPlatformIP6Address *address;
-        const NMPlatformIP6Route *  route;
-
-        nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &address)
-            nm_utils_get_reverse_dns_domains_ip6(&address->address, address->plen, domains);
+    nm_ip_config_iter_ip_address_for_each (&ipconf_iter, ip_config, &address) {
+        nm_utils_get_reverse_dns_domains_ip(addr_family,
+                                            address->address_ptr,
+                                            address->plen,
+                                            domains);
+    }
 
-        nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route) {
-            if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route))
-                nm_utils_get_reverse_dns_domains_ip6(&route->network, route->plen, domains);
+    nm_ip_config_iter_ip_route_for_each (&ipconf_iter, ip_config, &route) {
+        if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route)) {
+            nm_utils_get_reverse_dns_domains_ip(addr_family,
+                                                route->network_ptr,
+                                                route->plen,
+                                                domains);
         }
     }
 
@@ -1307,7 +1331,7 @@ get_ip_rdns_domains(NMIPConfig *ip_config)
 }
 
 static gboolean
-domain_ht_get_priority(GHashTable *ht, const char *domain, int *out_priority)
+_domain_track_get_priority(GHashTable *ht, const char *domain, int *out_priority)
 {
     gpointer ptr;
 
@@ -1321,11 +1345,11 @@ domain_ht_get_priority(GHashTable *ht, const char *domain, int *out_priority)
 
 /* Check if the domain is shadowed by a parent domain with more negative priority */
 static gboolean
-domain_is_shadowed(GHashTable * ht,
-                   const char * domain,
-                   int          priority,
-                   const char **out_parent,
-                   int *        out_parent_priority)
+_domain_track_is_shadowed(GHashTable * ht,
+                          const char * domain,
+                          int          priority,
+                          const char **out_parent,
+                          int *        out_parent_priority)
 {
     char *parent;
     int   parent_priority;
@@ -1335,7 +1359,7 @@ domain_is_shadowed(GHashTable * ht,
 
     nm_assert(!g_hash_table_contains(ht, domain));
 
-    if (domain_ht_get_priority(ht, "", &parent_priority)) {
+    if (_domain_track_get_priority(ht, "", &parent_priority)) {
         nm_assert(parent_priority <= priority);
         if (parent_priority < 0 && parent_priority < priority) {
             *out_parent          = "";
@@ -1347,7 +1371,7 @@ domain_is_shadowed(GHashTable * ht,
     parent = strchr(domain, '.');
     while (parent && parent[1]) {
         parent++;
-        if (domain_ht_get_priority(ht, parent, &parent_priority)) {
+        if (_domain_track_get_priority(ht, parent, &parent_priority)) {
             nm_assert(parent_priority <= priority);
             if (parent_priority < 0 && parent_priority < priority) {
                 *out_parent          = parent;
@@ -1362,18 +1386,18 @@ domain_is_shadowed(GHashTable * ht,
 }
 
 static void
-rebuild_domain_lists(NMDnsManager *self)
+_mgr_configs_data_construct(NMDnsManager *self)
 {
-    NMDnsIPConfigData *ip_data;
+    NMDnsConfigIPData *ip_data;
     gs_unref_hashtable GHashTable *ht               = NULL;
     gs_unref_hashtable GHashTable *wildcard_entries = NULL;
     CList *                        head;
     int                            prev_priority = G_MININT;
 
-    head = _ip_config_lst_head(self);
+    head = _mgr_get_ip_configs_lst_head(self);
 
 #if NM_MORE_ASSERTS
-    /* we call clear_domain_lists() at the end of update. We
+    /* we call _mgr_configs_data_clear() at the end of update. We
      * don't expect any domain settings here. */
     c_list_for_each_entry (ip_data, head, ip_config_lst) {
         nm_assert(!ip_data->domains.search);
@@ -1496,7 +1520,7 @@ rebuild_domain_lists(NMDnsManager *self)
                 break;
 
             /* Remove domains with lower priority */
-            if (domain_ht_get_priority(ht, domain_clean, &old_priority)) {
+            if (_domain_track_get_priority(ht, domain_clean, &old_priority)) {
                 nm_assert(old_priority <= priority);
                 if (old_priority < priority) {
                     _LOGT("plugin: drop domain %s%s%s (i=%d, p=%d) because it already exists "
@@ -1511,7 +1535,11 @@ rebuild_domain_lists(NMDnsManager *self)
                           old_priority);
                     continue;
                 }
-            } else if (domain_is_shadowed(ht, domain_clean, priority, &parent, &parent_priority)) {
+            } else if (_domain_track_is_shadowed(ht,
+                                                 domain_clean,
+                                                 priority,
+                                                 &parent,
+                                                 &parent_priority)) {
                 _LOGT("plugin: drop domain %s%s%s (i=%d, p=%d) shadowed by '%s' (p=%d)",
                       NM_PRINT_FMT_QUOTED(!check_default_route,
                                           "'",
@@ -1578,12 +1606,12 @@ rebuild_domain_lists(NMDnsManager *self)
 }
 
 static void
-clear_domain_lists(NMDnsManager *self)
+_mgr_configs_data_clear(NMDnsManager *self)
 {
-    NMDnsIPConfigData *ip_data;
+    NMDnsConfigIPData *ip_data;
     CList *            head;
 
-    head = _ip_config_lst_head(self);
+    head = _mgr_get_ip_configs_lst_head(self);
     c_list_for_each_entry (ip_data, head, ip_config_lst) {
         nm_clear_g_free(&ip_data->domains.search);
         nm_clear_pointer(&ip_data->domains.reverse, g_strfreev);
@@ -1593,6 +1621,8 @@ clear_domain_lists(NMDnsManager *self)
     }
 }
 
+/*****************************************************************************/
+
 static gboolean
 update_dns(NMDnsManager *self, gboolean no_caching, GError **error)
 {
@@ -1645,12 +1675,12 @@ update_dns(NMDnsManager *self, gboolean no_caching, GError **error)
                               &nis_domain);
 
     if (priv->plugin || priv->sd_resolve_plugin)
-        rebuild_domain_lists(self);
+        _mgr_configs_data_construct(self);
 
     if (priv->sd_resolve_plugin) {
         nm_dns_plugin_update(priv->sd_resolve_plugin,
                              global_config,
-                             _ip_config_lst_head(self),
+                             _mgr_get_ip_configs_lst_head(self),
                              priv->hostname,
                              NULL);
     }
@@ -1672,7 +1702,7 @@ update_dns(NMDnsManager *self, gboolean no_caching, GError **error)
         _LOGD("update-dns: updating plugin %s", plugin_name);
         if (!nm_dns_plugin_update(plugin,
                                   global_config,
-                                  _ip_config_lst_head(self),
+                                  _mgr_get_ip_configs_lst_head(self),
                                   priv->hostname,
                                   &plugin_error)) {
             _LOGW("update-dns: plugin %s update failed: %s", plugin_name, plugin_error->message);
@@ -1689,7 +1719,7 @@ plugin_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);
+    _mgr_configs_data_clear(self);
 
     update_resolv_conf_no_stub(self,
                                NM_CAST_STRV_CC(searches),
@@ -1702,6 +1732,8 @@ plugin_skip:;
      */
     if (caching) {
         const char *lladdr = "127.0.0.1";
+        gboolean    need_edns0;
+        gboolean    need_trust;
 
         if (NM_IS_DNS_SYSTEMD_RESOLVED(priv->plugin)) {
             /* systemd-resolved uses a different link-local address */
@@ -1711,6 +1743,21 @@ plugin_skip:;
         g_strfreev(nameservers);
         nameservers    = g_new0(char *, 2);
         nameservers[0] = g_strdup(lladdr);
+
+        need_edns0 = nm_utils_strv_find_first(options, -1, NM_SETTING_DNS_OPTION_EDNS0) < 0;
+        need_trust = nm_utils_strv_find_first(options, -1, NM_SETTING_DNS_OPTION_TRUST_AD) < 0;
+
+        if (need_edns0 || need_trust) {
+            gsize len;
+
+            len     = NM_PTRARRAY_LEN(options);
+            options = g_realloc(options, sizeof(char *) * (len + 3u));
+            if (need_edns0)
+                options[len++] = g_strdup(NM_SETTING_DNS_OPTION_EDNS0);
+            if (need_trust)
+                options[len++] = g_strdup(NM_SETTING_DNS_OPTION_TRUST_AD);
+            options[len] = NULL;
+        }
     }
 
     if (do_update) {
@@ -1788,11 +1835,11 @@ plugin_skip:;
 /*****************************************************************************/
 
 static void
-_ip_config_dns_priority_changed(gpointer config, GParamSpec *pspec, NMDnsIPConfigData *ip_data)
+_ip_config_dns_priority_changed(gpointer config, GParamSpec *pspec, NMDnsConfigIPData *ip_data)
 {
-    _ASSERT_ip_config_data(ip_data);
+    _ASSERT_dns_config_ip_data(ip_data);
 
-    NM_DNS_MANAGER_GET_PRIVATE(ip_data->data->self)->ip_config_lst_need_sort = TRUE;
+    NM_DNS_MANAGER_GET_PRIVATE(ip_data->data->self)->ip_configs_lst_need_sort = TRUE;
 }
 
 gboolean
@@ -1801,10 +1848,10 @@ nm_dns_manager_set_ip_config(NMDnsManager *    self,
                              NMDnsIPConfigType ip_config_type)
 {
     NMDnsManagerPrivate *priv;
-    NMDnsIPConfigData *  ip_data;
+    NMDnsConfigIPData *  ip_data;
     NMDnsConfigData *    data;
     int                  ifindex;
-    NMDnsIPConfigData ** p_best;
+    NMDnsConfigIPData ** p_best;
 
     g_return_val_if_fail(NM_IS_DNS_MANAGER(self), FALSE);
     g_return_val_if_fail(NM_IS_IP_CONFIG(ip_config), FALSE);
@@ -1814,11 +1861,11 @@ nm_dns_manager_set_ip_config(NMDnsManager *    self,
 
     priv = NM_DNS_MANAGER_GET_PRIVATE(self);
 
-    data = g_hash_table_lookup(priv->configs, &ifindex);
+    data = g_hash_table_lookup(priv->configs_dict, &ifindex);
     if (!data)
         ip_data = NULL;
     else
-        ip_data = _config_data_find_ip_config(data, ip_config);
+        ip_data = _dns_config_data_find_ip_config(data, ip_config);
 
     if (ip_config_type == NM_DNS_IP_CONFIG_TYPE_REMOVED) {
         if (!ip_data)
@@ -1828,9 +1875,9 @@ nm_dns_manager_set_ip_config(NMDnsManager *    self,
         if (priv->best_ip_config_6 == ip_data)
             priv->best_ip_config_6 = NULL;
         /* deleting a config doesn't invalidate the configs' sort order. */
-        _ip_config_data_free(ip_data);
+        _dns_config_ip_data_free(ip_data);
         if (c_list_is_empty(&data->data_lst_head))
-            g_hash_table_remove(priv->configs, &ifindex);
+            g_hash_table_remove(priv->configs_dict, &ifindex);
         goto changed;
     }
 
@@ -1846,16 +1893,18 @@ nm_dns_manager_set_ip_config(NMDnsManager *    self,
             .self          = self,
             .data_lst_head = C_LIST_INIT(data->data_lst_head),
         };
-        _ASSERT_config_data(data);
-        g_hash_table_add(priv->configs, data);
+        _ASSERT_dns_config_data(data);
+        g_hash_table_add(priv->configs_dict, data);
+        c_list_link_tail(&priv->configs_lst_head, &data->configs_lst);
+        priv->configs_lst_need_sort = TRUE;
     }
 
     if (!ip_data)
-        ip_data = _ip_config_data_new(data, ip_config, ip_config_type);
+        ip_data = _dns_config_ip_data_new(data, ip_config, ip_config_type);
     else
         ip_data->ip_config_type = ip_config_type;
 
-    priv->ip_config_lst_need_sort = TRUE;
+    priv->ip_configs_lst_need_sort = TRUE;
 
     p_best = NM_IS_IP4_CONFIG(ip_config) ? &priv->best_ip_config_4 : &priv->best_ip_config_6;
 
@@ -2375,7 +2424,7 @@ _get_config_variant(NMDnsManager *self)
     NMGlobalDnsConfig *  global_config;
     gs_free char *       str = NULL;
     GVariantBuilder      builder;
-    NMDnsIPConfigData *  ip_data;
+    NMDnsConfigIPData *  ip_data;
     const CList *        head;
     gs_unref_ptrarray GPtrArray *array_domains = NULL;
 
@@ -2391,7 +2440,7 @@ _get_config_variant(NMDnsManager *self)
 
     g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}"));
 
-    head = _ip_config_lst_head(self);
+    head = _mgr_get_ip_configs_lst_head(self);
     c_list_for_each_entry (ip_data, head, ip_config_lst) {
         const NMIPConfig *ip_config = ip_data->ip_config;
         GVariantBuilder   entry_builder;
@@ -2496,15 +2545,16 @@ nm_dns_manager_init(NMDnsManager *self)
 
     _LOGT("creating...");
 
-    c_list_init(&priv->ip_config_lst_head);
+    c_list_init(&priv->configs_lst_head);
+    c_list_init(&priv->ip_configs_lst_head);
 
     priv->config = g_object_ref(nm_config_get());
 
     G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMDnsConfigData, ifindex) == 0);
-    priv->configs = g_hash_table_new_full(nm_pint_hash,
-                                          nm_pint_equals,
-                                          (GDestroyNotify) _config_data_free,
-                                          NULL);
+    priv->configs_dict = g_hash_table_new_full(nm_pint_hash,
+                                               nm_pint_equals,
+                                               (GDestroyNotify) _dns_config_data_free,
+                                               NULL);
 
     /* Set the initial hash */
     compute_hash(self, NULL, NM_DNS_MANAGER_GET_PRIVATE(self)->hash);
@@ -2521,7 +2571,7 @@ dispose(GObject *object)
 {
     NMDnsManager *       self = NM_DNS_MANAGER(object);
     NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE(self);
-    NMDnsIPConfigData *  ip_data, *ip_data_safe;
+    NMDnsConfigIPData *  ip_data, *ip_data_safe;
 
     _LOGT("disposing");
 
@@ -2537,10 +2587,11 @@ dispose(GObject *object)
     priv->best_ip_config_4 = NULL;
     priv->best_ip_config_6 = NULL;
 
-    c_list_for_each_entry_safe (ip_data, ip_data_safe, &priv->ip_config_lst_head, ip_config_lst)
-        _ip_config_data_free(ip_data);
+    c_list_for_each_entry_safe (ip_data, ip_data_safe, &priv->ip_configs_lst_head, ip_config_lst)
+        _dns_config_ip_data_free(ip_data);
 
-    nm_clear_pointer(&priv->configs, g_hash_table_destroy);
+    nm_clear_pointer(&priv->configs_dict, g_hash_table_destroy);
+    nm_assert(c_list_is_empty(&priv->configs_lst_head));
 
     nm_clear_g_source(&priv->plugin_ratelimit.timer);
 
diff --git a/src/dns/nm-dns-manager.h b/src/core/dns/nm-dns-manager.h
index fb2f36d2..937ba62a 100644
--- a/src/dns/nm-dns-manager.h
+++ b/src/core/dns/nm-dns-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2005 Colin Walters <walters@redhat.com>
  * Copyright (C) 2004 - 2013 Red Hat, Inc.
@@ -25,6 +25,8 @@ enum {
     NM_DNS_PRIORITY_DEFAULT_VPN    = 50,
 };
 
+/*****************************************************************************/
+
 struct _NMDnsConfigData;
 struct _NMDnsManager;
 
@@ -60,14 +62,17 @@ typedef struct {
          * With systemd-resolved, this is the value for SetLinkDefaultRoute(). */
         bool has_default_route : 1;
     } domains;
-} NMDnsIPConfigData;
+} NMDnsConfigIPData;
 
 typedef struct _NMDnsConfigData {
     int                   ifindex;
     struct _NMDnsManager *self;
     CList                 data_lst_head;
+    CList                 configs_lst;
 } NMDnsConfigData;
 
+/*****************************************************************************/
+
 #define NM_TYPE_DNS_MANAGER (nm_dns_manager_get_type())
 #define NM_DNS_MANAGER(o)   (G_TYPE_CHECK_INSTANCE_CAST((o), NM_TYPE_DNS_MANAGER, NMDnsManager))
 #define NM_DNS_MANAGER_CLASS(k) \
diff --git a/src/dns/nm-dns-plugin.c b/src/core/dns/nm-dns-plugin.c
index e59feb3f..74d4eb2b 100644
--- a/src/dns/nm-dns-plugin.c
+++ b/src/core/dns/nm-dns-plugin.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 - 2012 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dns-plugin.h"
 
diff --git a/src/dns/nm-dns-plugin.h b/src/core/dns/nm-dns-plugin.h
index 5e87f59b..644d01e5 100644
--- a/src/dns/nm-dns-plugin.h
+++ b/src/core/dns/nm-dns-plugin.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 Red Hat, Inc.
  */
@@ -26,7 +26,7 @@ typedef struct {
     GObjectClass parent;
 
     /* Called when DNS information is changed.  'configs' is an array
-     * of pointers to NMDnsIPConfigData sorted by priority.
+     * of pointers to NMDnsConfigIPData sorted by priority.
      * 'global_config' is the optional global DNS
      * configuration.
      */
diff --git a/src/dns/nm-dns-systemd-resolved.c b/src/core/dns/nm-dns-systemd-resolved.c
index fe2e78af..f6e116f3 100644
--- a/src/dns/nm-dns-systemd-resolved.c
+++ b/src/core/dns/nm-dns-systemd-resolved.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 Dan Williams <dcbw@redhat.com>
  * Copyright (C) 2016 Sjoerd Simons <sjoerd@luon.net>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dns-systemd-resolved.h"
 
@@ -179,7 +179,7 @@ static gboolean
 update_add_ip_config(NMDnsSystemdResolved *self,
                      GVariantBuilder *     dns,
                      GVariantBuilder *     domains,
-                     NMDnsIPConfigData *   data)
+                     NMDnsConfigIPData *   data)
 {
     int         addr_family;
     gsize       addr_size;
@@ -256,7 +256,7 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
     g_variant_builder_open(&domains, G_VARIANT_TYPE("a(sb)"));
 
     c_list_for_each_entry (elem, &ic->configs_lst_head, lst) {
-        NMDnsIPConfigData *data      = elem->data;
+        NMDnsConfigIPData *data      = elem->data;
         NMIPConfig *       ip_config = data->ip_config;
 
         has_config |= update_add_ip_config(self, &dns, &domains, data);
@@ -423,7 +423,7 @@ update(NMDnsPlugin *            plugin,
     guint              interfaces_len;
     int                ifindex;
     gpointer           pointer;
-    NMDnsIPConfigData *ip_data;
+    NMDnsConfigIPData *ip_data;
     GHashTableIter     iter;
     guint              i;
 
diff --git a/src/dns/nm-dns-systemd-resolved.h b/src/core/dns/nm-dns-systemd-resolved.h
index ed2528ea..4ab04ab4 100644
--- a/src/dns/nm-dns-systemd-resolved.h
+++ b/src/core/dns/nm-dns-systemd-resolved.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 Red Hat, Inc.
  * Copyright (C) 2016 Sjoerd Simons <sjoerd@luon.net>
diff --git a/src/dns/nm-dns-unbound.c b/src/core/dns/nm-dns-unbound.c
index b9124759..15df3acf 100644
--- a/src/dns/nm-dns-unbound.c
+++ b/src/core/dns/nm-dns-unbound.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  * Author: Pavel Å imerda <psimerda@redhat.com>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dns-unbound.h"
 
diff --git a/src/dns/nm-dns-unbound.h b/src/core/dns/nm-dns-unbound.h
index 4129ba9e..feb33099 100644
--- a/src/dns/nm-dns-unbound.h
+++ b/src/core/dns/nm-dns-unbound.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
diff --git a/src/dnsmasq/nm-dnsmasq-manager.c b/src/core/dnsmasq/nm-dnsmasq-manager.c
index 2dd4afcb..82c002b0 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.c
+++ b/src/core/dnsmasq/nm-dnsmasq-manager.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2012 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dnsmasq-manager.h"
 
@@ -313,7 +313,7 @@ nm_dnsmasq_manager_new(const char *iface)
     NMDnsMasqManager *       manager;
     NMDnsMasqManagerPrivate *priv;
 
-    manager = (NMDnsMasqManager *) g_object_new(NM_TYPE_DNSMASQ_MANAGER, NULL);
+    manager = g_object_new(NM_TYPE_DNSMASQ_MANAGER, NULL);
 
     priv          = NM_DNSMASQ_MANAGER_GET_PRIVATE(manager);
     priv->iface   = g_strdup(iface);
diff --git a/src/dnsmasq/nm-dnsmasq-manager.h b/src/core/dnsmasq/nm-dnsmasq-manager.h
index 4f8b3055..272d1cea 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.h
+++ b/src/core/dnsmasq/nm-dnsmasq-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Red Hat, Inc.
  */
diff --git a/src/dnsmasq/nm-dnsmasq-utils.c b/src/core/dnsmasq/nm-dnsmasq-utils.c
index 3e430590..ea23a78d 100644
--- a/src/dnsmasq/nm-dnsmasq-utils.c
+++ b/src/core/dnsmasq/nm-dnsmasq-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <arpa/inet.h>
 
diff --git a/src/dnsmasq/nm-dnsmasq-utils.h b/src/core/dnsmasq/nm-dnsmasq-utils.h
index 321035bd..57bbc4c4 100644
--- a/src/dnsmasq/nm-dnsmasq-utils.h
+++ b/src/core/dnsmasq/nm-dnsmasq-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
diff --git a/src/dnsmasq/tests/meson.build b/src/core/dnsmasq/tests/meson.build
index 4584086e..565c18ad 100644
--- a/src/dnsmasq/tests/meson.build
+++ b/src/core/dnsmasq/tests/meson.build
@@ -1,11 +1,11 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 test_unit = 'test-dnsmasq-utils'
 
 exe = executable(
   test_unit,
   test_unit + '.c',
-  dependencies: libnetwork_manager_test_dep,
+  dependencies: libNetworkManagerTest_dep,
   c_args: test_c_flags,
 )
 
diff --git a/src/dnsmasq/tests/test-dnsmasq-utils.c b/src/core/dnsmasq/tests/test-dnsmasq-utils.c
index 4fa17576..4c1279f0 100644
--- a/src/dnsmasq/tests/test-dnsmasq-utils.c
+++ b/src/core/dnsmasq/tests/test-dnsmasq-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <arpa/inet.h>
 
diff --git a/src/core/initrd/meson.build b/src/core/initrd/meson.build
new file mode 100644
index 00000000..bd590e8c
--- /dev/null
+++ b/src/core/initrd/meson.build
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+libnmi_core = static_library(
+  'nmi-core',
+  sources: files(
+    'nmi-cmdline-reader.c',
+    'nmi-dt-reader.c',
+    'nmi-ibft-reader.c',
+  ),
+  dependencies: core_default_dep,
+  c_args: daemon_c_flags,
+)
+
+executable(
+  'nm-initrd-generator',
+  'nm-initrd-generator.c',
+  dependencies: core_default_dep,
+  c_args: daemon_c_flags,
+  link_with: [
+    libNetworkManagerBase,
+    libnmi_core,
+  ],
+  link_args: ldflags_linker_script_binary,
+  link_depends: linker_script_binary,
+  install: true,
+  install_dir: nm_libexecdir,
+)
+
+if enable_tests
+  subdir('tests')
+endif
diff --git a/src/initrd/nm-initrd-generator.c b/src/core/initrd/nm-initrd-generator.c
index 1a33713e..18c7258a 100644
--- a/src/initrd/nm-initrd-generator.c
+++ b/src/core/initrd/nm-initrd-generator.c
@@ -1,14 +1,15 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 #include "nm-core-utils.h"
 #include "nm-core-internal.h"
-#include "nm-keyfile/nm-keyfile-internal.h"
+#include "nm-keyfile-internal.h"
 #include "nm-initrd-generator.h"
 #include "nm-glib-aux/nm-io-utils.h"
+#include "nm-config.h"
 
 /*****************************************************************************/
 
@@ -63,6 +64,7 @@ err_out:
 
 #define DEFAULT_SYSFS_DIR       "/sys"
 #define DEFAULT_INITRD_DATA_DIR NMRUNDIR "/initrd"
+#define DEFAULT_RUN_CONFIG_DIR  NMRUNDIR "/conf.d"
 
 int
 main(int argc, char *argv[])
@@ -71,6 +73,7 @@ main(int argc, char *argv[])
     gs_free char *     connections_dir  = NULL;
     gs_free char *     initrd_dir       = NULL;
     gs_free char *     sysfs_dir        = NULL;
+    gs_free char *     run_config_dir   = NULL;
     gboolean           dump_to_stdout   = FALSE;
     gs_strfreev char **remaining        = NULL;
     GOptionEntry       option_entries[] = {
@@ -95,6 +98,13 @@ main(int argc, char *argv[])
          &sysfs_dir,
          "The sysfs mount point",
          DEFAULT_SYSFS_DIR},
+        {"run-config-dir",
+         'r',
+         0,
+         G_OPTION_ARG_FILENAME,
+         &run_config_dir,
+         "Output config directory",
+         DEFAULT_RUN_CONFIG_DIR},
         {"stdout",
          's',
          0,
@@ -108,11 +118,12 @@ main(int argc, char *argv[])
     gs_free_error GError *error                                = NULL;
     gs_free char *        hostname                             = NULL;
     int                   errsv;
+    gint64                carrier_timeout_sec = 0;
 
     option_context = g_option_context_new(
         "-- [ip=...] [rd.route=...] [bridge=...] [bond=...] [team=...] [vlan=...] "
         "[bootdev=...] [nameserver=...] [rd.peerdns=...] [rd.bootif=...] [BOOTIF=...] "
-        "[rd.znet=...] ... ");
+        "[rd.znet=...] [rd.net.timeout.carrier=...] ... ");
 
     g_option_context_set_summary(option_context, "Generate early NetworkManager configuration.");
     g_option_context_set_description(
@@ -138,29 +149,39 @@ main(int argc, char *argv[])
         sysfs_dir = g_strdup(DEFAULT_SYSFS_DIR);
     if (!initrd_dir)
         initrd_dir = g_strdup(DEFAULT_INITRD_DATA_DIR);
-    if (dump_to_stdout)
-        nm_clear_g_free(&connections_dir);
-
-    if (connections_dir && g_mkdir_with_parents(connections_dir, 0755) != 0) {
-        errsv = errno;
-        _LOGW(LOGD_CORE, "%s: %s", connections_dir, nm_strerror_native(errsv));
-        return 1;
-    }
+    if (!run_config_dir)
+        run_config_dir = g_strdup(DEFAULT_RUN_CONFIG_DIR);
 
-    connections = nmi_cmdline_reader_parse(sysfs_dir, (const char *const *) remaining, &hostname);
-
-    g_hash_table_foreach(connections, output_conn, connections_dir);
-    g_hash_table_destroy(connections);
+    connections = nmi_cmdline_reader_parse(sysfs_dir,
+                                           (const char *const *) remaining,
+                                           &hostname,
+                                           &carrier_timeout_sec);
 
     if (dump_to_stdout) {
+        nm_clear_g_free(&connections_dir);
+        nm_clear_g_free(&initrd_dir);
+        nm_clear_g_free(&run_config_dir);
         if (hostname)
             g_print("\n*** Hostname '%s' ***\n", hostname);
+        if (carrier_timeout_sec != 0)
+            g_print("\n*** Carrier Wait Timeout %" G_GINT64_FORMAT " sec ***\n",
+                    carrier_timeout_sec);
     } else {
+        if (g_mkdir_with_parents(connections_dir, 0755) != 0) {
+            errsv = errno;
+            _LOGW(LOGD_CORE, "%s: %s", connections_dir, nm_strerror_native(errsv));
+            return 1;
+        }
         if (g_mkdir_with_parents(initrd_dir, 0755) != 0) {
             errsv = errno;
             _LOGW(LOGD_CORE, "%s: %s", initrd_dir, nm_strerror_native(errsv));
             return 1;
         }
+        if (g_mkdir_with_parents(run_config_dir, 0755) != 0) {
+            errsv = errno;
+            _LOGW(LOGD_CORE, "%s: %s", run_config_dir, nm_strerror_native(errsv));
+            return 1;
+        }
 
         if (hostname) {
             gs_free char *hostname_file = NULL;
@@ -174,7 +195,32 @@ main(int argc, char *argv[])
                 return 1;
             }
         }
+        if (carrier_timeout_sec != 0) {
+            nm_auto_unref_keyfile GKeyFile *keyfile  = NULL;
+            gs_free char *                  filename = NULL;
+
+            keyfile = g_key_file_new();
+            g_key_file_set_list_separator(keyfile, NM_CONFIG_KEYFILE_LIST_SEPARATOR);
+            filename = g_strdup_printf("%s/15-carrier-timeout.conf", run_config_dir);
+
+            g_key_file_set_value(keyfile,
+                                 NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE "-15-carrier-timeout",
+                                 NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE,
+                                 "*");
+            g_key_file_set_int64(keyfile,
+                                 NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE "-15-carrier-timeout",
+                                 NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT,
+                                 carrier_timeout_sec * 1000);
+
+            if (!g_key_file_save_to_file(keyfile, filename, &error)) {
+                _LOGW(LOGD_CORE, "%s: %s", filename, error->message);
+                return 1;
+            }
+        }
     }
 
+    g_hash_table_foreach(connections, output_conn, connections_dir);
+    g_hash_table_destroy(connections);
+
     return 0;
 }
diff --git a/src/initrd/nm-initrd-generator.h b/src/core/initrd/nm-initrd-generator.h
index 69c24b1b..56dcfd68 100644
--- a/src/initrd/nm-initrd-generator.h
+++ b/src/core/initrd/nm-initrd-generator.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014, 2018 Red Hat, Inc.
  */
@@ -37,7 +37,9 @@ nmi_ibft_update_connection_from_nic(NMConnection *connection, GHashTable *nic, G
 
 NMConnection *nmi_dt_reader_parse(const char *sysfs_dir);
 
-GHashTable *
-nmi_cmdline_reader_parse(const char *sysfs_dir, const char *const *argv, char **hostname);
+GHashTable *nmi_cmdline_reader_parse(const char *       sysfs_dir,
+                                     const char *const *argv,
+                                     char **            hostname,
+                                     gint64 *           carrier_timeout_sec);
 
 #endif /* __NM_INITRD_GENERATOR_H__ */
diff --git a/src/initrd/nmi-cmdline-reader.c b/src/core/initrd/nmi-cmdline-reader.c
index 1c1b43eb..d95b0f9b 100644
--- a/src/initrd/nmi-cmdline-reader.c
+++ b/src/core/initrd/nmi-cmdline-reader.c
@@ -1,9 +1,12 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
+
+#include <linux/if_ether.h>
+#include <linux/if_infiniband.h>
 
 #include "nm-core-internal.h"
 #include "nm-initrd-generator.h"
@@ -34,6 +37,8 @@ typedef struct {
     gboolean ignore_auto_dns;
     int      dhcp_timeout;
     char *   dhcp4_vci;
+
+    gint64 carrier_timeout_sec;
 } Reader;
 
 static Reader *
@@ -416,7 +421,7 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
 
     tmp = get_word(&argument, ':');
     if (!*argument) {
-        /* ip={dhcp|on|any|dhcp6|auto6|ibft} */
+        /* ip={dhcp|on|any|dhcp6|auto6|link6|ibft} */
         kind = tmp;
     } else {
         tmp2 = get_word(&argument, ':');
@@ -429,8 +434,9 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
                          "dhcp6",
                          "auto",
                          "auto6",
+                         "link6",
                          "ibft")) {
-            /* <ifname>:{none|off|dhcp|on|any|dhcp6|auto|auto6|ibft} */
+            /* <ifname>:{none|off|dhcp|on|any|dhcp6|auto|auto6|link6|ibft} */
             iface_spec = tmp;
             kind       = tmp2;
         } else {
@@ -598,6 +604,19 @@ reader_parse_ip(Reader *reader, const char *sysfs_dir, char *argument)
                          NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
                          NULL);
         }
+    } else if (nm_streq0(kind, "link6")) {
+        g_object_set(s_ip6,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
+                     NM_SETTING_IP_CONFIG_MAY_FAIL,
+                     FALSE,
+                     NULL);
+        if (nm_setting_ip_config_get_num_addresses(s_ip4) == 0) {
+            g_object_set(s_ip4,
+                         NM_SETTING_IP_CONFIG_METHOD,
+                         NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
+                         NULL);
+        }
     } else if (nm_streq0(kind, "ibft")) {
         NMSettingWired *s_wired;
         const char *    mac = NULL;
@@ -701,8 +720,6 @@ reader_parse_master(Reader *reader, char *argument, const char *type_name, const
     char *               slaves;
     const char *         slave;
     char *               opts;
-    char *               opt;
-    const char *         opt_name;
     const char *         mtu = NULL;
 
     master = get_word(&argument, ':');
@@ -724,8 +741,21 @@ reader_parse_master(Reader *reader, char *argument, const char *type_name, const
 
         opts = get_word(&argument, ':');
         while (opts && *opts) {
+            gs_free_error GError *error = NULL;
+            char *                opt;
+            const char *          opt_name;
+
             opt      = get_word(&opts, ',');
             opt_name = get_word(&opt, '=');
+
+            if (!_nm_setting_bond_validate_option(opt_name, opt, &error)) {
+                _LOGW(LOGD_CORE,
+                      "Ignoring invalid bond option: %s%s%s = %s%s%s: %s",
+                      NM_PRINT_FMT_QUOTE_STRING(opt_name),
+                      NM_PRINT_FMT_QUOTE_STRING(opt),
+                      error->message);
+                continue;
+            }
             nm_setting_bond_add_option(s_bond, opt_name, opt);
         }
 
@@ -1019,7 +1049,10 @@ connection_set_needed_cb(gpointer key, gpointer value, gpointer user_data)
 }
 
 GHashTable *
-nmi_cmdline_reader_parse(const char *sysfs_dir, const char *const *argv, char **hostname)
+nmi_cmdline_reader_parse(const char *       sysfs_dir,
+                         const char *const *argv,
+                         char **            hostname,
+                         gint64 *           carrier_timeout_sec)
 {
     Reader *          reader;
     const char *      tag;
@@ -1053,6 +1086,9 @@ nmi_cmdline_reader_parse(const char *sysfs_dir, const char *const *argv, char **
         } else if (nm_streq(tag, "rd.net.dhcp.vendor-class")) {
             if (nm_utils_validate_dhcp4_vendor_class_id(argument, NULL))
                 nm_utils_strdup_reset(&reader->dhcp4_vci, argument);
+        } else if (nm_streq(tag, "rd.net.timeout.carrier")) {
+            reader->carrier_timeout_sec =
+                _nm_utils_ascii_str_to_int64(argument, 10, 0, G_MAXINT32, 0);
         }
     }
 
@@ -1140,14 +1176,21 @@ nmi_cmdline_reader_parse(const char *sysfs_dir, const char *const *argv, char **
         NMConnection *  connection;
         NMSettingWired *s_wired;
         const char *    bootif = bootif_val;
-
-        if (!nm_utils_hwaddr_valid(bootif, ETH_ALEN) && g_str_has_prefix(bootif, "01-")
-            && nm_utils_hwaddr_valid(&bootif[3], ETH_ALEN)) {
-            /*
-             * BOOTIF MAC address can be prefixed with a hardware type identifier.
-             * "01" stays for "wired", no other are known.
-             */
-            bootif += 3;
+        char            prefix[4];
+
+        if (!nm_utils_hwaddr_valid(bootif, ETH_ALEN)) {
+            strncpy(prefix, bootif, 3);
+            prefix[3] = '\0';
+
+            if (NM_IN_STRSET(prefix, "01-", "01:", "00-", "00:")
+                && nm_utils_hwaddr_valid(&bootif[3], ETH_ALEN)) {
+                /*
+                 * BOOTIF MAC address can be prefixed with a hardware type identifier.
+                 * "01" stays for "wired", "00" is also accepted as it means "undefined".
+                 * No others are known.
+                 */
+                bootif += 3;
+            }
         }
 
         connection = reader_get_connection(reader, NULL, NM_SETTING_WIRED_SETTING_NAME, FALSE);
@@ -1206,5 +1249,7 @@ nmi_cmdline_reader_parse(const char *sysfs_dir, const char *const *argv, char **
 
     NM_SET_OUT(hostname, g_steal_pointer(&reader->hostname));
 
+    NM_SET_OUT(carrier_timeout_sec, reader->carrier_timeout_sec);
+
     return reader_destroy(reader, FALSE);
 }
diff --git a/src/initrd/nmi-dt-reader.c b/src/core/initrd/nmi-dt-reader.c
index 3039d902..63e4524b 100644
--- a/src/initrd/nmi-dt-reader.c
+++ b/src/core/initrd/nmi-dt-reader.c
@@ -1,13 +1,14 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-initrd-generator.h"
 
 #include <arpa/inet.h>
+#include <linux/if_ether.h>
 
 #include "nm-core-internal.h"
 
diff --git a/src/initrd/nmi-ibft-reader.c b/src/core/initrd/nmi-ibft-reader.c
index b7ce4671..9895cda4 100644
--- a/src/initrd/nmi-ibft-reader.c
+++ b/src/core/initrd/nmi-ibft-reader.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-initrd-generator.h"
 
@@ -15,6 +15,7 @@
 #include <sys/inotify.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
+#include <linux/if_ether.h>
 
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
diff --git a/src/initrd/tests/meson.build b/src/core/initrd/tests/meson.build
index 1d0908b9..e47ff790 100644
--- a/src/initrd/tests/meson.build
+++ b/src/core/initrd/tests/meson.build
@@ -1,6 +1,4 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-c_flags = test_c_flags + ['-DTEST_INITRD_DIR="@0@"'.format(meson.current_source_dir())]
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 test_units = [
   'test-dt-reader',
@@ -12,8 +10,8 @@ foreach test_unit : test_units
   exe = executable(
     test_unit,
     test_unit + '.c',
-    dependencies: libnetwork_manager_test_dep,
-    c_args: c_flags,
+    dependencies: libNetworkManagerTest_dep,
+    c_args: test_c_flags,
     link_with: libnmi_core,
   )
 
diff --git a/src/initrd/tests/sysfs-bad-dns1/class/net/eth0/address b/src/core/initrd/tests/sysfs-bad-dns1/class/net/eth0/address
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-bad-dns1/class/net/eth0/address
+++ b/src/core/initrd/tests/sysfs-bad-dns1/class/net/eth0/address
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway
index bf6cf454..bf6cf454 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/gateway
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/index
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr
index 944c8d45..944c8d45 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/ip-addr
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/mac
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin
index 56a6051c..56a6051c 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/origin
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len
index 8fdd954d..8fdd954d 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/prefix-len
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns
index 2303dbdc..2303dbdc 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/primary-dns
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns
index 2eb9e1d0..2eb9e1d0 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/secondary-dns
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask
index 7cb9d0a9..7cb9d0a9 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/subnet-mask
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/ethernet0/vlan
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name
index 509a7ab8..509a7ab8 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/initiator/initiator-name
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/index
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr
index 7450fdf9..7450fdf9 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/ip-addr
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun
index c43d0930..c43d0930 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/lun
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port
index 58f0d919..58f0d919 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/port
diff --git a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name
index 32c9450a..32c9450a 100644
--- a/src/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name
+++ b/src/core/initrd/tests/sysfs-bad-dns1/firmware/ibft/target0/target-name
diff --git a/src/initrd/tests/sysfs-bad-dns2/class/net/eth0/address b/src/core/initrd/tests/sysfs-bad-dns2/class/net/eth0/address
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-bad-dns2/class/net/eth0/address
+++ b/src/core/initrd/tests/sysfs-bad-dns2/class/net/eth0/address
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway
index bf6cf454..bf6cf454 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/gateway
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/index
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr
index 944c8d45..944c8d45 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/ip-addr
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/mac
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin
index 56a6051c..56a6051c 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/origin
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len
index 8fdd954d..8fdd954d 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/prefix-len
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns
index 2e152cb0..2e152cb0 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/primary-dns
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns
index 5ed7b719..5ed7b719 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/secondary-dns
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask
index 7cb9d0a9..7cb9d0a9 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/subnet-mask
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/ethernet0/vlan
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name
index 509a7ab8..509a7ab8 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/initiator/initiator-name
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/index
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr
index 7450fdf9..7450fdf9 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/ip-addr
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun
index c43d0930..c43d0930 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/lun
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port
index 58f0d919..58f0d919 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/port
diff --git a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name
index 32c9450a..32c9450a 100644
--- a/src/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name
+++ b/src/core/initrd/tests/sysfs-bad-dns2/firmware/ibft/target0/target-name
diff --git a/src/initrd/tests/sysfs-bad-gateway/class/net/eth0/address b/src/core/initrd/tests/sysfs-bad-gateway/class/net/eth0/address
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-bad-gateway/class/net/eth0/address
+++ b/src/core/initrd/tests/sysfs-bad-gateway/class/net/eth0/address
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway
index 1970856e..1970856e 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/gateway
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/index
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr
index 944c8d45..944c8d45 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/ip-addr
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/mac
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin
index 56a6051c..56a6051c 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/origin
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len
index 8fdd954d..8fdd954d 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/prefix-len
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns
index 2e152cb0..2e152cb0 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/primary-dns
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns
index 2eb9e1d0..2eb9e1d0 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/secondary-dns
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask
index 7cb9d0a9..7cb9d0a9 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/subnet-mask
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/ethernet0/vlan
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name
index 509a7ab8..509a7ab8 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/initiator/initiator-name
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/index
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr
index 7450fdf9..7450fdf9 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/ip-addr
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun
index c43d0930..c43d0930 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/lun
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port
index 58f0d919..58f0d919 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/port
diff --git a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name
index 32c9450a..32c9450a 100644
--- a/src/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name
+++ b/src/core/initrd/tests/sysfs-bad-gateway/firmware/ibft/target0/target-name
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address b/src/core/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/class/net/eth0/address
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway
index bf6cf454..bf6cf454 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/gateway
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/index
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr
index d526f324..d526f324 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/ip-addr
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/mac
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin
index 56a6051c..56a6051c 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/origin
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len
index 8fdd954d..8fdd954d 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/prefix-len
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns
index 2e152cb0..2e152cb0 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/primary-dns
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns
index 2eb9e1d0..2eb9e1d0 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/secondary-dns
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask
index 7cb9d0a9..7cb9d0a9 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/subnet-mask
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/ethernet0/vlan
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name
index 509a7ab8..509a7ab8 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/initiator/initiator-name
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/index
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr
index 7450fdf9..7450fdf9 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/ip-addr
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun
index c43d0930..c43d0930 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/lun
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port
index 58f0d919..58f0d919 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/port
diff --git a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name
index 32c9450a..32c9450a 100644
--- a/src/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name
+++ b/src/core/initrd/tests/sysfs-bad-ipaddr/firmware/ibft/target0/target-name
diff --git a/src/initrd/tests/sysfs-dhcp/class/net/eth0/address b/src/core/initrd/tests/sysfs-dhcp/class/net/eth0/address
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-dhcp/class/net/eth0/address
+++ b/src/core/initrd/tests/sysfs-dhcp/class/net/eth0/address
diff --git a/src/initrd/tests/sysfs-dhcp/class/net/eth1/address b/src/core/initrd/tests/sysfs-dhcp/class/net/eth1/address
index befa7850..befa7850 100644
--- a/src/initrd/tests/sysfs-dhcp/class/net/eth1/address
+++ b/src/core/initrd/tests/sysfs-dhcp/class/net/eth1/address
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway
index 59ea5b82..59ea5b82 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/gateway
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/index
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/mac
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin
index e440e5c8..e440e5c8 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/origin
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns
index 2e152cb0..2e152cb0 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/primary-dns
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns
index 2eb9e1d0..2eb9e1d0 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/secondary-dns
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet0/vlan
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway
index 59ea5b82..59ea5b82 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/gateway
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index
index 56a6051c..56a6051c 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/index
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac
index befa7850..befa7850 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/mac
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin
index e440e5c8..e440e5c8 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/origin
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns
index 2e152cb0..2e152cb0 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/primary-dns
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns
index 2eb9e1d0..2eb9e1d0 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/secondary-dns
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/ethernet1/vlan
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name
index 509a7ab8..509a7ab8 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/initiator/initiator-name
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/index
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr
index 7450fdf9..7450fdf9 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/ip-addr
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun
index c43d0930..c43d0930 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/lun
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port
index 58f0d919..58f0d919 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/port
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name
index 32c9450a..32c9450a 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target0/target-name
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index
index 56a6051c..56a6051c 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/index
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr
index 7450fdf9..7450fdf9 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/ip-addr
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun
index c43d0930..c43d0930 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/lun
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port
index 58f0d919..58f0d919 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/port
diff --git a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name
index ac3a6a2a..ac3a6a2a 100644
--- a/src/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name
+++ b/src/core/initrd/tests/sysfs-dhcp/firmware/ibft/target1/target-name
diff --git a/src/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath b/src/core/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath
index 6f069ae0..6f069ae0 100644
--- a/src/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath
+++ b/src/core/initrd/tests/sysfs-dt-tftp/firmware/devicetree/base/chosen/bootpath
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request
index 034d423e..034d423e 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-request
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response
index 25982d6f..25982d6f 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootp-response
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath
index db880702..db880702 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/bootpath
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip
index 7bde8641..7bde8641 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/broadcast-ip
diff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip
index f108ba9b..f108ba9b 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-ip
diff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name
index fe0f76b8..fe0f76b8 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/client-name
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name
index b34f9f96..b34f9f96 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/domain-name
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip
index f0361835..f0361835 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/gateway-ip
diff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name
index f3e58052..f3e58052 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/name
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip
index d441cd83..d441cd83 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/netmask-ip
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path
index f76dd238..f76dd238 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/root-path
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip
index a2a4b2e8..a2a4b2e8 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/server-ip
diff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file
index c0e0e330..c0e0e330 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/tftp-file
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options
index f76dd238..f76dd238 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/chosen/vendor-options
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type
index df3c9d9f..df3c9d9f 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/device_type
Binary files differdiff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address
index c983e752..c983e752 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/local-mac-address
diff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address
index c983e752..c983e752 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/mac-address
diff --git a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name
index 88eaddfe..88eaddfe 100644
--- a/src/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name
+++ b/src/core/initrd/tests/sysfs-dt/firmware/devicetree/base/ethernet/name
Binary files differdiff --git a/src/initrd/tests/sysfs-static/class/net/eth0/address b/src/core/initrd/tests/sysfs-static/class/net/eth0/address
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-static/class/net/eth0/address
+++ b/src/core/initrd/tests/sysfs-static/class/net/eth0/address
diff --git a/src/initrd/tests/sysfs-static/class/net/eth1/address b/src/core/initrd/tests/sysfs-static/class/net/eth1/address
index befa7850..befa7850 100644
--- a/src/initrd/tests/sysfs-static/class/net/eth1/address
+++ b/src/core/initrd/tests/sysfs-static/class/net/eth1/address
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway
index bf6cf454..bf6cf454 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/gateway
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/index
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr
index 944c8d45..944c8d45 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/ip-addr
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/mac
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin
index 56a6051c..56a6051c 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/origin
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len
index 8fdd954d..8fdd954d 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/prefix-len
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns
index 2e152cb0..2e152cb0 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/primary-dns
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns
index 2eb9e1d0..2eb9e1d0 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/secondary-dns
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask
index 7cb9d0a9..7cb9d0a9 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/subnet-mask
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet0/vlan
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway
index 59ea5b82..59ea5b82 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/gateway
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index
index 56a6051c..56a6051c 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/index
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac
index befa7850..befa7850 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/mac
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin
index e440e5c8..e440e5c8 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/origin
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns
index 2e152cb0..2e152cb0 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/primary-dns
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns
index 2eb9e1d0..2eb9e1d0 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/secondary-dns
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/ethernet1/vlan
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name
index 509a7ab8..509a7ab8 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/initiator/initiator-name
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/target0/index
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/index
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr
index 7450fdf9..7450fdf9 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/ip-addr
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/lun
index c43d0930..c43d0930 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/target0/lun
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/lun
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/port
index 58f0d919..58f0d919 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/target0/port
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/port
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/target-name
index 32c9450a..32c9450a 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/target0/target-name
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target0/target-name
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/target1/index b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/index
index 56a6051c..56a6051c 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/target1/index
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/index
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr
index 7450fdf9..7450fdf9 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/ip-addr
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/target1/lun b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/lun
index c43d0930..c43d0930 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/target1/lun
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/lun
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/target1/port b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/port
index 58f0d919..58f0d919 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/target1/port
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/port
diff --git a/src/initrd/tests/sysfs-static/firmware/ibft/target1/target-name b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/target-name
index ac3a6a2a..ac3a6a2a 100644
--- a/src/initrd/tests/sysfs-static/firmware/ibft/target1/target-name
+++ b/src/core/initrd/tests/sysfs-static/firmware/ibft/target1/target-name
diff --git a/src/initrd/tests/sysfs-vlan/class/net/eth0/address b/src/core/initrd/tests/sysfs-vlan/class/net/eth0/address
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-vlan/class/net/eth0/address
+++ b/src/core/initrd/tests/sysfs-vlan/class/net/eth0/address
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/index
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr
index 4923230c..4923230c 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/ip-addr
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac
index 20659db7..20659db7 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/mac
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin
index 56a6051c..56a6051c 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/origin
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len
index cabf43b5..cabf43b5 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/prefix-len
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask
index 5a279b41..5a279b41 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/subnet-mask
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan
index d800886d..d800886d 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/ethernet0/vlan
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name
index 8e9cd2c4..8e9cd2c4 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/initiator/initiator-name
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/index
index c2270834..c2270834 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/target0/index
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/index
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr
index a32ce01b..a32ce01b 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/ip-addr
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun
index f309ca6a..f309ca6a 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/lun
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/port
index 58f0d919..58f0d919 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/target0/port
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/port
diff --git a/src/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name
index f0e61157..f0e61157 100644
--- a/src/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name
+++ b/src/core/initrd/tests/sysfs-vlan/firmware/ibft/target0/target-name
diff --git a/src/initrd/tests/sysfs/class/net/eth0/address b/src/core/initrd/tests/sysfs/class/net/eth0/address
index 1f2f610e..1f2f610e 100644
--- a/src/initrd/tests/sysfs/class/net/eth0/address
+++ b/src/core/initrd/tests/sysfs/class/net/eth0/address
diff --git a/src/initrd/tests/sysfs/class/net/eth2/address b/src/core/initrd/tests/sysfs/class/net/eth2/address
index 6ec1078a..6ec1078a 100644
--- a/src/initrd/tests/sysfs/class/net/eth2/address
+++ b/src/core/initrd/tests/sysfs/class/net/eth2/address
diff --git a/src/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id
index 590d9cd2..590d9cd2 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_id
diff --git a/src/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id
index 35d4d09c..35d4d09c 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/oem_table_id
diff --git a/src/initrd/tests/sysfs/firmware/ibft/acpi_header/signature b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/signature
index e3a5d7ef..e3a5d7ef 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/acpi_header/signature
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/acpi_header/signature
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp
index 98a0d797..98a0d797 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/dhcp
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/flags b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/flags
index 00750edc..00750edc 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/flags
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/flags
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway
index 7f378f8f..7f378f8f 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/gateway
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname
index 3ed5163c..3ed5163c 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/hostname
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/index b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/index
index 573541ac..573541ac 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/index
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/index
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr
index 98a0d797..98a0d797 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/ip-addr
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/mac b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/mac
index f2441319..f2441319 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/mac
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/mac
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/origin b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/origin
index b8626c4c..b8626c4c 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/origin
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/origin
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len
index 900731ff..900731ff 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/prefix-len
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns
index e7511d5a..e7511d5a 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/primary-dns
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns
index 1f6c5c95..1f6c5c95 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/secondary-dns
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask
index 5df93454..5df93454 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/subnet-mask
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan
index 7cc86ad1..7cc86ad1 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet0/vlan
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp
index 61ef5585..61ef5585 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/dhcp
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/flags b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/flags
index 00750edc..00750edc 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/flags
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/flags
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway
index 2096f194..2096f194 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/gateway
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname
index 5dc539d5..5dc539d5 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/hostname
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/index b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/index
index 0cfbf088..0cfbf088 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/index
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/index
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr
index 61ef5585..61ef5585 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/ip-addr
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/mac b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/mac
index 2fc3d525..2fc3d525 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/mac
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/mac
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/origin b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/origin
index 00750edc..00750edc 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/origin
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/origin
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len
index a45fd52c..a45fd52c 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/prefix-len
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns
index 6fa8c742..6fa8c742 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/primary-dns
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns
index a7269ff5..a7269ff5 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/secondary-dns
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask
index d30f9e9f..d30f9e9f 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/subnet-mask
diff --git a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan
index 573541ac..573541ac 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/ethernet2/vlan
diff --git a/src/initrd/tests/sysfs/firmware/ibft/initiator/flags b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/flags
index 00750edc..00750edc 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/initiator/flags
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/flags
diff --git a/src/initrd/tests/sysfs/firmware/ibft/initiator/index b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/index
index 573541ac..573541ac 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/initiator/index
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/index
diff --git a/src/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name
index 337b5a0f..337b5a0f 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/initiator-name
diff --git a/src/initrd/tests/sysfs/firmware/ibft/initiator/isns-server b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/isns-server
index 4aff9398..4aff9398 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/initiator/isns-server
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/isns-server
diff --git a/src/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server
index a26fb649..a26fb649 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/pri-radius-server
diff --git a/src/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server
index 908be27f..908be27f 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/sec-radius-server
diff --git a/src/initrd/tests/sysfs/firmware/ibft/initiator/slp-server b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/slp-server
index fca9e24b..fca9e24b 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/initiator/slp-server
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/initiator/slp-server
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target0/chap-type b/src/core/initrd/tests/sysfs/firmware/ibft/target0/chap-type
index 573541ac..573541ac 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target0/chap-type
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/chap-type
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target0/flags b/src/core/initrd/tests/sysfs/firmware/ibft/target0/flags
index 00750edc..00750edc 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target0/flags
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/flags
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target0/index b/src/core/initrd/tests/sysfs/firmware/ibft/target0/index
index 573541ac..573541ac 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target0/index
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/index
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target0/ip-addr b/src/core/initrd/tests/sysfs/firmware/ibft/target0/ip-addr
index 994b6e9c..994b6e9c 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target0/ip-addr
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/ip-addr
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target0/lun b/src/core/initrd/tests/sysfs/firmware/ibft/target0/lun
index 7c295bd4..7c295bd4 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target0/lun
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/lun
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc b/src/core/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc
index 573541ac..573541ac 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/nic-assoc
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target0/port b/src/core/initrd/tests/sysfs/firmware/ibft/target0/port
index 53f0295f..53f0295f 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target0/port
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target0/port
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target2/chap-type b/src/core/initrd/tests/sysfs/firmware/ibft/target2/chap-type
index 573541ac..573541ac 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target2/chap-type
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/chap-type
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target2/flags b/src/core/initrd/tests/sysfs/firmware/ibft/target2/flags
index 00750edc..00750edc 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target2/flags
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/flags
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target2/index b/src/core/initrd/tests/sysfs/firmware/ibft/target2/index
index 0cfbf088..0cfbf088 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target2/index
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/index
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target2/ip-addr b/src/core/initrd/tests/sysfs/firmware/ibft/target2/ip-addr
index a210477e..a210477e 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target2/ip-addr
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/ip-addr
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target2/lun b/src/core/initrd/tests/sysfs/firmware/ibft/target2/lun
index 7c295bd4..7c295bd4 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target2/lun
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/lun
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc b/src/core/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc
index 0cfbf088..0cfbf088 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/nic-assoc
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target2/port b/src/core/initrd/tests/sysfs/firmware/ibft/target2/port
index 53f0295f..53f0295f 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target2/port
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/port
diff --git a/src/initrd/tests/sysfs/firmware/ibft/target2/target-name b/src/core/initrd/tests/sysfs/firmware/ibft/target2/target-name
index f4094320..f4094320 100644
--- a/src/initrd/tests/sysfs/firmware/ibft/target2/target-name
+++ b/src/core/initrd/tests/sysfs/firmware/ibft/target2/target-name
diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/core/initrd/tests/test-cmdline-reader.c
index 14a83c08..cb65cd33 100644
--- a/src/initrd/tests/test-cmdline-reader.c
+++ b/src/core/initrd/tests/test-cmdline-reader.c
@@ -1,14 +1,13 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <netinet/ether.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -20,26 +19,75 @@
 
 #include "nm-test-utils-core.h"
 
+#define TEST_INITRD_DIR NM_BUILD_SRCDIR "/src/core/initrd/tests"
+
+/*****************************************************************************/
+
+#define _parse(ARGV, out_hostname, out_carrier_timeout_sec)                            \
+    ({                                                                                 \
+        const char *const *const _ARGV                    = (ARGV);                    \
+        char **const             _out_hostname            = (out_hostname);            \
+        gint64 *const            _out_carrier_timeout_sec = (out_carrier_timeout_sec); \
+        GHashTable *             _connections;                                         \
+                                                                                       \
+        _connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs",              \
+                                                _ARGV,                                 \
+                                                _out_hostname,                         \
+                                                _out_carrier_timeout_sec);             \
+                                                                                       \
+        g_assert(_connections);                                                        \
+                                                                                       \
+        _connections;                                                                  \
+    })
+
+#define _parse_cons(ARGV)                                                                    \
+    ({                                                                                       \
+        GHashTable *  _con_connections;                                                      \
+        gs_free char *_con_hostname            = NULL;                                       \
+        gint64        _con_carrier_timeout_sec = 0;                                          \
+                                                                                             \
+        _con_connections = _parse((ARGV),                                                    \
+                                  nmtst_get_rand_bool() ? &_con_hostname : NULL,             \
+                                  nmtst_get_rand_bool() ? &_con_carrier_timeout_sec : NULL); \
+        g_assert_cmpstr(_con_hostname, ==, NULL);                                            \
+        g_assert_cmpint(_con_carrier_timeout_sec, ==, 0);                                    \
+                                                                                             \
+        _con_connections;                                                                    \
+    })
+
+#define _parse_con(ARGV, connection_name)                                        \
+    ({                                                                           \
+        gs_unref_hashtable GHashTable *_1_connections = NULL;                    \
+        NMConnection *                 _1_connection;                            \
+        const char *const              _1_connection_name = (connection_name);   \
+                                                                                 \
+        g_assert(_1_connection_name);                                            \
+                                                                                 \
+        _1_connections = _parse_cons((ARGV));                                    \
+                                                                                 \
+        g_assert_cmpint(g_hash_table_size(_1_connections), ==, 1);               \
+                                                                                 \
+        _1_connection = g_hash_table_lookup(_1_connections, _1_connection_name); \
+        g_assert(NM_IS_CONNECTION(_1_connection));                               \
+                                                                                 \
+        nmtst_assert_connection_verifies_without_normalization(_1_connection);   \
+                                                                                 \
+        NM_CONNECTION(g_object_ref(_1_connection));                              \
+    })
+
+/*****************************************************************************/
+
 static void
 test_auto(void)
 {
-    gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *            ARGV        = NM_MAKE_STRV("ip=auto");
-    NMConnection *                 connection;
-    NMSettingConnection *          s_con;
-    NMSettingWired *               s_wired;
-    NMSettingIPConfig *            s_ip4;
-    NMSettingIPConfig *            s_ip6;
-    gs_free char *                 hostname = NULL;
-
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
+    const char *const *ARGV                  = NM_MAKE_STRV("ip=auto");
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
 
-    connection = g_hash_table_lookup(connections, "default_connection");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
+    connection = _parse_con(ARGV, "default_connection");
 
     g_assert(!nm_connection_get_setting_vlan(connection));
 
@@ -89,15 +137,16 @@ test_dhcp_with_hostname(void)
     NMSettingWired *               s_wired;
     NMSettingIPConfig *            s_ip4;
     NMSettingIPConfig *            s_ip6;
-    gs_free char *                 hostname = NULL;
+    gs_free char *                 hostname            = NULL;
+    gint64                         carrier_timeout_sec = 0;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse(ARGV, &hostname, &carrier_timeout_sec);
     g_assert_cmpint(g_hash_table_size(connections), ==, 1);
     g_assert_cmpstr(hostname, ==, "host1");
+    g_assert_cmpint(carrier_timeout_sec, ==, 0);
 
     connection = g_hash_table_lookup(connections, "default_connection");
-    g_assert(connection);
+
     nmtst_assert_connection_verifies_without_normalization(connection);
 
     g_assert(!nm_connection_get_setting_vlan(connection));
@@ -139,22 +188,13 @@ test_dhcp_with_mtu(void)
     guint              i;
 
     for (i = 0; i < G_N_ELEMENTS(ARGV); i++) {
-        gs_unref_hashtable GHashTable *connections = NULL;
-        NMConnection *                 connection;
-        NMSettingConnection *          s_con;
-        NMSettingWired *               s_wired;
-        NMSettingIPConfig *            s_ip4;
-        NMSettingIPConfig *            s_ip6;
-        gs_free char *                 hostname = NULL;
-
-        connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV[i], &hostname);
-        g_assert(connections);
-        g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-        g_assert_cmpstr(hostname, ==, NULL);
+        gs_unref_object NMConnection *connection = NULL;
+        NMSettingConnection *         s_con;
+        NMSettingWired *              s_wired;
+        NMSettingIPConfig *           s_ip4;
+        NMSettingIPConfig *           s_ip6;
 
-        connection = g_hash_table_lookup(connections, "default_connection");
-        g_assert(connection);
-        nmtst_assert_connection_verifies_without_normalization(connection);
+        connection = _parse_con(ARGV[i], "default_connection");
 
         s_con = nm_connection_get_setting_connection(connection);
         g_assert(s_con);
@@ -192,22 +232,14 @@ test_dhcp_with_mtu(void)
 static void
 test_if_auto_with_mtu(void)
 {
-    gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *            ARGV        = NM_MAKE_STRV("ip=eth0:auto:1666");
-    NMConnection *                 connection;
-    NMSettingWired *               s_wired;
-    NMSettingIPConfig *            s_ip4;
-    NMSettingIPConfig *            s_ip6;
-    gs_free char *                 hostname = NULL;
+    const char *const *ARGV                  = NM_MAKE_STRV("ip=eth0:auto:1666");
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
+    connection = _parse_con(ARGV, "eth0");
 
-    connection = g_hash_table_lookup(connections, "eth0");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
 
     s_wired = nm_connection_get_setting_wired(connection);
@@ -228,21 +260,13 @@ test_if_auto_with_mtu(void)
 static void
 test_if_dhcp6(void)
 {
-    gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *            ARGV        = NM_MAKE_STRV("ip=eth1:dhcp6");
-    NMConnection *                 connection;
-    NMSettingIPConfig *            s_ip4;
-    NMSettingIPConfig *            s_ip6;
-    gs_free char *                 hostname = NULL;
+    const char *const *ARGV                  = NM_MAKE_STRV("ip=eth1:dhcp6");
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
+    connection = _parse_con(ARGV, "eth1");
 
-    connection = g_hash_table_lookup(connections, "eth1");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth1");
 
     s_ip4 = nm_connection_get_setting_ip4_config(connection);
@@ -261,22 +285,14 @@ test_if_dhcp6(void)
 static void
 test_if_auto_with_mtu_and_mac(void)
 {
-    gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *            ARGV = NM_MAKE_STRV("ip=eth2:auto6:2048:00:53:ef:12:34:56");
-    NMConnection *                 connection;
-    NMSettingWired *               s_wired;
-    NMSettingIPConfig *            s_ip4;
-    NMSettingIPConfig *            s_ip6;
-    gs_free char *                 hostname = NULL;
+    const char *const *ARGV                  = NM_MAKE_STRV("ip=eth2:auto6:2048:00:53:ef:12:34:56");
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
+    connection = _parse_con(ARGV, "eth2");
 
-    connection = g_hash_table_lookup(connections, "eth2");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth2");
 
     s_wired = nm_connection_get_setting_wired(connection);
@@ -310,15 +326,15 @@ test_if_ip4_manual(void)
     NMSettingIPConfig *            s_ip4;
     NMSettingIPConfig *            s_ip6;
     NMIPAddress *                  ip_addr;
-    gs_free char *                 hostname = NULL;
+    gs_free char *                 hostname            = NULL;
+    gint64                         carrier_timeout_sec = 0;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse(ARGV, &hostname, &carrier_timeout_sec);
     g_assert_cmpint(g_hash_table_size(connections), ==, 2);
     g_assert_cmpstr(hostname, ==, "hostname1.example.com");
+    g_assert_cmpint(carrier_timeout_sec, ==, 0);
 
     connection = g_hash_table_lookup(connections, "eth3");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth3");
 
@@ -351,7 +367,6 @@ test_if_ip4_manual(void)
     g_assert(nm_setting_ip_config_get_may_fail(s_ip6));
 
     connection = g_hash_table_lookup(connections, "eth4");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth4");
 
@@ -386,15 +401,15 @@ test_if_ip6_manual(void)
     NMConnection *                 connection;
     NMSettingIPConfig *            s_ip6;
     NMIPAddress *                  ip_addr;
-    gs_free char *                 hostname = NULL;
+    gs_free char *                 hostname            = NULL;
+    gint64                         carrier_timeout_sec = 0;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse(ARGV, &hostname, &carrier_timeout_sec);
     g_assert_cmpint(g_hash_table_size(connections), ==, 1);
     g_assert_cmpstr(hostname, ==, "hostname0.example.com");
+    g_assert_cmpint(carrier_timeout_sec, ==, 0);
 
     connection = g_hash_table_lookup(connections, "eth4");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth4");
 
@@ -427,7 +442,6 @@ test_if_off(void)
     NMConnection *                 connection;
     NMSettingIPConfig *            s_ip4;
     NMSettingIPConfig *            s_ip6;
-    gs_free char *                 hostname = NULL;
     struct {
         const char name[32];
         const char ipv4_method[32];
@@ -442,14 +456,11 @@ test_if_off(void)
         {"ens5", NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NM_SETTING_IP6_CONFIG_METHOD_MANUAL},
     };
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, G_N_ELEMENTS(conn_expected));
-    g_assert_cmpstr(hostname, ==, NULL);
 
     for (int i = 0; i < G_N_ELEMENTS(conn_expected); ++i) {
         connection = g_hash_table_lookup(connections, conn_expected[i].name);
-        g_assert(connection);
         nmtst_assert_connection_verifies_without_normalization(connection);
 
         s_ip4 = nm_connection_get_setting_ip4_config(connection);
@@ -472,15 +483,15 @@ test_if_mac_ifname(void)
     NMSettingIPConfig *            s_ip6;
     NMSettingWired *               s_wired;
     NMIPAddress *                  ip_addr;
-    gs_free char *                 hostname = NULL;
+    gs_free char *                 hostname            = NULL;
+    gint64                         carrier_timeout_sec = 0;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse(ARGV, &hostname, &carrier_timeout_sec);
     g_assert_cmpint(g_hash_table_size(connections), ==, 1);
     g_assert_cmpstr(hostname, ==, "hostname0");
+    g_assert_cmpint(carrier_timeout_sec, ==, 0);
 
     connection = g_hash_table_lookup(connections, "00:11:22:33:44:55");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "00:11:22:33:44:55");
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, NULL);
@@ -510,25 +521,17 @@ test_if_mac_ifname(void)
 static void
 test_multiple_merge(void)
 {
-    gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *            ARGV =
+    const char *const *ARGV =
         NM_MAKE_STRV("ip=192.0.2.2/16:::::eth0", "ip=[2001:db8::2]:::56::eth0");
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingIPConfig *  s_ip6;
-    NMIPAddress *        ip_addr;
-    gs_free char *       hostname = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMIPAddress *                 ip_addr;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
+    connection = _parse_con(ARGV, "eth0");
 
-    connection = g_hash_table_lookup(connections, "eth0");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
 
     s_con = nm_connection_get_setting_connection(connection);
@@ -567,18 +570,20 @@ static void
 test_multiple_bootdev(void)
 {
     gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *            ARGV =
-        NM_MAKE_STRV("nameserver=1.2.3.4", "ip=eth3:auto6", "ip=eth4:dhcp", "bootdev=eth4");
+
+    const char *const *ARGV = NM_MAKE_STRV("nameserver=1.2.3.4",
+                                           "ip=eth3:auto6",
+                                           "ip=eth4:dhcp",
+                                           "ip=eth5:link6",
+                                           "bootdev=eth4");
+
     NMConnection *       connection;
     NMSettingConnection *s_con;
     NMSettingIPConfig *  s_ip4;
     NMSettingIPConfig *  s_ip6;
-    gs_free char *       hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 2);
-    g_assert_cmpstr(hostname, ==, NULL);
+    connections = _parse_cons(ARGV);
+    g_assert_cmpint(g_hash_table_size(connections), ==, 3);
 
     connection = g_hash_table_lookup(connections, "eth3");
     g_assert(connection);
@@ -601,6 +606,17 @@ test_multiple_bootdev(void)
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
     g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 1);
     g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "1.2.3.4");
+
+    connection = g_hash_table_lookup(connections, "eth5");
+    g_assert(connection);
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1);
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                    ==,
+                    NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL);
 }
 
 static void
@@ -610,15 +626,11 @@ test_bootdev(void)
     const char *const *            ARGV        = NM_MAKE_STRV("vlan=vlan2:ens5", "bootdev=ens3");
     NMConnection *                 connection;
     NMSettingConnection *          s_con;
-    gs_free char *                 hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 3);
-    g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "ens3");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
 
     s_con = nm_connection_get_setting_connection(connection);
@@ -633,7 +645,6 @@ test_bootdev(void)
                     NMI_WAIT_DEVICE_TIMEOUT_MS);
 
     connection = g_hash_table_lookup(connections, "vlan2");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
 
     s_con = nm_connection_get_setting_connection(connection);
@@ -645,7 +656,6 @@ test_bootdev(void)
     g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "vlan2");
 
     connection = g_hash_table_lookup(connections, "ens5");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
 
     s_con = nm_connection_get_setting_connection(connection);
@@ -676,15 +686,11 @@ test_some_more(void)
     NMSettingIPConfig *            s_ip4;
     NMSettingIPConfig *            s_ip6;
     NMIPRoute *                    ip_route;
-    gs_free char *                 hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 2);
-    g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "eth1");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
 
     s_con = nm_connection_get_setting_connection(connection);
@@ -719,7 +725,6 @@ test_some_more(void)
     g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "2001:db8:3::53");
 
     connection = g_hash_table_lookup(connections, "ens10");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
 
     s_con = nm_connection_get_setting_connection(connection);
@@ -766,15 +771,11 @@ test_bond(void)
     NMSettingBond *                s_bond;
     NMIPRoute *                    ip_route;
     const char *                   master_uuid;
-    gs_free char *                 hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 3);
-    g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "bong0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_connection_type(connection),
                     ==,
@@ -812,7 +813,6 @@ test_bond(void)
     g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "mode"), ==, "balance-rr");
 
     connection = g_hash_table_lookup(connections, "eth0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
 
@@ -829,7 +829,6 @@ test_bond(void)
                     NM_CONNECTION_MULTI_CONNECT_SINGLE);
 
     connection = g_hash_table_lookup(connections, "eth1");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth1");
 
@@ -862,15 +861,11 @@ test_bond_ip(void)
     NMSettingBond *      s_bond;
     NMIPAddress *        ip_addr;
     const char *         master_uuid;
-    gs_free char *       hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 3);
-    g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "bond0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_connection_type(connection),
                     ==,
@@ -915,7 +910,6 @@ test_bond_ip(void)
     g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "mode"), ==, "balance-rr");
 
     connection = g_hash_table_lookup(connections, "eth0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
 
@@ -932,7 +926,6 @@ test_bond_ip(void)
                     NM_CONNECTION_MULTI_CONNECT_SINGLE);
 
     connection = g_hash_table_lookup(connections, "eth1");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth1");
 
@@ -960,16 +953,12 @@ test_bond_default(void)
     NMSettingIPConfig *            s_ip6;
     NMSettingBond *                s_bond;
     const char *                   master_uuid;
-    gs_free char *                 hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 2);
-    g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "bond0");
 
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_connection_type(connection),
                     ==,
@@ -1000,7 +989,6 @@ test_bond_default(void)
     g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, "mode"), ==, "balance-rr");
 
     connection = g_hash_table_lookup(connections, "eth0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
 
@@ -1031,15 +1019,11 @@ test_bridge(void)
     NMSettingBridge *              s_bridge;
     NMIPRoute *                    ip_route;
     const char *                   master_uuid;
-    gs_free char *                 hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 3);
-    g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "bridge0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_connection_type(connection),
                     ==,
@@ -1077,7 +1061,6 @@ test_bridge(void)
     g_assert_cmpint(nm_setting_bridge_get_stp(s_bridge), ==, FALSE);
 
     connection = g_hash_table_lookup(connections, "eth0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
 
@@ -1096,7 +1079,6 @@ test_bridge(void)
                     NM_CONNECTION_MULTI_CONNECT_SINGLE);
 
     connection = g_hash_table_lookup(connections, "eth1");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth1");
 
@@ -1126,16 +1108,12 @@ test_bridge_default(void)
     NMSettingIPConfig *            s_ip6;
     NMSettingBridge *              s_bridge;
     const char *                   master_uuid;
-    gs_free char *                 hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 2);
-    g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "br0");
 
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_connection_type(connection),
                     ==,
@@ -1164,7 +1142,6 @@ test_bridge_default(void)
     g_assert(s_bridge);
 
     connection = g_hash_table_lookup(connections, "eth0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
 
@@ -1197,16 +1174,12 @@ test_bridge_ip(void)
     NMSettingWired *     s_wired;
     NMSettingBridge *    s_bridge;
     const char *         master_uuid;
-    gs_free char *       hostname = NULL;
     guint                i;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 11);
-    g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "bridge123");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_connection_type(connection),
                     ==,
@@ -1237,7 +1210,6 @@ test_bridge_ip(void)
         nm_sprintf_buf(ifname, "eth%u", i);
 
         connection = g_hash_table_lookup(connections, ifname);
-        g_assert(connection);
         nmtst_assert_connection_verifies_without_normalization(connection);
         g_assert_cmpstr(nm_connection_get_id(connection), ==, ifname);
 
@@ -1268,15 +1240,11 @@ test_team(void)
     NMSettingIPConfig *            s_ip6;
     NMSettingTeam *                s_team;
     const char *                   master_uuid;
-    gs_free char *                 hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 3);
-    g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "team0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_connection_type(connection),
                     ==,
@@ -1307,7 +1275,6 @@ test_team(void)
     g_assert(s_team);
 
     connection = g_hash_table_lookup(connections, "eth0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
 
@@ -1324,7 +1291,6 @@ test_team(void)
                     NM_CONNECTION_MULTI_CONNECT_SINGLE);
 
     connection = g_hash_table_lookup(connections, "eth1");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth1");
 
@@ -1355,16 +1321,12 @@ test_vlan(void)
         NMSettingIPConfig *            s_ip4;
         NMSettingIPConfig *            s_ip6;
         NMSettingVlan *                s_vlan;
-        gs_free char *                 hostname = NULL;
 
-        connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV[i], &hostname);
-        g_assert(connections);
+        connections = _parse_cons(ARGV[i]);
         g_assert_cmpint(g_hash_table_size(connections), ==, 2);
-        g_assert_cmpstr(hostname, ==, NULL);
 
         /* VLAN eth0.100 */
         connection = g_hash_table_lookup(connections, "eth0.100");
-        g_assert(connection);
         nmtst_assert_connection_verifies_without_normalization(connection);
         g_assert_cmpstr(nm_connection_get_connection_type(connection),
                         ==,
@@ -1390,7 +1352,6 @@ test_vlan(void)
 
         /* Ethernet eth0 */
         connection = g_hash_table_lookup(connections, "eth0");
-        g_assert(connection);
         nmtst_assert_connection_verifies_without_normalization(connection);
         g_assert_cmpstr(nm_connection_get_connection_type(connection),
                         ==,
@@ -1425,16 +1386,12 @@ test_vlan_with_dhcp_on_parent(void)
         NMSettingIPConfig *            s_ip4;
         NMSettingIPConfig *            s_ip6;
         NMSettingVlan *                s_vlan;
-        gs_free char *                 hostname = NULL;
 
-        connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV[i], &hostname);
-        g_assert(connections);
+        connections = _parse_cons(ARGV[i]);
         g_assert_cmpint(g_hash_table_size(connections), ==, 2);
-        g_assert_cmpstr(hostname, ==, NULL);
 
         /* VLAN eth0.100 */
         connection = g_hash_table_lookup(connections, "eth0.100");
-        g_assert(connection);
         nmtst_assert_connection_verifies_without_normalization(connection);
         g_assert_cmpstr(nm_connection_get_connection_type(connection),
                         ==,
@@ -1460,7 +1417,6 @@ test_vlan_with_dhcp_on_parent(void)
 
         /* Ethernet eth0 */
         connection = g_hash_table_lookup(connections, "eth0");
-        g_assert(connection);
         nmtst_assert_connection_verifies_without_normalization(connection);
         g_assert_cmpstr(nm_connection_get_connection_type(connection),
                         ==,
@@ -1502,16 +1458,12 @@ test_vlan_over_bond(void)
         NMSettingIPConfig *            s_ip4;
         NMSettingIPConfig *            s_ip6;
         NMSettingVlan *                s_vlan;
-        gs_free char *                 hostname = NULL;
 
-        connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV[i], &hostname);
-        g_assert(connections);
+        connections = _parse_cons(ARGV[i]);
         g_assert_cmpint(g_hash_table_size(connections), ==, 4);
-        g_assert_cmpstr(hostname, ==, NULL);
 
         /* VLAN vlan1 */
         connection = g_hash_table_lookup(connections, "vlan1");
-        g_assert(connection);
         nmtst_assert_connection_verifies_without_normalization(connection);
         g_assert_cmpstr(nm_connection_get_connection_type(connection),
                         ==,
@@ -1537,7 +1489,6 @@ test_vlan_over_bond(void)
 
         /* Bond bond2 */
         connection = g_hash_table_lookup(connections, "bond2");
-        g_assert(connection);
         nmtst_assert_connection_verifies_without_normalization(connection);
         g_assert_cmpstr(nm_connection_get_connection_type(connection),
                         ==,
@@ -1567,19 +1518,11 @@ test_vlan_over_bond(void)
 static void
 test_ibft_ip_dev(void)
 {
-    const char *const *ARGV                    = NM_MAKE_STRV("ip=eth0:ibft");
-    gs_unref_hashtable GHashTable *connections = NULL;
-    NMSettingConnection *          s_con;
-    NMConnection *                 connection;
-    gs_free char *                 hostname = NULL;
-
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
+    const char *const *  ARGV = NM_MAKE_STRV("ip=eth0:ibft");
+    NMSettingConnection *s_con;
+    gs_unref_object NMConnection *connection = NULL;
 
-    connection = g_hash_table_lookup(connections, "eth0");
-    g_assert(connection);
+    connection = _parse_con(ARGV, "eth0");
 
     s_con = nm_connection_get_setting_connection(connection);
     g_assert(s_con);
@@ -1592,19 +1535,11 @@ test_ibft_ip_dev(void)
 static void
 test_ibft_ip_dev_mac(void)
 {
-    const char *const *ARGV                    = NM_MAKE_STRV("ip=00-53-06-66-ab-01:ibft");
-    gs_unref_hashtable GHashTable *connections = NULL;
-    NMSettingConnection *          s_con;
-    NMConnection *                 connection;
-    gs_free char *                 hostname = NULL;
-
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
+    const char *const *  ARGV = NM_MAKE_STRV("ip=00-53-06-66-ab-01:ibft");
+    NMSettingConnection *s_con;
+    gs_unref_object NMConnection *connection = NULL;
 
-    connection = g_hash_table_lookup(connections, "00:53:06:66:AB:01");
-    g_assert(connection);
+    connection = _parse_con(ARGV, "00:53:06:66:AB:01");
 
     s_con = nm_connection_get_setting_connection(connection);
     g_assert(s_con);
@@ -1619,21 +1554,17 @@ _test_ibft_ip(const char *const *ARGV)
 {
     gs_unref_hashtable GHashTable *connections = NULL;
     NMConnection *                 connection;
-    gs_free char *                 hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 2);
-    g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "ibft0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
+
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "iBFT VLAN Connection 0");
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, NULL);
 
     connection = g_hash_table_lookup(connections, "ibft2");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "iBFT Connection 2");
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, NULL);
@@ -1660,12 +1591,9 @@ test_ignore_extra(void)
 {
     gs_unref_hashtable GHashTable *connections = NULL;
     const char *const *            ARGV        = NM_MAKE_STRV("blabla", "extra", "lalala");
-    gs_free char *                 hostname    = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 0);
-    g_assert_cmpstr(hostname, ==, NULL);
 }
 
 static void
@@ -1686,12 +1614,13 @@ test_rd_znet(void)
         {.name = "portno", .value_str = "1"},
     };
     int           i_s390_options_keys;
-    gs_free char *hostname = NULL;
+    gs_free char *hostname            = NULL;
+    gint64        carrier_timeout_sec = 0;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse(ARGV, &hostname, &carrier_timeout_sec);
     g_assert_cmpint(g_hash_table_size(connections), ==, 2);
     g_assert_cmpstr(hostname, ==, "foo.example.com");
+    g_assert_cmpint(carrier_timeout_sec, ==, 0);
 
     connection = g_hash_table_lookup(connections, "enc800");
     g_assert(NM_IS_CONNECTION(connection));
@@ -1772,12 +1701,13 @@ test_rd_znet_legacy(void)
                      "net.ifnames=0");
     NMConnection *       connection;
     NMSettingConnection *s_con;
-    gs_free char *       hostname = NULL;
+    gs_free char *       hostname            = NULL;
+    gint64               carrier_timeout_sec = 0;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse(ARGV, &hostname, &carrier_timeout_sec);
     g_assert_cmpint(g_hash_table_size(connections), ==, 2);
     g_assert_cmpstr(hostname, ==, "foo.example.com");
+    g_assert_cmpint(carrier_timeout_sec, ==, 0);
 
     connection = g_hash_table_lookup(connections, "eth0");
     g_assert(NM_IS_CONNECTION(connection));
@@ -1812,33 +1742,22 @@ test_rd_znet_no_ip(void)
     gs_unref_hashtable GHashTable *connections = NULL;
     const char *const *const       ARGV =
         NM_MAKE_STRV("rd.znet=qeth,0.0.0800,0.0.0801,0.0.0802,layer2=0,portno=1");
-    gs_free char *hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 0);
-    g_assert_cmpstr(hostname, ==, NULL);
 }
 
 static void
 test_bootif_ip(void)
 {
-    gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *            ARGV = NM_MAKE_STRV("BOOTIF=00:53:AB:cd:02:03", "ip=dhcp");
-    NMConnection *                 connection;
-    NMSettingWired *               s_wired;
-    NMSettingIPConfig *            s_ip4;
-    NMSettingIPConfig *            s_ip6;
-    gs_free char *                 hostname = NULL;
+    const char *const *ARGV                  = NM_MAKE_STRV("BOOTIF=00:53:AB:cd:02:03", "ip=dhcp");
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
+    connection = _parse_con(ARGV, "default_connection");
 
-    connection = g_hash_table_lookup(connections, "default_connection");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "Wired Connection");
 
     s_wired = nm_connection_get_setting_wired(connection);
@@ -1867,15 +1786,11 @@ test_neednet(void)
                                            "bridge=br0:eno3");
     NMConnection *                 connection;
     NMSettingConnection *          s_con;
-    gs_free char *                 hostname = NULL;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 4);
-    g_assert_cmpstr(hostname, ==, NULL);
 
     connection = g_hash_table_lookup(connections, "eno1");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     s_con = nm_connection_get_setting_connection(connection);
     g_assert(s_con);
@@ -1885,7 +1800,6 @@ test_neednet(void)
                     NMI_WAIT_DEVICE_TIMEOUT_MS);
 
     connection = g_hash_table_lookup(connections, "eno2");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     s_con = nm_connection_get_setting_connection(connection);
     g_assert(s_con);
@@ -1895,7 +1809,6 @@ test_neednet(void)
                     NMI_WAIT_DEVICE_TIMEOUT_MS);
 
     connection = g_hash_table_lookup(connections, "eno3");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     s_con = nm_connection_get_setting_connection(connection);
     g_assert(s_con);
@@ -1905,7 +1818,6 @@ test_neednet(void)
                     NMI_WAIT_DEVICE_TIMEOUT_MS);
 
     connection = g_hash_table_lookup(connections, "br0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
     s_con = nm_connection_get_setting_connection(connection);
     g_assert(s_con);
@@ -1916,22 +1828,14 @@ test_neednet(void)
 static void
 test_bootif_no_ip(void)
 {
-    gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *            ARGV        = NM_MAKE_STRV("BOOTIF=00:53:AB:cd:02:03");
-    NMConnection *                 connection;
-    NMSettingWired *               s_wired;
-    NMSettingIPConfig *            s_ip4;
-    NMSettingIPConfig *            s_ip6;
-    gs_free char *                 hostname = NULL;
+    const char *const *ARGV                  = NM_MAKE_STRV("BOOTIF=00:53:AB:cd:02:03");
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
+    connection = _parse_con(ARGV, "default_connection");
 
-    connection = g_hash_table_lookup(connections, "default_connection");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "Wired Connection");
 
     s_wired = nm_connection_get_setting_wired(connection);
@@ -1952,59 +1856,68 @@ test_bootif_no_ip(void)
 static void
 test_bootif_hwtype(void)
 {
-    gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *ARGV = NM_MAKE_STRV("ip=eth0:dhcp", "BOOTIF=01-00-53-AB-cd-02-03");
-    NMConnection *     connection;
-    NMSettingWired *   s_wired;
-    NMSettingIPConfig *s_ip4;
-    NMSettingIPConfig *s_ip6;
-    gs_free char *     hostname = NULL;
+    const char *const *ARGV0  = NM_MAKE_STRV("ip=eth0:dhcp", "BOOTIF=01-00-53-AB-cd-02-03");
+    const char *const *ARGV1  = NM_MAKE_STRV("ip=eth0:dhcp", "BOOTIF=00-00-53-Ab-cD-02-03");
+    const char *const *ARGV[] = {ARGV0, ARGV1};
+    guint              i;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 2);
-    g_assert_cmpstr(hostname, ==, NULL);
+    for (i = 0; i < G_N_ELEMENTS(ARGV); i++) {
+        gs_unref_hashtable GHashTable *connections = NULL;
+        NMConnection *                 connection;
+        NMSettingWired *               s_wired;
+        NMSettingIPConfig *            s_ip4;
+        NMSettingIPConfig *            s_ip6;
 
-    connection = g_hash_table_lookup(connections, "eth0");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
-    g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
+        connections = _parse_cons(ARGV[i]);
+        g_assert_cmpint(g_hash_table_size(connections), ==, 2);
 
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(!nm_setting_wired_get_mac_address(s_wired));
-    g_assert(s_wired);
+        connection = g_hash_table_lookup(connections, "eth0");
+        nmtst_assert_connection_verifies_without_normalization(connection);
+        g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
-    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
-    g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
-    g_assert(!nm_setting_ip_config_get_may_fail(s_ip4));
+        s_wired = nm_connection_get_setting_wired(connection);
+        g_assert(!nm_setting_wired_get_mac_address(s_wired));
+        g_assert(s_wired);
 
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
-    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
-    g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6));
+        s_ip4 = nm_connection_get_setting_ip4_config(connection);
+        g_assert(s_ip4);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                        ==,
+                        NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+        g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
+        g_assert(!nm_setting_ip_config_get_may_fail(s_ip4));
 
-    connection = g_hash_table_lookup(connections, "bootif_connection");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
-    g_assert_cmpstr(nm_connection_get_id(connection), ==, "BOOTIF Connection");
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        g_assert(s_ip6);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                        ==,
+                        NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+        g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6));
 
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert_cmpstr(nm_setting_wired_get_mac_address(s_wired), ==, "00:53:AB:CD:02:03");
-    g_assert(s_wired);
+        connection = g_hash_table_lookup(connections, "bootif_connection");
+        nmtst_assert_connection_verifies_without_normalization(connection);
+        g_assert_cmpstr(nm_connection_get_id(connection), ==, "BOOTIF Connection");
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
-    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
-    g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
-    g_assert(nm_setting_ip_config_get_may_fail(s_ip4));
+        s_wired = nm_connection_get_setting_wired(connection);
+        g_assert_cmpstr(nm_setting_wired_get_mac_address(s_wired), ==, "00:53:AB:CD:02:03");
+        g_assert(s_wired);
 
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
-    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
-    g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6));
-    g_assert(nm_setting_ip_config_get_may_fail(s_ip6));
+        s_ip4 = nm_connection_get_setting_ip4_config(connection);
+        g_assert(s_ip4);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                        ==,
+                        NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+        g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
+        g_assert(nm_setting_ip_config_get_may_fail(s_ip4));
+
+        s_ip6 = nm_connection_get_setting_ip6_config(connection);
+        g_assert(s_ip6);
+        g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                        ==,
+                        NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+        g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6));
+        g_assert(nm_setting_ip_config_get_may_fail(s_ip6));
+    }
 }
 
 /* Check that nameservers are assigned to all existing
@@ -2024,15 +1937,15 @@ test_nameserver(void)
                      "nameserver=[2606:4700:4700::1111]");
     NMConnection *     connection;
     NMSettingIPConfig *s_ip;
-    gs_free char *     hostname = NULL;
+    gs_free char *     hostname            = NULL;
+    gint64             carrier_timeout_sec = 0;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse(ARGV, &hostname, &carrier_timeout_sec);
     g_assert_cmpint(g_hash_table_size(connections), ==, 3);
     g_assert_cmpstr(hostname, ==, "foo.example.com");
+    g_assert_cmpint(carrier_timeout_sec, ==, 0);
 
     connection = g_hash_table_lookup(connections, "eth0");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
 
     s_ip = nm_connection_get_setting_ip4_config(connection);
@@ -2042,7 +1955,6 @@ test_nameserver(void)
     g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip, 1), ==, "1.0.0.1");
 
     connection = g_hash_table_lookup(connections, "eth1");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
 
     s_ip = nm_connection_get_setting_ip6_config(connection);
@@ -2051,7 +1963,6 @@ test_nameserver(void)
     g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip, 0), ==, "2606:4700:4700::1111");
 
     connection = g_hash_table_lookup(connections, "eth2");
-    g_assert(connection);
     nmtst_assert_connection_verifies_without_normalization(connection);
 
     s_ip = nm_connection_get_setting_ip4_config(connection);
@@ -2065,77 +1976,52 @@ static void
 test_bootif_off(void)
 {
     gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *ARGV     = NM_MAKE_STRV("BOOTIF=01-00-53-AB-cd-02-03", "rd.bootif=0");
-    gs_free char *     hostname = NULL;
+    const char *const *ARGV = NM_MAKE_STRV("BOOTIF=01-00-53-AB-cd-02-03", "rd.bootif=0");
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
+    connections = _parse_cons(ARGV);
     g_assert_cmpint(g_hash_table_size(connections), ==, 0);
-    g_assert_cmpstr(hostname, ==, NULL);
 }
 
 static void
 test_dhcp_vendor_class_id(void)
 {
-    gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const * ARGV = NM_MAKE_STRV("rd.net.dhcp.vendor-class=testvci", "ip=eno1:dhcp");
-    NMConnection *      connection;
-    NMSettingIP4Config *s_ip4;
-    gs_free char *      hostname          = NULL;
-    gs_free char *      vci_long          = NULL;
-    char                vci_arg_long[512] = {0};
-
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
-
-    connection = g_hash_table_lookup(connections, "eno1");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
-    s_ip4 = NM_SETTING_IP4_CONFIG(nm_connection_get_setting_ip4_config(connection));
+    const char *const *ARGV;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIP4Config *          s_ip4;
+    gs_free char *                vci_long          = NULL;
+    char                          vci_arg_long[512] = {0};
+
+    ARGV       = NM_MAKE_STRV("rd.net.dhcp.vendor-class=testvci", "ip=eno1:dhcp");
+    connection = _parse_con(ARGV, "eno1");
+    s_ip4      = NM_SETTING_IP4_CONFIG(nm_connection_get_setting_ip4_config(connection));
     g_assert_cmpstr(nm_setting_ip4_config_get_dhcp_vendor_class_identifier(s_ip4), ==, "testvci");
 
-    nm_clear_pointer(&connections, g_hash_table_unref);
+    g_clear_object(&connection);
 
-    ARGV        = NM_MAKE_STRV("rd.net.dhcp.vendor-class", "ip=eno1:dhcp");
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    connection  = g_hash_table_lookup(connections, "eno1");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
-    s_ip4 = NM_SETTING_IP4_CONFIG(nm_connection_get_setting_ip4_config(connection));
+    ARGV       = NM_MAKE_STRV("rd.net.dhcp.vendor-class", "ip=eno1:dhcp");
+    connection = _parse_con(ARGV, "eno1");
+    s_ip4      = NM_SETTING_IP4_CONFIG(nm_connection_get_setting_ip4_config(connection));
     g_assert(nm_setting_ip4_config_get_dhcp_vendor_class_identifier(s_ip4) == NULL);
 
-    nm_clear_pointer(&connections, g_hash_table_unref);
+    g_clear_object(&connection);
 
     memset(vci_arg_long, 'A', 400);
-    vci_long    = g_strdup_printf("rd.net.dhcp.vendor-class=%s", vci_arg_long);
-    ARGV        = NM_MAKE_STRV(vci_long, "ip=eno1:dhcp");
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    connection  = g_hash_table_lookup(connections, "eno1");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
-    s_ip4 = NM_SETTING_IP4_CONFIG(nm_connection_get_setting_ip4_config(connection));
+    vci_long   = g_strdup_printf("rd.net.dhcp.vendor-class=%s", vci_arg_long);
+    ARGV       = NM_MAKE_STRV(vci_long, "ip=eno1:dhcp");
+    connection = _parse_con(ARGV, "eno1");
+    s_ip4      = NM_SETTING_IP4_CONFIG(nm_connection_get_setting_ip4_config(connection));
     g_assert(nm_setting_ip4_config_get_dhcp_vendor_class_identifier(s_ip4) == NULL);
 }
 
 static void
 test_infiniband_iface(void)
 {
-    gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *            ARGV        = NM_MAKE_STRV("ip=ib1:dhcp");
-    NMConnection *                 connection;
-    NMSettingInfiniband *          s_ib;
-    gs_free char *                 hostname = NULL;
+    const char *const *ARGV                  = NM_MAKE_STRV("ip=ib1:dhcp");
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingInfiniband *         s_ib;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
+    connection = _parse_con(ARGV, "ib1");
 
-    connection = g_hash_table_lookup(connections, "ib1");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_connection_type(connection),
                     ==,
                     NM_SETTING_INFINIBAND_SETTING_NAME);
@@ -2146,22 +2032,13 @@ test_infiniband_iface(void)
 static void
 test_infiniband_mac(void)
 {
-    gs_unref_hashtable GHashTable *connections = NULL;
-    const char *const *            ARGV =
+    const char *const *ARGV =
         NM_MAKE_STRV("ip=00-11-22-33-44-55-66-77-88-99-aa-bb-cc-dd-ee-ff-00-11-22-33:dhcp");
-    NMConnection *       connection;
-    NMSettingInfiniband *s_ib;
-    gs_free char *       hostname = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingInfiniband *         s_ib;
 
-    connections = nmi_cmdline_reader_parse(TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
-    g_assert(connections);
-    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
-    g_assert_cmpstr(hostname, ==, NULL);
+    connection = _parse_con(ARGV, "00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33");
 
-    connection = g_hash_table_lookup(connections,
-                                     "00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33");
-    g_assert(connection);
-    nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_connection_type(connection),
                     ==,
                     NM_SETTING_INFINIBAND_SETTING_NAME);
@@ -2173,6 +2050,20 @@ test_infiniband_mac(void)
                     "00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33");
 }
 
+static void
+test_carrier_timeout(void)
+{
+    gs_unref_hashtable GHashTable *connections         = NULL;
+    const char *const *            ARGV                = NM_MAKE_STRV("rd.net.timeout.carrier=20");
+    gs_free char *                 hostname            = NULL;
+    gint64                         carrier_timeout_sec = 0;
+
+    connections = _parse(ARGV, &hostname, &carrier_timeout_sec);
+    g_assert_cmpint(g_hash_table_size(connections), ==, 0);
+    g_assert_cmpstr(hostname, ==, NULL);
+    g_assert_cmpint(carrier_timeout_sec, ==, 20);
+}
+
 NMTST_DEFINE();
 
 int
@@ -2221,6 +2112,7 @@ main(int argc, char **argv)
     g_test_add_func("/initrd/cmdline/dhcp/vendor_class_id", test_dhcp_vendor_class_id);
     g_test_add_func("/initrd/cmdline/infiniband/iface", test_infiniband_iface);
     g_test_add_func("/initrd/cmdline/infiniband/mac", test_infiniband_mac);
+    g_test_add_func("/initrd/cmdline/carrier_timeout", test_carrier_timeout);
 
     return g_test_run();
 }
diff --git a/src/initrd/tests/test-dt-reader.c b/src/core/initrd/tests/test-dt-reader.c
index 0d457782..619a4e6f 100644
--- a/src/initrd/tests/test-dt-reader.c
+++ b/src/core/initrd/tests/test-dt-reader.c
@@ -1,14 +1,13 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <netinet/ether.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -20,6 +19,10 @@
 
 #include "nm-test-utils-core.h"
 
+#define TEST_INITRD_DIR NM_BUILD_SRCDIR "/src/core/initrd/tests"
+
+/*****************************************************************************/
+
 static void
 test_read_dt_ofw(void)
 {
diff --git a/src/initrd/tests/test-ibft-reader.c b/src/core/initrd/tests/test-ibft-reader.c
index 51942b18..1d263b9e 100644
--- a/src/initrd/tests/test-ibft-reader.c
+++ b/src/core/initrd/tests/test-ibft-reader.c
@@ -1,14 +1,13 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <netinet/ether.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -20,6 +19,10 @@
 
 #include "nm-test-utils-core.h"
 
+#define TEST_INITRD_DIR NM_BUILD_SRCDIR "/src/core/initrd/tests"
+
+/*****************************************************************************/
+
 static NMConnection *
 read_connection(const char *sysfs_dir, const char *expected_mac, GError **error)
 {
diff --git a/src/main-utils.c b/src/core/main-utils.c
index 3bc28014..9bc497ca 100644
--- a/src/main-utils.c
+++ b/src/core/main-utils.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2012 Red Hat, Inc.
  * Copyright (C) 2005 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <stdio.h>
 #include <fcntl.h>
diff --git a/src/main-utils.h b/src/core/main-utils.h
index 0cd4ee47..2eb731a1 100644
--- a/src/main-utils.h
+++ b/src/core/main-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
diff --git a/src/main.c b/src/core/main.c
index 467a1bed..9b79bcbb 100644
--- a/src/main.c
+++ b/src/core/main.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2005 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <getopt.h>
 #include <locale.h>
@@ -426,8 +426,9 @@ main(int argc, char *argv[])
     }
 
     nm_log_info(LOGD_CORE,
-                "NetworkManager (version " NM_DIST_VERSION ") is starting... (%s)",
-                nm_config_get_first_start(config) ? "for the first time" : "after a restart");
+                "NetworkManager (version " NM_DIST_VERSION ") is starting... (%s%s)",
+                nm_config_get_first_start(config) ? "for the first time" : "after a restart",
+                NM_MORE_ASSERTS != 0 ? ", asserts:" G_STRINGIFY(NM_MORE_ASSERTS) : "");
 
     nm_log_info(LOGD_CORE,
                 "Read config: %s",
diff --git a/src/core/meson.build b/src/core/meson.build
new file mode 100644
index 00000000..368a3e85
--- /dev/null
+++ b/src/core/meson.build
@@ -0,0 +1,333 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+src_inc = include_directories('.')
+
+core_plugin_dep = declare_dependency(
+  sources: libnm_core_enum_sources[1],
+  include_directories: [
+    src_inc,
+    top_inc,
+    shared_inc,
+    libnm_core_inc,
+  ],
+  dependencies: [
+    glib_dep,
+  ],
+)
+
+core_default_dep = declare_dependency(
+  sources: libnm_core_enum_sources[1],
+  include_directories: src_inc,
+  dependencies: [
+    libnm_core_nm_default_dep,
+    libnm_log_core_dep,
+    libnm_platform_dep,
+  ],
+)
+
+install_data(
+  'org.freedesktop.NetworkManager.conf',
+  install_dir: dbus_conf_dir,
+)
+
+subdir('systemd')
+
+core_plugins = []
+
+daemon_c_flags = [
+  '-DG_LOG_DOMAIN="NetworkManager"',
+]
+
+platform_wifi_wext_source = files()
+if enable_wext
+  platform_wifi_wext_source += files('platform/wifi/nm-wifi-utils-wext.c')
+endif
+
+libNetworkManagerBase = static_library(
+  'NetworkManagerBase',
+  sources: files(
+    'dhcp/nm-dhcp-client.c',
+    'dhcp/nm-dhcp-manager.c',
+    'dhcp/nm-dhcp-nettools.c',
+    'dhcp/nm-dhcp-systemd.c',
+    'dhcp/nm-dhcp-utils.c',
+    'dhcp/nm-dhcp-options.c',
+    'ndisc/nm-lndp-ndisc.c',
+    'ndisc/nm-ndisc.c',
+    'platform/wifi/nm-wifi-utils-nl80211.c',
+    'platform/wifi/nm-wifi-utils.c',
+    'platform/wpan/nm-wpan-utils.c',
+    'platform/nm-linux-platform.c',
+    'platform/nm-platform.c',
+    'platform/nmp-object.c',
+    'platform/nmp-rules-manager.c',
+    'main-utils.c',
+    'NetworkManagerUtils.c',
+    'nm-core-utils.c',
+    'nm-dbus-object.c',
+    'nm-dbus-utils.c',
+    'nm-netns.c',
+    'nm-l3-config-data.c',
+    'nm-l3-ipv4ll.c',
+    'nm-l3cfg.c',
+    'nm-ip-config.c',
+    'nm-ip4-config.c',
+    'nm-ip6-config.c',
+  ) + platform_wifi_wext_source,
+  dependencies: [
+    core_default_dep,
+    libn_dhcp4_dep,
+    libnm_core_dep,
+    libnm_systemd_shared_dep,
+    libnm_udev_aux_dep,
+    libsystemd_dep,
+    libudev_dep,
+  ],
+  c_args: daemon_c_flags,
+)
+
+nm_deps = [
+  core_default_dep,
+  dl_dep,
+  libn_acd_dep,
+  libndp_dep,
+  libudev_dep,
+  logind_dep,
+]
+
+if enable_concheck
+  nm_deps += libcurl_dep
+endif
+
+if enable_libaudit
+  nm_deps += libaudit_dep
+endif
+
+if enable_libpsl
+  nm_deps += libpsl_dep
+endif
+
+if enable_selinux
+  nm_deps += selinux_dep
+endif
+
+libNetworkManager = static_library(
+  'NetworkManager',
+  sources: files(
+    'devices/nm-acd-manager.c',
+    'devices/nm-device-6lowpan.c',
+    'devices/nm-device-bond.c',
+    'devices/nm-device-bridge.c',
+    'devices/nm-device.c',
+    'devices/nm-device-dummy.c',
+    'devices/nm-device-ethernet.c',
+    'devices/nm-device-ethernet-utils.c',
+    'devices/nm-device-factory.c',
+    'devices/nm-device-generic.c',
+    'devices/nm-device-infiniband.c',
+    'devices/nm-device-ip-tunnel.c',
+    'devices/nm-device-macsec.c',
+    'devices/nm-device-macvlan.c',
+    'devices/nm-device-ppp.c',
+    'devices/nm-device-tun.c',
+    'devices/nm-device-veth.c',
+    'devices/nm-device-vlan.c',
+    'devices/nm-device-vrf.c',
+    'devices/nm-device-vxlan.c',
+    'devices/nm-device-wireguard.c',
+    'devices/nm-device-wpan.c',
+    'devices/nm-lldp-listener.c',
+    'dhcp/nm-dhcp-dhclient.c',
+    'dhcp/nm-dhcp-dhclient-utils.c',
+    'dhcp/nm-dhcp-dhcpcanon.c',
+    'dhcp/nm-dhcp-dhcpcd.c',
+    'dhcp/nm-dhcp-listener.c',
+    'dns/nm-dns-dnsmasq.c',
+    'dns/nm-dns-manager.c',
+    'dns/nm-dns-plugin.c',
+    'dns/nm-dns-systemd-resolved.c',
+    'dns/nm-dns-unbound.c',
+    'dnsmasq/nm-dnsmasq-manager.c',
+    'dnsmasq/nm-dnsmasq-utils.c',
+    'ppp/nm-ppp-manager-call.c',
+    'settings/plugins/keyfile/nms-keyfile-storage.c',
+    'settings/plugins/keyfile/nms-keyfile-plugin.c',
+    'settings/plugins/keyfile/nms-keyfile-reader.c',
+    'settings/plugins/keyfile/nms-keyfile-utils.c',
+    'settings/plugins/keyfile/nms-keyfile-writer.c',
+    'settings/nm-agent-manager.c',
+    'settings/nm-secret-agent.c',
+    'settings/nm-settings.c',
+    'settings/nm-settings-connection.c',
+    'settings/nm-settings-plugin.c',
+    'settings/nm-settings-storage.c',
+    'settings/nm-settings-utils.c',
+    'supplicant/nm-supplicant-config.c',
+    'supplicant/nm-supplicant-interface.c',
+    'supplicant/nm-supplicant-manager.c',
+    'supplicant/nm-supplicant-settings-verify.c',
+    'vpn/nm-vpn-connection.c',
+    'vpn/nm-vpn-manager.c',
+    'nm-active-connection.c',
+    'nm-act-request.c',
+    'nm-audit-manager.c',
+    'nm-auth-manager.c',
+    'nm-auth-utils.c',
+    'nm-dbus-manager.c',
+    'nm-checkpoint.c',
+    'nm-checkpoint-manager.c',
+    'nm-config.c',
+    'nm-config-data.c',
+    'nm-connectivity.c',
+    'nm-dcb.c',
+    'nm-dhcp-config.c',
+    'nm-dispatcher.c',
+    'nm-firewall-manager.c',
+    'nm-hostname-manager.c',
+    'nm-keep-alive.c',
+    'nm-manager.c',
+    'nm-pacrunner-manager.c',
+    'nm-policy.c',
+    'nm-proxy-config.c',
+    'nm-rfkill-manager.c',
+    'nm-session-monitor.c',
+    'nm-sleep-monitor.c',
+  ),
+  dependencies: nm_deps,
+  c_args: daemon_c_flags,
+  link_with: [
+    libNetworkManagerBase,
+    libnm_systemd_core,
+    libnm_systemd_shared,
+  ],
+)
+
+executable(
+  'nm-iface-helper',
+  'nm-iface-helper.c',
+  dependencies: [
+    core_default_dep,
+    dl_dep,
+    libndp_dep,
+    libudev_dep,
+    libn_acd_dep,
+  ],
+  c_args: daemon_c_flags,
+  link_with: [
+    libNetworkManagerBase,
+    libnm_systemd_core,
+    libnm_systemd_shared,
+  ],
+  link_args: ldflags_linker_script_binary,
+  link_depends: linker_script_binary,
+  install: true,
+  install_dir: nm_libexecdir,
+)
+
+if enable_tests
+  test_c_flags = daemon_c_flags
+  if require_root_tests
+    test_c_flags += ['-DREQUIRE_ROOT_TESTS=1']
+  endif
+
+  libNetworkManagerTest = static_library(
+    'NetworkManagerTest',
+    sources: files(
+      'ndisc/nm-fake-ndisc.c',
+      'platform/tests/test-common.c',
+      'platform/nm-fake-platform.c',
+    ),
+    dependencies: core_default_dep,
+    c_args: test_c_flags,
+    link_with: libNetworkManager,
+  )
+
+  libNetworkManagerTest_dep = declare_dependency(
+    dependencies: core_default_dep,
+    link_with: libNetworkManagerTest,
+  )
+
+  subdir('dnsmasq/tests')
+  subdir('ndisc/tests')
+  subdir('platform/tests')
+  subdir('supplicant/tests')
+  subdir('tests')
+endif
+
+subdir('dhcp')
+
+if enable_ppp
+  subdir('ppp')
+endif
+
+subdir('devices')
+subdir('initrd')
+subdir('settings/plugins')
+
+# NetworkManager binary
+
+# libNetworkManager.a, as built by meson doesn't contain all symbols
+# from libNetworkManagerBase.a and other static libraries, unless we
+# add dependencies with link_whole, only supported in meson >= 0.46.
+# Create an executable with full symbols that we use in place of the
+# library to enumerate the symbols.
+NetworkManager_all_sym = executable(
+  'NetworkManager-all-sym',
+  'main.c',
+  dependencies: nm_deps,
+  c_args: daemon_c_flags,
+  link_args: '-Wl,--no-gc-sections',
+  link_whole: [
+    libNetworkManager,
+    libNetworkManagerBase,
+    libnm_core,
+  ],
+)
+
+ver_script = custom_target(
+  'NetworkManager.ver',
+  output: 'NetworkManager.ver',
+  depends: [
+    NetworkManager_all_sym,
+    core_plugins,
+  ],
+  command: [
+    join_paths(source_root, 'tools', 'create-exports-NetworkManager.sh'),
+    '--called-from-build',
+    source_root,
+  ],
+)
+
+NetworkManager = executable(
+  'NetworkManager',
+  'main.c',
+  dependencies: nm_deps,
+  c_args: daemon_c_flags,
+  link_with: libNetworkManager,
+  link_args: [
+    '-rdynamic',
+    '-Wl,--version-script,@0@'.format(ver_script.full_path()),
+  ],
+  link_depends: ver_script,
+  install: true,
+  install_dir: nm_sbindir,
+)
+
+if enable_tests
+  foreach plugin: core_plugins
+    plugin_path = plugin.full_path()
+
+    test(
+      'sym/' + plugin_path.split('/')[-1],
+      NetworkManager,
+      args: '--version',
+      env: ['LD_BIND_NOW=1', 'LD_PRELOAD=' + plugin_path],
+    )
+  endforeach
+endif
+
+test(
+  'check-config-options',
+  find_program(join_paths(source_root, 'tools', 'check-config-options.sh')),
+  args: source_root,
+)
diff --git a/src/ndisc/nm-fake-ndisc.c b/src/core/ndisc/nm-fake-ndisc.c
index bd9f5f7e..ffe07ab6 100644
--- a/src/ndisc/nm-fake-ndisc.c
+++ b/src/core/ndisc/nm-fake-ndisc.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-fake-ndisc.h"
 
@@ -30,11 +30,10 @@ typedef struct {
 
 typedef struct {
     struct in6_addr    network;
-    int                plen;
     struct in6_addr    gateway;
-    guint32            timestamp;
-    guint32            lifetime;
-    guint32            preferred;
+    gint64             expiry_msec;
+    gint64             expiry_preferred_msec;
+    int                plen;
     NMIcmpv6RouterPref preference;
 } FakePrefix;
 
@@ -128,8 +127,7 @@ void
 nm_fake_ndisc_add_gateway(NMFakeNDisc *      self,
                           guint              ra_id,
                           const char *       addr,
-                          guint32            timestamp,
-                          guint32            lifetime,
+                          gint64             expiry_msec,
                           NMIcmpv6RouterPref preference)
 {
     NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
@@ -137,12 +135,12 @@ nm_fake_ndisc_add_gateway(NMFakeNDisc *      self,
     NMNDiscGateway *    gw;
 
     g_assert(ra);
-    g_array_set_size(ra->gateways, ra->gateways->len + 1);
-    gw = &g_array_index(ra->gateways, NMNDiscGateway, ra->gateways->len - 1);
-    g_assert(inet_pton(AF_INET6, addr, &gw->address) == 1);
-    gw->timestamp  = timestamp;
-    gw->lifetime   = lifetime;
-    gw->preference = preference;
+
+    gw = nm_g_array_append_new(ra->gateways, NMNDiscGateway);
+    if (inet_pton(AF_INET6, addr, &gw->address) != 1)
+        g_assert_not_reached();
+    gw->expiry_msec = expiry_msec;
+    gw->preference  = preference;
 }
 
 void
@@ -151,9 +149,8 @@ nm_fake_ndisc_add_prefix(NMFakeNDisc *      self,
                          const char *       network,
                          guint              plen,
                          const char *       gateway,
-                         guint32            timestamp,
-                         guint32            lifetime,
-                         guint32            preferred,
+                         gint64             expiry_msec,
+                         gint64             expiry_preferred_msec,
                          NMIcmpv6RouterPref preference)
 {
     NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
@@ -161,54 +158,52 @@ nm_fake_ndisc_add_prefix(NMFakeNDisc *      self,
     FakePrefix *        prefix;
 
     g_assert(ra);
-    g_array_set_size(ra->prefixes, ra->prefixes->len + 1);
-    prefix = &g_array_index(ra->prefixes, FakePrefix, ra->prefixes->len - 1);
-    memset(prefix, 0, sizeof(*prefix));
-    g_assert(inet_pton(AF_INET6, network, &prefix->network) == 1);
-    g_assert(inet_pton(AF_INET6, gateway, &prefix->gateway) == 1);
-    prefix->plen       = plen;
-    prefix->timestamp  = timestamp;
-    prefix->lifetime   = lifetime;
-    prefix->preferred  = preferred;
-    prefix->preference = preference;
+
+    prefix  = nm_g_array_append_new(ra->prefixes, FakePrefix);
+    *prefix = (FakePrefix){
+        .plen                  = plen,
+        .expiry_msec           = expiry_msec,
+        .expiry_preferred_msec = expiry_preferred_msec,
+        .preference            = preference,
+    };
+    if (inet_pton(AF_INET6, network, &prefix->network) != 1)
+        g_assert_not_reached();
+    if (inet_pton(AF_INET6, gateway, &prefix->gateway) != 1)
+        g_assert_not_reached();
 }
 
 void
 nm_fake_ndisc_add_dns_server(NMFakeNDisc *self,
                              guint        ra_id,
                              const char * address,
-                             guint32      timestamp,
-                             guint32      lifetime)
+                             gint64       expiry_msec)
 {
     NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
     FakeRa *            ra   = find_ra(priv->ras, ra_id);
     NMNDiscDNSServer *  dns;
 
     g_assert(ra);
-    g_array_set_size(ra->dns_servers, ra->dns_servers->len + 1);
-    dns = &g_array_index(ra->dns_servers, NMNDiscDNSServer, ra->dns_servers->len - 1);
-    g_assert(inet_pton(AF_INET6, address, &dns->address) == 1);
-    dns->timestamp = timestamp;
-    dns->lifetime  = lifetime;
+
+    dns = nm_g_array_append_new(ra->dns_servers, NMNDiscDNSServer);
+
+    dns->expiry_msec = expiry_msec;
+    if (inet_pton(AF_INET6, address, &dns->address) != 1)
+        g_assert_not_reached();
 }
 
 void
-nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self,
-                             guint        ra_id,
-                             const char * domain,
-                             guint32      timestamp,
-                             guint32      lifetime)
+nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self, guint ra_id, const char *domain, gint64 expiry_msec)
 {
     NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE(self);
     FakeRa *            ra   = find_ra(priv->ras, ra_id);
     NMNDiscDNSDomain *  dns;
 
     g_assert(ra);
-    g_array_set_size(ra->dns_domains, ra->dns_domains->len + 1);
-    dns            = &g_array_index(ra->dns_domains, NMNDiscDNSDomain, ra->dns_domains->len - 1);
-    dns->domain    = g_strdup(domain);
-    dns->timestamp = timestamp;
-    dns->lifetime  = lifetime;
+
+    dns = nm_g_array_append_new(ra->dns_domains, NMNDiscDNSDomain);
+
+    dns->domain      = g_strdup(domain);
+    dns->expiry_msec = expiry_msec;
 }
 
 gboolean
@@ -222,6 +217,7 @@ nm_fake_ndisc_done(NMFakeNDisc *self)
 static gboolean
 send_rs(NMNDisc *ndisc, GError **error)
 {
+    _LOGT("send_rs()");
     g_signal_emit(ndisc, signals[RS_SENT], 0);
     return TRUE;
 }
@@ -229,13 +225,13 @@ send_rs(NMNDisc *ndisc, GError **error)
 static gboolean
 receive_ra(gpointer user_data)
 {
-    NMFakeNDisc *        self    = user_data;
-    NMFakeNDiscPrivate * priv    = NM_FAKE_NDISC_GET_PRIVATE(self);
-    NMNDisc *            ndisc   = NM_NDISC(self);
-    NMNDiscDataInternal *rdata   = ndisc->rdata;
-    FakeRa *             ra      = priv->ras->data;
-    NMNDiscConfigMap     changed = 0;
-    gint32               now     = nm_utils_get_monotonic_timestamp_sec();
+    NMFakeNDisc *        self     = user_data;
+    NMFakeNDiscPrivate * priv     = NM_FAKE_NDISC_GET_PRIVATE(self);
+    NMNDisc *            ndisc    = NM_NDISC(self);
+    NMNDiscDataInternal *rdata    = ndisc->rdata;
+    FakeRa *             ra       = priv->ras->data;
+    NMNDiscConfigMap     changed  = 0;
+    const gint64         now_msec = nm_utils_get_monotonic_timestamp_msec();
     guint                i;
     NMNDiscDHCPLevel     dhcp_level;
 
@@ -250,53 +246,51 @@ receive_ra(gpointer user_data)
     }
 
     for (i = 0; i < ra->gateways->len; i++) {
-        NMNDiscGateway *item = &g_array_index(ra->gateways, NMNDiscGateway, i);
+        const NMNDiscGateway *item = &g_array_index(ra->gateways, NMNDiscGateway, i);
 
-        if (nm_ndisc_add_gateway(ndisc, item))
+        if (nm_ndisc_add_gateway(ndisc, item, now_msec))
             changed |= NM_NDISC_CONFIG_GATEWAYS;
     }
 
     for (i = 0; i < ra->prefixes->len; i++) {
-        FakePrefix * item  = &g_array_index(ra->prefixes, FakePrefix, i);
-        NMNDiscRoute route = {
-            .network    = item->network,
-            .plen       = item->plen,
-            .gateway    = item->gateway,
-            .timestamp  = item->timestamp,
-            .lifetime   = item->lifetime,
-            .preference = item->preference,
+        FakePrefix *       item  = &g_array_index(ra->prefixes, FakePrefix, i);
+        const NMNDiscRoute route = {
+            .network     = item->network,
+            .plen        = item->plen,
+            .gateway     = item->gateway,
+            .expiry_msec = item->expiry_msec,
+            .preference  = item->preference,
         };
 
         g_assert(route.plen > 0 && route.plen <= 128);
 
-        if (nm_ndisc_add_route(ndisc, &route))
+        if (nm_ndisc_add_route(ndisc, &route, now_msec))
             changed |= NM_NDISC_CONFIG_ROUTES;
 
         if (item->plen == 64) {
-            NMNDiscAddress address = {
-                .address     = item->network,
-                .timestamp   = item->timestamp,
-                .lifetime    = item->lifetime,
-                .preferred   = item->preferred,
-                .dad_counter = 0,
+            const NMNDiscAddress address = {
+                .address               = item->network,
+                .expiry_msec           = item->expiry_msec,
+                .expiry_preferred_msec = item->expiry_preferred_msec,
+                .dad_counter           = 0,
             };
 
-            if (nm_ndisc_complete_and_add_address(ndisc, &address, now))
+            if (nm_ndisc_complete_and_add_address(ndisc, &address, now_msec))
                 changed |= NM_NDISC_CONFIG_ADDRESSES;
         }
     }
 
     for (i = 0; i < ra->dns_servers->len; i++) {
-        NMNDiscDNSServer *item = &g_array_index(ra->dns_servers, NMNDiscDNSServer, i);
+        const NMNDiscDNSServer *item = &g_array_index(ra->dns_servers, NMNDiscDNSServer, i);
 
-        if (nm_ndisc_add_dns_server(ndisc, item))
+        if (nm_ndisc_add_dns_server(ndisc, item, now_msec))
             changed |= NM_NDISC_CONFIG_DNS_SERVERS;
     }
 
     for (i = 0; i < ra->dns_domains->len; i++) {
-        NMNDiscDNSDomain *item = &g_array_index(ra->dns_domains, NMNDiscDNSDomain, i);
+        const NMNDiscDNSDomain *item = &g_array_index(ra->dns_domains, NMNDiscDNSDomain, i);
 
-        if (nm_ndisc_add_dns_domain(ndisc, item))
+        if (nm_ndisc_add_dns_domain(ndisc, item, now_msec))
             changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
     }
 
@@ -313,7 +307,7 @@ receive_ra(gpointer user_data)
     priv->ras = g_slist_remove(priv->ras, priv->ras->data);
     fake_ra_free(ra);
 
-    nm_ndisc_ra_received(NM_NDISC(self), now, changed);
+    nm_ndisc_ra_received(NM_NDISC(self), now_msec, changed);
 
     /* Schedule next RA */
     if (priv->ras) {
@@ -378,7 +372,7 @@ nm_fake_ndisc_new(int ifindex, const char *ifname)
                         NM_NDISC_ROUTER_SOLICITATIONS,
                         NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT,
                         NM_NDISC_ROUTER_SOLICITATION_INTERVAL,
-                        NM_NDISC_ROUTER_SOLICITATION_INTERVAL_DEFAULT,
+                        NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL,
                         NM_NDISC_RA_TIMEOUT,
                         30u,
                         NULL);
diff --git a/src/ndisc/nm-fake-ndisc.h b/src/core/ndisc/nm-fake-ndisc.h
index 47f40560..677f15fc 100644
--- a/src/ndisc/nm-fake-ndisc.h
+++ b/src/core/ndisc/nm-fake-ndisc.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
@@ -35,8 +35,7 @@ guint nm_fake_ndisc_add_ra(NMFakeNDisc *    self,
 void nm_fake_ndisc_add_gateway(NMFakeNDisc *      self,
                                guint              ra_id,
                                const char *       addr,
-                               guint32            timestamp,
-                               guint32            lifetime,
+                               gint64             expiry_msec,
                                NMIcmpv6RouterPref preference);
 
 void nm_fake_ndisc_add_prefix(NMFakeNDisc *      self,
@@ -44,22 +43,19 @@ void nm_fake_ndisc_add_prefix(NMFakeNDisc *      self,
                               const char *       network,
                               guint              plen,
                               const char *       gateway,
-                              guint32            timestamp,
-                              guint32            lifetime,
-                              guint32            preferred,
+                              gint64             expiry_msec,
+                              gint64             expiry_preferred_msec,
                               NMIcmpv6RouterPref preference);
 
 void nm_fake_ndisc_add_dns_server(NMFakeNDisc *self,
                                   guint        ra_id,
                                   const char * address,
-                                  guint32      timestamp,
-                                  guint32      lifetime);
+                                  gint64       expiry_msec);
 
 void nm_fake_ndisc_add_dns_domain(NMFakeNDisc *self,
                                   guint        ra_id,
                                   const char * domain,
-                                  guint32      timestamp,
-                                  guint32      lifetime);
+                                  gint64       expiry_msec);
 
 void nm_fake_ndisc_emit_new_ras(NMFakeNDisc *self);
 
diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/core/ndisc/nm-lndp-ndisc.c
index 22695c32..f773478f 100644
--- a/src/ndisc/nm-lndp-ndisc.c
+++ b/src/core/ndisc/nm-lndp-ndisc.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-lndp-ndisc.h"
 
@@ -13,10 +13,12 @@
 #include <stdarg.h>
 #include <ndp.h>
 
+#include "nm-glib-aux/nm-str-buf.h"
+#include "systemd/nm-sd-utils-shared.h"
 #include "nm-ndisc-private.h"
 #include "NetworkManagerUtils.h"
 #include "platform/nm-platform.h"
-#include "platform/nmp-netns.h"
+#include "nm-platform/nmp-netns.h"
 
 #define _NMLOG_PREFIX_NAME "ndisc-lndp"
 
@@ -83,14 +85,26 @@ send_rs(NMNDisc *ndisc, GError **error)
 static NMIcmpv6RouterPref
 _route_preference_coerce(enum ndp_route_preference pref)
 {
+#define _ASSERT_ENUM(v1, v2)                                      \
+    G_STMT_START                                                  \
+    {                                                             \
+        G_STATIC_ASSERT((NMIcmpv6RouterPref)(v1) == (v2));        \
+        G_STATIC_ASSERT((enum ndp_route_preference)(v2) == (v1)); \
+        G_STATIC_ASSERT((gint64)(v1) == (v2));                    \
+        G_STATIC_ASSERT((gint64)(v2) == (v1));                    \
+    }                                                             \
+    G_STMT_END
+
     switch (pref) {
     case NDP_ROUTE_PREF_LOW:
-        return NM_ICMPV6_ROUTER_PREF_LOW;
     case NDP_ROUTE_PREF_MEDIUM:
-        return NM_ICMPV6_ROUTER_PREF_MEDIUM;
     case NDP_ROUTE_PREF_HIGH:
-        return NM_ICMPV6_ROUTER_PREF_HIGH;
+        _ASSERT_ENUM(NDP_ROUTE_PREF_LOW, NM_ICMPV6_ROUTER_PREF_LOW);
+        _ASSERT_ENUM(NDP_ROUTE_PREF_MEDIUM, NM_ICMPV6_ROUTER_PREF_MEDIUM);
+        _ASSERT_ENUM(NDP_ROUTE_PREF_HIGH, NM_ICMPV6_ROUTER_PREF_HIGH);
+        return (NMIcmpv6RouterPref) pref;
     }
+
     /* unexpected value must be treated as MEDIUM (RFC 4191). */
     return NM_ICMPV6_ROUTER_PREF_MEDIUM;
 }
@@ -103,7 +117,7 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
     NMNDiscConfigMap     changed = 0;
     struct ndp_msgra *   msgra   = ndp_msgra(msg);
     struct in6_addr      gateway_addr;
-    gint32               now = nm_utils_get_monotonic_timestamp_sec();
+    const gint64         now_msec = nm_utils_get_monotonic_timestamp_msec();
     int                  offset;
     int                  hop_limit;
     guint32              val;
@@ -119,7 +133,9 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
      * single time when the configuration is finished and updates can
      * come at any time.
      */
-    _LOGD("received router advertisement at %d", (int) now);
+    _LOGD("received router advertisement at timestamp %" G_GINT64_FORMAT ".%03d seconds",
+          now_msec / 1000,
+          (int) (now_msec % 1000));
 
     gateway_addr = *ndp_msg_addrto(msg);
     if (IN6_IS_ADDR_UNSPECIFIED(&gateway_addr))
@@ -161,13 +177,18 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
      */
     {
         const NMNDiscGateway gateway = {
-            .address    = gateway_addr,
-            .timestamp  = now,
-            .lifetime   = ndp_msgra_router_lifetime(msgra),
-            .preference = _route_preference_coerce(ndp_msgra_route_preference(msgra)),
+            .address     = gateway_addr,
+            .expiry_msec = _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msgra_router_lifetime(msgra)),
+            .preference  = _route_preference_coerce(ndp_msgra_route_preference(msgra)),
         };
 
-        if (nm_ndisc_add_gateway(ndisc, &gateway))
+        /* https://tools.ietf.org/html/rfc2461#section-4.2
+         *   > A Lifetime of 0 indicates that the router is not a
+         *   > default router and SHOULD NOT appear on the default
+         *   > router list.
+         * We handle that by tracking a gateway that expires right now. */
+
+        if (nm_ndisc_add_gateway(ndisc, &gateway, now_msec))
             changed |= NM_NDISC_CONFIG_GATEWAYS;
     }
 
@@ -190,71 +211,71 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 
         if (ndp_msg_opt_prefix_flag_on_link(msg, offset)) {
             const NMNDiscRoute route = {
-                .network   = r_network,
-                .plen      = r_plen,
-                .timestamp = now,
-                .lifetime  = ndp_msg_opt_prefix_valid_time(msg, offset),
+                .network = r_network,
+                .plen    = r_plen,
+                .expiry_msec =
+                    _nm_ndisc_lifetime_to_expiry(now_msec,
+                                                 ndp_msg_opt_prefix_valid_time(msg, offset)),
             };
 
-            if (nm_ndisc_add_route(ndisc, &route))
+            if (nm_ndisc_add_route(ndisc, &route, now_msec))
                 changed |= NM_NDISC_CONFIG_ROUTES;
         }
 
         /* Address */
         if (r_plen == 64 && ndp_msg_opt_prefix_flag_auto_addr_conf(msg, offset)) {
-            NMNDiscAddress address = {
-                .address   = r_network,
-                .timestamp = now,
-                .lifetime  = ndp_msg_opt_prefix_valid_time(msg, offset),
-                .preferred = ndp_msg_opt_prefix_preferred_time(msg, offset),
+            const guint32 valid_time = ndp_msg_opt_prefix_valid_time(msg, offset);
+            const guint32 preferred_time =
+                NM_MIN(ndp_msg_opt_prefix_preferred_time(msg, offset), valid_time);
+            const NMNDiscAddress address = {
+                .address               = r_network,
+                .expiry_msec           = _nm_ndisc_lifetime_to_expiry(now_msec, valid_time),
+                .expiry_preferred_msec = _nm_ndisc_lifetime_to_expiry(now_msec, preferred_time),
             };
 
-            if (address.preferred <= address.lifetime) {
-                if (nm_ndisc_complete_and_add_address(ndisc, &address, now))
-                    changed |= NM_NDISC_CONFIG_ADDRESSES;
-            }
+            if (nm_ndisc_complete_and_add_address(ndisc, &address, now_msec))
+                changed |= NM_NDISC_CONFIG_ADDRESSES;
         }
     }
     ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_ROUTE) {
-        NMNDiscRoute route = {
-            .gateway    = gateway_addr,
-            .plen       = ndp_msg_opt_route_prefix_len(msg, offset),
-            .timestamp  = now,
-            .lifetime   = ndp_msg_opt_route_lifetime(msg, offset),
-            .preference = _route_preference_coerce(ndp_msg_opt_route_preference(msg, offset)),
-        };
+        guint8          plen = ndp_msg_opt_route_prefix_len(msg, offset);
+        struct in6_addr network;
 
-        if (route.plen == 0 || route.plen > 128)
+        if (plen == 0 || plen > 128)
             continue;
 
-        /* Routers through this particular gateway */
-        nm_utils_ip6_address_clear_host_address(&route.network,
+        nm_utils_ip6_address_clear_host_address(&network,
                                                 ndp_msg_opt_route_prefix(msg, offset),
-                                                route.plen);
-        if (nm_ndisc_add_route(ndisc, &route))
-            changed |= NM_NDISC_CONFIG_ROUTES;
+                                                plen);
+
+        {
+            const NMNDiscRoute route = {
+                .network = network,
+                .gateway = gateway_addr,
+                .plen    = plen,
+                .expiry_msec =
+                    _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_route_lifetime(msg, offset)),
+                .preference = _route_preference_coerce(ndp_msg_opt_route_preference(msg, offset)),
+            };
+
+            /* Routers through this particular gateway */
+            if (nm_ndisc_add_route(ndisc, &route, now_msec))
+                changed |= NM_NDISC_CONFIG_ROUTES;
+        }
     }
 
-    /* DNS information */
     ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_RDNSS) {
         struct in6_addr *addr;
         int              addr_index;
 
         ndp_msg_opt_rdnss_for_each_addr (addr, addr_index, msg, offset) {
-            NMNDiscDNSServer dns_server = {
-                .address   = *addr,
-                .timestamp = now,
-                .lifetime  = ndp_msg_opt_rdnss_lifetime(msg, offset),
+            const NMNDiscDNSServer dns_server = {
+                .address = *addr,
+                .expiry_msec =
+                    _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_rdnss_lifetime(msg, offset)),
             };
 
-            /* 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 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)
-                dns_server.lifetime = 7200;
-            if (nm_ndisc_add_dns_server(ndisc, &dns_server))
+            if (nm_ndisc_add_dns_server(ndisc, &dns_server, now_msec))
                 changed |= NM_NDISC_CONFIG_DNS_SERVERS;
         }
     }
@@ -263,20 +284,13 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
         int   domain_index;
 
         ndp_msg_opt_dnssl_for_each_domain (domain, domain_index, msg, offset) {
-            NMNDiscDNSDomain dns_domain = {
-                .domain    = domain,
-                .timestamp = now,
-                .lifetime  = ndp_msg_opt_dnssl_lifetime(msg, offset),
+            const NMNDiscDNSDomain dns_domain = {
+                .domain = domain,
+                .expiry_msec =
+                    _nm_ndisc_lifetime_to_expiry(now_msec, ndp_msg_opt_dnssl_lifetime(msg, offset)),
             };
 
-            /* 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 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)
-                dns_domain.lifetime = 7200;
-            if (nm_ndisc_add_dns_domain(ndisc, &dns_domain))
+            if (nm_ndisc_add_dns_domain(ndisc, &dns_domain, now_msec))
                 changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
         }
     }
@@ -316,50 +330,69 @@ receive_ra(struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
         }
     }
 
-    nm_ndisc_ra_received(ndisc, now, changed);
+    nm_ndisc_ra_received(ndisc, now_msec, changed);
     return 0;
 }
 
 static void *
-_ndp_msg_add_option(struct ndp_msg *msg, int len)
+_ndp_msg_add_option(struct ndp_msg *msg, gsize len)
 {
-    void *ret = (uint8_t *) msg + ndp_msg_payload_len(msg);
+    gsize payload_len = ndp_msg_payload_len(msg);
+    void *ret         = &((uint8_t *) msg)[payload_len];
+
+    nm_assert(len <= G_MAXSIZE - payload_len);
+    len += payload_len;
 
-    len += ndp_msg_payload_len(msg);
     if (len > ndp_msg_payload_maxlen(msg))
         return NULL;
 
     ndp_msg_payload_len_set(msg, len);
+    nm_assert(len == ndp_msg_payload_len(msg));
     return ret;
 }
 
+/*****************************************************************************/
+
+/* "Recursive DNS Server Option" at https://tools.ietf.org/html/rfc8106#section-5.1 */
+
 #define NM_ND_OPT_RDNSS 25
-typedef struct {
+
+typedef struct _nm_packed {
     struct nd_opt_hdr header;
     uint16_t          reserved;
     uint32_t          lifetime;
     struct in6_addr   addrs[0];
 } NMLndpRdnssOption;
 
+G_STATIC_ASSERT(sizeof(NMLndpRdnssOption) == 8u);
+
+/*****************************************************************************/
+
+/* "DNS Search List Option" at https://tools.ietf.org/html/rfc8106#section-5.2 */
+
 #define NM_ND_OPT_DNSSL 31
-typedef struct {
+
+typedef struct _nm_packed {
     struct nd_opt_hdr header;
     uint16_t          reserved;
     uint32_t          lifetime;
-    char              search_list[0];
+    uint8_t           search_list[0];
 } NMLndpDnsslOption;
 
+G_STATIC_ASSERT(sizeof(NMLndpDnsslOption) == 8u);
+
+/*****************************************************************************/
+
 static gboolean
 send_ra(NMNDisc *ndisc, GError **error)
 {
-    NMLndpNDiscPrivate *       priv  = NM_LNDP_NDISC_GET_PRIVATE(ndisc);
-    NMNDiscDataInternal *      rdata = ndisc->rdata;
-    gint32                     now   = nm_utils_get_monotonic_timestamp_sec();
-    int                        errsv;
-    struct in6_addr *          addr;
-    struct ndp_msg *           msg;
-    struct nd_opt_prefix_info *prefix;
-    int                        i;
+    NMLndpNDiscPrivate *     priv  = NM_LNDP_NDISC_GET_PRIVATE(ndisc);
+    NMNDiscDataInternal *    rdata = ndisc->rdata;
+    int                      errsv;
+    struct in6_addr *        addr;
+    struct ndp_msg *         msg;
+    guint                    i;
+    nm_auto_str_buf NMStrBuf sbuf = NM_STR_BUF_INIT(0, FALSE);
 
     errsv = ndp_msg_new(&msg, NDP_MSG_RA);
     if (errsv) {
@@ -384,16 +417,8 @@ send_ra(NMNDisc *ndisc, GError **error)
     /* The device let us know about all addresses that the device got
      * whose prefixes are suitable for delegating. Let's announce them. */
     for (i = 0; i < rdata->addresses->len; i++) {
-        NMNDiscAddress *address = &g_array_index(rdata->addresses, NMNDiscAddress, i);
-        guint32 age      = NM_CLAMP((gint64) now - (gint64) address->timestamp, 0, G_MAXUINT32 - 1);
-        guint32 lifetime = address->lifetime;
-        guint32 preferred = address->preferred;
-
-        /* Clamp the life times if they're not forever. */
-        if (lifetime != NM_NDISC_INFINITY)
-            lifetime = lifetime > age ? lifetime - age : 0;
-        if (preferred != NM_NDISC_INFINITY)
-            preferred = preferred > age ? preferred - age : 0;
+        const NMNDiscAddress *     address = &g_array_index(rdata->addresses, NMNDiscAddress, i);
+        struct nd_opt_prefix_info *prefix;
 
         prefix = _ndp_msg_add_option(msg, sizeof(*prefix));
         if (!prefix) {
@@ -407,67 +432,117 @@ send_ra(NMNDisc *ndisc, GError **error)
         prefix->nd_opt_pi_prefix_len = 64;
         prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_ONLINK;
         prefix->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO;
-        prefix->nd_opt_pi_valid_time          = htonl(lifetime);
-        prefix->nd_opt_pi_preferred_time      = htonl(preferred);
+        prefix->nd_opt_pi_valid_time =
+            htonl(_nm_ndisc_lifetime_from_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP,
+                                                 address->expiry_msec,
+                                                 TRUE));
+        prefix->nd_opt_pi_preferred_time =
+            htonl(_nm_ndisc_lifetime_from_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP,
+                                                 address->expiry_preferred_msec,
+                                                 TRUE));
         prefix->nd_opt_pi_prefix.s6_addr32[0] = address->address.s6_addr32[0];
         prefix->nd_opt_pi_prefix.s6_addr32[1] = address->address.s6_addr32[1];
         prefix->nd_opt_pi_prefix.s6_addr32[2] = 0;
         prefix->nd_opt_pi_prefix.s6_addr32[3] = 0;
     }
 
-    if (rdata->dns_servers->len) {
+    if (rdata->dns_servers->len > 0u) {
         NMLndpRdnssOption *option;
-        int len = sizeof(*option) + sizeof(option->addrs[0]) * rdata->dns_servers->len;
+        gsize len = sizeof(*option) + (sizeof(option->addrs[0]) * rdata->dns_servers->len);
 
         option = _ndp_msg_add_option(msg, len);
-        if (option) {
-            option->header.nd_opt_type = NM_ND_OPT_RDNSS;
-            option->header.nd_opt_len  = len / 8;
-            option->lifetime           = htonl(900);
-
-            for (i = 0; i < rdata->dns_servers->len; i++) {
-                NMNDiscDNSServer *dns_server =
-                    &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
-                option->addrs[i] = dns_server->address;
-            }
-        } else {
+        if (!option) {
             _LOGW("The RA is too big, had to omit DNS information.");
+            goto dns_servers_done;
+        }
+
+        option->header.nd_opt_type = NM_ND_OPT_RDNSS;
+        option->header.nd_opt_len  = len / 8;
+        option->lifetime           = htonl(900);
+
+        for (i = 0; i < rdata->dns_servers->len; i++) {
+            const NMNDiscDNSServer *dns_server =
+                &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
+
+            option->addrs[i] = dns_server->address;
         }
     }
+dns_servers_done:
 
-    if (rdata->dns_domains->len) {
+    if (rdata->dns_domains->len > 0u) {
         NMLndpDnsslOption *option;
-        NMNDiscDNSDomain * dns_server;
-        int                len = sizeof(*option);
-        char *             search_list;
+        gsize              padding;
+        gsize              len;
+
+        nm_str_buf_reset(&sbuf);
 
         for (i = 0; i < rdata->dns_domains->len; i++) {
-            dns_server = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
-            len += strlen(dns_server->domain) + 2;
-        }
-        len = (len + 8) & ~0x7;
+            const NMNDiscDNSDomain *dns_domain =
+                &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
+            const char *domain = dns_domain->domain;
+            gsize       domain_l;
+            gsize       n_reserved;
+            int         r;
+
+            if (nm_str_is_empty(domain)) {
+                nm_assert_not_reached();
+                continue;
+            }
 
-        option = _ndp_msg_add_option(msg, len);
-        if (option) {
-            option->header.nd_opt_type = NM_ND_OPT_DNSSL;
-            option->header.nd_opt_len  = len / 8;
-            option->lifetime           = htonl(900);
-
-            search_list = option->search_list;
-            for (i = 0; i < rdata->dns_domains->len; i++) {
-                NMNDiscDNSDomain *dns_domain =
-                    &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
-                uint8_t domain_len = strlen(dns_domain->domain);
-
-                *search_list++ = domain_len;
-                memcpy(search_list, dns_domain->domain, domain_len);
-                search_list += domain_len;
-                *search_list++ = '\0';
+            domain_l = strlen(domain);
+
+            nm_str_buf_maybe_expand(&sbuf, domain_l + 2u, FALSE);
+            n_reserved = sbuf.allocated - sbuf.len;
+
+            r = nm_sd_dns_name_to_wire_format(
+                domain,
+                (guint8 *) (&nm_str_buf_get_str_unsafe(&sbuf)[sbuf.len]),
+                n_reserved,
+                FALSE);
+
+            if (r < 0 || ((gsize) r) > n_reserved) {
+                nm_assert(r != -ENOBUFS);
+                nm_assert(r < 0);
+                /* we don't expect errors here, unless the domain name is invalid.
+                 * That should have been caught (and rejected) by upper layers, but
+                 * at this point it seems dangerous to assert (as it's hard to review
+                 * that all callers got it correct). So instead silently ignore the error. */
+                continue;
             }
-        } else {
+
+            nm_str_buf_set_size(&sbuf, sbuf.len + ((gsize) r), TRUE, FALSE);
+        }
+
+        if (sbuf.len == 0) {
+            /* no valid domains? */
+            goto dns_domains_done;
+        }
+
+        len     = sizeof(*option) + sbuf.len;
+        padding = len % 8u;
+        if (padding != 0u) {
+            padding = 8u - padding;
+            len += padding;
+        }
+
+        nm_assert(len % 8u == 0u);
+        nm_assert(len > 0u);
+        nm_assert(len / 8u >= 2u);
+
+        if (len / 8u >= 256u || !(option = _ndp_msg_add_option(msg, len))) {
             _LOGW("The RA is too big, had to omit DNS search list.");
+            goto dns_domains_done;
         }
+
+        nm_str_buf_append_c_len(&sbuf, '\0', padding);
+
+        option->header.nd_opt_type = NM_ND_OPT_DNSSL;
+        option->header.nd_opt_len  = len / 8u;
+        option->reserved           = 0;
+        option->lifetime           = htonl(900);
+        memcpy(option->search_list, nm_str_buf_get_str_unsafe(&sbuf), sbuf.len);
     }
+dns_domains_done:
 
     errsv = ndp_msg_send(priv->ndp, msg);
 
@@ -638,13 +713,12 @@ nm_lndp_ndisc_get_sysctl(NMPlatform *platform,
         NM_SET_OUT(out_router_solicitations, router_solicitations);
     }
     if (out_router_solicitation_interval || out_default_ra_timeout) {
-        router_solicitation_interval =
-            ipv6_sysctl_get(platform,
-                            ifname,
-                            "router_solicitation_interval",
-                            1,
-                            G_MAXINT32,
-                            NM_NDISC_ROUTER_SOLICITATION_INTERVAL_DEFAULT);
+        router_solicitation_interval = ipv6_sysctl_get(platform,
+                                                       ifname,
+                                                       "router_solicitation_interval",
+                                                       1,
+                                                       G_MAXINT32,
+                                                       NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL);
         NM_SET_OUT(out_router_solicitation_interval, router_solicitation_interval);
     }
     if (out_default_ra_timeout) {
diff --git a/src/ndisc/nm-lndp-ndisc.h b/src/core/ndisc/nm-lndp-ndisc.h
index a275be3e..96fc9737 100644
--- a/src/ndisc/nm-lndp-ndisc.h
+++ b/src/core/ndisc/nm-lndp-ndisc.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
diff --git a/src/ndisc/nm-ndisc-private.h b/src/core/ndisc/nm-ndisc-private.h
index a79a920b..04739124 100644
--- a/src/ndisc/nm-ndisc-private.h
+++ b/src/core/ndisc/nm-ndisc-private.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
@@ -21,14 +21,15 @@ struct _NMNDiscDataInternal {
 
 typedef struct _NMNDiscDataInternal NMNDiscDataInternal;
 
-void nm_ndisc_ra_received(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed);
+void nm_ndisc_ra_received(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed);
 void nm_ndisc_rs_received(NMNDisc *ndisc);
 
-gboolean nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new);
-gboolean nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s);
-gboolean nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new);
-gboolean nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new);
-gboolean nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new);
+gboolean nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new_item, gint64 now_msec);
+gboolean
+nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new_item, gint64 now_msec);
+gboolean nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec);
+gboolean nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64 now_msec);
+gboolean nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new_item, gint64 now_msec);
 
 /*****************************************************************************/
 
diff --git a/src/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c
index f8805c02..a5c1b935 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/core/ndisc/nm-ndisc.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-ndisc.h"
 
@@ -15,43 +15,50 @@
 #include "nm-ndisc-private.h"
 #include "nm-utils.h"
 #include "platform/nm-platform.h"
-#include "platform/nmp-netns.h"
+#include "nm-platform/nmp-netns.h"
 #include "nm-l3-config-data.h"
 
 #define _NMLOG_PREFIX_NAME "ndisc"
 
+#define RFC7559_IRT ((gint32) 4)    /* RFC7559, Initial Retransmission Time, in seconds */
+#define RFC7559_MRT ((gint32) 3600) /* RFC7559, Maximum Retransmission Time, in seconds */
+
+#define _SIZE_MAX_GATEWAYS    100u
+#define _SIZE_MAX_ADDRESSES   100u
+#define _SIZE_MAX_ROUTES      1000u
+#define _SIZE_MAX_DNS_SERVERS 64u
+#define _SIZE_MAX_DNS_DOMAINS 64u
+
 /*****************************************************************************/
 
 struct _NMNDiscPrivate {
     /* this *must* be the first field. */
     NMNDiscDataInternal rdata;
 
-    char *   last_error;
+    char *last_error;
+
     GSource *ra_timeout_source;
 
-    union {
-        gint32 solicitations_left;
-        gint32 announcements_left;
-    };
-    union {
-        guint send_rs_id;
-        guint send_ra_id;
-    };
-    union {
-        gint32 last_rs;
-        gint32 last_ra;
-    };
-    guint              timeout_id; /* prefix/dns/etc lifetime timeout */
+    gint32 announcements_left;
+    guint  send_ra_id;
+    gint32 last_ra;
+
+    gint32 solicit_retransmit_time_msec;
+
+    GSource *solicit_timer_source;
+
+    GSource *timeout_expire_source;
+
     NMUtilsIPv6IfaceId iid;
 
     /* immutable values: */
     int                           ifindex;
     char *                        ifname;
     char *                        network_id;
+    guint                         max_addresses;
     NMSettingIP6ConfigAddrGenMode addr_gen_mode;
     NMUtilsStableType             stable_type;
     guint32                       ra_timeout;
-    gint32                        max_addresses;
     gint32                        router_solicitations;
     gint32                        router_solicitation_interval;
     NMNDiscNodeType               node_type;
@@ -84,7 +91,8 @@ G_DEFINE_TYPE(NMNDisc, nm_ndisc, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
-static void _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed);
+static void     _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed);
+static gboolean timeout_expire_cb(gpointer user_data);
 
 /*****************************************************************************/
 
@@ -102,6 +110,7 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
     guint32                                 ifa_flags;
     guint8                                  plen;
     guint                                   i;
+    const gint32                            now_sec = nm_utils_get_monotonic_timestamp_sec();
 
     l3cd = nm_l3_config_data_new(multi_idx, ifindex);
 
@@ -129,12 +138,17 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
         NMPlatformIP6Address  a;
 
         a = (NMPlatformIP6Address){
-            .ifindex     = ifindex,
-            .address     = ndisc_addr->address,
-            .plen        = plen,
-            .timestamp   = ndisc_addr->timestamp,
-            .lifetime    = ndisc_addr->lifetime,
-            .preferred   = MIN(ndisc_addr->lifetime, ndisc_addr->preferred),
+            .ifindex   = ifindex,
+            .address   = ndisc_addr->address,
+            .plen      = plen,
+            .timestamp = now_sec,
+            .lifetime  = _nm_ndisc_lifetime_from_expiry(((gint64) now_sec) * 1000,
+                                                       ndisc_addr->expiry_msec,
+                                                       TRUE),
+            .preferred = _nm_ndisc_lifetime_from_expiry(
+                ((gint64) now_sec) * 1000,
+                NM_MIN(ndisc_addr->expiry_msec, ndisc_addr->expiry_preferred_msec),
+                TRUE),
             .addr_source = NM_IP_CONFIG_SOURCE_NDISC,
             .n_ifa_flags = ifa_flags,
         };
@@ -220,77 +234,40 @@ _preference_to_priority(NMIcmpv6RouterPref pref)
 
 /*****************************************************************************/
 
-/* we rely on the fact, that _EXPIRY_INFINITY > any other valid gint64 timestamps. */
-#define _EXPIRY_INFINITY G_MAXINT64
-
-static gint64
-get_expiry_time(guint32 timestamp, guint32 lifetime)
-{
-    nm_assert(timestamp > 0);
-    nm_assert(timestamp <= G_MAXINT32);
-
-    if (lifetime == NM_NDISC_INFINITY)
-        return _EXPIRY_INFINITY;
-    return ((gint64) timestamp) + ((gint64) lifetime);
-}
-
-#define get_expiry(item)                                    \
-    ({                                                      \
-        typeof(item) _item = (item);                        \
-        nm_assert(_item);                                   \
-        get_expiry_time(_item->timestamp, _item->lifetime); \
-    })
-
-#define get_expiry_half(item)                                         \
-    ({                                                                \
-        typeof(item) _item = (item);                                  \
-        nm_assert(_item);                                             \
-        (_item->lifetime == NM_NDISC_INFINITY)                        \
-            ? _EXPIRY_INFINITY                                        \
-            : get_expiry_time(_item->timestamp, _item->lifetime / 2); \
-    })
-
-#define get_expiry_preferred(item)                           \
-    ({                                                       \
-        typeof(item) _item = (item);                         \
-        nm_assert(_item);                                    \
-        get_expiry_time(_item->timestamp, _item->preferred); \
-    })
-
 static gboolean
-expiry_next(gint32 now_s, gint64 expiry_timestamp, gint32 *nextevent)
+expiry_next(gint64 now_msec, gint64 expiry_msec, gint64 *next_msec)
 {
-    gint32 e;
-
-    if (expiry_timestamp == _EXPIRY_INFINITY)
+    if (expiry_msec == NM_NDISC_EXPIRY_INFINITY)
         return TRUE;
-    e = MIN(expiry_timestamp, ((gint64)(G_MAXINT32 - 1)));
-    if (now_s >= e)
+
+    if (expiry_msec <= now_msec) {
+        /* expired. */
         return FALSE;
-    if (nextevent) {
-        if (*nextevent > e)
-            *nextevent = e;
     }
+
+    if (next_msec) {
+        if (*next_msec > expiry_msec)
+            *next_msec = expiry_msec;
+    }
+
+    /* the timestamp is good (not yet expired) */
     return TRUE;
 }
 
 static const char *
-_get_exp(char *buf, gsize buf_size, gint64 now_ns, gint64 expiry_time)
+_get_exp(char *buf, gsize buf_size, gint64 now_msec, gint64 expiry_time)
 {
     int l;
 
-    if (expiry_time == _EXPIRY_INFINITY)
+    if (expiry_time == NM_NDISC_EXPIRY_INFINITY)
         return "permanent";
-    l = g_snprintf(buf,
-                   buf_size,
-                   "%.4f",
-                   ((double) ((expiry_time * NM_UTILS_NSEC_PER_SEC) - now_ns))
-                       / ((double) NM_UTILS_NSEC_PER_SEC));
+    l = g_snprintf(buf, buf_size, "%.3f", ((double) (expiry_time - now_msec)) / 1000);
     nm_assert(l < buf_size);
     return buf;
 }
 
-#define get_exp(buf, now_ns, item) _get_exp((buf), G_N_ELEMENTS(buf), (now_ns), (get_expiry(item)))
+#define get_exp(buf, now_msec, item) \
+    _get_exp((buf), G_N_ELEMENTS(buf), (now_msec), (item)->expiry_msec)
 
 /*****************************************************************************/
 
@@ -361,17 +338,16 @@ _ASSERT_data_gateways(const NMNDiscDataInternal *data)
         const NMNDiscGateway *item = &g_array_index(data->gateways, NMNDiscGateway, i);
 
         nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&item->address));
-        nm_assert(item->timestamp > 0 && item->timestamp <= G_MAXINT32);
         for (j = 0; j < i; j++) {
             const NMNDiscGateway *item2 = &g_array_index(data->gateways, NMNDiscGateway, j);
 
             nm_assert(!IN6_ARE_ADDR_EQUAL(&item->address, &item2->address));
         }
 
-        nm_assert(item->lifetime > 0);
-        if (i > 0)
+        if (i > 0) {
             nm_assert(_preference_to_priority(item_prev->preference)
                       >= _preference_to_priority(item->preference));
+        }
 
         item_prev = item;
     }
@@ -417,7 +393,7 @@ nm_ndisc_emit_config_change(NMNDisc *self, NMNDiscConfigMap changed)
 /*****************************************************************************/
 
 gboolean
-nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new)
+nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new_item, gint64 now_msec)
 {
     NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
     guint                i;
@@ -426,41 +402,46 @@ nm_ndisc_add_gateway(NMNDisc *ndisc, const NMNDiscGateway *new)
     for (i = 0; i < rdata->gateways->len;) {
         NMNDiscGateway *item = &g_array_index(rdata->gateways, NMNDiscGateway, i);
 
-        if (IN6_ARE_ADDR_EQUAL(&item->address, &new->address)) {
-            if (new->lifetime == 0) {
+        if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) {
+            if (new_item->expiry_msec <= now_msec) {
                 g_array_remove_index(rdata->gateways, i);
                 _ASSERT_data_gateways(rdata);
                 return TRUE;
             }
 
-            if (item->preference != new->preference) {
+            if (item->preference != new_item->preference) {
                 g_array_remove_index(rdata->gateways, i);
                 continue;
             }
 
-            if (get_expiry(item) == get_expiry(new))
+            if (item->expiry_msec == new_item->expiry_msec)
                 return FALSE;
 
-            *item = *new;
+            item->expiry_msec = new_item->expiry_msec;
             _ASSERT_data_gateways(rdata);
             return TRUE;
         }
 
         /* Put before less preferable gateways. */
-        if (_preference_to_priority(item->preference) < _preference_to_priority(new->preference)
+        if (_preference_to_priority(item->preference)
+                < _preference_to_priority(new_item->preference)
             && insert_idx == G_MAXUINT)
             insert_idx = i;
 
         i++;
     }
 
-    if (new->lifetime) {
-        g_array_insert_val(rdata->gateways,
-                           insert_idx == G_MAXUINT ? rdata->gateways->len : insert_idx,
-                           *new);
-    }
+    if (rdata->gateways->len >= _SIZE_MAX_GATEWAYS)
+        return FALSE;
+
+    if (new_item->expiry_msec <= now_msec)
+        return FALSE;
+
+    g_array_insert_val(rdata->gateways,
+                       insert_idx == G_MAXUINT ? rdata->gateways->len : insert_idx,
+                       *new_item);
     _ASSERT_data_gateways(rdata);
-    return !!new->lifetime;
+    return TRUE;
 }
 
 /**
@@ -513,25 +494,27 @@ complete_address(NMNDisc *ndisc, NMNDiscAddress *addr)
         return TRUE;
     }
 
-    _LOGW("complete-address: can't generate a new EUI-64 address");
+    _LOGW("complete-address: can't generate a new_item EUI-64 address");
     return FALSE;
 }
 
 static gboolean
-nm_ndisc_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s, gboolean from_ra)
+nm_ndisc_add_address(NMNDisc *             ndisc,
+                     const NMNDiscAddress *new_item,
+                     gint64                now_msec,
+                     gboolean              from_ra)
 {
     NMNDiscPrivate *     priv  = NM_NDISC_GET_PRIVATE(ndisc);
     NMNDiscDataInternal *rdata = &priv->rdata;
-    NMNDiscAddress       new2;
+    NMNDiscAddress *     new2;
     NMNDiscAddress *     existing = NULL;
     guint                i;
 
-    nm_assert(new);
-    nm_assert(new->timestamp > 0 && new->timestamp < G_MAXINT32);
-    nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&new->address));
-    nm_assert(!IN6_IS_ADDR_LINKLOCAL(&new->address));
-    nm_assert(new->preferred <= new->lifetime);
-    nm_assert(!from_ra || now_s > 0);
+    nm_assert(new_item);
+    nm_assert(!IN6_IS_ADDR_UNSPECIFIED(&new_item->address));
+    nm_assert(!IN6_IS_ADDR_LINKLOCAL(&new_item->address));
+    nm_assert(new_item->expiry_preferred_msec <= new_item->expiry_msec);
+    nm_assert((!!from_ra) == (now_msec > 0));
 
     for (i = 0; i < rdata->addresses->len; i++) {
         NMNDiscAddress *item = &g_array_index(rdata->addresses, NMNDiscAddress, i);
@@ -539,12 +522,12 @@ nm_ndisc_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s, gb
         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 implicitly length /64). */
-            if (memcmp(&item->address, &new->address, 8) == 0) {
+            if (memcmp(&item->address, &new_item->address, 8) == 0) {
                 existing = item;
                 break;
             }
         } else {
-            if (IN6_ARE_ADDR_EQUAL(&item->address, &new->address)) {
+            if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) {
                 existing = item;
                 break;
             }
@@ -552,67 +535,60 @@ nm_ndisc_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s, gb
     }
 
     if (existing) {
-        if (from_ra) {
-            const gint32 NM_NDISC_PREFIX_LFT_MIN = 7200; /* seconds, RFC4862 5.5.3.e */
-            gint64       old_expiry_lifetime, old_expiry_preferred;
-
-            old_expiry_lifetime  = get_expiry(existing);
-            old_expiry_preferred = get_expiry_preferred(existing);
+        gint64 new_expiry_preferred_msec;
+        gint64 new_expiry_msec;
 
-            if (new->lifetime == NM_NDISC_INFINITY)
-                existing->lifetime = NM_NDISC_INFINITY;
+        if (from_ra) {
+            if (new_item->expiry_msec == NM_NDISC_EXPIRY_INFINITY)
+                new_expiry_msec = NM_NDISC_EXPIRY_INFINITY;
             else {
-                gint64 new_lifetime, remaining_lifetime;
+                const gint64 NDISC_PREFIX_LFT_MIN_MSEC = 7200 * 1000; /* RFC4862 5.5.3.e */
+                gint64       new_lifetime;
+                gint64       existing_lifetime;
 
-                /* see RFC4862 5.5.3.e */
-                if (existing->lifetime == NM_NDISC_INFINITY)
-                    remaining_lifetime = G_MAXINT64;
+                new_lifetime = new_item->expiry_msec - now_msec;
+                if (existing->expiry_msec == NM_NDISC_EXPIRY_INFINITY)
+                    existing_lifetime = G_MAXINT64;
                 else
-                    remaining_lifetime = ((gint64) existing->timestamp)
-                                         + ((gint64) existing->lifetime) - ((gint64) now_s);
-                new_lifetime =
-                    ((gint64) new->timestamp) + ((gint64) new->lifetime) - ((gint64) now_s);
-
-                if (new_lifetime > (gint64) NM_NDISC_PREFIX_LFT_MIN
-                    || new_lifetime > remaining_lifetime) {
-                    existing->timestamp = now_s;
-                    existing->lifetime = CLAMP(new_lifetime, (gint64) 0, (gint64)(G_MAXUINT32 - 1));
-                } else if (remaining_lifetime <= (gint64) NM_NDISC_PREFIX_LFT_MIN) {
+                    existing_lifetime = existing->expiry_msec - now_msec;
+
+                /* see RFC4862 5.5.3.e */
+                if (new_lifetime >= NDISC_PREFIX_LFT_MIN_MSEC
+                    || new_lifetime >= existing_lifetime) {
+                    /* either extend the lifetime of the new_item lifetime is longer than
+                     * NDISC_PREFIX_LFT_MIN_MSEC. */
+                    new_expiry_msec = new_item->expiry_msec;
+                } else if (existing_lifetime <= NDISC_PREFIX_LFT_MIN_MSEC) {
                     /* keep the current lifetime. */
+                    new_expiry_msec = existing->expiry_msec;
                 } else {
-                    existing->timestamp = now_s;
-                    existing->lifetime  = NM_NDISC_PREFIX_LFT_MIN;
+                    /* trim the current lifetime to NDISC_PREFIX_LFT_MIN_MSEC. */
+                    new_expiry_msec = now_msec + NDISC_PREFIX_LFT_MIN_MSEC;
                 }
             }
 
-            if (new->preferred == NM_NDISC_INFINITY) {
-                nm_assert(existing->lifetime == NM_NDISC_INFINITY);
-                existing->preferred = new->preferred;
-            } else {
-                existing->preferred = NM_CLAMP(((gint64) new->timestamp) + ((gint64) new->preferred)
-                                                   - ((gint64) existing->timestamp),
-                                               0,
-                                               G_MAXUINT32 - 1);
-                if (existing->lifetime != NM_NDISC_INFINITY)
-                    existing->preferred = MIN(existing->preferred, existing->lifetime);
+            new_expiry_preferred_msec =
+                NM_MIN(new_item->expiry_preferred_msec, new_item->expiry_msec);
+            new_expiry_preferred_msec = NM_MIN(new_expiry_preferred_msec, new_expiry_msec);
+        } else {
+            if (new_item->expiry_msec <= now_msec) {
+                g_array_remove_index(rdata->addresses, i);
+                return TRUE;
             }
 
-            return old_expiry_lifetime != get_expiry(existing)
-                   || old_expiry_preferred != get_expiry_preferred(existing);
-        }
-
-        if (new->lifetime == 0) {
-            g_array_remove_index(rdata->addresses, i);
-            return TRUE;
+            new_expiry_msec = new_item->expiry_msec;
+            new_expiry_preferred_msec =
+                NM_MIN(new_item->expiry_preferred_msec, new_item->expiry_msec);
         }
 
-        if (get_expiry(existing) == get_expiry(new)
-            && get_expiry_preferred(existing) == get_expiry_preferred(new))
+        /* the dad_counter does not get modified. */
+        if (new_expiry_msec == existing->expiry_msec
+            && new_expiry_preferred_msec == existing->expiry_preferred_msec) {
             return FALSE;
+        }
 
-        existing->timestamp = new->timestamp;
-        existing->lifetime  = new->lifetime;
-        existing->preferred = new->preferred;
+        existing->expiry_msec           = new_expiry_msec;
+        existing->expiry_preferred_msec = new_expiry_preferred_msec;
         return TRUE;
     }
 
@@ -620,39 +596,45 @@ nm_ndisc_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s, gb
      * what the kernel does, because it considers *all* addresses (including
      * static and other temporary addresses).
      **/
-    if (priv->max_addresses && rdata->addresses->len >= priv->max_addresses)
+    if (rdata->addresses->len >= priv->max_addresses)
         return FALSE;
 
-    if (new->lifetime == 0)
+    if (new_item->expiry_msec <= now_msec)
         return FALSE;
 
+    new2 = nm_g_array_append_new(rdata->addresses, NMNDiscAddress);
+
+    *new2 = *new_item;
+
+    new2->expiry_preferred_msec = NM_MIN(new2->expiry_preferred_msec, new2->expiry_msec);
+
     if (from_ra) {
-        new2             = *new;
-        new2.dad_counter = 0;
-        if (!complete_address(ndisc, &new2))
+        new2->dad_counter = 0;
+        if (!complete_address(ndisc, new2)) {
+            g_array_set_size(rdata->addresses, rdata->addresses->len - 1);
             return FALSE;
-        new = &new2;
+        }
     }
 
-    g_array_append_val(rdata->addresses, *new);
     return TRUE;
 }
 
 gboolean
-nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new, gint32 now_s)
+nm_ndisc_complete_and_add_address(NMNDisc *ndisc, const NMNDiscAddress *new_item, gint64 now_msec)
 {
-    return nm_ndisc_add_address(ndisc, new, now_s, TRUE);
+    return nm_ndisc_add_address(ndisc, new_item, now_msec, TRUE);
 }
 
 gboolean
-nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new)
+nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new_item, gint64 now_msec)
 {
     NMNDiscPrivate *     priv;
     NMNDiscDataInternal *rdata;
     guint                i;
     guint                insert_idx = G_MAXUINT;
+    gboolean             changed    = FALSE;
 
-    if (new->plen == 0 || new->plen > 128) {
+    if (new_item->plen == 0 || new_item->plen > 128) {
         /* Only expect non-default routes.  The router has no idea what the
          * local configuration or user preferences are, so sending routes
          * with a prefix length of 0 must be ignored by NMNDisc.
@@ -669,41 +651,51 @@ nm_ndisc_add_route(NMNDisc *ndisc, const NMNDiscRoute *new)
     for (i = 0; i < rdata->routes->len;) {
         NMNDiscRoute *item = &g_array_index(rdata->routes, NMNDiscRoute, i);
 
-        if (IN6_ARE_ADDR_EQUAL(&item->network, &new->network) && item->plen == new->plen) {
-            if (new->lifetime == 0) {
+        if (IN6_ARE_ADDR_EQUAL(&item->network, &new_item->network)
+            && item->plen == new_item->plen) {
+            if (new_item->expiry_msec <= now_msec) {
                 g_array_remove_index(rdata->routes, i);
                 return TRUE;
             }
 
-            if (item->preference != new->preference) {
+            if (item->preference != new_item->preference) {
                 g_array_remove_index(rdata->routes, i);
+                changed = TRUE;
                 continue;
             }
 
-            if (get_expiry(item) == get_expiry(new)
-                && IN6_ARE_ADDR_EQUAL(&item->gateway, &new->gateway))
+            if (item->expiry_msec == new_item->expiry_msec
+                && IN6_ARE_ADDR_EQUAL(&item->gateway, &new_item->gateway))
                 return FALSE;
 
-            *item = *new;
+            item->expiry_msec = new_item->expiry_msec;
+            item->gateway     = new_item->gateway;
             return TRUE;
         }
 
         /* Put before less preferable routes. */
-        if (_preference_to_priority(item->preference) < _preference_to_priority(new->preference)
+        if (_preference_to_priority(item->preference)
+                < _preference_to_priority(new_item->preference)
             && insert_idx == G_MAXUINT)
             insert_idx = i;
 
         i++;
     }
 
-    if (new->lifetime) {
-        g_array_insert_val(rdata->routes, insert_idx == G_MAXUINT ? 0u : insert_idx, *new);
+    if (rdata->routes->len >= _SIZE_MAX_ROUTES)
+        return FALSE;
+
+    if (new_item->expiry_msec <= now_msec) {
+        nm_assert(!changed);
+        return FALSE;
     }
-    return !!new->lifetime;
+
+    g_array_insert_val(rdata->routes, insert_idx == G_MAXUINT ? 0u : insert_idx, *new_item);
+    return TRUE;
 }
 
 gboolean
-nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new)
+nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new_item, gint64 now_msec)
 {
     NMNDiscPrivate *     priv;
     NMNDiscDataInternal *rdata;
@@ -715,28 +707,33 @@ nm_ndisc_add_dns_server(NMNDisc *ndisc, const NMNDiscDNSServer *new)
     for (i = 0; i < rdata->dns_servers->len; i++) {
         NMNDiscDNSServer *item = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
 
-        if (IN6_ARE_ADDR_EQUAL(&item->address, &new->address)) {
-            if (new->lifetime == 0) {
+        if (IN6_ARE_ADDR_EQUAL(&item->address, &new_item->address)) {
+            if (new_item->expiry_msec <= now_msec) {
                 g_array_remove_index(rdata->dns_servers, i);
                 return TRUE;
             }
 
-            if (get_expiry(item) == get_expiry(new))
+            if (item->expiry_msec == new_item->expiry_msec)
                 return FALSE;
 
-            *item = *new;
+            item->expiry_msec = new_item->expiry_msec;
             return TRUE;
         }
     }
 
-    if (new->lifetime)
-        g_array_append_val(rdata->dns_servers, *new);
-    return !!new->lifetime;
+    if (rdata->dns_servers->len >= _SIZE_MAX_DNS_SERVERS)
+        return FALSE;
+
+    if (new_item->expiry_msec <= now_msec)
+        return FALSE;
+
+    g_array_append_val(rdata->dns_servers, *new_item);
+    return TRUE;
 }
 
-/* Copies new->domain if 'new' is added to the dns_domains list */
+/* Copies new_item->domain if 'new_item' is added to the dns_domains list */
 gboolean
-nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new)
+nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new_item, gint64 now_msec)
 {
     NMNDiscPrivate *     priv;
     NMNDiscDataInternal *rdata;
@@ -749,101 +746,155 @@ nm_ndisc_add_dns_domain(NMNDisc *ndisc, const NMNDiscDNSDomain *new)
     for (i = 0; i < rdata->dns_domains->len; i++) {
         item = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
 
-        if (!g_strcmp0(item->domain, new->domain)) {
-            if (new->lifetime == 0) {
+        if (nm_streq(item->domain, new_item->domain)) {
+            if (new_item->expiry_msec <= now_msec) {
                 g_array_remove_index(rdata->dns_domains, i);
                 return TRUE;
             }
 
-            if (get_expiry(item) == get_expiry(new))
+            if (item->expiry_msec == new_item->expiry_msec)
                 return FALSE;
 
-            item->timestamp = new->timestamp;
-            item->lifetime  = new->lifetime;
+            item->expiry_msec = new_item->expiry_msec;
             return TRUE;
         }
     }
 
-    if (new->lifetime) {
-        g_array_append_val(rdata->dns_domains, *new);
-        item = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, rdata->dns_domains->len - 1);
-        item->domain = g_strdup(new->domain);
-    }
-    return !!new->lifetime;
+    if (rdata->dns_domains->len >= _SIZE_MAX_DNS_DOMAINS)
+        return FALSE;
+
+    if (new_item->expiry_msec <= now_msec)
+        return FALSE;
+
+    item  = nm_g_array_append_new(rdata->dns_domains, NMNDiscDNSDomain);
+    *item = (NMNDiscDNSDomain){
+        .domain      = g_strdup(new_item->domain),
+        .expiry_msec = new_item->expiry_msec,
+    };
+    return TRUE;
 }
 
 /*****************************************************************************/
 
-#define _MAYBE_WARN(...)                                                       \
-    G_STMT_START                                                               \
-    {                                                                          \
-        gboolean _different_message;                                           \
-                                                                               \
-        _different_message = g_strcmp0(priv->last_error, error->message) != 0; \
-        _NMLOG(_different_message ? LOGL_WARN : LOGL_DEBUG, __VA_ARGS__);      \
-        if (_different_message) {                                              \
-            nm_clear_g_free(&priv->last_error);                                \
-            priv->last_error = g_strdup(error->message);                       \
-        }                                                                      \
-    }                                                                          \
+#define _MAYBE_WARN(...)                                                   \
+    G_STMT_START                                                           \
+    {                                                                      \
+        gboolean _different_message;                                       \
+                                                                           \
+        _different_message = !nm_streq0(priv->last_error, error->message); \
+        _NMLOG(_different_message ? LOGL_WARN : LOGL_DEBUG, __VA_ARGS__);  \
+        if (_different_message) {                                          \
+            nm_clear_g_free(&priv->last_error);                            \
+            priv->last_error = g_strdup(error->message);                   \
+        }                                                                  \
+    }                                                                      \
     G_STMT_END
 
-static gboolean
-send_rs_timeout(NMNDisc *ndisc)
+static gint32
+solicit_retransmit_time_jitter(gint32 solicit_retransmit_time_msec)
 {
-    nm_auto_pop_netns NMPNetns *netns = NULL;
-    NMNDiscClass *              klass = NM_NDISC_GET_CLASS(ndisc);
-    NMNDiscPrivate *            priv  = NM_NDISC_GET_PRIVATE(ndisc);
-    GError *                    error = NULL;
+    gint32 ten_percent;
 
-    priv->send_rs_id = 0;
+    nm_assert(solicit_retransmit_time_msec > 0);
+    nm_assert(solicit_retransmit_time_msec < 3 * RFC7559_MRT * 1000);
 
-    if (!nm_ndisc_netns_push(ndisc, &netns))
-        return G_SOURCE_REMOVE;
+    /* Add a ±10% jitter.
+     *
+     * This is the "RAND" parameter from https://tools.ietf.org/html/rfc3315#section-14
+     * as requested by RFC7559.  */
+
+    ten_percent = NM_MAX(1, solicit_retransmit_time_msec / 10);
 
-    if (klass->send_rs(ndisc, &error)) {
-        _LOGD("router solicitation sent");
-        priv->solicitations_left--;
+    return solicit_retransmit_time_msec - ten_percent
+           + ((gint32)(g_random_int() % (2u * ((guint32) ten_percent))));
+}
+
+static gboolean
+solicit_timer_cb(gpointer user_data)
+{
+    const gint32      TIMEOUT_APPROX_THRESHOLD_SEC = 10000;
+    nm_auto_pop_netns NMPNetns *netns              = NULL;
+    NMNDisc *                   ndisc              = user_data;
+    NMNDiscClass *              klass              = NM_NDISC_GET_CLASS(ndisc);
+    NMNDiscPrivate *            priv               = NM_NDISC_GET_PRIVATE(ndisc);
+    gs_free_error GError *error                    = NULL;
+    gint32                timeout_msec;
+
+    if (!nm_ndisc_netns_push(ndisc, &netns)) {
+        nm_utils_error_set(&error,
+                           NM_UTILS_ERROR_UNKNOWN,
+                           "failure to switch netns for soliciting routers");
+    } else
+        klass->send_rs(ndisc, &error);
+
+    if (error)
+        _MAYBE_WARN("solicit: failure sending router solicitation: %s", error->message);
+    else {
+        _LOGT("solicit: router solicitation sent");
         nm_clear_g_free(&priv->last_error);
-    } else {
-        _MAYBE_WARN("failure sending router solicitation: %s", error->message);
-        g_clear_error(&error);
     }
 
-    priv->last_rs = nm_utils_get_monotonic_timestamp_sec();
-    if (priv->solicitations_left > 0) {
-        _LOGD("scheduling router solicitation retry in %d seconds.",
-              (int) priv->router_solicitation_interval);
-        priv->send_rs_id = g_timeout_add_seconds(priv->router_solicitation_interval,
-                                                 (GSourceFunc) send_rs_timeout,
-                                                 ndisc);
+    /* https://tools.ietf.org/html/rfc4861#section-6.3.7 describes how to send solicitations:
+     *
+     *   > ... a host SHOULD transmit up to MAX_RTR_SOLICITATIONS Router Solicitation messages,
+     *   > each separated by at least RTR_SOLICITATION_INTERVAL seconds.
+     *
+     * but this was extended by https://tools.ietf.org/html/rfc7559#section-2 to send continuously
+     * and with exponential backoff (detailed the algorithm in https://tools.ietf.org/html/rfc3315#section-14).
+     * We do RFC7559.
+     */
+    if (priv->solicit_retransmit_time_msec == 0) {
+        G_STATIC_ASSERT(RFC7559_IRT == NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL);
+        priv->solicit_retransmit_time_msec = solicit_retransmit_time_jitter(RFC7559_IRT * 1000);
+        timeout_msec                       = priv->solicit_retransmit_time_msec;
     } else {
-        _LOGD("did not receive a router advertisement after %d solicitations.",
-              (int) priv->router_solicitations);
+        priv->solicit_retransmit_time_msec +=
+            solicit_retransmit_time_jitter(priv->solicit_retransmit_time_msec);
+        timeout_msec = priv->solicit_retransmit_time_msec;
+        if (priv->solicit_retransmit_time_msec > RFC7559_MRT * 1000) {
+            priv->solicit_retransmit_time_msec = RFC7559_MRT * 1000;
+            timeout_msec = solicit_retransmit_time_jitter(priv->solicit_retransmit_time_msec);
+        }
     }
 
-    return G_SOURCE_REMOVE;
+    _LOGD("solicit: schedule sending next solicitation in%s %.3f seconds",
+          timeout_msec / 1000 >= TIMEOUT_APPROX_THRESHOLD_SEC ? " about" : "",
+          ((double) timeout_msec) / 1000);
+
+    nm_clear_g_source_inst(&priv->solicit_timer_source);
+    priv->solicit_timer_source = nm_g_timeout_add_source_approx(timeout_msec,
+                                                                TIMEOUT_APPROX_THRESHOLD_SEC,
+                                                                solicit_timer_cb,
+                                                                ndisc);
+    return G_SOURCE_CONTINUE;
 }
 
 static void
-solicit_routers(NMNDisc *ndisc)
+solicit_timer_start(NMNDisc *ndisc)
 {
     NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc);
-    gint32          now, next;
-    gint64          t;
+    gint32          delay_msec;
 
-    if (priv->send_rs_id)
-        return;
+    /* rfc4861, Section 6.3.7:
+     *
+     * We should randomly wait up to NM_NDISC_RFC4861_MAX_RTR_SOLICITATION_DELAY (1 second)
+     * before sending the first RS. RFC4861 is from 2007, I don't think 1 second is
+     * a suitable delay in 2021. Wait only up to 250 msec instead. */
+
+    delay_msec =
+        g_random_int() % ((guint32)(NM_NDISC_RFC4861_MAX_RTR_SOLICITATION_DELAY * 1000 / 4));
 
-    now                      = nm_utils_get_monotonic_timestamp_sec();
-    priv->solicitations_left = priv->router_solicitations;
+    _LOGD("solicit: schedule sending first solicitation (of %d) in %.3f seconds",
+          priv->router_solicitations,
+          ((double) delay_msec) / 1000);
 
-    t    = (((gint64) priv->last_rs) + priv->router_solicitation_interval) - now;
-    next = CLAMP(t, 0, G_MAXINT32);
-    _LOGD("scheduling explicit router solicitation request in %" G_GINT32_FORMAT " seconds.", next);
-    priv->send_rs_id = g_timeout_add_seconds((guint32) next, (GSourceFunc) send_rs_timeout, ndisc);
+    priv->solicit_retransmit_time_msec = 0;
+    nm_clear_g_source_inst(&priv->solicit_timer_source);
+    priv->solicit_timer_source = nm_g_timeout_add_source(delay_msec, solicit_timer_cb, ndisc);
 }
 
+/*****************************************************************************/
+
 static gboolean
 announce_router(NMNDisc *ndisc)
 {
@@ -946,12 +997,16 @@ nm_ndisc_set_config(NMNDisc *     ndisc,
     }
 
     for (i = 0; i < dns_servers->len; i++) {
-        if (nm_ndisc_add_dns_server(ndisc, &g_array_index(dns_servers, NMNDiscDNSServer, i)))
+        if (nm_ndisc_add_dns_server(ndisc,
+                                    &g_array_index(dns_servers, NMNDiscDNSServer, i),
+                                    G_MININT64))
             changed = TRUE;
     }
 
     for (i = 0; i < dns_domains->len; i++) {
-        if (nm_ndisc_add_dns_domain(ndisc, &g_array_index(dns_domains, NMNDiscDNSDomain, i)))
+        if (nm_ndisc_add_dns_domain(ndisc,
+                                    &g_array_index(dns_domains, NMNDiscDNSDomain, i),
+                                    G_MININT64))
             changed = TRUE;
     }
 
@@ -999,7 +1054,7 @@ nm_ndisc_set_iid(NMNDisc *ndisc, const NMUtilsIPv6IfaceId iid)
             _LOGD("IPv6 interface identifier changed, flushing addresses");
             g_array_remove_range(rdata->addresses, 0, rdata->addresses->len);
             nm_ndisc_emit_config_change(ndisc, NM_NDISC_CONFIG_ADDRESSES);
-            solicit_routers(ndisc);
+            solicit_timer_start(ndisc);
         }
         return TRUE;
     }
@@ -1008,13 +1063,13 @@ nm_ndisc_set_iid(NMNDisc *ndisc, const NMUtilsIPv6IfaceId iid)
 }
 
 static gboolean
-ndisc_ra_timeout_cb(gpointer user_data)
+ra_timeout_cb(gpointer user_data)
 {
     NMNDisc *ndisc = NM_NDISC(user_data);
 
     nm_clear_g_source_inst(&NM_NDISC_GET_PRIVATE(ndisc)->ra_timeout_source);
     g_signal_emit(ndisc, signals[RA_TIMEOUT_SIGNAL], 0);
-    return G_SOURCE_REMOVE;
+    return G_SOURCE_CONTINUE;
 }
 
 void
@@ -1053,15 +1108,10 @@ nm_ndisc_start(NMNDisc *ndisc)
                 timeout_msec = priv->ra_timeout * 1000u;
             else
                 timeout_msec = G_MAXUINT;
-            priv->ra_timeout_source = nm_g_timeout_source_new(timeout_msec,
-                                                              G_PRIORITY_DEFAULT,
-                                                              ndisc_ra_timeout_cb,
-                                                              ndisc,
-                                                              NULL);
-            g_source_attach(priv->ra_timeout_source, NULL);
+            priv->ra_timeout_source = nm_g_timeout_add_source(timeout_msec, ra_timeout_cb, ndisc);
         }
 
-        solicit_routers(ndisc);
+        solicit_timer_start(ndisc);
         return;
     }
 
@@ -1099,20 +1149,16 @@ nm_ndisc_stop(NMNDisc *ndisc)
     g_array_set_size(rdata->dns_domains, 0);
     priv->rdata.public.hop_limit = 64;
 
-    /* Start at very low number so that last_rs - router_solicitation_interval
-     * is much lower than nm_utils_get_monotonic_timestamp_sec() at startup.
-     */
-    priv->last_rs = G_MININT32;
     nm_clear_g_source_inst(&priv->ra_timeout_source);
-    nm_clear_g_source(&priv->send_rs_id);
     nm_clear_g_source(&priv->send_ra_id);
     nm_clear_g_free(&priv->last_error);
-    nm_clear_g_source(&priv->timeout_id);
+    nm_clear_g_source_inst(&priv->timeout_expire_source);
+
+    priv->solicit_retransmit_time_msec = 0;
+    nm_clear_g_source_inst(&priv->solicit_timer_source);
 
-    priv->solicitations_left = 0;
     priv->announcements_left = 0;
 
-    priv->last_rs = G_MININT32;
     priv->last_ra = G_MININT32;
 }
 
@@ -1189,15 +1235,15 @@ _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed)
     NMNDiscDataInternal *rdata;
     guint                i;
     char                 changedstr[CONFIG_MAP_MAX_STR];
-    char                 addrstr[INET6_ADDRSTRLEN];
+    char                 addrstr[NM_UTILS_INET_ADDRSTRLEN];
     char                 str_pref[35];
     char                 str_exp[100];
-    gint64               now_ns;
+    gint64               now_msec;
 
     if (!_LOGD_ENABLED())
         return;
 
-    now_ns = nm_utils_get_monotonic_timestamp_nsec();
+    now_msec = nm_utils_get_monotonic_timestamp_msec();
 
     priv  = NM_NDISC_GET_PRIVATE(ndisc);
     rdata = &priv->rdata;
@@ -1214,215 +1260,300 @@ _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed)
         _LOGD("  retrans timer  : %u", (guint) rdata->public.retrans_timer_ms);
 
     for (i = 0; i < rdata->gateways->len; i++) {
-        NMNDiscGateway *gateway = &g_array_index(rdata->gateways, NMNDiscGateway, i);
+        const NMNDiscGateway *gateway = &g_array_index(rdata->gateways, NMNDiscGateway, i);
 
-        inet_ntop(AF_INET6, &gateway->address, addrstr, sizeof(addrstr));
         _LOGD("  gateway %s pref %s exp %s",
-              addrstr,
+              _nm_utils_inet6_ntop(&gateway->address, addrstr),
               nm_icmpv6_router_pref_to_string(gateway->preference, str_pref, sizeof(str_pref)),
-              get_exp(str_exp, now_ns, gateway));
+              get_exp(str_exp, now_msec, gateway));
     }
     for (i = 0; i < rdata->addresses->len; i++) {
         const NMNDiscAddress *address = &g_array_index(rdata->addresses, NMNDiscAddress, i);
 
-        inet_ntop(AF_INET6, &address->address, addrstr, sizeof(addrstr));
-        _LOGD("  address %s exp %s", addrstr, get_exp(str_exp, now_ns, address));
+        _LOGD("  address %s exp %s",
+              _nm_utils_inet6_ntop(&address->address, addrstr),
+              get_exp(str_exp, now_msec, address));
     }
     for (i = 0; i < rdata->routes->len; i++) {
-        NMNDiscRoute *route = &g_array_index(rdata->routes, NMNDiscRoute, i);
-        char          sbuf[NM_UTILS_INET_ADDRSTRLEN];
+        const 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,
+              _nm_utils_inet6_ntop(&route->network, addrstr),
               (guint) route->plen,
               _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));
+              get_exp(str_exp, now_msec, route));
     }
     for (i = 0; i < rdata->dns_servers->len; i++) {
-        NMNDiscDNSServer *dns_server = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
+        const NMNDiscDNSServer *dns_server =
+            &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
 
-        inet_ntop(AF_INET6, &dns_server->address, addrstr, sizeof(addrstr));
-        _LOGD("  dns_server %s exp %s", addrstr, get_exp(str_exp, now_ns, dns_server));
+        _LOGD("  dns_server %s exp %s",
+              _nm_utils_inet6_ntop(&dns_server->address, addrstr),
+              get_exp(str_exp, now_msec, dns_server));
     }
     for (i = 0; i < rdata->dns_domains->len; i++) {
-        NMNDiscDNSDomain *dns_domain = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
+        const NMNDiscDNSDomain *dns_domain =
+            &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
 
-        _LOGD("  dns_domain %s exp %s", dns_domain->domain, get_exp(str_exp, now_ns, dns_domain));
+        _LOGD("  dns_domain %s exp %s", dns_domain->domain, get_exp(str_exp, now_msec, dns_domain));
     }
 }
 
+/*****************************************************************************/
+
+static gboolean
+_array_set_size_max(GArray *array, guint size_max)
+{
+    nm_assert(array);
+    nm_assert(size_max > 0u);
+
+    if (array->len <= size_max)
+        return FALSE;
+
+    g_array_set_size(array, size_max);
+    return TRUE;
+}
+
 static void
-clean_gateways(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent)
+clean_gateways(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
 {
-    NMNDiscDataInternal *rdata;
+    NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    NMNDiscGateway *     arr;
     guint                i;
+    guint                j;
 
-    rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    if (rdata->gateways->len == 0)
+        return;
 
-    for (i = 0; i < rdata->gateways->len;) {
-        NMNDiscGateway *item = &g_array_index(rdata->gateways, NMNDiscGateway, i);
+    arr = &g_array_index(rdata->gateways, NMNDiscGateway, 0);
 
-        if (!expiry_next(now, get_expiry(item), nextevent)) {
-            g_array_remove_index(rdata->gateways, i);
-            *changed |= NM_NDISC_CONFIG_GATEWAYS;
+    for (i = 0, j = 0; i < rdata->gateways->len; i++) {
+        if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
             continue;
-        }
+        if (i != j)
+            arr[j] = arr[i];
+        j++;
+    }
 
-        i++;
+    if (i != j) {
+        *changed |= NM_NDISC_CONFIG_GATEWAYS;
+        g_array_set_size(rdata->gateways, j);
     }
 
+    if (_array_set_size_max(rdata->gateways, _SIZE_MAX_GATEWAYS))
+        *changed |= NM_NDISC_CONFIG_GATEWAYS;
+
     _ASSERT_data_gateways(rdata);
 }
 
 static void
-clean_addresses(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent)
+clean_addresses(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
 {
-    NMNDiscDataInternal *rdata;
+    NMNDiscPrivate *     priv  = NM_NDISC_GET_PRIVATE(ndisc);
+    NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    NMNDiscAddress *     arr;
     guint                i;
+    guint                j;
 
-    rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    if (rdata->addresses->len == 0)
+        return;
 
-    for (i = 0; i < rdata->addresses->len;) {
-        const NMNDiscAddress *item = &g_array_index(rdata->addresses, NMNDiscAddress, i);
+    arr = &g_array_index(rdata->addresses, NMNDiscAddress, 0);
 
-        if (!expiry_next(now, get_expiry(item), nextevent)) {
-            g_array_remove_index(rdata->addresses, i);
-            *changed |= NM_NDISC_CONFIG_ADDRESSES;
+    for (i = 0, j = 0; i < rdata->addresses->len; i++) {
+        if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
             continue;
-        }
+        if (i != j)
+            arr[j] = arr[i];
+        j++;
+    }
 
-        i++;
+    if (i != j) {
+        *changed = NM_NDISC_CONFIG_ADDRESSES;
+        g_array_set_size(rdata->addresses, j);
     }
+
+    if (_array_set_size_max(rdata->gateways, priv->max_addresses))
+        *changed |= NM_NDISC_CONFIG_ADDRESSES;
 }
 
 static void
-clean_routes(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent)
+clean_routes(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
 {
-    NMNDiscDataInternal *rdata;
+    NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    NMNDiscRoute *       arr;
     guint                i;
+    guint                j;
 
-    rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    if (rdata->routes->len == 0)
+        return;
 
-    for (i = 0; i < rdata->routes->len;) {
-        NMNDiscRoute *item = &g_array_index(rdata->routes, NMNDiscRoute, i);
+    arr = &g_array_index(rdata->routes, NMNDiscRoute, 0);
 
-        if (!expiry_next(now, get_expiry(item), nextevent)) {
-            g_array_remove_index(rdata->routes, i);
-            *changed |= NM_NDISC_CONFIG_ROUTES;
+    for (i = 0, j = 0; i < rdata->routes->len; i++) {
+        if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
             continue;
-        }
+        if (i != j)
+            arr[j] = arr[i];
+        j++;
+    }
 
-        i++;
+    if (i != j) {
+        *changed |= NM_NDISC_CONFIG_ROUTES;
+        g_array_set_size(rdata->routes, j);
     }
+
+    if (_array_set_size_max(rdata->gateways, _SIZE_MAX_ROUTES))
+        *changed |= NM_NDISC_CONFIG_ROUTES;
 }
 
 static void
-clean_dns_servers(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent)
+clean_dns_servers(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
 {
-    NMNDiscDataInternal *rdata;
+    NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    NMNDiscDNSServer *   arr;
     guint                i;
+    guint                j;
 
-    rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    if (rdata->dns_servers->len == 0)
+        return;
 
-    for (i = 0; i < rdata->dns_servers->len;) {
-        NMNDiscDNSServer *item = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, i);
-        gint64            refresh;
+    arr = &g_array_index(rdata->dns_servers, NMNDiscDNSServer, 0);
 
-        refresh = get_expiry_half(item);
-        if (refresh != _EXPIRY_INFINITY) {
-            if (!expiry_next(now, get_expiry(item), NULL)) {
-                g_array_remove_index(rdata->dns_servers, i);
-                *changed |= NM_NDISC_CONFIG_DNS_SERVERS;
-                continue;
-            }
+    for (i = 0, j = 0; i < rdata->dns_servers->len; i++) {
+        if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
+            continue;
+        if (i != j)
+            arr[j] = arr[i];
+        j++;
+    }
 
-            if (now >= refresh)
-                solicit_routers(ndisc);
-            else if (*nextevent > refresh)
-                *nextevent = refresh;
-        }
-        i++;
+    if (i != j) {
+        *changed |= NM_NDISC_CONFIG_DNS_SERVERS;
+        g_array_set_size(rdata->dns_servers, j);
     }
+
+    if (_array_set_size_max(rdata->gateways, _SIZE_MAX_DNS_SERVERS))
+        *changed |= NM_NDISC_CONFIG_DNS_SERVERS;
 }
 
 static void
-clean_dns_domains(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent)
+clean_dns_domains(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap *changed, gint64 *next_msec)
 {
-    NMNDiscDataInternal *rdata;
+    NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    NMNDiscDNSDomain *   arr;
     guint                i;
+    guint                j;
 
-    rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
+    if (rdata->dns_domains->len == 0)
+        return;
 
-    for (i = 0; i < rdata->dns_domains->len;) {
-        NMNDiscDNSDomain *item = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, i);
-        gint64            refresh;
+    arr = &g_array_index(rdata->dns_domains, NMNDiscDNSDomain, 0);
 
-        refresh = get_expiry_half(item);
-        if (refresh != _EXPIRY_INFINITY) {
-            if (!expiry_next(now, get_expiry(item), NULL)) {
-                g_array_remove_index(rdata->dns_domains, i);
-                *changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
-                continue;
-            }
+    for (i = 0, j = 0; i < rdata->dns_domains->len; i++) {
+        if (!expiry_next(now_msec, arr[i].expiry_msec, next_msec))
+            continue;
 
-            if (now >= refresh)
-                solicit_routers(ndisc);
-            else if (*nextevent > refresh)
-                *nextevent = refresh;
+        if (i != j) {
+            g_free(arr[j].domain);
+            arr[j]        = arr[i];
+            arr[i].domain = NULL;
         }
-        i++;
+
+        j++;
     }
-}
 
-static gboolean timeout_cb(gpointer user_data);
+    if (i != 0) {
+        *changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
+        g_array_set_size(rdata->dns_domains, j);
+    }
+
+    if (_array_set_size_max(rdata->gateways, _SIZE_MAX_DNS_DOMAINS))
+        *changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
+}
 
 static void
-check_timestamps(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed)
+check_timestamps(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed)
 {
-    NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc);
-    /* Use a magic date in the distant future (~68 years) */
-    gint32 nextevent = G_MAXINT32;
+    NMNDiscPrivate *priv      = NM_NDISC_GET_PRIVATE(ndisc);
+    gint64          next_msec = G_MAXINT64;
 
-    nm_clear_g_source(&priv->timeout_id);
+    _LOGT("router-data: check for changed router advertisement data");
 
-    clean_gateways(ndisc, now, &changed, &nextevent);
-    clean_addresses(ndisc, now, &changed, &nextevent);
-    clean_routes(ndisc, now, &changed, &nextevent);
-    clean_dns_servers(ndisc, now, &changed, &nextevent);
-    clean_dns_domains(ndisc, now, &changed, &nextevent);
+    clean_gateways(ndisc, now_msec, &changed, &next_msec);
+    clean_addresses(ndisc, now_msec, &changed, &next_msec);
+    clean_routes(ndisc, now_msec, &changed, &next_msec);
+    clean_dns_servers(ndisc, now_msec, &changed, &next_msec);
+    clean_dns_domains(ndisc, now_msec, &changed, &next_msec);
 
-    if (nextevent != G_MAXINT32) {
-        if (nextevent <= now)
-            g_return_if_reached();
-        _LOGD("scheduling next now/lifetime check: %d seconds", (int) (nextevent - now));
-        priv->timeout_id = g_timeout_add_seconds(nextevent - now, timeout_cb, ndisc);
+    nm_assert(next_msec > now_msec);
+
+    nm_clear_g_source_inst(&priv->timeout_expire_source);
+
+    if (next_msec == NM_NDISC_EXPIRY_INFINITY)
+        _LOGD("router-data: next lifetime expiration will happen: never");
+    else {
+        const gint64 timeout_msec = NM_MIN(next_msec - now_msec, ((gint64) G_MAXINT32));
+        const guint  TIMEOUT_APPROX_THRESHOLD_SEC = 10000;
+
+        _LOGD("router-data: next lifetime expiration will happen: in %s%.3f seconds",
+              (timeout_msec / 1000) >= TIMEOUT_APPROX_THRESHOLD_SEC ? " about" : "",
+              ((double) timeout_msec) / 1000);
+
+        priv->timeout_expire_source = nm_g_timeout_add_source_approx(timeout_msec,
+                                                                     TIMEOUT_APPROX_THRESHOLD_SEC,
+                                                                     timeout_expire_cb,
+                                                                     ndisc);
     }
 
-    if (changed)
+    /* When we receive an RA, we don't disable solicitations entirely. Instead,
+     * we set the interval the maximum (RFC7559_MRT).
+     *
+     * This contradicts https://tools.ietf.org/html/rfc7559#section-2.1, which says
+     * that we SHOULD stop sending RS if we receive an RA -- but only on a multicast
+     * capable link and if the RA has a valid router lifetime.
+     *
+     * But we really want to recover from a dead router on the network, so we
+     * don't want to cease sending RS entirely.
+     *
+     * But we only re-schedule the timer if the current interval is not already
+     * "RFC7559_MRT * 1000". Otherwise, we already have a slow interval counter
+     * pending. */
+    if (priv->solicit_retransmit_time_msec != RFC7559_MRT * 1000) {
+        gint32 timeout_msec;
+
+        priv->solicit_retransmit_time_msec = RFC7559_MRT * 1000;
+        timeout_msec = solicit_retransmit_time_jitter(priv->solicit_retransmit_time_msec);
+
+        _LOGD("solicit: schedule sending next (slow) solicitation in about %.3f seconds",
+              ((double) timeout_msec) / 1000);
+
+        nm_clear_g_source_inst(&priv->solicit_timer_source);
+        priv->solicit_timer_source =
+            nm_g_timeout_add_source_approx(timeout_msec, 0, solicit_timer_cb, ndisc);
+    }
+
+    if (changed != NM_NDISC_CONFIG_NONE)
         nm_ndisc_emit_config_change(ndisc, changed);
 }
 
 static gboolean
-timeout_cb(gpointer user_data)
+timeout_expire_cb(gpointer user_data)
 {
-    NMNDisc *self = user_data;
-
-    NM_NDISC_GET_PRIVATE(self)->timeout_id = 0;
-    check_timestamps(self, nm_utils_get_monotonic_timestamp_sec(), 0);
-    return G_SOURCE_REMOVE;
+    check_timestamps(user_data, nm_utils_get_monotonic_timestamp_msec(), NM_NDISC_CONFIG_NONE);
+    return G_SOURCE_CONTINUE;
 }
 
 void
-nm_ndisc_ra_received(NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed)
+nm_ndisc_ra_received(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed)
 {
     NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc);
 
     nm_clear_g_source_inst(&priv->ra_timeout_source);
-    nm_clear_g_source(&priv->send_rs_id);
     nm_clear_g_free(&priv->last_error);
-    check_timestamps(ndisc, now, changed);
+    check_timestamps(ndisc, now_msec, changed);
 }
 
 void
@@ -1447,6 +1578,7 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
 {
     NMNDisc *       self = NM_NDISC(object);
     NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(self);
+    int             i;
 
     switch (prop_id) {
     case PROP_PLATFORM:
@@ -1488,7 +1620,14 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
         break;
     case PROP_MAX_ADDRESSES:
         /* construct-only */
-        priv->max_addresses = g_value_get_int(value);
+        i = g_value_get_int(value);
+        nm_assert(i >= 0);
+        priv->max_addresses = i;
+
+        if (priv->max_addresses <= 0)
+            priv->max_addresses = _SIZE_MAX_ADDRESSES;
+        else if (priv->max_addresses > 3u * _SIZE_MAX_ADDRESSES)
+            priv->max_addresses = 3u * _SIZE_MAX_ADDRESSES;
         break;
     case PROP_RA_TIMEOUT:
         /* construct-only */
@@ -1532,11 +1671,6 @@ nm_ndisc_init(NMNDisc *ndisc)
     rdata->dns_domains = g_array_new(FALSE, FALSE, sizeof(NMNDiscDNSDomain));
     g_array_set_clear_func(rdata->dns_domains, dns_domain_free);
     priv->rdata.public.hop_limit = 64;
-
-    /* Start at very low number so that last_rs - router_solicitation_interval
-     * is much lower than nm_utils_get_monotonic_timestamp_sec() at startup.
-     */
-    priv->last_rs = G_MININT32;
 }
 
 static void
@@ -1546,11 +1680,11 @@ dispose(GObject *object)
     NMNDiscPrivate *priv  = NM_NDISC_GET_PRIVATE(ndisc);
 
     nm_clear_g_source_inst(&priv->ra_timeout_source);
-    nm_clear_g_source(&priv->send_rs_id);
+    nm_clear_g_source_inst(&priv->solicit_timer_source);
     nm_clear_g_source(&priv->send_ra_id);
     nm_clear_g_free(&priv->last_error);
 
-    nm_clear_g_source(&priv->timeout_id);
+    nm_clear_g_source_inst(&priv->timeout_expire_source);
 
     G_OBJECT_CLASS(nm_ndisc_parent_class)->dispose(object);
 }
@@ -1661,7 +1795,7 @@ nm_ndisc_class_init(NMNDiscClass *klass)
                          "",
                          1,
                          G_MAXINT32,
-                         NM_NDISC_ROUTER_SOLICITATION_INTERVAL_DEFAULT,
+                         NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL,
                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
     obj_properties[PROP_NODE_TYPE] =
         g_param_spec_int(NM_NDISC_NODE_TYPE,
diff --git a/src/ndisc/nm-ndisc.h b/src/core/ndisc/nm-ndisc.h
index 0578c45b..93aee4db 100644
--- a/src/ndisc/nm-ndisc.h
+++ b/src/core/ndisc/nm-ndisc.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
@@ -48,48 +48,91 @@ typedef enum {
     NM_NDISC_DHCP_LEVEL_MANAGED
 } NMNDiscDHCPLevel;
 
-/* we rely on the fact that NM_NDISC_INFINITY is the largest possible
- * time duration (G_MAXUINT32) and that the range of finite values
- * goes from 0 to G_MAXUINT32-1. */
-#define NM_NDISC_INFINITY G_MAXUINT32
+#define NM_NDISC_INFINITY_U32 ((uint32_t) -1)
 
-struct _NMNDiscGateway {
+/* It's important that this is G_MAXINT64, so that we can meaningfully do
+ * MIN(e1, e2) to find the minimum expiry time (and properly handle if any
+ * of them is infinity).
+ *
+ * While usually you assign this to "expiry_msec", you might say the
+ * unit of it is milliseconds. But of course, infinity has not really a unit. */
+#define NM_NDISC_EXPIRY_INFINITY G_MAXINT64
+
+/* in common cases, the expiry_msec tracks the timestamp in nm_utils_get_monotonic_timestamp_mses()
+ * timestamp when the item expires.
+ *
+ * When we configure an NMNDiscAddress to be announced via the router advertisement,
+ * then that address does not have a fixed expiry point in time, instead, the expiry
+ * really contains the lifetime from the moment when we send the router advertisement.
+ * In that case, the expiry_msec is more a "lifetime" that starts counting at timestamp
+ * zero.
+ *
+ * The unit is milliseconds (but of course, the timestamp is zero, so it doesn't really matter). */
+#define NM_NDISC_EXPIRY_BASE_TIMESTAMP ((gint64) 0)
+
+static inline gint64
+_nm_ndisc_lifetime_to_expiry(gint64 now_msec, guint32 lifetime)
+{
+    if (lifetime == NM_NDISC_INFINITY_U32)
+        return NM_NDISC_EXPIRY_INFINITY;
+    return now_msec + (((gint64) lifetime) * 1000);
+}
+
+static inline gint64
+_nm_ndisc_lifetime_from_expiry(gint64 now_msec, gint64 expiry_msec, gboolean ceil)
+{
+    gint64 diff;
+
+    if (expiry_msec == NM_NDISC_EXPIRY_INFINITY)
+        return NM_NDISC_INFINITY_U32;
+
+    /* we don't expect nor handle integer overflow. The time stamp and expiry
+     * should be reasonably small so that it cannot happen. */
+
+    diff = expiry_msec - now_msec;
+
+    if (diff <= 0)
+        return 0;
+
+    if (ceil) {
+        /* we ceil() towards the next full second (instead of floor()). */
+        diff += 999;
+    }
+
+    return NM_MIN(diff / 1000, (gint64)(G_MAXUINT32 - 1));
+}
+
+/*****************************************************************************/
+
+typedef struct _NMNDiscGateway {
     struct in6_addr    address;
-    guint32            timestamp;
-    guint32            lifetime;
+    gint64             expiry_msec;
     NMIcmpv6RouterPref preference;
-};
-typedef struct _NMNDiscGateway NMNDiscGateway;
+} NMNDiscGateway;
 
-struct _NMNDiscAddress {
+typedef struct _NMNDiscAddress {
     struct in6_addr address;
+    gint64          expiry_msec;
+    gint64          expiry_preferred_msec;
     guint8          dad_counter;
-    guint32         timestamp;
-    guint32         lifetime;
-    guint32         preferred;
-};
-typedef struct _NMNDiscAddress NMNDiscAddress;
+} NMNDiscAddress;
 
-struct _NMNDiscRoute {
+typedef struct _NMNDiscRoute {
     struct in6_addr    network;
-    guint8             plen;
     struct in6_addr    gateway;
-    guint32            timestamp;
-    guint32            lifetime;
+    gint64             expiry_msec;
     NMIcmpv6RouterPref preference;
-};
-typedef struct _NMNDiscRoute NMNDiscRoute;
+    guint8             plen;
+} NMNDiscRoute;
 
 typedef struct {
     struct in6_addr address;
-    guint32         timestamp;
-    guint32         lifetime;
+    gint64          expiry_msec;
 } NMNDiscDNSServer;
 
 typedef struct {
-    char *  domain;
-    guint32 timestamp;
-    guint32 lifetime;
+    char * domain;
+    gint64 expiry_msec;
 } NMNDiscDNSDomain;
 
 typedef enum {
@@ -112,15 +155,17 @@ typedef enum {
     NM_NDISC_NODE_TYPE_ROUTER,
 } NMNDiscNodeType;
 
-#define NM_NDISC_MAX_ADDRESSES_DEFAULT                16
-#define NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT         3 /* RFC4861 MAX_RTR_SOLICITATIONS */
-#define NM_NDISC_ROUTER_SOLICITATION_INTERVAL_DEFAULT 4 /* RFC4861 RTR_SOLICITATION_INTERVAL */
-#define NM_NDISC_ROUTER_ADVERTISEMENTS_DEFAULT        3 /* RFC4861 MAX_INITIAL_RTR_ADVERTISEMENTS */
-#define NM_NDISC_ROUTER_ADVERT_DELAY                  3 /* RFC4861 MIN_DELAY_BETWEEN_RAS */
-#define NM_NDISC_ROUTER_ADVERT_INITIAL_INTERVAL       16 /* RFC4861 MAX_INITIAL_RTR_ADVERT_INTERVAL */
-#define NM_NDISC_ROUTER_ADVERT_DELAY_MS               500 /* RFC4861 MAX_RA_DELAY_TIME */
-#define NM_NDISC_ROUTER_ADVERT_MAX_INTERVAL           600 /* RFC4861 MaxRtrAdvInterval default */
-#define NM_NDISC_ROUTER_LIFETIME                      900 /* 1.5 * NM_NDISC_ROUTER_ADVERT_MAX_INTERVAL */
+#define NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL  4 /* seconds */
+#define NM_NDISC_RFC4861_MAX_RTR_SOLICITATION_DELAY 1 /* seconds */
+
+#define NM_NDISC_MAX_ADDRESSES_DEFAULT          16
+#define NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT   3 /* RFC4861, MAX_RTR_SOLICITATIONS */
+#define NM_NDISC_ROUTER_ADVERTISEMENTS_DEFAULT  3 /* RFC4861, MAX_INITIAL_RTR_ADVERTISEMENTS */
+#define NM_NDISC_ROUTER_ADVERT_DELAY            3 /* RFC4861, MIN_DELAY_BETWEEN_RAS */
+#define NM_NDISC_ROUTER_ADVERT_INITIAL_INTERVAL 16 /* RFC4861, MAX_INITIAL_RTR_ADVERT_INTERVAL */
+#define NM_NDISC_ROUTER_ADVERT_DELAY_MS         500 /* RFC4861, MAX_RA_DELAY_TIME */
+#define NM_NDISC_ROUTER_ADVERT_MAX_INTERVAL     600 /* RFC4861, MaxRtrAdvInterval default */
+#define NM_NDISC_ROUTER_LIFETIME                900 /* 1.5 * NM_NDISC_ROUTER_ADVERT_MAX_INTERVAL */
 
 struct _NMNDiscPrivate;
 struct _NMNDiscDataInternal;
diff --git a/src/ndisc/tests/meson.build b/src/core/ndisc/tests/meson.build
index 349eebba..6a2e70f0 100644
--- a/src/ndisc/tests/meson.build
+++ b/src/core/ndisc/tests/meson.build
@@ -1,11 +1,11 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 test_unit = 'test-ndisc-fake'
 
 exe = executable(
   test_unit,
   test_unit + '.c',
-  dependencies: libnetwork_manager_test_dep,
+  dependencies: libNetworkManagerTest_dep,
   c_args: test_c_flags,
 )
 
@@ -21,6 +21,6 @@ test = 'test-ndisc-linux'
 exe = executable(
   test,
   test + '.c',
-  dependencies: libnetwork_manager_test_dep,
+  dependencies: libNetworkManagerTest_dep,
   c_args: test_c_flags,
 )
diff --git a/src/core/ndisc/tests/test-ndisc-fake.c b/src/core/ndisc/tests/test-ndisc-fake.c
new file mode 100644
index 00000000..f201bc56
--- /dev/null
+++ b/src/core/ndisc/tests/test-ndisc-fake.c
@@ -0,0 +1,657 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include <syslog.h>
+
+#include "ndisc/nm-ndisc.h"
+#include "ndisc/nm-fake-ndisc.h"
+
+#include "platform/nm-fake-platform.h"
+
+#include "nm-test-utils-core.h"
+
+/*****************************************************************************/
+
+static NMFakeNDisc *
+ndisc_new(void)
+{
+    NMNDisc *          ndisc;
+    const int          ifindex = 1;
+    const char *       ifname  = nm_platform_link_get_name(NM_PLATFORM_GET, ifindex);
+    NMUtilsIPv6IfaceId iid     = {};
+
+    ndisc        = nm_fake_ndisc_new(ifindex, ifname);
+    iid.id_u8[7] = 1;
+    nm_ndisc_set_iid(ndisc, iid);
+    g_assert(ndisc);
+
+    return NM_FAKE_NDISC(ndisc);
+}
+
+/*****************************************************************************/
+
+static void
+match_gateway(const NMNDiscData *rdata,
+              guint              idx,
+              const char *       addr,
+              gint64             expiry_msec,
+              NMIcmpv6RouterPref pref)
+{
+    const NMNDiscGateway *gw;
+
+    g_assert(rdata);
+    g_assert_cmpint(idx, <, rdata->gateways_n);
+    g_assert(rdata->gateways);
+
+    gw = &rdata->gateways[idx];
+
+    nmtst_assert_ip6_address(&gw->address, addr);
+    g_assert_cmpint(gw->expiry_msec, ==, expiry_msec);
+    g_assert_cmpint(gw->preference, ==, pref);
+}
+
+#define match_address(rdata, idx, addr, _expiry_msec, _expiry_preferred_msec)     \
+    G_STMT_START                                                                  \
+    {                                                                             \
+        const NMNDiscData *   _rdata = (rdata);                                   \
+        guint                 _idx   = (idx);                                     \
+        const NMNDiscAddress *_a;                                                 \
+                                                                                  \
+        g_assert(_rdata);                                                         \
+        g_assert_cmpint(_idx, <, _rdata->addresses_n);                            \
+        g_assert(_rdata->addresses);                                              \
+                                                                                  \
+        _a = &_rdata->addresses[_idx];                                            \
+                                                                                  \
+        nmtst_assert_ip6_address(&_a->address, (addr));                           \
+        g_assert_cmpint(_a->expiry_msec, ==, (_expiry_msec));                     \
+        g_assert_cmpint(_a->expiry_preferred_msec, ==, (_expiry_preferred_msec)); \
+    }                                                                             \
+    G_STMT_END
+
+#define match_route(rdata, idx, nw, pl, gw, _expiry_msec, pref) \
+    G_STMT_START                                                \
+    {                                                           \
+        const NMNDiscData * _rdata = (rdata);                   \
+        guint               _idx   = (idx);                     \
+        const NMNDiscRoute *_r;                                 \
+        int                 _plen = (pl);                       \
+                                                                \
+        g_assert(_rdata);                                       \
+        g_assert_cmpint(_idx, <, _rdata->routes_n);             \
+        g_assert(_rdata->routes);                               \
+        g_assert(_plen > 0 && _plen <= 128);                    \
+                                                                \
+        _r = &_rdata->routes[idx];                              \
+                                                                \
+        nmtst_assert_ip6_address(&_r->network, (nw));           \
+        g_assert_cmpint((int) _r->plen, ==, _plen);             \
+        nmtst_assert_ip6_address(&_r->gateway, (gw));           \
+        g_assert_cmpint(_r->expiry_msec, ==, (_expiry_msec));   \
+        g_assert_cmpint(_r->preference, ==, (pref));            \
+    }                                                           \
+    G_STMT_END
+
+static void
+match_dns_server(const NMNDiscData *rdata, guint idx, const char *addr, gint64 expiry_msec)
+{
+    const NMNDiscDNSServer *dns;
+
+    g_assert(rdata);
+    g_assert_cmpint(idx, <, rdata->dns_servers_n);
+    g_assert(rdata->dns_servers);
+
+    dns = &rdata->dns_servers[idx];
+
+    nmtst_assert_ip6_address(&dns->address, addr);
+    g_assert_cmpint(dns->expiry_msec, ==, expiry_msec);
+}
+
+static void
+match_dns_domain(const NMNDiscData *rdata, guint idx, const char *domain, gint64 expiry_msec)
+{
+    const NMNDiscDNSDomain *dns;
+
+    g_assert(rdata);
+    g_assert_cmpint(idx, <, rdata->dns_domains_n);
+    g_assert(rdata->dns_domains);
+
+    dns = &rdata->dns_domains[idx];
+
+    g_assert_cmpstr(dns->domain, ==, domain);
+    g_assert_cmpint(dns->expiry_msec, ==, expiry_msec);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+    GMainLoop *loop;
+    gint64     timestamp_msec_1;
+    guint      counter;
+    guint      rs_counter;
+    gint64     first_solicit_msec;
+    guint32    timeout_id;
+} TestData;
+
+/*****************************************************************************/
+
+static void
+test_simple_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data)
+{
+    NMNDiscConfigMap changed = changed_int;
+
+    switch (data->counter++) {
+    case 0:
+        g_assert_cmpint(changed,
+                        ==,
+                        NM_NDISC_CONFIG_DHCP_LEVEL | NM_NDISC_CONFIG_GATEWAYS
+                            | NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES
+                            | NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS
+                            | NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU);
+        g_assert_cmpint(rdata->dhcp_level, ==, NM_NDISC_DHCP_LEVEL_OTHERCONF);
+        match_gateway(rdata,
+                      0,
+                      "fe80::1",
+                      data->timestamp_msec_1 + 10000,
+                      NM_ICMPV6_ROUTER_PREF_MEDIUM);
+        match_address(rdata,
+                      0,
+                      "2001:db8:a:a::1",
+                      data->timestamp_msec_1 + 10000,
+                      data->timestamp_msec_1 + 10000);
+        match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 10);
+        match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp_msec_1 + 10000);
+        match_dns_domain(rdata, 0, "foobar.com", data->timestamp_msec_1 + 3500);
+
+        g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
+        break;
+    case 1:
+        g_main_loop_quit(data->loop);
+        break;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static void
+test_simple(void)
+{
+    nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
+    gs_unref_object NMFakeNDisc *ndisc      = ndisc_new();
+    const gint64                 now_msec   = nm_utils_get_monotonic_timestamp_msec();
+    TestData                     data       = {
+        .loop             = loop,
+        .timestamp_msec_1 = now_msec,
+    };
+    guint id;
+
+    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_OTHERCONF, 4, 1500);
+    g_assert(id);
+
+    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_MEDIUM);
+    nm_fake_ndisc_add_prefix(ndisc,
+                             id,
+                             "2001:db8:a:a::",
+                             64,
+                             "fe80::1",
+                             now_msec + 10000,
+                             now_msec + 10000,
+                             10);
+    nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec + 10000);
+    nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now_msec + 3500);
+
+    g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_simple_changed), &data);
+
+    nm_ndisc_start(NM_NDISC(ndisc));
+    nmtst_main_loop_run_assert(data.loop, 15000);
+    g_assert_cmpint(data.counter, ==, 2);
+}
+
+/*****************************************************************************/
+
+static void
+test_everything_rs_sent(NMNDisc *ndisc, TestData *data)
+{
+    g_assert_cmpint(data->rs_counter, ==, 0);
+    data->rs_counter++;
+}
+
+static void
+test_everything_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data)
+{
+    NMNDiscConfigMap changed = changed_int;
+
+    if (data->counter == 0) {
+        g_assert_cmpint(data->rs_counter, ==, 1);
+        g_assert_cmpint(changed,
+                        ==,
+                        NM_NDISC_CONFIG_DHCP_LEVEL | NM_NDISC_CONFIG_GATEWAYS
+                            | NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES
+                            | NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS
+                            | NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU);
+        match_gateway(rdata,
+                      0,
+                      "fe80::1",
+                      data->timestamp_msec_1 + 10000,
+                      NM_ICMPV6_ROUTER_PREF_MEDIUM);
+        match_address(rdata,
+                      0,
+                      "2001:db8:a:a::1",
+                      data->timestamp_msec_1 + 10000,
+                      data->timestamp_msec_1 + 10000);
+        match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 10);
+        match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp_msec_1 + 10000);
+        match_dns_domain(rdata, 0, "foobar.com", data->timestamp_msec_1 + 10000);
+    } else if (data->counter == 1) {
+        g_assert_cmpint(changed,
+                        ==,
+                        NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES
+                            | NM_NDISC_CONFIG_ROUTES | NM_NDISC_CONFIG_DNS_SERVERS
+                            | NM_NDISC_CONFIG_DNS_DOMAINS);
+
+        g_assert_cmpint(rdata->gateways_n, ==, 1);
+        match_gateway(rdata,
+                      0,
+                      "fe80::2",
+                      data->timestamp_msec_1 + 10000,
+                      NM_ICMPV6_ROUTER_PREF_MEDIUM);
+        g_assert_cmpint(rdata->addresses_n, ==, 2);
+        match_address(rdata,
+                      0,
+                      "2001:db8:a:a::1",
+                      data->timestamp_msec_1 + 10000,
+                      data->timestamp_msec_1);
+        match_address(rdata,
+                      1,
+                      "2001:db8:a:b::1",
+                      data->timestamp_msec_1 + 10000,
+                      data->timestamp_msec_1 + 10000);
+        g_assert_cmpint(rdata->routes_n, ==, 1);
+        match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 10000, 10);
+        g_assert_cmpint(rdata->dns_servers_n, ==, 1);
+        match_dns_server(rdata, 0, "2001:db8:c:c::2", data->timestamp_msec_1 + 10000);
+        g_assert_cmpint(rdata->dns_domains_n, ==, 1);
+        match_dns_domain(rdata, 0, "foobar2.com", data->timestamp_msec_1 + 10000);
+
+        g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
+        g_main_loop_quit(data->loop);
+    } else
+        g_assert_not_reached();
+
+    data->counter++;
+}
+
+static void
+test_everything(void)
+{
+    nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
+    gs_unref_object NMFakeNDisc *ndisc      = ndisc_new();
+    const gint64                 now_msec   = nm_utils_get_monotonic_timestamp_msec();
+    TestData                     data       = {
+        .loop             = loop,
+        .timestamp_msec_1 = now_msec,
+    };
+    guint id;
+
+    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
+    g_assert(id);
+    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_MEDIUM);
+    nm_fake_ndisc_add_prefix(ndisc,
+                             id,
+                             "2001:db8:a:a::",
+                             64,
+                             "fe80::1",
+                             now_msec + 10000,
+                             now_msec + 10000,
+                             10);
+    nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec + 10000);
+    nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now_msec + 10000);
+
+    /* expire everything from the first RA in the second */
+    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
+    g_assert(id);
+    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec, NM_ICMPV6_ROUTER_PREF_MEDIUM);
+    nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now_msec, now_msec, 0);
+    nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec);
+    nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now_msec);
+
+    /* and add some new stuff */
+    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_MEDIUM);
+    nm_fake_ndisc_add_prefix(ndisc,
+                             id,
+                             "2001:db8:a:b::",
+                             64,
+                             "fe80::2",
+                             now_msec + 10000,
+                             now_msec + 10000,
+                             10);
+    nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::2", now_msec + 10000);
+    nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar2.com", now_msec + 10000);
+
+    g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_everything_changed), &data);
+    g_signal_connect(ndisc, NM_FAKE_NDISC_RS_SENT, G_CALLBACK(test_everything_rs_sent), &data);
+
+    nm_ndisc_start(NM_NDISC(ndisc));
+    nmtst_main_loop_run_assert(data.loop, 15000);
+    g_assert_cmpint(data.counter, ==, 2);
+    g_assert_cmpint(data.rs_counter, ==, 1);
+}
+
+static void
+test_preference_order_cb(NMNDisc *          ndisc,
+                         const NMNDiscData *rdata,
+                         guint              changed_int,
+                         TestData *         data)
+{
+    NMNDiscConfigMap changed = changed_int;
+
+    if (data->counter == 1) {
+        g_assert_cmpint(changed,
+                        ==,
+                        NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES
+                            | NM_NDISC_CONFIG_ROUTES);
+
+        g_assert_cmpint(rdata->gateways_n, ==, 2);
+        match_gateway(rdata,
+                      0,
+                      "fe80::1",
+                      data->timestamp_msec_1 + 10000,
+                      NM_ICMPV6_ROUTER_PREF_HIGH);
+        match_gateway(rdata,
+                      1,
+                      "fe80::2",
+                      data->timestamp_msec_1 + 11000,
+                      NM_ICMPV6_ROUTER_PREF_LOW);
+        g_assert_cmpint(rdata->addresses_n, ==, 2);
+        match_address(rdata,
+                      0,
+                      "2001:db8:a:a::1",
+                      data->timestamp_msec_1 + 10000,
+                      data->timestamp_msec_1 + 10000);
+        match_address(rdata,
+                      1,
+                      "2001:db8:a:b::1",
+                      data->timestamp_msec_1 + 11000,
+                      data->timestamp_msec_1 + 10000);
+        g_assert_cmpint(rdata->routes_n, ==, 2);
+        match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 11000, 10);
+        match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 5);
+
+        g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
+        g_main_loop_quit(data->loop);
+    }
+
+    data->counter++;
+}
+
+static void
+test_preference_order(void)
+{
+    nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
+    gs_unref_object NMFakeNDisc *ndisc      = ndisc_new();
+    const gint64                 now_msec   = nm_utils_get_monotonic_timestamp_msec();
+    TestData                     data       = {
+        .loop             = loop,
+        .timestamp_msec_1 = now_msec,
+    };
+    guint id;
+
+    /* Test insertion order of gateways */
+
+    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
+    g_assert(id);
+    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_HIGH);
+    nm_fake_ndisc_add_prefix(ndisc,
+                             id,
+                             "2001:db8:a:a::",
+                             64,
+                             "fe80::1",
+                             now_msec + 10000,
+                             now_msec + 10000,
+                             5);
+
+    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
+    g_assert(id);
+    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", now_msec + 11000, NM_ICMPV6_ROUTER_PREF_LOW);
+    nm_fake_ndisc_add_prefix(ndisc,
+                             id,
+                             "2001:db8:a:b::",
+                             64,
+                             "fe80::2",
+                             now_msec + 11000,
+                             now_msec + 10000,
+                             10);
+
+    g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_preference_order_cb), &data);
+
+    nm_ndisc_start(NM_NDISC(ndisc));
+    nmtst_main_loop_run_assert(data.loop, 15000);
+    g_assert_cmpint(data.counter, ==, 2);
+}
+
+static void
+test_preference_changed_cb(NMNDisc *          ndisc,
+                           const NMNDiscData *rdata,
+                           guint              changed_int,
+                           TestData *         data)
+{
+    NMNDiscConfigMap changed = changed_int;
+
+    if (data->counter == 1) {
+        g_assert_cmpint(changed,
+                        ==,
+                        NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES
+                            | NM_NDISC_CONFIG_ROUTES);
+        g_assert_cmpint(rdata->gateways_n, ==, 2);
+        match_gateway(rdata,
+                      0,
+                      "fe80::2",
+                      data->timestamp_msec_1 + 11000,
+                      NM_ICMPV6_ROUTER_PREF_MEDIUM);
+        match_gateway(rdata,
+                      1,
+                      "fe80::1",
+                      data->timestamp_msec_1 + 10000,
+                      NM_ICMPV6_ROUTER_PREF_LOW);
+        g_assert_cmpint(rdata->addresses_n, ==, 2);
+        match_address(rdata,
+                      0,
+                      "2001:db8:a:a::1",
+                      data->timestamp_msec_1 + 10000,
+                      data->timestamp_msec_1 + 10000);
+        match_address(rdata,
+                      1,
+                      "2001:db8:a:b::1",
+                      data->timestamp_msec_1 + 11000,
+                      data->timestamp_msec_1 + 11000);
+        g_assert_cmpint(rdata->routes_n, ==, 2);
+        match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 11000, 10);
+        match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 10000, 5);
+    } else if (data->counter == 2) {
+        g_assert_cmpint(changed,
+                        ==,
+                        NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES
+                            | NM_NDISC_CONFIG_ROUTES);
+
+        g_assert_cmpint(rdata->gateways_n, ==, 2);
+        match_gateway(rdata,
+                      0,
+                      "fe80::1",
+                      data->timestamp_msec_1 + 12000,
+                      NM_ICMPV6_ROUTER_PREF_HIGH);
+        match_gateway(rdata,
+                      1,
+                      "fe80::2",
+                      data->timestamp_msec_1 + 11000,
+                      NM_ICMPV6_ROUTER_PREF_MEDIUM);
+        g_assert_cmpint(rdata->addresses_n, ==, 2);
+        match_address(rdata,
+                      0,
+                      "2001:db8:a:a::1",
+                      data->timestamp_msec_1 + 12000,
+                      data->timestamp_msec_1 + 12000);
+        match_address(rdata,
+                      1,
+                      "2001:db8:a:b::1",
+                      data->timestamp_msec_1 + 11000,
+                      data->timestamp_msec_1 + 11000);
+        g_assert_cmpint(rdata->routes_n, ==, 2);
+        match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp_msec_1 + 12000, 15);
+        match_route(rdata, 1, "2001:db8:a:b::", 64, "fe80::2", data->timestamp_msec_1 + 11000, 10);
+
+        g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
+        g_main_loop_quit(data->loop);
+    }
+
+    data->counter++;
+}
+
+static void
+test_preference_changed(void)
+{
+    nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
+    gs_unref_object NMFakeNDisc *ndisc      = ndisc_new();
+    const gint64                 now_msec   = nm_utils_get_monotonic_timestamp_msec();
+    TestData                     data       = {
+        .loop             = loop,
+        .timestamp_msec_1 = now_msec,
+    };
+    guint id;
+
+    /* Test that when a low-preference and medium gateway send advertisements,
+     * that if the low-preference gateway switches to high-preference, we do
+     * not get duplicates in the gateway list.
+     */
+
+    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
+    g_assert(id);
+    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_LOW);
+    nm_fake_ndisc_add_prefix(ndisc,
+                             id,
+                             "2001:db8:a:a::",
+                             64,
+                             "fe80::1",
+                             now_msec + 10000,
+                             now_msec + 10000,
+                             5);
+
+    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
+    g_assert(id);
+    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", now_msec + 11000, NM_ICMPV6_ROUTER_PREF_MEDIUM);
+    nm_fake_ndisc_add_prefix(ndisc,
+                             id,
+                             "2001:db8:a:b::",
+                             64,
+                             "fe80::2",
+                             now_msec + 11000,
+                             now_msec + 11000,
+                             10);
+
+    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
+    g_assert(id);
+    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 12000, NM_ICMPV6_ROUTER_PREF_HIGH);
+    nm_fake_ndisc_add_prefix(ndisc,
+                             id,
+                             "2001:db8:a:a::",
+                             64,
+                             "fe80::1",
+                             now_msec + 12000,
+                             now_msec + 12000,
+                             15);
+
+    g_signal_connect(ndisc,
+                     NM_NDISC_CONFIG_RECEIVED,
+                     G_CALLBACK(test_preference_changed_cb),
+                     &data);
+
+    nm_ndisc_start(NM_NDISC(ndisc));
+    nmtst_main_loop_run_assert(data.loop, 15000);
+    g_assert_cmpint(data.counter, ==, 3);
+}
+
+/*****************************************************************************/
+
+static void
+_test_dns_solicit_loop_changed(NMNDisc *          ndisc,
+                               const NMNDiscData *rdata,
+                               guint              changed_int,
+                               TestData *         data)
+{
+    data->counter++;
+}
+
+static void
+_test_dns_solicit_loop_rs_sent(NMFakeNDisc *ndisc, TestData *data)
+{
+    data->rs_counter++;
+}
+
+static void
+test_dns_solicit_loop(void)
+{
+    nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new(NULL, FALSE);
+    gs_unref_object NMFakeNDisc *ndisc      = ndisc_new();
+    const gint64                 now_msec   = nm_utils_get_monotonic_timestamp_msec();
+    TestData                     data       = {
+        .loop             = loop,
+        .timestamp_msec_1 = now_msec,
+    };
+    guint id;
+
+    /* Ensure that no solicitation loop happens when DNS servers or domains
+     * stop being sent in advertisements.  This can happen if two routers
+     * send RAs, but the one sending DNS info stops responding, or if one
+     * router removes the DNS info from the RA without zero-lifetiming them
+     * first.
+     */
+
+    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
+    g_assert(id);
+    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now_msec + 10000, NM_ICMPV6_ROUTER_PREF_LOW);
+    nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now_msec + 6000);
+
+    g_signal_connect(ndisc,
+                     NM_NDISC_CONFIG_RECEIVED,
+                     G_CALLBACK(_test_dns_solicit_loop_changed),
+                     &data);
+    g_signal_connect(ndisc,
+                     NM_FAKE_NDISC_RS_SENT,
+                     G_CALLBACK(_test_dns_solicit_loop_rs_sent),
+                     &data);
+
+    nm_ndisc_start(NM_NDISC(ndisc));
+    if (nmtst_main_loop_run(data.loop, 10000))
+        g_error("we expect to run the loop until timeout. What is wrong?");
+    g_assert_cmpint(data.counter, ==, 3);
+    g_assert_cmpint(data.rs_counter, ==, 1);
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE();
+
+int
+main(int argc, char **argv)
+{
+    nmtst_init_with_logging(&argc, &argv, NULL, "DEFAULT");
+
+    if (nmtst_test_quick()) {
+        g_print("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n",
+                g_get_prgname() ?: "test-ndisc-fake");
+        return g_test_run();
+    }
+
+    nm_fake_platform_setup();
+
+    g_test_add_func("/ndisc/simple", test_simple);
+    g_test_add_func("/ndisc/everything-changed", test_everything);
+    g_test_add_func("/ndisc/preference-order", test_preference_order);
+    g_test_add_func("/ndisc/preference-changed", test_preference_changed);
+    g_test_add_func("/ndisc/dns-solicit-loop", test_dns_solicit_loop);
+
+    return g_test_run();
+}
diff --git a/src/ndisc/tests/test-ndisc-linux.c b/src/core/ndisc/tests/test-ndisc-linux.c
index 7e5e258b..9a5df60b 100644
--- a/src/ndisc/tests/test-ndisc-linux.c
+++ b/src/core/ndisc/tests/test-ndisc-linux.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <syslog.h>
 
diff --git a/src/nm-act-request.c b/src/core/nm-act-request.c
index 67936ca0..defe3e8c 100644
--- a/src/nm-act-request.c
+++ b/src/core/nm-act-request.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2012 Red Hat, Inc.
  * Copyright (C) 2007 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-act-request.h"
 
@@ -22,14 +22,8 @@
 #include "nm-libnm-core-intern/nm-auth-subject.h"
 
 typedef struct {
-    char *table;
-    char *rule;
-} ShareRule;
-
-typedef struct {
-    CList    call_ids_lst_head;
-    gboolean shared;
-    GSList * share_rules;
+    CList              call_ids_lst_head;
+    NMUtilsShareRules *share_rules;
 } NMActRequestPrivate;
 
 struct _NMActRequest {
@@ -254,109 +248,32 @@ nm_act_request_clear_secrets(NMActRequest *self)
 
 /*****************************************************************************/
 
-static void
-clear_share_rules(NMActRequest *req)
-{
-    NMActRequestPrivate *priv = NM_ACT_REQUEST_GET_PRIVATE(req);
-    GSList *             iter;
-
-    for (iter = priv->share_rules; iter; iter = g_slist_next(iter)) {
-        ShareRule *rule = (ShareRule *) iter->data;
-
-        g_free(rule->table);
-        g_free(rule->rule);
-        g_free(rule);
-    }
-
-    g_slist_free(priv->share_rules);
-    priv->share_rules = NULL;
-}
-
-void
-nm_act_request_set_shared(NMActRequest *req, gboolean shared)
-{
-    NMActRequestPrivate *priv = NM_ACT_REQUEST_GET_PRIVATE(req);
-    GSList *             list, *iter;
-
-    g_return_if_fail(NM_IS_ACT_REQUEST(req));
-
-    NM_ACT_REQUEST_GET_PRIVATE(req)->shared = shared;
-
-    /* Tear the rules down in reverse order when sharing is stopped */
-    list = g_slist_copy(priv->share_rules);
-    if (!shared)
-        list = g_slist_reverse(list);
-
-    /* Send the rules to iptables */
-    for (iter = list; iter; iter = g_slist_next(iter)) {
-        ShareRule *        rule    = (ShareRule *) iter->data;
-        char *             envp[1] = {NULL};
-        gs_strfreev char **argv    = NULL;
-        gs_free char *     cmd     = NULL;
-
-        cmd = g_strdup_printf("%s --table %s %s %s",
-                              IPTABLES_PATH,
-                              rule->table,
-                              shared ? "--insert" : "--delete",
-                              rule->rule);
-        if (!cmd)
-            continue;
-
-        argv = g_strsplit(cmd, " ", 0);
-        if (argv && argv[0]) {
-            int     status;
-            GError *error = NULL;
-
-            nm_log_info(LOGD_SHARING, "Executing: %s", cmd);
-            if (!g_spawn_sync("/",
-                              argv,
-                              envp,
-                              G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
-                              NULL,
-                              NULL,
-                              NULL,
-                              NULL,
-                              &status,
-                              &error)) {
-                nm_log_warn(LOGD_SHARING, "Error executing command: %s", error->message);
-                g_clear_error(&error);
-            } else if (WEXITSTATUS(status)) {
-                nm_log_warn(LOGD_SHARING,
-                            "** Command returned exit status %d.",
-                            WEXITSTATUS(status));
-            }
-        }
-    }
-
-    g_slist_free(list);
-
-    /* Clear the share rule list when sharing is stopped */
-    if (!shared)
-        clear_share_rules(req);
-}
-
-gboolean
+NMUtilsShareRules *
 nm_act_request_get_shared(NMActRequest *req)
 {
     g_return_val_if_fail(NM_IS_ACT_REQUEST(req), FALSE);
 
-    return NM_ACT_REQUEST_GET_PRIVATE(req)->shared;
+    return NM_ACT_REQUEST_GET_PRIVATE(req)->share_rules;
 }
 
 void
-nm_act_request_add_share_rule(NMActRequest *req, const char *table, const char *table_rule)
+nm_act_request_set_shared(NMActRequest *req, NMUtilsShareRules *rules)
 {
     NMActRequestPrivate *priv = NM_ACT_REQUEST_GET_PRIVATE(req);
-    ShareRule *          rule;
 
     g_return_if_fail(NM_IS_ACT_REQUEST(req));
-    g_return_if_fail(table != NULL);
-    g_return_if_fail(table_rule != NULL);
 
-    rule              = g_malloc0(sizeof(ShareRule));
-    rule->table       = g_strdup(table);
-    rule->rule        = g_strdup(table_rule);
-    priv->share_rules = g_slist_prepend(priv->share_rules, rule);
+    if (priv->share_rules == rules)
+        return;
+
+    if (priv->share_rules) {
+        nm_utils_share_rules_apply(priv->share_rules, FALSE);
+        priv->share_rules = NULL;
+    }
+    if (rules) {
+        priv->share_rules = rules;
+        nm_utils_share_rules_apply(priv->share_rules, TRUE);
+    }
 }
 
 /*****************************************************************************/
@@ -558,24 +475,24 @@ nm_act_request_new(NMSettingsConnection * settings_connection,
     g_return_val_if_fail(NM_IS_DEVICE(device), NULL);
     g_return_val_if_fail(NM_IS_AUTH_SUBJECT(subject), NULL);
 
-    return (NMActRequest *) g_object_new(NM_TYPE_ACT_REQUEST,
-                                         NM_ACTIVE_CONNECTION_INT_APPLIED_CONNECTION,
-                                         applied_connection,
-                                         NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION,
-                                         settings_connection,
-                                         NM_ACTIVE_CONNECTION_INT_DEVICE,
-                                         device,
-                                         NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT,
-                                         specific_object,
-                                         NM_ACTIVE_CONNECTION_INT_SUBJECT,
-                                         subject,
-                                         NM_ACTIVE_CONNECTION_INT_ACTIVATION_TYPE,
-                                         (int) activation_type,
-                                         NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON,
-                                         (int) activation_reason,
-                                         NM_ACTIVE_CONNECTION_STATE_FLAGS,
-                                         (guint) initial_state_flags,
-                                         NULL);
+    return g_object_new(NM_TYPE_ACT_REQUEST,
+                        NM_ACTIVE_CONNECTION_INT_APPLIED_CONNECTION,
+                        applied_connection,
+                        NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION,
+                        settings_connection,
+                        NM_ACTIVE_CONNECTION_INT_DEVICE,
+                        device,
+                        NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT,
+                        specific_object,
+                        NM_ACTIVE_CONNECTION_INT_SUBJECT,
+                        subject,
+                        NM_ACTIVE_CONNECTION_INT_ACTIVATION_TYPE,
+                        (int) activation_type,
+                        NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON,
+                        (int) activation_reason,
+                        NM_ACTIVE_CONNECTION_STATE_FLAGS,
+                        (guint) initial_state_flags,
+                        NULL);
 }
 
 static void
@@ -589,10 +506,9 @@ dispose(GObject *object)
     c_list_for_each_entry_safe (call_id, call_id_safe, &priv->call_ids_lst_head, call_ids_lst)
         _do_cancel_secrets(self, call_id, TRUE);
 
-    /* Clear any share rules */
     if (priv->share_rules) {
-        nm_act_request_set_shared(NM_ACT_REQUEST(object), FALSE);
-        clear_share_rules(NM_ACT_REQUEST(object));
+        nm_utils_share_rules_apply(priv->share_rules, FALSE);
+        nm_clear_pointer(&priv->share_rules, nm_utils_share_rules_free);
     }
 
     G_OBJECT_CLASS(nm_act_request_parent_class)->dispose(object);
diff --git a/src/nm-act-request.h b/src/core/nm-act-request.h
index 65c8abf5..2a568430 100644
--- a/src/nm-act-request.h
+++ b/src/core/nm-act-request.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2012 Red Hat, Inc.
  */
@@ -36,11 +36,15 @@ NMSettingsConnection *nm_act_request_get_settings_connection(NMActRequest *req);
 
 NMConnection *nm_act_request_get_applied_connection(NMActRequest *req);
 
-gboolean nm_act_request_get_shared(NMActRequest *req);
+/*****************************************************************************/
 
-void nm_act_request_set_shared(NMActRequest *req, gboolean shared);
+struct _NMUtilsShareRules;
 
-void nm_act_request_add_share_rule(NMActRequest *req, const char *table, const char *rule);
+struct _NMUtilsShareRules *nm_act_request_get_shared(NMActRequest *req);
+
+void nm_act_request_set_shared(NMActRequest *req, struct _NMUtilsShareRules *rules);
+
+/*****************************************************************************/
 
 /* Secrets handling */
 
diff --git a/src/nm-active-connection.c b/src/core/nm-active-connection.c
index a8b8e2f3..3c8c472e 100644
--- a/src/nm-active-connection.c
+++ b/src/core/nm-active-connection.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-active-connection.h"
 
diff --git a/src/nm-active-connection.h b/src/core/nm-active-connection.h
index 9c39835d..15c68e6e 100644
--- a/src/nm-active-connection.h
+++ b/src/core/nm-active-connection.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2012 Red Hat, Inc.
  */
diff --git a/src/nm-audit-manager.c b/src/core/nm-audit-manager.c
index 527a3904..8f419472 100644
--- a/src/nm-audit-manager.c
+++ b/src/core/nm-audit-manager.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-audit-manager.h"
 
diff --git a/src/nm-audit-manager.h b/src/core/nm-audit-manager.h
index 65076513..fdabbf28 100644
--- a/src/nm-audit-manager.h
+++ b/src/core/nm-audit-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
diff --git a/src/nm-auth-manager.c b/src/core/nm-auth-manager.c
index 1cb4834a..9470838c 100644
--- a/src/nm-auth-manager.c
+++ b/src/core/nm-auth-manager.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-auth-manager.h"
 
diff --git a/src/nm-auth-manager.h b/src/core/nm-auth-manager.h
index 8dc821df..991083c1 100644
--- a/src/nm-auth-manager.h
+++ b/src/core/nm-auth-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
diff --git a/src/nm-auth-utils.c b/src/core/nm-auth-utils.c
index b4bbd824..12da5749 100644
--- a/src/nm-auth-utils.c
+++ b/src/core/nm-auth-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-auth-utils.h"
 
diff --git a/src/nm-auth-utils.h b/src/core/nm-auth-utils.h
index 309c5b51..999dacf5 100644
--- a/src/nm-auth-utils.h
+++ b/src/core/nm-auth-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 Red Hat, Inc.
  */
diff --git a/src/nm-checkpoint-manager.c b/src/core/nm-checkpoint-manager.c
index 5d36213b..b8c9101a 100644
--- a/src/nm-checkpoint-manager.c
+++ b/src/core/nm-checkpoint-manager.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-checkpoint-manager.h"
 
diff --git a/src/nm-checkpoint-manager.h b/src/core/nm-checkpoint-manager.h
index 051cfd52..7fe86d60 100644
--- a/src/nm-checkpoint-manager.h
+++ b/src/core/nm-checkpoint-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
diff --git a/src/nm-checkpoint.c b/src/core/nm-checkpoint.c
index b26f7e80..2088c247 100644
--- a/src/nm-checkpoint.c
+++ b/src/core/nm-checkpoint.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-checkpoint.h"
 
diff --git a/src/nm-checkpoint.h b/src/core/nm-checkpoint.h
index 7e2fff59..28c936fa 100644
--- a/src/nm-checkpoint.h
+++ b/src/core/nm-checkpoint.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
diff --git a/src/nm-config-data.c b/src/core/nm-config-data.c
index 4320e26b..c62c6773 100644
--- a/src/nm-config-data.c
+++ b/src/core/nm-config-data.c
@@ -1,18 +1,18 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  * Copyright (C) 2013 Thomas Bechtold <thomasbechtold@jpberlin.de>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-config-data.h"
 
 #include "nm-config.h"
 #include "devices/nm-device.h"
 #include "nm-core-internal.h"
-#include "nm-keyfile/nm-keyfile-internal.h"
-#include "nm-keyfile/nm-keyfile-utils.h"
+#include "nm-keyfile-internal.h"
+#include "nm-keyfile-utils.h"
 
 /*****************************************************************************/
 
@@ -1345,8 +1345,8 @@ global_dns_equal(NMGlobalDnsConfig *old, NMGlobalDnsConfig *new)
     if (!old || !new)
         return FALSE;
 
-    if (!_nm_utils_strv_equal(old->options, new->options)
-        || !_nm_utils_strv_equal(old->searches, new->searches))
+    if (!nm_utils_strv_equal(old->options, new->options)
+        || !nm_utils_strv_equal(old->searches, new->searches))
         return FALSE;
 
     if ((!old->domains || !new->domains) && old->domains != new->domains)
@@ -1364,8 +1364,8 @@ global_dns_equal(NMGlobalDnsConfig *old, NMGlobalDnsConfig *new)
         domain_old = value_old;
         domain_new = value_new;
 
-        if (!_nm_utils_strv_equal(domain_old->options, domain_new->options)
-            || !_nm_utils_strv_equal(domain_old->servers, domain_new->servers))
+        if (!nm_utils_strv_equal(domain_old->options, domain_new->options)
+            || !nm_utils_strv_equal(domain_old->servers, domain_new->servers))
             return FALSE;
     }
 
diff --git a/src/nm-config-data.h b/src/core/nm-config-data.h
index c15e6642..07cffb43 100644
--- a/src/nm-config-data.h
+++ b/src/core/nm-config-data.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
diff --git a/src/nm-config.c b/src/core/nm-config.c
index 8f46661d..55323919 100644
--- a/src/nm-config.c
+++ b/src/core/nm-config.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  * Copyright (C) 2013 Thomas Bechtold <thomasbechtold@jpberlin.de>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-config.h"
 
@@ -14,8 +14,8 @@
 #include "devices/nm-device.h"
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
-#include "nm-keyfile/nm-keyfile-internal.h"
-#include "nm-keyfile/nm-keyfile-utils.h"
+#include "nm-keyfile-internal.h"
+#include "nm-keyfile-utils.h"
 
 #define DEFAULT_CONFIG_MAIN_FILE     NMCONFDIR "/NetworkManager.conf"
 #define DEFAULT_CONFIG_DIR           NMCONFDIR "/conf.d"
@@ -885,6 +885,7 @@ static const ConfigGroup config_groups[] = {
                              NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND,
                              NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS,
                              NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK,
+                             NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT,
                              NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE,
                              NM_CONFIG_KEYFILE_KEY_STOP_MATCH, ),
     },
diff --git a/src/nm-config.h b/src/core/nm-config.h
index 9f866f29..6f3f514c 100644
--- a/src/nm-config.h
+++ b/src/core/nm-config.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  * Copyright (C) 2013 Thomas Bechtold <thomasbechtold@jpberlin.de>
@@ -88,6 +88,7 @@
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK \
     "wifi.scan-generate-mac-address-mask"
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT "carrier-wait-timeout"
+#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT "wifi.iwd.autoconnect"
 
 #define NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE "match-device"
 #define NM_CONFIG_KEYFILE_KEY_STOP_MATCH   "stop-match"
diff --git a/src/nm-connectivity.c b/src/core/nm-connectivity.c
index 3cd1d4b8..f847c8f4 100644
--- a/src/nm-connectivity.c
+++ b/src/core/nm-connectivity.c
@@ -1,11 +1,11 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Thomas Bechtold <thomasbechtold@jpberlin.de>
  * Copyright (C) 2011 Dan Williams <dcbw@redhat.com>
  * Copyright (C) 2016 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-connectivity.h"
 
diff --git a/src/nm-connectivity.h b/src/core/nm-connectivity.h
index 620eff35..c67668ce 100644
--- a/src/nm-connectivity.h
+++ b/src/core/nm-connectivity.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Thomas Bechtold <thomasbechtold@jpberlin.de>
  * Copyright (C) 2017 Red Hat, Inc.
diff --git a/src/nm-core-utils.c b/src/core/nm-core-utils.c
index 7adc011e..9075c30d 100644
--- a/src/nm-core-utils.c
+++ b/src/core/nm-core-utils.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2018 Red Hat, Inc.
  * Copyright (C) 2005 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-core-utils.h"
 
@@ -187,51 +187,46 @@ nm_utils_exp10(gint16 ex)
 
 /*****************************************************************************/
 
-/*
- * nm_ethernet_address_is_valid:
- * @addr: pointer to a binary or ASCII Ethernet address
- * @len: length of @addr, or -1 if @addr is ASCII
- *
- * Compares an Ethernet address against known invalid addresses.
-
- * Returns: %TRUE if @addr is a valid Ethernet address, %FALSE if it is not.
- */
 gboolean
-nm_ethernet_address_is_valid(gconstpointer addr, gssize len)
+nm_ether_addr_is_valid(const NMEtherAddr *addr)
 {
-    guint8 invalid_addr[4][ETH_ALEN] = {
+    static const guint8 invalid_addr[][ETH_ALEN] = {
         {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
         {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
         {0x44, 0x44, 0x44, 0x44, 0x44, 0x44},
         {0x00, 0x30, 0xb4, 0x00, 0x00, 0x00}, /* prism54 dummy MAC */
     };
-    guint8 addr_bin[ETH_ALEN];
-    guint  i;
+    int i;
 
-    if (!addr) {
-        g_return_val_if_fail(len == -1 || len == ETH_ALEN, FALSE);
+    if (!addr)
         return FALSE;
-    }
-
-    if (len == -1) {
-        if (!nm_utils_hwaddr_aton(addr, addr_bin, ETH_ALEN))
-            return FALSE;
-        addr = addr_bin;
-    } else if (len != ETH_ALEN)
-        g_return_val_if_reached(FALSE);
 
     /* Check for multicast address */
-    if ((((guint8 *) addr)[0]) & 0x01)
+    if (addr->ether_addr_octet[0] & 0x01u)
         return FALSE;
 
-    for (i = 0; i < G_N_ELEMENTS(invalid_addr); i++) {
-        if (nm_utils_hwaddr_matches(addr, ETH_ALEN, invalid_addr[i], ETH_ALEN))
+    for (i = 0; i < (int) G_N_ELEMENTS(invalid_addr); i++) {
+        if (memcmp(addr, invalid_addr[i], ETH_ALEN) == 0)
             return FALSE;
     }
 
     return TRUE;
 }
 
+gboolean
+nm_ether_addr_is_valid_str(const char *str)
+{
+    NMEtherAddr addr_bin;
+
+    if (!str)
+        return FALSE;
+
+    if (!nm_utils_hwaddr_aton(str, &addr_bin, ETH_ALEN))
+        return FALSE;
+
+    return nm_ether_addr_is_valid(&addr_bin);
+}
+
 /*****************************************************************************/
 
 void
@@ -4040,6 +4035,30 @@ nm_utils_create_dhcp_iaid(gboolean      legacy_unstable_byteorder,
     }
 }
 
+GBytes *
+nm_utils_dhcp_client_id_duid(guint32 iaid, const guint8 *duid, gsize duid_len)
+{
+    struct _nm_packed {
+        guint8  type;
+        guint32 iaid;
+        guint8  duid[];
+    } * client_id;
+    gsize total_size;
+
+    /* the @duid must include the 16 bit duid-type and the data (of max 128 bytes). */
+    g_return_val_if_fail(duid_len > 2 && duid_len < 128 + 2, NULL);
+    g_return_val_if_fail(duid, NULL);
+
+    total_size = sizeof(*client_id) + duid_len;
+
+    client_id = g_malloc(total_size);
+
+    client_id->type = 255;
+    unaligned_write_be32(&client_id->iaid, iaid);
+    memcpy(client_id->duid, duid, duid_len);
+    return g_bytes_new_take(client_id, total_size);
+}
+
 /**
  * nm_utils_dhcp_client_id_systemd_node_specific_full:
  * @iaid: the IAID (identity association identifier) in native byte order
@@ -4250,29 +4269,6 @@ nm_utils_g_value_set_strv(GValue *value, GPtrArray *strings)
 
 /*****************************************************************************/
 
-void
-nm_utils_ifname_cpy(char *dst, const char *name)
-{
-    int i;
-
-    g_return_if_fail(dst);
-    g_return_if_fail(name && name[0]);
-
-    nm_assert(nm_utils_ifname_valid_kernel(name, NULL));
-
-    /* ensures NUL padding of the entire IFNAMSIZ buffer. */
-
-    for (i = 0; i < (int) IFNAMSIZ && name[i] != '\0'; i++)
-        dst[i] = name[i];
-
-    nm_assert(name[i] == '\0');
-
-    for (; i < (int) IFNAMSIZ; i++)
-        dst[i] = '\0';
-}
-
-/*****************************************************************************/
-
 /**
  * Takes a pair @timestamp and @duration, and returns the remaining duration based
  * on the new timestamp @now.
@@ -4386,7 +4382,7 @@ nm_utils_dnsmasq_status_to_string(int status, char *dest, gsize size)
 }
 
 /**
- * nm_utils_get_reverse_dns_domains_ip4:
+ * nm_utils_get_reverse_dns_domains_ip_4:
  * @addr: IP address in network order
  * @plen: prefix length
  * @domains: array for results
@@ -4395,7 +4391,7 @@ nm_utils_dnsmasq_status_to_string(int status, char *dest, gsize size)
  * append them to @domains.
  */
 void
-nm_utils_get_reverse_dns_domains_ip4(guint32 addr, guint8 plen, GPtrArray *domains)
+nm_utils_get_reverse_dns_domains_ip_4(guint32 addr, guint8 plen, GPtrArray *domains)
 {
     guint32 ip, ip2, mask;
     guchar *p;
@@ -4434,7 +4430,7 @@ nm_utils_get_reverse_dns_domains_ip4(guint32 addr, guint8 plen, GPtrArray *domai
 }
 
 /**
- * nm_utils_get_reverse_dns_domains_ip6:
+ * nm_utils_get_reverse_dns_domains_ip_6:
  * @addr: IPv6 address
  * @plen: prefix length
  * @domains: array for results
@@ -4443,7 +4439,7 @@ nm_utils_get_reverse_dns_domains_ip4(guint32 addr, guint8 plen, GPtrArray *domai
  * append them to @domains.
  */
 void
-nm_utils_get_reverse_dns_domains_ip6(const struct in6_addr *ip, guint8 plen, GPtrArray *domains)
+nm_utils_get_reverse_dns_domains_ip_6(const struct in6_addr *ip, guint8 plen, GPtrArray *domains)
 {
     struct in6_addr addr;
     guint           nibbles, bits, entries;
diff --git a/src/nm-core-utils.h b/src/core/nm-core-utils.h
index 237574c5..5dc4d432 100644
--- a/src/nm-core-utils.h
+++ b/src/core/nm-core-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2016 Red Hat, Inc.
  * Copyright (C) 2005 - 2008 Novell, Inc.
@@ -54,7 +54,7 @@ void _nm_singleton_instance_register_destruction(GObject *instance);
         if (G_UNLIKELY(!singleton_instance)) {                                              \
             g_assert(!(_already_created_##GETTER) || (NM_DEFINE_SINGLETON_ALLOW_MULTIPLE)); \
             (_already_created_##GETTER) = TRUE;                                             \
-            singleton_instance          = (g_object_new(GTYPE, ##__VA_ARGS__, NULL));       \
+            singleton_instance          = g_object_new(GTYPE, ##__VA_ARGS__, NULL);         \
             g_assert(singleton_instance);                                                   \
             nm_singleton_instance_register();                                               \
             nm_log_dbg(LOGD_CORE,                                                           \
@@ -95,7 +95,8 @@ void _nm_singleton_instance_register_destruction(GObject *instance);
 
 /*****************************************************************************/
 
-gboolean nm_ethernet_address_is_valid(gconstpointer addr, gssize len);
+gboolean nm_ether_addr_is_valid(const NMEtherAddr *addr);
+gboolean nm_ether_addr_is_valid_str(const char *str);
 
 gconstpointer
 nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint8 plen);
@@ -209,7 +210,7 @@ nm_utils_ip6_route_metric_normalize(guint32 metric)
 static inline guint32
 nm_utils_ip_route_metric_normalize(int addr_family, guint32 metric)
 {
-    return addr_family == AF_INET6 ? nm_utils_ip6_route_metric_normalize(metric) : metric;
+    return NM_IS_IPv4(addr_family) ? metric : nm_utils_ip6_route_metric_normalize(metric);
 }
 
 static inline guint32
@@ -448,6 +449,8 @@ guint32 nm_utils_create_dhcp_iaid(gboolean      legacy_unstable_byteorder,
                                   const guint8 *interface_id,
                                   gsize         interface_id_len);
 
+GBytes *nm_utils_dhcp_client_id_duid(guint32 iaid, const guint8 *duid, gsize duid_len);
+
 GBytes *nm_utils_dhcp_client_id_systemd_node_specific_full(guint32       iaid,
                                                            const guint8 *machine_id,
                                                            gsize         machine_id_len);
@@ -498,8 +501,6 @@ void             _nm_utils_set_testing(NMUtilsTestFlags flags);
 
 void nm_utils_g_value_set_strv(GValue *value, GPtrArray *strings);
 
-void nm_utils_ifname_cpy(char *dst, const char *name);
-
 guint32
 nm_utils_lifetime_rebase_relative_time_on_now(guint32 timestamp, guint32 duration, gint32 now);
 
@@ -531,9 +532,21 @@ nm_utils_ip4_address_is_zeronet(in_addr_t network)
 
 const char *nm_utils_dnsmasq_status_to_string(int status, char *dest, gsize size);
 
-void nm_utils_get_reverse_dns_domains_ip4(guint32 ip, guint8 plen, GPtrArray *domains);
+void nm_utils_get_reverse_dns_domains_ip_4(guint32 ip, guint8 plen, GPtrArray *domains);
 void
-nm_utils_get_reverse_dns_domains_ip6(const struct in6_addr *ip, guint8 plen, GPtrArray *domains);
+nm_utils_get_reverse_dns_domains_ip_6(const struct in6_addr *ip, guint8 plen, GPtrArray *domains);
+
+static inline void
+nm_utils_get_reverse_dns_domains_ip(int           addr_family,
+                                    gconstpointer addr,
+                                    guint8        plen,
+                                    GPtrArray *   domains)
+{
+    if (NM_IS_IPv4(addr_family))
+        nm_utils_get_reverse_dns_domains_ip_4(*((const in_addr_t *) addr), plen, domains);
+    else
+        nm_utils_get_reverse_dns_domains_ip_6(addr, plen, domains);
+}
 
 struct stat;
 
@@ -546,7 +559,7 @@ char *   nm_utils_format_con_diff_for_audit(GHashTable *diff);
 /* this enum is compatible with ICMPV6_ROUTER_PREF_* (from <linux/icmpv6.h>,
  * the values for netlink attribute RTA_PREF) and "enum ndp_route_preference"
  * from <ndp.h>. */
-typedef enum {
+typedef enum _nm_packed {
     NM_ICMPV6_ROUTER_PREF_MEDIUM  = 0x0, /* ICMPV6_ROUTER_PREF_MEDIUM */
     NM_ICMPV6_ROUTER_PREF_LOW     = 0x3, /* ICMPV6_ROUTER_PREF_LOW */
     NM_ICMPV6_ROUTER_PREF_HIGH    = 0x1, /* ICMPV6_ROUTER_PREF_HIGH */
diff --git a/src/nm-dbus-manager.c b/src/core/nm-dbus-manager.c
index 00cb6a9f..4f34ee52 100644
--- a/src/nm-dbus-manager.c
+++ b/src/core/nm-dbus-manager.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2006 - 2013 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dbus-manager.h"
 
diff --git a/src/nm-dbus-manager.h b/src/core/nm-dbus-manager.h
index 5d20e0d5..06876af0 100644
--- a/src/nm-dbus-manager.h
+++ b/src/core/nm-dbus-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2006 - 2008 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
diff --git a/src/nm-dbus-object.c b/src/core/nm-dbus-object.c
index f49610ab..6f294fe2 100644
--- a/src/nm-dbus-object.c
+++ b/src/core/nm-dbus-object.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dbus-object.h"
 
diff --git a/src/nm-dbus-object.h b/src/core/nm-dbus-object.h
index e7deef53..d94bd5df 100644
--- a/src/nm-dbus-object.h
+++ b/src/core/nm-dbus-object.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/src/nm-dbus-utils.c b/src/core/nm-dbus-utils.c
index d5e8cb3a..5890bdf2 100644
--- a/src/nm-dbus-utils.c
+++ b/src/core/nm-dbus-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dbus-utils.h"
 
diff --git a/src/nm-dbus-utils.h b/src/core/nm-dbus-utils.h
index 48177973..501e1996 100644
--- a/src/nm-dbus-utils.h
+++ b/src/core/nm-dbus-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/src/nm-dcb.c b/src/core/nm-dcb.c
index d7c047b3..b3958caa 100644
--- a/src/nm-dcb.c
+++ b/src/core/nm-dcb.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <sys/wait.h>
 
diff --git a/src/nm-dcb.h b/src/core/nm-dcb.h
index 3024c30a..7cb5e7ef 100644
--- a/src/nm-dcb.h
+++ b/src/core/nm-dcb.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
diff --git a/src/core/nm-default-daemon.h b/src/core/nm-default-daemon.h
new file mode 100644
index 00000000..65ca4b54
--- /dev/null
+++ b/src/core/nm-default-daemon.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEFAULT_DAEMON_H__
+#define __NM_DEFAULT_DAEMON_H__
+
+/*****************************************************************************/
+
+#include "nm-glib-aux/nm-default-glib-i18n-prog.h"
+
+#undef NETWORKMANAGER_COMPILATION
+#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_DAEMON
+
+/*****************************************************************************/
+
+#include "nm-version.h"
+
+#include "nm-core-types.h"
+#include "nm-types.h"
+#include "nm-log-core/nm-logging.h"
+
+/*****************************************************************************/
+
+#endif /* __NM_DEFAULT_DAEMON_H__ */
diff --git a/src/nm-dhcp-config.c b/src/core/nm-dhcp-config.c
index 3788e4f3..718d3c90 100644
--- a/src/nm-dhcp-config.c
+++ b/src/core/nm-dhcp-config.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dhcp-config.h"
 
diff --git a/src/nm-dhcp-config.h b/src/core/nm-dhcp-config.h
index dbd0b5e7..9e68f2fb 100644
--- a/src/nm-dhcp-config.h
+++ b/src/core/nm-dhcp-config.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Red Hat, Inc.
  */
diff --git a/src/nm-dispatcher.c b/src/core/nm-dispatcher.c
index feab265d..0ab409f5 100644
--- a/src/nm-dispatcher.c
+++ b/src/core/nm-dispatcher.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2018 Red Hat, Inc.
  * Copyright (C) 2005 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dispatcher.h"
 
@@ -167,140 +167,131 @@ dump_proxy_to_props(NMProxyConfig *proxy, GVariantBuilder *builder)
 }
 
 static void
-dump_ip4_to_props(NMIP4Config *ip4, GVariantBuilder *builder)
+dump_ip_to_props(NMIPConfig *ip, GVariantBuilder *builder)
 {
-    GVariantBuilder             int_builder;
-    NMDedupMultiIter            ipconf_iter;
-    gboolean                    first;
-    guint                       n, i;
-    const NMPlatformIP4Address *addr;
-    const NMPlatformIP4Route *  route;
-    guint32                     array[4];
-
-    /* Addresses */
-    g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aau"));
-    first = TRUE;
-    nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &addr) {
-        const NMPObject *default_route;
-
-        array[0] = addr->address;
-        array[1] = addr->plen;
-        array[2] = (first && (default_route = nm_ip4_config_best_default_route_get(ip4)))
-                       ? NMP_OBJECT_CAST_IP4_ROUTE(default_route)->gateway
-                       : (guint32) 0;
-        g_variant_builder_add(
-            &int_builder,
-            "@au",
-            g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, array, 3, sizeof(guint32)));
-        first = FALSE;
+    const int        addr_family = nm_ip_config_get_addr_family(ip);
+    const int        IS_IPv4     = NM_IS_IPv4(addr_family);
+    const NMPObject *obj;
+    GVariantBuilder  int_builder;
+    NMDedupMultiIter ipconf_iter;
+    GVariant *       var1;
+    GVariant *       var2;
+    guint            n;
+    guint            i;
+    const NMPObject *default_route;
+
+    if (IS_IPv4)
+        g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aau"));
+    else
+        g_variant_builder_init(&int_builder, G_VARIANT_TYPE("a(ayuay)"));
+    default_route = nm_ip_config_best_default_route_get(ip);
+    if (IS_IPv4)
+        nm_ip_config_iter_ip4_address_init(&ipconf_iter, NM_IP4_CONFIG(ip));
+    else
+        nm_ip_config_iter_ip6_address_init(&ipconf_iter, NM_IP6_CONFIG(ip));
+    while (nm_platform_dedup_multi_iter_next_obj(&ipconf_iter,
+                                                 &obj,
+                                                 NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4))) {
+        const NMPlatformIPXAddress *addr = NMP_OBJECT_CAST_IPX_ADDRESS(obj);
+
+        if (IS_IPv4) {
+            guint32   array[3];
+            in_addr_t gw;
+
+            gw = 0u;
+            if (default_route) {
+                gw            = NMP_OBJECT_CAST_IP4_ROUTE(default_route)->gateway;
+                default_route = NULL;
+            }
+            array[0] = addr->a4.address;
+            array[1] = addr->a4.plen;
+            array[2] = gw;
+            g_variant_builder_add(
+                &int_builder,
+                "@au",
+                g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, array, 3, sizeof(guint32)));
+        } else {
+            const struct in6_addr *gw = &in6addr_any;
+
+            if (default_route) {
+                gw            = &NMP_OBJECT_CAST_IP6_ROUTE(default_route)->gateway;
+                default_route = NULL;
+            }
+            var1 = nm_g_variant_new_ay_in6addr(&addr->a6.address);
+            var2 = nm_g_variant_new_ay_in6addr(gw);
+            g_variant_builder_add(&int_builder, "(@ayu@ay)", var1, addr->a6.plen, var2);
+        }
     }
     g_variant_builder_add(builder, "{sv}", "addresses", g_variant_builder_end(&int_builder));
 
-    /* DNS servers */
-    g_variant_builder_init(&int_builder, G_VARIANT_TYPE("au"));
-    n = nm_ip4_config_get_num_nameservers(ip4);
-    for (i = 0; i < n; i++)
-        g_variant_builder_add(&int_builder, "u", nm_ip4_config_get_nameserver(ip4, i));
+    if (IS_IPv4)
+        g_variant_builder_init(&int_builder, G_VARIANT_TYPE("au"));
+    else
+        g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aay"));
+    n = nm_ip_config_get_num_nameservers(ip);
+    for (i = 0; i < n; i++) {
+        if (IS_IPv4) {
+            g_variant_builder_add(&int_builder,
+                                  "u",
+                                  nm_ip4_config_get_nameserver(NM_IP4_CONFIG(ip), i));
+        } else {
+            var1 = nm_g_variant_new_ay_in6addr(nm_ip6_config_get_nameserver(NM_IP6_CONFIG(ip), i));
+            g_variant_builder_add(&int_builder, "@ay", var1);
+        }
+    }
     g_variant_builder_add(builder, "{sv}", "nameservers", g_variant_builder_end(&int_builder));
 
-    /* Search domains */
     g_variant_builder_init(&int_builder, G_VARIANT_TYPE("as"));
-    n = nm_ip4_config_get_num_domains(ip4);
+    n = nm_ip_config_get_num_domains(ip);
     for (i = 0; i < n; i++)
-        g_variant_builder_add(&int_builder, "s", nm_ip4_config_get_domain(ip4, i));
+        g_variant_builder_add(&int_builder, "s", nm_ip_config_get_domain(ip, i));
     g_variant_builder_add(builder, "{sv}", "domains", g_variant_builder_end(&int_builder));
 
-    /* WINS servers */
-    g_variant_builder_init(&int_builder, G_VARIANT_TYPE("au"));
-    n = nm_ip4_config_get_num_wins(ip4);
-    for (i = 0; i < n; i++)
-        g_variant_builder_add(&int_builder, "u", nm_ip4_config_get_wins(ip4, i));
-    g_variant_builder_add(builder, "{sv}", "wins-servers", g_variant_builder_end(&int_builder));
-
-    /* Static routes */
-    g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aau"));
-    nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route) {
-        if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route))
-            continue;
-        array[0] = route->network;
-        array[1] = route->plen;
-        array[2] = route->gateway;
-        array[3] = route->metric;
-        g_variant_builder_add(
-            &int_builder,
-            "@au",
-            g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, array, 4, sizeof(guint32)));
+    if (IS_IPv4) {
+        g_variant_builder_init(&int_builder, G_VARIANT_TYPE("au"));
+        n = nm_ip4_config_get_num_wins(NM_IP4_CONFIG(ip));
+        for (i = 0; i < n; i++)
+            g_variant_builder_add(&int_builder, "u", nm_ip4_config_get_wins(NM_IP4_CONFIG(ip), i));
+        g_variant_builder_add(builder, "{sv}", "wins-servers", g_variant_builder_end(&int_builder));
     }
-    g_variant_builder_add(builder, "{sv}", "routes", g_variant_builder_end(&int_builder));
-}
 
-static void
-dump_ip6_to_props(NMIP6Config *ip6, GVariantBuilder *builder)
-{
-    GVariantBuilder             int_builder;
-    NMDedupMultiIter            ipconf_iter;
-    guint                       n, i;
-    gboolean                    first;
-    const NMPlatformIP6Address *addr;
-    const NMPlatformIP6Route *  route;
-    GVariant *                  ip, *gw;
-
-    /* Addresses */
-    g_variant_builder_init(&int_builder, G_VARIANT_TYPE("a(ayuay)"));
-
-    first = TRUE;
-    nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &addr) {
-        const NMPObject *default_route;
-
-        ip = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
-                                       &addr->address,
-                                       sizeof(struct in6_addr),
-                                       1);
-        gw = g_variant_new_fixed_array(
-            G_VARIANT_TYPE_BYTE,
-            (first && (default_route = nm_ip6_config_best_default_route_get(ip6)))
-                ? &NMP_OBJECT_CAST_IP6_ROUTE(default_route)->gateway
-                : &in6addr_any,
-            sizeof(struct in6_addr),
-            1);
-        g_variant_builder_add(&int_builder, "(@ayu@ay)", ip, addr->plen, gw);
-        first = FALSE;
-    }
-    g_variant_builder_add(builder, "{sv}", "addresses", g_variant_builder_end(&int_builder));
-
-    /* DNS servers */
-    g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aay"));
-    n = nm_ip6_config_get_num_nameservers(ip6);
-    for (i = 0; i < n; i++) {
-        ip = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
-                                       nm_ip6_config_get_nameserver(ip6, i),
-                                       sizeof(struct in6_addr),
-                                       1);
-        g_variant_builder_add(&int_builder, "@ay", ip);
-    }
-    g_variant_builder_add(builder, "{sv}", "nameservers", g_variant_builder_end(&int_builder));
-
-    /* Search domains */
-    g_variant_builder_init(&int_builder, G_VARIANT_TYPE("as"));
-    n = nm_ip6_config_get_num_domains(ip6);
-    for (i = 0; i < n; i++)
-        g_variant_builder_add(&int_builder, "s", nm_ip6_config_get_domain(ip6, i));
-    g_variant_builder_add(builder, "{sv}", "domains", g_variant_builder_end(&int_builder));
+    if (IS_IPv4)
+        g_variant_builder_init(&int_builder, G_VARIANT_TYPE("aau"));
+    else
+        g_variant_builder_init(&int_builder, G_VARIANT_TYPE("a(ayuayu)"));
+    if (IS_IPv4)
+        nm_ip_config_iter_ip4_route_init(&ipconf_iter, NM_IP4_CONFIG(ip));
+    else
+        nm_ip_config_iter_ip6_route_init(&ipconf_iter, NM_IP6_CONFIG(ip));
+    while (nm_platform_dedup_multi_iter_next_obj(&ipconf_iter,
+                                                 &obj,
+                                                 NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4))) {
+        const NMPlatformIPXRoute *route = NMP_OBJECT_CAST_IPX_ROUTE(obj);
 
-    /* Static routes */
-    g_variant_builder_init(&int_builder, G_VARIANT_TYPE("a(ayuayu)"));
-    nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route) {
         if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route))
             continue;
-        ip = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
-                                       &route->network,
-                                       sizeof(struct in6_addr),
-                                       1);
-        gw = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
-                                       &route->gateway,
-                                       sizeof(struct in6_addr),
-                                       1);
-        g_variant_builder_add(&int_builder, "(@ayu@ayu)", ip, route->plen, gw, route->metric);
+
+        if (IS_IPv4) {
+            guint32 array[4];
+
+            array[0] = route->r4.network;
+            array[1] = route->r4.plen;
+            array[2] = route->r4.gateway;
+            array[3] = route->r4.metric;
+            g_variant_builder_add(
+                &int_builder,
+                "@au",
+                g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, array, 4, sizeof(guint32)));
+        } else {
+            var1 = nm_g_variant_new_ay_in6addr(&route->r6.network);
+            var2 = nm_g_variant_new_ay_in6addr(&route->r6.gateway);
+            g_variant_builder_add(&int_builder,
+                                  "(@ayu@ayu)",
+                                  var1,
+                                  route->r6.plen,
+                                  var2,
+                                  route->r6.metric);
+        }
     }
     g_variant_builder_add(builder, "{sv}", "routes", g_variant_builder_end(&int_builder));
 }
@@ -350,11 +341,11 @@ fill_device_props(NMDevice *       device,
 
     ip4_config = nm_device_get_ip4_config(device);
     if (ip4_config)
-        dump_ip4_to_props(ip4_config, ip4_builder);
+        dump_ip_to_props(NM_IP_CONFIG(ip4_config), ip4_builder);
 
     ip6_config = nm_device_get_ip6_config(device);
     if (ip6_config)
-        dump_ip6_to_props(ip6_config, ip6_builder);
+        dump_ip_to_props(NM_IP_CONFIG(ip6_config), ip6_builder);
 
     dhcp_config = nm_device_get_dhcp_config(device, AF_INET);
     if (dhcp_config)
@@ -376,9 +367,9 @@ fill_vpn_props(NMProxyConfig *  proxy_config,
     if (proxy_config)
         dump_proxy_to_props(proxy_config, proxy_builder);
     if (ip4_config)
-        dump_ip4_to_props(ip4_config, ip4_builder);
+        dump_ip_to_props(NM_IP_CONFIG(ip4_config), ip4_builder);
     if (ip6_config)
-        dump_ip6_to_props(ip6_config, ip6_builder);
+        dump_ip_to_props(NM_IP_CONFIG(ip6_config), ip6_builder);
 }
 
 static const char *
diff --git a/src/nm-dispatcher.h b/src/core/nm-dispatcher.h
index 202f44b0..d588de4e 100644
--- a/src/nm-dispatcher.h
+++ b/src/core/nm-dispatcher.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2012 Red Hat, Inc.
  * Copyright (C) 2005 - 2008 Novell, Inc.
diff --git a/src/nm-firewall-manager.c b/src/core/nm-firewall-manager.c
index 7ae9ee89..8f476e51 100644
--- a/src/nm-firewall-manager.c
+++ b/src/core/nm-firewall-manager.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 - 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-firewall-manager.h"
 
diff --git a/src/nm-firewall-manager.h b/src/core/nm-firewall-manager.h
index 3b165854..1ce802b2 100644
--- a/src/nm-firewall-manager.h
+++ b/src/core/nm-firewall-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Red Hat, Inc.
  */
diff --git a/src/nm-hostname-manager.c b/src/core/nm-hostname-manager.c
index 413b756a..c99c8326 100644
--- a/src/nm-hostname-manager.c
+++ b/src/core/nm-hostname-manager.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-hostname-manager.h"
 
diff --git a/src/nm-hostname-manager.h b/src/core/nm-hostname-manager.h
index f88501f0..0fa13771 100644
--- a/src/nm-hostname-manager.h
+++ b/src/core/nm-hostname-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Søren Sandmann <sandmann@daimi.au.dk>
  * Dan Williams <dcbw@redhat.com>
diff --git a/src/nm-iface-helper.c b/src/core/nm-iface-helper.c
index e5d85967..b6e9981d 100644
--- a/src/nm-iface-helper.c
+++ b/src/core/nm-iface-helper.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <glib-unix.h>
 #include <getopt.h>
@@ -21,7 +21,7 @@
 #include "main-utils.h"
 #include "NetworkManagerUtils.h"
 #include "platform/nm-linux-platform.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "dhcp/nm-dhcp-manager.h"
 #include "ndisc/nm-ndisc.h"
 #include "ndisc/nm-lndp-ndisc.h"
diff --git a/src/nm-ip-config.c b/src/core/nm-ip-config.c
index d9bbf850..2426eb49 100644
--- a/src/nm-ip-config.c
+++ b/src/core/nm-ip-config.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-ip-config.h"
 
diff --git a/src/nm-ip-config.h b/src/core/nm-ip-config.h
index d5f4e5a4..8e1a5934 100644
--- a/src/nm-ip-config.h
+++ b/src/core/nm-ip-config.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2013 Red Hat, Inc.
  */
diff --git a/src/nm-ip4-config.c b/src/core/nm-ip4-config.c
index 9d2b459d..c49dfb4c 100644
--- a/src/nm-ip4-config.c
+++ b/src/core/nm-ip4-config.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-ip4-config.h"
 
@@ -17,7 +17,7 @@
 #include "nm-utils.h"
 #include "platform/nmp-object.h"
 #include "platform/nm-platform.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
 #include "nm-dbus-object.h"
@@ -365,7 +365,8 @@ nm_ip4_config_lookup_addresses(const NMIP4Config *self)
 void
 nm_ip_config_iter_ip4_address_init(NMDedupMultiIter *ipconf_iter, const NMIP4Config *self)
 {
-    g_return_if_fail(NM_IS_IP4_CONFIG(self));
+    nm_assert(NM_IS_IP4_CONFIG(self));
+
     nm_dedup_multi_iter_init(ipconf_iter, nm_ip4_config_lookup_addresses(self));
 }
 
@@ -383,6 +384,7 @@ void
 nm_ip_config_iter_ip4_route_init(NMDedupMultiIter *ipconf_iter, const NMIP4Config *self)
 {
     nm_assert(NM_IS_IP4_CONFIG(self));
+
     nm_dedup_multi_iter_init(ipconf_iter, nm_ip4_config_lookup_routes(self));
 }
 
@@ -3120,12 +3122,12 @@ NMIP4Config *
 nm_ip4_config_new(NMDedupMultiIndex *multi_idx, int ifindex)
 {
     g_return_val_if_fail(ifindex >= -1, NULL);
-    return (NMIP4Config *) g_object_new(NM_TYPE_IP4_CONFIG,
-                                        NM_IP4_CONFIG_MULTI_IDX,
-                                        multi_idx,
-                                        NM_IP4_CONFIG_IFINDEX,
-                                        ifindex,
-                                        NULL);
+    return g_object_new(NM_TYPE_IP4_CONFIG,
+                        NM_IP4_CONFIG_MULTI_IDX,
+                        multi_idx,
+                        NM_IP4_CONFIG_IFINDEX,
+                        ifindex,
+                        NULL);
 }
 
 static void
diff --git a/src/nm-ip4-config.h b/src/core/nm-ip4-config.h
index ccfca74c..a795e152 100644
--- a/src/nm-ip4-config.h
+++ b/src/core/nm-ip4-config.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2013 Red Hat, Inc.
  */
@@ -319,14 +319,21 @@ NM_IS_IP_CONFIG_ADDR_FAMILY(gconstpointer config, int addr_family)
         NM_CONSTCAST_FULL(NMIPConfig, (config), _configx, NMIP4Config, NMIP6Config); \
     })
 
+static inline gboolean
+nm_ip_config_is_ipv4(const NMIPConfig *config)
+{
+    if (NM_IP_CONFIG_GET_CLASS(config)->is_ipv4) {
+        nm_assert(NM_IS_IP4_CONFIG(config));
+        return TRUE;
+    }
+    nm_assert(NM_IS_IP6_CONFIG(config));
+    return FALSE;
+}
+
 static inline int
 nm_ip_config_get_addr_family(const NMIPConfig *config)
 {
-    if (NM_IS_IP4_CONFIG(config))
-        return AF_INET;
-    if (NM_IS_IP6_CONFIG(config))
-        return AF_INET6;
-    g_return_val_if_reached(AF_UNSPEC);
+    return nm_ip_config_is_ipv4(config) ? AF_INET : AF_INET6;
 }
 
 #define _NM_IP_CONFIG_DISPATCH(config, v4_func, v6_func, ...)       \
@@ -334,10 +341,9 @@ nm_ip_config_get_addr_family(const NMIPConfig *config)
     {                                                               \
         gconstpointer _config = (config);                           \
                                                                     \
-        if (NM_IS_IP4_CONFIG(_config)) {                            \
+        if (nm_ip_config_is_ipv4(_config)) {                        \
             return v4_func((NMIP4Config *) _config, ##__VA_ARGS__); \
         } else {                                                    \
-            nm_assert(NM_IS_IP6_CONFIG(_config));                   \
             return v6_func((NMIP6Config *) _config, ##__VA_ARGS__); \
         }                                                           \
     }                                                               \
@@ -348,10 +354,9 @@ nm_ip_config_get_addr_family(const NMIPConfig *config)
     {                                                              \
         gconstpointer _config = (config);                          \
                                                                    \
-        if (NM_IS_IP4_CONFIG(_config)) {                           \
+        if (nm_ip_config_is_ipv4(_config)) {                       \
             v4_func((NMIP4Config *) _config, ##__VA_ARGS__);       \
         } else {                                                   \
-            nm_assert(NM_IS_IP6_CONFIG(_config));                  \
             v6_func((NMIP6Config *) _config, ##__VA_ARGS__);       \
         }                                                          \
     }                                                              \
@@ -369,6 +374,38 @@ nm_ip_config_hash(const NMIPConfig *self, GChecksum *sum, gboolean dns_only)
     _NM_IP_CONFIG_DISPATCH_VOID(self, nm_ip4_config_hash, nm_ip6_config_hash, sum, dns_only);
 }
 
+static inline gconstpointer
+nm_ip_config_get_first_address(NMIPConfig *self)
+{
+    _NM_IP_CONFIG_DISPATCH(self, nm_ip4_config_get_first_address, nm_ip6_config_get_first_address);
+}
+
+static inline void
+nm_ip_config_iter_ip_address_init(NMDedupMultiIter *iter, const NMIPConfig *self)
+{
+    if (nm_ip_config_is_ipv4(self))
+        nm_ip_config_iter_ip4_address_init(iter, (const NMIP4Config *) self);
+    else
+        nm_ip_config_iter_ip6_address_init(iter, (const NMIP6Config *) self);
+}
+
+#define nm_ip_config_iter_ip_address_for_each(iter, self, address) \
+    for (nm_ip_config_iter_ip_address_init((iter), (self));        \
+         nm_platform_dedup_multi_iter_next_ip_address((iter), (address));)
+
+static inline void
+nm_ip_config_iter_ip_route_init(NMDedupMultiIter *iter, const NMIPConfig *self)
+{
+    if (nm_ip_config_is_ipv4(self))
+        nm_ip_config_iter_ip4_route_init(iter, (const NMIP4Config *) self);
+    else
+        nm_ip_config_iter_ip6_route_init(iter, (const NMIP6Config *) self);
+}
+
+#define nm_ip_config_iter_ip_route_for_each(iter, self, route) \
+    for (nm_ip_config_iter_ip_route_init((iter), (self));      \
+         nm_platform_dedup_multi_iter_next_ip_route((iter), (route));)
+
 static inline void
 nm_ip_config_add_address(NMIPConfig *self, const NMPlatformIPAddress *address)
 {
@@ -543,11 +580,9 @@ nm_ip_config_set_never_default(NMIPConfig *self, gboolean never_default)
         gpointer      _dst = (dst);                                                           \
         gconstpointer _src = (src);                                                           \
                                                                                               \
-        if (NM_IS_IP4_CONFIG(_dst)) {                                                         \
-            nm_assert(NM_IS_IP4_CONFIG(_src));                                                \
+        if (nm_ip_config_is_ipv4(_dst)) {                                                     \
             _return v4_func((NMIP4Config *) _dst, (const NMIP4Config *) _src, ##__VA_ARGS__); \
         } else {                                                                              \
-            nm_assert(NM_IS_IP6_CONFIG(_src));                                                \
             _return v6_func((NMIP6Config *) _dst, (const NMIP6Config *) _src, ##__VA_ARGS__); \
         }                                                                                     \
     }                                                                                         \
@@ -610,7 +645,8 @@ nm_ip_config_intersect_alloc(const NMIPConfig *a,
                              gboolean          intersect_routes,
                              guint32           default_route_metric_penalty)
 {
-    if (NM_IS_IP4_CONFIG(a)) {
+    if (nm_ip_config_is_ipv4(a)) {
+        nm_assert(NM_IS_IP4_CONFIG(a));
         nm_assert(NM_IS_IP4_CONFIG(b));
         return (NMIPConfig *) nm_ip4_config_intersect_alloc((const NMIP4Config *) a,
                                                             (const NMIP4Config *) b,
diff --git a/src/nm-ip6-config.c b/src/core/nm-ip6-config.c
index 407aa1d6..1f7def34 100644
--- a/src/nm-ip6-config.c
+++ b/src/core/nm-ip6-config.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-ip6-config.h"
 
@@ -18,7 +18,7 @@
 #include "nm-utils.h"
 #include "platform/nmp-object.h"
 #include "platform/nm-platform.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 #include "nm-ip4-config.h"
@@ -158,7 +158,8 @@ nm_ip6_config_lookup_addresses(const NMIP6Config *self)
 void
 nm_ip_config_iter_ip6_address_init(NMDedupMultiIter *ipconf_iter, const NMIP6Config *self)
 {
-    g_return_if_fail(NM_IS_IP6_CONFIG(self));
+    nm_assert(NM_IS_IP6_CONFIG(self));
+
     nm_dedup_multi_iter_init(ipconf_iter, nm_ip6_config_lookup_addresses(self));
 }
 
@@ -175,7 +176,8 @@ nm_ip6_config_lookup_routes(const NMIP6Config *self)
 void
 nm_ip_config_iter_ip6_route_init(NMDedupMultiIter *ipconf_iter, const NMIP6Config *self)
 {
-    g_return_if_fail(NM_IS_IP6_CONFIG(self));
+    nm_assert(NM_IS_IP6_CONFIG(self));
+
     nm_dedup_multi_iter_init(ipconf_iter, nm_ip6_config_lookup_routes(self));
 }
 
@@ -1495,6 +1497,7 @@ nm_ip6_config_reset_addresses_ndisc(NMIP6Config *         self,
     NMIP6ConfigPrivate *priv;
     guint               i;
     gboolean            changed = FALSE;
+    gint32              base_time_sec;
 
     g_return_if_fail(NM_IS_IP6_CONFIG(self));
 
@@ -1502,6 +1505,16 @@ nm_ip6_config_reset_addresses_ndisc(NMIP6Config *         self,
 
     g_return_if_fail(priv->ifindex > 0);
 
+    /* the base-timestamp doesn't matter it's only an anchor for the
+     * expiry. However, try to re-use the same base-time for a while
+     * by rounding it to 10000 seconds.
+     *
+     * That is because we deduplicate and NMPlatformIP6Address instances
+     * so using the same timestamps is preferable. */
+    base_time_sec = nm_utils_get_monotonic_timestamp_sec();
+    base_time_sec = (base_time_sec / 10000) * 10000;
+    base_time_sec = NM_MAX(1, base_time_sec);
+
     nm_dedup_multi_index_dirty_set_idx(priv->multi_idx, &priv->idx_ip6_addresses);
 
     for (i = 0; i < addresses_n; i++) {
@@ -1514,9 +1527,13 @@ nm_ip6_config_reset_addresses_ndisc(NMIP6Config *         self,
         a->ifindex     = priv->ifindex;
         a->address     = ndisc_addr->address;
         a->plen        = plen;
-        a->timestamp   = ndisc_addr->timestamp;
-        a->lifetime    = ndisc_addr->lifetime;
-        a->preferred   = MIN(ndisc_addr->lifetime, ndisc_addr->preferred);
+        a->timestamp   = base_time_sec,
+        a->lifetime    = _nm_ndisc_lifetime_from_expiry(((gint64) base_time_sec) * 1000,
+                                                     ndisc_addr->expiry_msec,
+                                                     TRUE),
+        a->preferred   = _nm_ndisc_lifetime_from_expiry(((gint64) base_time_sec) * 1000,
+                                                      ndisc_addr->expiry_preferred_msec,
+                                                      TRUE),
         a->addr_source = NM_IP_CONFIG_SOURCE_NDISC;
         a->n_ifa_flags = ifa_flags;
 
@@ -2554,12 +2571,12 @@ NMIP6Config *
 nm_ip6_config_new(NMDedupMultiIndex *multi_idx, int ifindex)
 {
     g_return_val_if_fail(ifindex >= -1, NULL);
-    return (NMIP6Config *) g_object_new(NM_TYPE_IP6_CONFIG,
-                                        NM_IP6_CONFIG_MULTI_IDX,
-                                        multi_idx,
-                                        NM_IP6_CONFIG_IFINDEX,
-                                        ifindex,
-                                        NULL);
+    return g_object_new(NM_TYPE_IP6_CONFIG,
+                        NM_IP6_CONFIG_MULTI_IDX,
+                        multi_idx,
+                        NM_IP6_CONFIG_IFINDEX,
+                        ifindex,
+                        NULL);
 }
 
 NMIP6Config *
diff --git a/src/nm-ip6-config.h b/src/core/nm-ip6-config.h
index a5a28a1a..398186da 100644
--- a/src/nm-ip6-config.h
+++ b/src/core/nm-ip6-config.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2013 Red Hat, Inc.
  */
diff --git a/src/nm-keep-alive.c b/src/core/nm-keep-alive.c
index 2b6e19e8..197e30ea 100644
--- a/src/nm-keep-alive.c
+++ b/src/core/nm-keep-alive.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-keep-alive.h"
 
diff --git a/src/nm-keep-alive.h b/src/core/nm-keep-alive.h
index 98c13273..2c2b4faa 100644
--- a/src/nm-keep-alive.h
+++ b/src/core/nm-keep-alive.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/src/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index 3cc5d670..5f9af7ac 100644
--- a/src/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -1,6 +1,6 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-l3-config-data.h"
 
@@ -11,7 +11,7 @@
 #include "nm-glib-aux/nm-enum-utils.h"
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "platform/nmp-object.h"
 #include "NetworkManagerUtils.h"
 
@@ -385,8 +385,10 @@ nm_l3_config_data_log(const NML3ConfigData *self,
         const NMPObject *obj;
 
         i = 0;
-        nm_l3_config_data_iter_obj_for_each(&iter, self, &obj, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4))
-        {
+        nm_l3_config_data_iter_obj_for_each (&iter,
+                                             self,
+                                             &obj,
+                                             NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
             _L("address%c[%u]: %s",
                nm_utils_addr_family_to_char(addr_family),
                i,
@@ -407,8 +409,7 @@ nm_l3_config_data_log(const NML3ConfigData *self,
         }
 
         i = 0;
-        nm_l3_config_data_iter_obj_for_each(&iter, self, &obj, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4))
-        {
+        nm_l3_config_data_iter_obj_for_each (&iter, self, &obj, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
             _L("route%c[%u]: %s%s",
                nm_utils_addr_family_to_char(addr_family),
                i,
@@ -586,9 +587,7 @@ _route_valid_6(const NMPlatformIP6Route *r)
 static gboolean
 _route_valid(int addr_family, gconstpointer r)
 {
-    nm_assert_addr_family(addr_family);
-
-    return addr_family == AF_INET ? _route_valid_4(r) : _route_valid_6(r);
+    return NM_IS_IPv4(addr_family) ? _route_valid_4(r) : _route_valid_6(r);
 }
 
 static gboolean
@@ -914,8 +913,7 @@ nm_l3_config_data_has_routes_with_type_local(const NML3ConfigData *self, int add
     }
 
     val = FALSE;
-    nm_l3_config_data_iter_obj_for_each(&iter, self, &obj, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4))
-    {
+    nm_l3_config_data_iter_obj_for_each (&iter, self, &obj, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
         if (NMP_OBJECT_CAST_IP_ROUTE(obj)->type_coerced
             == nm_platform_route_type_coerce(RTN_LOCAL)) {
             val = TRUE;
@@ -989,8 +987,7 @@ nm_l3_config_data_get_first_obj(const NML3ConfigData *self,
 
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
 
-    nm_l3_config_data_iter_obj_for_each(&iter, self, &obj, obj_type)
-    {
+    nm_l3_config_data_iter_obj_for_each (&iter, self, &obj, obj_type) {
         if (!predicate || predicate(obj))
             return obj;
     }
@@ -1205,8 +1202,7 @@ nm_l3_config_data_add_address_full(NML3ConfigData *           self,
     nm_assert(!obj_new || NMP_OBJECT_GET_ADDR_FAMILY(obj_new) == addr_family);
 
     changed = _l3_config_data_add_obj(self->multi_idx,
-                                      addr_family == AF_INET ? &self->idx_addresses_4
-                                                             : &self->idx_addresses_6,
+                                      &self->idx_addresses_x[NM_IS_IPv4(addr_family)],
                                       self->ifindex,
                                       obj_new,
                                       (const NMPlatformObject *) pl_new,
@@ -1253,7 +1249,7 @@ nm_l3_config_data_add_route_full(NML3ConfigData *         self,
     else
         self->has_routes_with_type_local_6_set = FALSE;
     if (_l3_config_data_add_obj(self->multi_idx,
-                                addr_family == AF_INET ? &self->idx_routes_4 : &self->idx_routes_6,
+                                &self->idx_routes_x[NM_IS_IPv4(addr_family)],
                                 self->ifindex,
                                 obj_new,
                                 (const NMPlatformObject *) pl_new,
@@ -1880,11 +1876,10 @@ _data_get_direct_route_for_host(const NML3ConfigData *self,
     if (nm_ip_addr_is_null(addr_family, host))
         return NULL;
 
-    nm_l3_config_data_iter_obj_for_each(&ipconf_iter,
-                                        self,
-                                        &item_obj,
-                                        NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4))
-    {
+    nm_l3_config_data_iter_obj_for_each (&ipconf_iter,
+                                         self,
+                                         &item_obj,
+                                         NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
         const NMPlatformIPXRoute *item = NMP_OBJECT_CAST_IPX_ROUTE(item_obj);
 
         if (nm_ip_addr_is_null(addr_family,
@@ -1941,8 +1936,7 @@ nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, gboolea
      * As we don't do that for IPv4 and manual IPv6 addresses. Add them here
      * as dependent routes. */
 
-    nm_l3_config_data_iter_obj_for_each(&iter, self, &my_addr_obj, NMP_OBJECT_TYPE_IP4_ADDRESS)
-    {
+    nm_l3_config_data_iter_obj_for_each (&iter, self, &my_addr_obj, NMP_OBJECT_TYPE_IP4_ADDRESS) {
         const NMPlatformIP4Address *const my_addr = NMP_OBJECT_CAST_IP4_ADDRESS(my_addr_obj);
         in_addr_t                         network_4;
         NMPlatformIPXRoute                rx;
@@ -2033,11 +2027,10 @@ nm_l3_config_data_add_dependent_routes(NML3ConfigData *self,
         nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx);
     }
 
-    nm_l3_config_data_iter_obj_for_each(&iter,
-                                        self,
-                                        &my_addr_obj,
-                                        NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4))
-    {
+    nm_l3_config_data_iter_obj_for_each (&iter,
+                                         self,
+                                         &my_addr_obj,
+                                         NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
         const NMPlatformIPXAddress *const my_addr = NMP_OBJECT_CAST_IPX_ADDRESS(my_addr_obj);
 
         if (my_addr->ax.external)
@@ -2152,11 +2145,10 @@ nm_l3_config_data_add_dependent_routes(NML3ConfigData *self,
         }
     }
 
-    nm_l3_config_data_iter_obj_for_each(&iter,
-                                        self,
-                                        &my_route_obj,
-                                        NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4))
-    {
+    nm_l3_config_data_iter_obj_for_each (&iter,
+                                         self,
+                                         &my_route_obj,
+                                         NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
         const NMPlatformIPXRoute *my_route = NMP_OBJECT_CAST_IPX_ROUTE(my_route_obj);
         NMPObject *               new_route;
         NMPlatformIPXRoute *      new_r;
@@ -2547,22 +2539,38 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
         const NML3ConfigDatFlags has_dns_priority_flag =
             NM_L3_CONFIG_DAT_FLAGS_HAS_DNS_PRIORITY(IS_IPv4);
 
-        nm_l3_config_data_iter_obj_for_each(&iter, src, &obj, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4))
-        {
-            if (hook_add_addr && !hook_add_addr(src, obj, hook_user_data))
+        nm_l3_config_data_iter_obj_for_each (&iter,
+                                             src,
+                                             &obj,
+                                             NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
+            NMPlatformIPXAddress       addr_stack;
+            const NMPlatformIPAddress *addr             = NULL;
+            NMTernary                  ip4acd_not_ready = NM_TERNARY_DEFAULT;
+
+            if (hook_add_addr && !hook_add_addr(src, obj, &ip4acd_not_ready, hook_user_data))
                 continue;
 
+            if (IS_IPv4 && ip4acd_not_ready != NM_TERNARY_DEFAULT
+                && (!!ip4acd_not_ready) != NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) {
+                addr_stack.a4                  = *NMP_OBJECT_CAST_IP4_ADDRESS(obj);
+                addr_stack.a4.ip4acd_not_ready = (!!ip4acd_not_ready);
+                addr                           = &addr_stack.ax;
+            } else
+                nm_assert(IS_IPv4 || ip4acd_not_ready == NM_TERNARY_DEFAULT);
+
             nm_l3_config_data_add_address_full(self,
                                                addr_family,
-                                               obj,
-                                               NULL,
+                                               addr ? NULL : obj,
+                                               addr,
                                                NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE,
                                                NULL);
         }
 
         if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES)) {
-            nm_l3_config_data_iter_obj_for_each(&iter, src, &obj, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4))
-            {
+            nm_l3_config_data_iter_obj_for_each (&iter,
+                                                 src,
+                                                 &obj,
+                                                 NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
                 const NMPlatformIPRoute *r_src = NMP_OBJECT_CAST_IP_ROUTE(obj);
                 NMPlatformIPXRoute       r;
 
@@ -2589,7 +2597,7 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
                 if (r_src->metric_any) {
                     _ensure_r();
                     r.rx.metric_any = FALSE;
-                    r.rx.metric_any = default_route_metric_x[IS_IPv4];
+                    r.rx.metric = nm_add_clamped_u32(r.rx.metric, default_route_metric_x[IS_IPv4]);
                 }
 
                 if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r_src)) {
@@ -2655,8 +2663,7 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
     if (self->llmnr == NM_SETTING_CONNECTION_LLMNR_DEFAULT)
         self->llmnr = src->llmnr;
 
-    if (self->metered == NM_TERNARY_DEFAULT)
-        self->metered = src->metered;
+    self->metered = NM_MAX((NMTernary) self->metered, (NMTernary) src->metered);
 
     if (self->ip6_privacy == NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN)
         self->ip6_privacy = src->ip6_privacy;
diff --git a/src/nm-l3-config-data.h b/src/core/nm-l3-config-data.h
index 81dae7de..b3d9cd32 100644
--- a/src/nm-l3-config-data.h
+++ b/src/core/nm-l3-config-data.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #ifndef __NM_L3_CONFIG_DATA_H__
 #define __NM_L3_CONFIG_DATA_H__
@@ -141,6 +141,7 @@ NML3ConfigData *nm_l3_config_data_new_from_platform(NMDedupMultiIndex *       mu
 
 typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData *l3cd,
                                               const NMPObject *     obj,
+                                              NMTernary *           out_ip4acd_not_ready,
                                               gpointer              user_data);
 
 void nm_l3_config_data_merge(NML3ConfigData *      self,
@@ -222,21 +223,13 @@ const NMDedupMultiHeadEntry *nm_l3_config_data_lookup_objs(const NML3ConfigData
 static inline const NMDedupMultiHeadEntry *
 nm_l3_config_data_lookup_addresses(const NML3ConfigData *self, int addr_family)
 {
-    nm_assert_addr_family(addr_family);
-
-    return nm_l3_config_data_lookup_objs(self,
-                                         addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ADDRESS
-                                                                : NMP_OBJECT_TYPE_IP6_ADDRESS);
+    return nm_l3_config_data_lookup_objs(self, NMP_OBJECT_TYPE_IP_ADDRESS(NM_IS_IPv4(addr_family)));
 }
 
 static inline const NMDedupMultiHeadEntry *
 nm_l3_config_data_lookup_routes(const NML3ConfigData *self, int addr_family)
 {
-    nm_assert_addr_family(addr_family);
-
-    return nm_l3_config_data_lookup_objs(self,
-                                         addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ROUTE
-                                                                : NMP_OBJECT_TYPE_IP6_ROUTE);
+    return nm_l3_config_data_lookup_objs(self, NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)));
 }
 
 #define nm_l3_config_data_iter_obj_for_each(iter, self, obj, type)                        \
diff --git a/src/core/nm-l3-ipv4ll.c b/src/core/nm-l3-ipv4ll.c
new file mode 100644
index 00000000..28ceb39e
--- /dev/null
+++ b/src/core/nm-l3-ipv4ll.c
@@ -0,0 +1,1059 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-l3-ipv4ll.h"
+
+#include <net/if.h>
+#include <linux/if_ether.h>
+
+#include "n-acd/src/n-acd.h"
+#include "nm-core-utils.h"
+
+#define ADDR_IPV4LL_PREFIX_LEN 16
+
+#define TIMED_OUT_TIME_FACTOR 5u
+
+/*****************************************************************************/
+
+typedef enum {
+    TIMED_OUT_STATE_IS_NOT_TIMED_OUT,
+    TIMED_OUT_STATE_IS_TIMED_OUT,
+    TIMED_OUT_STATE_HAVE_TIMER_RUNNING,
+} TimedOutState;
+
+struct _NML3IPv4LLRegistration {
+    NML3IPv4LL *self;
+    CList       reg_lst;
+    guint       timeout_msec;
+};
+
+G_STATIC_ASSERT(G_STRUCT_OFFSET(NML3IPv4LLRegistration, self) == 0);
+
+struct _NML3IPv4LL {
+    NML3Cfg *                l3cfg;
+    int                      ref_count;
+    in_addr_t                addr;
+    guint                    reg_timeout_msec;
+    CList                    reg_lst_head;
+    NML3CfgCommitTypeHandle *l3cfg_commit_handle;
+    GSource *                state_change_on_idle_source;
+    GSource *                timed_out_source;
+    const NML3ConfigData *   l3cd;
+    const NMPObject *        plobj;
+    struct {
+        nm_le64_t value;
+        nm_le64_t generation;
+    } seed;
+    gint64          timed_out_expiry_msec;
+    gulong          l3cfg_signal_notify_id;
+    NML3IPv4LLState state;
+    NMEtherAddr     seed_mac;
+    NMEtherAddr     mac;
+    bool            seed_set : 1;
+    bool            mac_set : 1;
+    bool            notify_on_idle : 1;
+    bool            reg_changed : 1;
+    bool            l3cd_timeout_msec_changed : 1;
+
+    /* not yet used. */
+    bool seed_reset_generation : 1;
+};
+
+G_STATIC_ASSERT(G_STRUCT_OFFSET(NML3IPv4LL, ref_count) == sizeof(gpointer));
+
+#define L3CD_TAG(self) (&(((const char *) self)[1]))
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_IP4
+#define _NMLOG_PREFIX_NAME "ipv4ll"
+#define _NMLOG(level, ...)                                                             \
+    G_STMT_START                                                                       \
+    {                                                                                  \
+        nm_log((level),                                                                \
+               (_NMLOG_DOMAIN),                                                        \
+               NULL,                                                                   \
+               NULL,                                                                   \
+               _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT                        \
+                                  ",ifindex=%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+               NM_HASH_OBFUSCATE_PTR(self),                                            \
+               nm_l3cfg_get_ifindex((self)->l3cfg) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    }                                                                                  \
+    G_STMT_END
+
+/*****************************************************************************/
+
+static void _ipv4ll_state_change_on_idle(NML3IPv4LL *self);
+
+static void _ipv4ll_state_change(NML3IPv4LL *self, gboolean is_on_idle_handler);
+
+static void _ipv4ll_set_timed_out_update(NML3IPv4LL *self, TimedOutState new_state);
+
+/*****************************************************************************/
+
+NM_UTILS_ENUM2STR_DEFINE(nm_l3_ipv4ll_state_to_string,
+                         NML3IPv4LLState,
+                         NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_UNKNOWN, "unknown"),
+                         NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_DISABLED, "disabled"),
+                         NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_WAIT_FOR_LINK, "wait-for-link"),
+                         NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_EXTERNAL, "external"),
+                         NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_PROBING, "probing"),
+                         NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_DEFENDING, "defending"),
+                         NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_READY, "ready"), );
+
+/*****************************************************************************/
+
+#define _ASSERT(self)                                                                        \
+    G_STMT_START                                                                             \
+    {                                                                                        \
+        NML3IPv4LL *const _self = (self);                                                    \
+                                                                                             \
+        nm_assert(NM_IS_L3_IPV4LL(_self));                                                   \
+        if (NM_MORE_ASSERTS > 5) {                                                           \
+            nm_assert(_self->addr == 0u || nm_utils_ip4_address_is_link_local(_self->addr)); \
+            nm_assert(!_self->l3cd || NM_IS_L3_CONFIG_DATA(_self->l3cd));                    \
+        }                                                                                    \
+    }                                                                                        \
+    G_STMT_END
+
+/*****************************************************************************/
+
+NML3Cfg *
+nm_l3_ipv4ll_get_l3cfg(NML3IPv4LL *self)
+{
+    nm_assert(NM_IS_L3_IPV4LL(self));
+
+    return self->l3cfg;
+}
+
+int
+nm_l3_ipv4ll_get_ifindex(NML3IPv4LL *self)
+{
+    nm_assert(NM_IS_L3_IPV4LL(self));
+
+    return nm_l3cfg_get_ifindex(self->l3cfg);
+}
+
+NMPlatform *
+nm_l3_ipv4ll_get_platform(NML3IPv4LL *self)
+{
+    nm_assert(NM_IS_L3_IPV4LL(self));
+
+    return nm_l3cfg_get_platform(self->l3cfg);
+}
+
+/*****************************************************************************/
+
+NML3IPv4LLState
+nm_l3_ipv4ll_get_state(NML3IPv4LL *self)
+{
+    nm_assert(NM_IS_L3_IPV4LL(self));
+
+    return self->state;
+}
+
+static gboolean
+_ipv4ll_is_timed_out(NML3IPv4LL *self)
+{
+    _ASSERT(self);
+
+    return self->timed_out_expiry_msec != 0 && !self->timed_out_source;
+}
+
+gboolean
+nm_l3_ipv4ll_is_timed_out(NML3IPv4LL *self)
+{
+    nm_assert(NM_IS_L3_IPV4LL(self));
+
+    return _ipv4ll_is_timed_out(self);
+}
+
+in_addr_t
+nm_l3_ipv4ll_get_addr(NML3IPv4LL *self)
+{
+    nm_assert(NM_IS_L3_IPV4LL(self));
+
+    return self->addr;
+}
+
+const NML3ConfigData *
+nm_l3_ipv4ll_get_l3cd(NML3IPv4LL *self)
+{
+    nm_assert(NM_IS_L3_IPV4LL(self));
+
+    return self->l3cd;
+}
+
+static void
+_ipv4ll_emit_signal_notify(NML3IPv4LL *self)
+{
+    NML3ConfigNotifyData notify_data;
+
+    self->notify_on_idle = FALSE;
+
+    notify_data.notify_type  = NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT;
+    notify_data.ipv4ll_event = (typeof(notify_data.ipv4ll_event)){
+        .ipv4ll = self,
+    };
+    _nm_l3cfg_emit_signal_notify(self->l3cfg, &notify_data);
+}
+
+/*****************************************************************************/
+
+static NML3IPv4LLRegistration *
+_registration_update(NML3IPv4LL *            self,
+                     NML3IPv4LLRegistration *reg,
+                     gboolean                add,
+                     guint                   timeout_msec)
+{
+    nm_auto_unref_l3ipv4ll NML3IPv4LL *self_unref_on_exit = NULL;
+    gboolean                           changed            = FALSE;
+
+    if (reg) {
+        nm_assert(!self);
+        _ASSERT(reg->self);
+        self = reg->self;
+        nm_assert(c_list_contains(&self->reg_lst_head, &reg->reg_lst));
+        nm_assert(self == nm_l3_ipv4ll_register_get_instance(reg));
+    } else {
+        nm_assert(add);
+        _ASSERT(self);
+    }
+
+    if (!add) {
+        _LOGT("registration[" NM_HASH_OBFUSCATE_PTR_FMT "]: remove", NM_HASH_OBFUSCATE_PTR(reg));
+        c_list_unlink_stale(&reg->reg_lst);
+        if (c_list_is_empty(&self->reg_lst_head))
+            self_unref_on_exit = self;
+        nm_g_slice_free(reg);
+        reg = NULL;
+        goto out;
+    }
+
+    if (!reg) {
+        reg  = g_slice_new(NML3IPv4LLRegistration);
+        *reg = (NML3IPv4LLRegistration){
+            .self         = self,
+            .timeout_msec = timeout_msec,
+        };
+
+        if (c_list_is_empty(&self->reg_lst_head))
+            nm_l3_ipv4ll_ref(self);
+        c_list_link_tail(&self->reg_lst_head, &reg->reg_lst);
+        changed = TRUE;
+        _LOGT("registration[" NM_HASH_OBFUSCATE_PTR_FMT "]: add (timeout_msec=%u)",
+              NM_HASH_OBFUSCATE_PTR(reg),
+              timeout_msec);
+    } else {
+        if (reg->timeout_msec != timeout_msec) {
+            reg->timeout_msec = timeout_msec;
+            changed           = TRUE;
+        }
+        if (changed) {
+            _LOGT("registration[" NM_HASH_OBFUSCATE_PTR_FMT "]: update (timeout_msec=%u)",
+                  NM_HASH_OBFUSCATE_PTR(reg),
+                  timeout_msec);
+        }
+    }
+
+out:
+    if (changed) {
+        self->reg_changed = TRUE;
+        _ipv4ll_state_change(self, FALSE);
+    }
+    return reg;
+}
+
+NML3IPv4LLRegistration *
+nm_l3_ipv4ll_register_new(NML3IPv4LL *self, guint timeout_msec)
+{
+    return _registration_update(self, NULL, TRUE, timeout_msec);
+}
+
+NML3IPv4LLRegistration *
+nm_l3_ipv4ll_register_update(NML3IPv4LLRegistration *reg, guint timeout_msec)
+{
+    return _registration_update(NULL, reg, TRUE, timeout_msec);
+}
+
+NML3IPv4LLRegistration *
+nm_l3_ipv4ll_register_remove(NML3IPv4LLRegistration *reg)
+{
+    return _registration_update(NULL, reg, FALSE, 0);
+}
+
+/*****************************************************************************/
+
+static gboolean
+_ip4_address_is_link_local(const NMPlatformIP4Address *a)
+{
+    nm_assert(a);
+
+    return nm_utils_ip4_address_is_link_local(a->address) && a->plen == ADDR_IPV4LL_PREFIX_LEN
+           && a->address == a->peer_address;
+}
+
+static gboolean
+_acd_info_is_good(const NML3AcdAddrInfo *acd_info)
+{
+    if (!acd_info)
+        return TRUE;
+
+    switch (acd_info->state) {
+    case NM_L3_ACD_ADDR_STATE_INIT:
+    case NM_L3_ACD_ADDR_STATE_PROBING:
+    case NM_L3_ACD_ADDR_STATE_READY:
+    case NM_L3_ACD_ADDR_STATE_DEFENDING:
+    case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+        return TRUE;
+    case NM_L3_ACD_ADDR_STATE_USED:
+    case NM_L3_ACD_ADDR_STATE_CONFLICT:
+        return FALSE;
+    }
+    nm_assert_not_reached();
+    return FALSE;
+}
+
+/*****************************************************************************/
+
+static NMPlatformIP4Address *
+_l3cd_config_plat_init_addr(NMPlatformIP4Address *a, int ifindex, in_addr_t addr)
+{
+    nm_assert(nm_utils_ip4_address_is_link_local(addr));
+
+    *a = (NMPlatformIP4Address){
+        .ifindex      = ifindex,
+        .address      = addr,
+        .peer_address = addr,
+        .plen         = ADDR_IPV4LL_PREFIX_LEN,
+        .addr_source  = NM_IP_CONFIG_SOURCE_IP4LL,
+    };
+    return a;
+}
+
+static NMPlatformIP4Route *
+_l3cd_config_plat_init_route(NMPlatformIP4Route *r, int ifindex)
+{
+    *r = (NMPlatformIP4Route){
+        .ifindex    = ifindex,
+        .network    = htonl(0xE0000000u),
+        .plen       = 4,
+        .rt_source  = NM_IP_CONFIG_SOURCE_IP4LL,
+        .table_any  = TRUE,
+        .metric_any = TRUE,
+    };
+    return r;
+}
+
+static const NML3ConfigData *
+_l3cd_config_create(int ifindex, in_addr_t addr, NMDedupMultiIndex *multi_idx)
+{
+    nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
+    NMPlatformIP4Address                    a;
+    NMPlatformIP4Route                      r;
+
+    nm_assert(nm_utils_ip4_address_is_link_local(addr));
+    nm_assert(ifindex > 0);
+    nm_assert(multi_idx);
+
+    l3cd = nm_l3_config_data_new(multi_idx, ifindex);
+    nm_l3_config_data_set_source(l3cd, NM_IP_CONFIG_SOURCE_IP4LL);
+
+    nm_l3_config_data_add_address_4(l3cd, _l3cd_config_plat_init_addr(&a, ifindex, addr));
+    nm_l3_config_data_add_route_4(l3cd, _l3cd_config_plat_init_route(&r, ifindex));
+
+    return nm_l3_config_data_seal(g_steal_pointer(&l3cd));
+}
+
+static in_addr_t
+_l3cd_config_get_addr(const NML3ConfigData *l3cd)
+{
+    NMDedupMultiIter            iter;
+    const NMPlatformIP4Address *pladdr;
+
+    if (!l3cd)
+        return 0u;
+
+    nm_l3_config_data_iter_ip4_address_for_each (&iter, l3cd, &pladdr) {
+        const in_addr_t addr = pladdr->address;
+
+        nm_assert(_ip4_address_is_link_local(pladdr));
+#if NM_MORE_ASSERTS > 10
+        {
+            nm_auto_unref_l3cd const NML3ConfigData *l3cd2 = NULL;
+
+            l3cd2 = _l3cd_config_create(nm_l3_config_data_get_ifindex(l3cd),
+                                        addr,
+                                        nm_l3_config_data_get_multi_idx(l3cd));
+            nm_assert(nm_l3_config_data_equal(l3cd2, l3cd));
+        }
+#endif
+        return addr;
+    }
+
+    return nm_assert_unreachable_val(0u);
+}
+
+/*****************************************************************************/
+
+static void
+_ipv4ll_addrgen(NML3IPv4LL *self, gboolean generate_new_addr)
+{
+    CSipHash  state;
+    char      sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+    gboolean  seed_changed = FALSE;
+    in_addr_t addr_new;
+    guint64   h;
+
+    _ASSERT(self);
+
+    /* MAC_HASH_KEY is the same as used by systemd. */
+#define MAC_HASH_KEY          \
+    ((const guint8[16]){0xdf, \
+                        0x04, \
+                        0x22, \
+                        0x98, \
+                        0x3f, \
+                        0xad, \
+                        0x14, \
+                        0x52, \
+                        0xf9, \
+                        0x87, \
+                        0x2e, \
+                        0xd1, \
+                        0x9c, \
+                        0x70, \
+                        0xe2, \
+                        0xf2})
+
+    if (self->mac_set && (!self->seed_set || !nm_ether_addr_equal(&self->mac, &self->seed_mac))) {
+        /* systemd's ipv4ll library by default only hashes the MAC address (as we do here).
+         * This is also what previous versions of NetworkManager did (whenn using sd_ipv4ll).
+         *
+         * On the other hand, systemd-networkd uses net_get_name_persistent() of the device
+         * mixed with /etc/machine-id.
+         *
+         * See also: https://tools.ietf.org/html/rfc3927#section-2.1
+         *
+         * FIXME(l3cfg): At this point, maybe we should also mix it with nm_utils_host_id_get().
+         * This would get the behavior closer to what systemd-networkd does.
+         * Don't do that for now, because it would be a change in behavior compared
+         * to earlier versions of NetworkManager.  */
+
+        c_siphash_init(&state, MAC_HASH_KEY);
+        c_siphash_append(&state, self->mac.ether_addr_octet, ETH_ALEN);
+        h = c_siphash_finalize(&state);
+
+        _LOGT("addr-gen: %sset seed (for " NM_ETHER_ADDR_FORMAT_STR ")",
+              self->seed_set ? "re" : "",
+              NM_ETHER_ADDR_FORMAT_VAL(&self->mac));
+
+        self->seed_set              = TRUE;
+        self->seed_mac              = self->mac;
+        self->seed.generation       = htole64(0);
+        self->seed.value            = htole64(h);
+        self->seed_reset_generation = FALSE;
+        self->addr                  = 0u;
+
+        seed_changed = TRUE;
+    }
+
+    if (!self->seed_set) {
+        /* we have no seed set (and consequently no MAC address set either).
+         * We cannot generate an address. */
+        nm_assert(self->addr == 0u);
+        return;
+    }
+
+    nm_assert(seed_changed || self->seed.generation != htole64(0u));
+
+    if (self->seed_reset_generation) {
+        _LOGT("addr-gen: reset seed (generation only)");
+        self->seed.generation = htole64(0);
+        self->addr            = 0u;
+        seed_changed          = TRUE;
+    }
+
+    if (!seed_changed && !generate_new_addr) {
+        /* neither did the caller request a new address, nor was the seed changed. The current
+         * address is still to be used. */
+        nm_assert(nm_utils_ip4_address_is_link_local(self->addr));
+        return;
+    }
+
+gen_addr:
+
+#define PICK_HASH_KEY         \
+    ((const guint8[16]){0x15, \
+                        0xac, \
+                        0x82, \
+                        0xa6, \
+                        0xd6, \
+                        0x3f, \
+                        0x49, \
+                        0x78, \
+                        0x98, \
+                        0x77, \
+                        0x5d, \
+                        0x0c, \
+                        0x69, \
+                        0x02, \
+                        0x94, \
+                        0x0b})
+
+    h = c_siphash_hash(PICK_HASH_KEY, (const guint8 *) &self->seed, sizeof(self->seed));
+
+    self->seed.generation = htole64(le64toh(self->seed.generation) + 1u);
+
+    addr_new = htonl(h & UINT32_C(0x0000FFFF)) | NM_IPV4LL_NETWORK;
+
+    if (self->addr == addr_new || NM_IN_SET(ntohl(addr_new) & 0x0000FF00u, 0x0000u, 0xFF00u))
+        goto gen_addr;
+
+    nm_assert(nm_utils_ip4_address_is_link_local(addr_new));
+
+    _LOGT("addr-gen: set address %s", _nm_utils_inet4_ntop(addr_new, sbuf_addr));
+    self->addr = addr_new;
+}
+
+/*****************************************************************************/
+
+static void
+_ipv4ll_update_link(NML3IPv4LL *self, const NMPObject *plobj)
+{
+    char                 sbuf[ETH_ALEN * 3];
+    nm_auto_nmpobj const NMPObject *pllink_old = NULL;
+    const NMEtherAddr *             mac_new;
+    gboolean                        changed;
+
+    if (self->plobj == plobj)
+        return;
+
+    pllink_old  = g_steal_pointer(&self->plobj);
+    self->plobj = nmp_object_ref(plobj);
+
+    mac_new = NULL;
+    if (plobj) {
+        const NMPlatformLink *pllink = NMP_OBJECT_CAST_LINK(plobj);
+
+        if (pllink->l_address.len == ETH_ALEN)
+            mac_new = &pllink->l_address.ether_addr;
+    }
+
+    changed = FALSE;
+    if (!mac_new) {
+        if (self->mac_set) {
+            changed       = TRUE;
+            self->mac_set = FALSE;
+        }
+    } else {
+        if (!self->mac_set || !nm_ether_addr_equal(mac_new, &self->mac)) {
+            changed       = TRUE;
+            self->mac_set = TRUE;
+            self->mac     = *mac_new;
+        }
+    }
+
+    if (changed) {
+        _LOGT("mac changed: %s",
+              self->mac_set ? _nm_utils_hwaddr_ntoa(&self->mac, ETH_ALEN, TRUE, sbuf, sizeof(sbuf))
+                            : "unset");
+    }
+}
+
+/*****************************************************************************/
+
+static void
+_l3cd_config_add(NML3IPv4LL *self)
+{
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
+    char                                     sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+    gboolean                                 changed;
+
+    _ASSERT(self);
+    nm_assert(self->addr != 0u);
+    nm_assert(self->reg_timeout_msec > 0u);
+
+    if (_l3cd_config_get_addr(self->l3cd) != self->addr) {
+        l3cd = _l3cd_config_create(nm_l3_ipv4ll_get_ifindex(self),
+                                   self->addr,
+                                   nm_l3cfg_get_multi_idx(self->l3cfg));
+        nm_assert(!nm_l3_config_data_equal(l3cd, self->l3cd));
+        changed = TRUE;
+    } else
+        changed = FALSE;
+
+    if (!changed && !self->l3cd_timeout_msec_changed)
+        return;
+
+    self->l3cd_timeout_msec_changed = FALSE;
+
+    _LOGT("add l3cd config with %s (acd-timeout %u msec%s)",
+          _nm_utils_inet4_ntop(self->addr, sbuf_addr),
+          self->reg_timeout_msec,
+          changed ? "" : " changed");
+
+    if (changed) {
+        NM_SWAP(&l3cd, &self->l3cd);
+        self->notify_on_idle = TRUE;
+    }
+
+    if (!nm_l3cfg_add_config(self->l3cfg,
+                             L3CD_TAG(self),
+                             TRUE,
+                             self->l3cd,
+                             NM_L3CFG_CONFIG_PRIORITY_IPV4LL,
+                             0,
+                             0,
+                             NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4,
+                             NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
+                             0,
+                             0,
+                             NM_L3_ACD_DEFEND_TYPE_ONCE,
+                             self->reg_timeout_msec,
+                             NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD))
+        nm_assert_not_reached();
+
+    self->l3cfg_commit_handle = nm_l3cfg_commit_type_register(self->l3cfg,
+                                                              NM_L3_CFG_COMMIT_TYPE_ASSUME,
+                                                              self->l3cfg_commit_handle);
+    nm_l3cfg_commit_on_idle_schedule(self->l3cfg);
+}
+
+static gboolean
+_l3cd_config_remove(NML3IPv4LL *self)
+{
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
+
+    nm_assert(NM_IS_L3_IPV4LL(self));
+
+    if (!self->l3cd)
+        return FALSE;
+
+    _LOGT("remove l3cd config");
+
+    self->notify_on_idle = TRUE;
+
+    l3cd = g_steal_pointer(&self->l3cd);
+    if (!nm_l3cfg_remove_config(self->l3cfg, L3CD_TAG(self), l3cd))
+        nm_assert_not_reached();
+
+    nm_l3cfg_commit_type_unregister(self->l3cfg, g_steal_pointer(&self->l3cfg_commit_handle));
+    nm_l3cfg_commit_on_idle_schedule(self->l3cfg);
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static const NMPlatformIP4Address *
+_ipv4ll_platform_ip4_address_lookup(NML3IPv4LL *self, in_addr_t addr)
+{
+    const NMPlatformIP4Address *pladdr;
+
+    if (addr == 0u)
+        return NULL;
+
+    nm_assert(nm_utils_ip4_address_is_link_local(addr));
+
+    pladdr = nm_platform_ip4_address_get(nm_l3_ipv4ll_get_platform(self),
+                                         nm_l3_ipv4ll_get_ifindex(self),
+                                         addr,
+                                         ADDR_IPV4LL_PREFIX_LEN,
+                                         addr);
+
+    nm_assert(!pladdr || pladdr->address == addr);
+    nm_assert(!pladdr || _ip4_address_is_link_local(pladdr));
+    return pladdr;
+}
+
+static const NML3AcdAddrInfo *
+_ipv4ll_l3cfg_get_acd_addr_info(NML3IPv4LL *self, in_addr_t addr)
+{
+    if (addr == 0u)
+        return NULL;
+
+    nm_assert(nm_utils_ip4_address_is_link_local(addr));
+    return nm_l3cfg_get_acd_addr_info(self->l3cfg, addr);
+}
+
+static const NMPlatformIP4Address *
+_ipv4ll_platform_find_addr(NML3IPv4LL *self, const NML3AcdAddrInfo **out_acd_info)
+{
+    const NMPlatformIP4Address *addr_without_acd_info = NULL;
+    NMDedupMultiIter            iter;
+    NMPLookup                   lookup;
+    const NMPObject *           obj;
+    const NML3AcdAddrInfo *     acd_info;
+    const NMPlatformIP4Address *addr;
+
+    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, nm_l3_ipv4ll_get_ifindex(self));
+    nm_platform_iter_obj_for_each (&iter, nm_l3_ipv4ll_get_platform(self), &lookup, &obj) {
+        addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj);
+        if (!_ip4_address_is_link_local(addr))
+            continue;
+
+        acd_info = _ipv4ll_l3cfg_get_acd_addr_info(self, addr->address);
+        if (!_acd_info_is_good(acd_info))
+            continue;
+
+        if (acd_info) {
+            /* We have a good acd_info. We won't find a better one. Return it. */
+            NM_SET_OUT(out_acd_info, acd_info);
+            return addr;
+        }
+
+        if (!addr_without_acd_info) {
+            /* remember a potential candidate address that has no acd_info. */
+            addr_without_acd_info = addr;
+        }
+    }
+
+    if (addr_without_acd_info) {
+        NM_SET_OUT(out_acd_info, NULL);
+        return addr_without_acd_info;
+    }
+
+    return NULL;
+}
+
+/*****************************************************************************/
+
+static gboolean
+_ipv4ll_set_timed_out_timeout_cb(gpointer user_data)
+{
+    NML3IPv4LL *self = user_data;
+
+    _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_IS_TIMED_OUT);
+    if (self->notify_on_idle)
+        _ipv4ll_emit_signal_notify(self);
+    return G_SOURCE_REMOVE;
+}
+
+static void
+_ipv4ll_set_timed_out_update(NML3IPv4LL *self, TimedOutState new_state)
+{
+    gboolean before;
+
+    before = _ipv4ll_is_timed_out(self);
+
+    switch (new_state) {
+    case TIMED_OUT_STATE_IS_TIMED_OUT:
+        if (self->timed_out_expiry_msec == 0) {
+            nm_assert(!self->timed_out_source);
+            self->timed_out_expiry_msec = 1;
+        }
+        nm_clear_g_source_inst(&self->timed_out_source);
+        break;
+    case TIMED_OUT_STATE_IS_NOT_TIMED_OUT:
+        self->timed_out_expiry_msec = 0;
+        nm_clear_g_source_inst(&self->timed_out_source);
+        break;
+    case TIMED_OUT_STATE_HAVE_TIMER_RUNNING:
+    {
+        gint64 now_msec = nm_utils_get_monotonic_timestamp_msec();
+        guint  timeout_msec;
+        gint64 expiry_msec;
+
+        nm_assert(self->reg_timeout_msec > 0u);
+
+        timeout_msec = nm_mult_clamped_u(TIMED_OUT_TIME_FACTOR, self->reg_timeout_msec);
+        expiry_msec  = now_msec + timeout_msec;
+
+        if (self->timed_out_expiry_msec == 0 || self->timed_out_expiry_msec < expiry_msec) {
+            self->timed_out_expiry_msec = expiry_msec;
+            nm_clear_g_source_inst(&self->timed_out_source);
+            self->timed_out_source = nm_g_timeout_source_new(timeout_msec,
+                                                             G_PRIORITY_DEFAULT,
+                                                             _ipv4ll_set_timed_out_timeout_cb,
+                                                             self,
+                                                             NULL);
+            g_source_attach(self->timed_out_source, NULL);
+        }
+        break;
+    }
+    }
+
+    if (before != _ipv4ll_is_timed_out(self)) {
+        self->notify_on_idle = TRUE;
+        _LOGT("state: set timed-out-is-bad=%d", (!before));
+    }
+}
+
+static gboolean
+_ipv4ll_set_state(NML3IPv4LL *self, NML3IPv4LLState state)
+{
+    char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+    char sbuf100[100];
+
+    if (self->state == state)
+        return FALSE;
+    self->state          = state;
+    self->notify_on_idle = TRUE;
+    _LOGT("state: set state %s (addr=%s)",
+          nm_l3_ipv4ll_state_to_string(state, sbuf100, sizeof(sbuf100)),
+          _nm_utils_inet4_ntop(self->addr, sbuf_addr));
+    return TRUE;
+}
+
+static void
+_ipv4ll_state_change(NML3IPv4LL *self, gboolean is_on_idle_handler)
+{
+    nm_auto_unref_l3ipv4ll NML3IPv4LL *self_keep_alive = NULL;
+    const NMPlatformIP4Address *       pladdr;
+    const NML3AcdAddrInfo *            acd_info;
+    gboolean                           generate_new_addr;
+    NML3IPv4LLState                    new_state;
+    in_addr_t                          addr0;
+    NML3IPv4LLRegistration *           reg;
+
+    _ASSERT(self);
+
+    self_keep_alive = nm_l3_ipv4ll_ref(self);
+
+    nm_clear_g_source_inst(&self->state_change_on_idle_source);
+
+    addr0 = self->addr;
+
+    if (self->reg_changed) {
+        guint timeout_msec = self->reg_timeout_msec;
+
+        self->reg_changed = FALSE;
+
+        if (c_list_is_empty(&self->reg_lst_head))
+            timeout_msec = 0;
+        else {
+            timeout_msec = G_MAXUINT;
+            c_list_for_each_entry (reg, &self->reg_lst_head, reg_lst) {
+                if (reg->timeout_msec < timeout_msec)
+                    timeout_msec = reg->timeout_msec;
+                if (reg->timeout_msec == 0)
+                    break;
+            }
+        }
+        if (self->reg_timeout_msec != timeout_msec) {
+            self->reg_timeout_msec          = timeout_msec;
+            self->l3cd_timeout_msec_changed = TRUE;
+        }
+    }
+
+    if (self->reg_timeout_msec == 0) {
+        _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_IS_NOT_TIMED_OUT);
+        if (_ipv4ll_set_state(self, NM_L3_IPV4LL_STATE_DISABLED))
+            _l3cd_config_remove(self);
+        goto out_notify;
+    }
+
+    if (!self->mac_set) {
+        _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_HAVE_TIMER_RUNNING);
+        if (_ipv4ll_set_state(self, NM_L3_IPV4LL_STATE_WAIT_FOR_LINK))
+            _l3cd_config_remove(self);
+        else
+            nm_assert(!self->l3cd);
+        goto out_notify;
+    }
+
+    if (self->state <= NM_L3_IPV4LL_STATE_EXTERNAL) {
+        pladdr = _ipv4ll_platform_ip4_address_lookup(self, self->addr);
+        if (pladdr) {
+            if (!_acd_info_is_good(_ipv4ll_l3cfg_get_acd_addr_info(self, self->addr)))
+                pladdr = NULL;
+        }
+        if (!pladdr)
+            pladdr = _ipv4ll_platform_find_addr(self, NULL);
+
+        if (pladdr) {
+            /* we have an externally configured address. Check whether we can use it. */
+            self->addr           = pladdr->address;
+            self->notify_on_idle = TRUE;
+            _ipv4ll_set_state(self, NM_L3_IPV4LL_STATE_EXTERNAL);
+            _l3cd_config_add(self);
+            _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_IS_NOT_TIMED_OUT);
+            goto out_notify;
+        }
+    }
+
+    generate_new_addr = FALSE;
+    while (TRUE) {
+        _ipv4ll_addrgen(self, generate_new_addr);
+        acd_info = _ipv4ll_l3cfg_get_acd_addr_info(self, self->addr);
+        if (_acd_info_is_good(acd_info))
+            break;
+        generate_new_addr = TRUE;
+    }
+
+    nm_assert(_acd_info_is_good(acd_info));
+    switch (acd_info ? acd_info->state : NM_L3_ACD_ADDR_STATE_INIT) {
+    case NM_L3_ACD_ADDR_STATE_INIT:
+    case NM_L3_ACD_ADDR_STATE_PROBING:
+        new_state = NM_L3_IPV4LL_STATE_PROBING;
+        _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_HAVE_TIMER_RUNNING);
+        goto out_is_good_1;
+    case NM_L3_ACD_ADDR_STATE_READY:
+        new_state = NM_L3_IPV4LL_STATE_READY;
+        _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_HAVE_TIMER_RUNNING);
+        goto out_is_good_1;
+    case NM_L3_ACD_ADDR_STATE_DEFENDING:
+        new_state = NM_L3_IPV4LL_STATE_DEFENDING;
+        _ipv4ll_set_timed_out_update(self, TIMED_OUT_STATE_IS_NOT_TIMED_OUT);
+        goto out_is_good_1;
+    case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+    case NM_L3_ACD_ADDR_STATE_USED:
+    case NM_L3_ACD_ADDR_STATE_CONFLICT:
+        nm_assert_not_reached();
+        goto out_notify;
+    }
+    nm_assert_not_reached();
+    goto out_notify;
+out_is_good_1:
+    _ipv4ll_set_state(self, new_state);
+    _l3cd_config_add(self);
+    if (self->addr != addr0)
+        self->notify_on_idle = TRUE;
+    goto out_notify;
+
+out_notify:
+    if (self->notify_on_idle) {
+        if (is_on_idle_handler)
+            _ipv4ll_emit_signal_notify(self);
+        else
+            _ipv4ll_state_change_on_idle(self);
+    }
+}
+
+static gboolean
+_ipv4ll_state_change_on_idle_cb(gpointer user_data)
+{
+    NML3IPv4LL *self = user_data;
+
+    _ipv4ll_state_change(self, TRUE);
+    return G_SOURCE_REMOVE;
+}
+
+static void
+_ipv4ll_state_change_on_idle(NML3IPv4LL *self)
+{
+    nm_assert(NM_IS_L3_IPV4LL(self));
+
+    if (!self->state_change_on_idle_source) {
+        self->state_change_on_idle_source =
+            nm_g_idle_source_new(G_PRIORITY_DEFAULT, _ipv4ll_state_change_on_idle_cb, self, NULL);
+        g_source_attach(self->state_change_on_idle_source, NULL);
+    }
+}
+
+/*****************************************************************************/
+
+static void
+_l3cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NML3IPv4LL *self)
+{
+    if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE) {
+        /* NMl3Cfg only reloads the platform link during the idle handler. Pick it up now. */
+        _ipv4ll_update_link(self, nm_l3cfg_get_plobj(l3cfg, FALSE));
+
+        /* theoretically, this even is already on an idle handler. However, we share
+         * the call with other signal handlers, so at this point we don't want to
+         * emit additional signals. Thus pass %FALSE to _ipv4ll_state_change(). */
+        _ipv4ll_state_change(self, FALSE);
+        return;
+    }
+
+    if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) {
+        if (self->l3cd
+            && nm_l3_acd_addr_info_find_track_info(&notify_data->acd_event.info,
+                                                   L3CD_TAG(self),
+                                                   self->l3cd,
+                                                   NULL)) {
+            _ipv4ll_state_change(self, FALSE);
+        }
+        return;
+    }
+}
+
+/*****************************************************************************/
+
+NML3IPv4LL *
+nm_l3_ipv4ll_new(NML3Cfg *l3cfg)
+{
+    NML3IPv4LL *self;
+
+    g_return_val_if_fail(NM_IS_L3CFG(l3cfg), NULL);
+
+    self  = g_slice_new(NML3IPv4LL);
+    *self = (NML3IPv4LL){
+        .l3cfg                       = g_object_ref(l3cfg),
+        .ref_count                   = 1,
+        .reg_lst_head                = C_LIST_INIT(self->reg_lst_head),
+        .l3cfg_commit_handle         = NULL,
+        .state_change_on_idle_source = NULL,
+        .l3cd                        = NULL,
+        .plobj                       = NULL,
+        .addr                        = 0u,
+        .state                       = NM_L3_IPV4LL_STATE_DISABLED,
+        .reg_timeout_msec            = 0,
+        .notify_on_idle              = TRUE,
+        .l3cfg_signal_notify_id =
+            g_signal_connect(l3cfg, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_l3cfg_notify_cb), self),
+        .seed_set              = FALSE,
+        .seed_reset_generation = FALSE,
+    };
+
+    _LOGT("created: l3cfg=" NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(l3cfg));
+
+    _ipv4ll_update_link(self, nm_l3cfg_get_plobj(l3cfg, FALSE));
+    _ipv4ll_state_change(self, FALSE);
+    return self;
+}
+
+NML3IPv4LL *
+nm_l3_ipv4ll_ref(NML3IPv4LL *self)
+{
+    if (!self)
+        return NULL;
+
+    _ASSERT(self);
+
+    nm_assert(self->ref_count < G_MAXINT);
+    self->ref_count++;
+    return self;
+}
+
+void
+nm_l3_ipv4ll_unref(NML3IPv4LL *self)
+{
+    if (!self)
+        return;
+
+    _ASSERT(self);
+
+    if (--self->ref_count > 0)
+        return;
+
+    if (nm_l3cfg_get_ipv4ll(self->l3cfg) == self)
+        _nm_l3cfg_unregister_ipv4ll(self->l3cfg);
+
+    _LOGT("finalize");
+
+    nm_assert(c_list_is_empty(&self->reg_lst_head));
+
+    if (self->l3cd) {
+        nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
+
+        l3cd = g_steal_pointer(&self->l3cd);
+        if (!nm_l3cfg_remove_config(self->l3cfg, L3CD_TAG(self), l3cd))
+            nm_assert_not_reached();
+
+        nm_l3cfg_commit_type_unregister(self->l3cfg, g_steal_pointer(&self->l3cfg_commit_handle));
+        nm_l3cfg_commit_on_idle_schedule(self->l3cfg);
+    } else
+        nm_assert(!self->l3cfg_commit_handle);
+
+    nm_clear_g_source_inst(&self->state_change_on_idle_source);
+    nm_clear_g_source_inst(&self->timed_out_source);
+
+    nm_clear_g_signal_handler(self->l3cfg, &self->l3cfg_signal_notify_id);
+
+    g_clear_object(&self->l3cfg);
+    nmp_object_unref(self->plobj);
+    nm_g_slice_free(self);
+}
diff --git a/src/core/nm-l3-ipv4ll.h b/src/core/nm-l3-ipv4ll.h
new file mode 100644
index 00000000..b39762a8
--- /dev/null
+++ b/src/core/nm-l3-ipv4ll.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef __NM_L3_IPV4LL_H__
+#define __NM_L3_IPV4LL_H__
+
+#include "nm-l3cfg.h"
+
+/*****************************************************************************/
+
+typedef enum _nm_packed {
+    NM_L3_IPV4LL_STATE_UNKNOWN,
+    NM_L3_IPV4LL_STATE_DISABLED,
+    NM_L3_IPV4LL_STATE_WAIT_FOR_LINK,
+    NM_L3_IPV4LL_STATE_EXTERNAL,
+    NM_L3_IPV4LL_STATE_PROBING,
+    NM_L3_IPV4LL_STATE_READY,
+    NM_L3_IPV4LL_STATE_DEFENDING,
+} NML3IPv4LLState;
+
+const char *nm_l3_ipv4ll_state_to_string(NML3IPv4LLState val, char *buf, gsize len);
+
+static inline gboolean
+nm_l3_ipv4ll_state_is_good(NML3IPv4LLState state)
+{
+    switch (state) {
+    case NM_L3_IPV4LL_STATE_UNKNOWN:
+    case NM_L3_IPV4LL_STATE_DISABLED:
+    case NM_L3_IPV4LL_STATE_WAIT_FOR_LINK:
+    case NM_L3_IPV4LL_STATE_PROBING:
+        return FALSE;
+    case NM_L3_IPV4LL_STATE_EXTERNAL:
+    case NM_L3_IPV4LL_STATE_READY:
+    case NM_L3_IPV4LL_STATE_DEFENDING:
+        return TRUE;
+    }
+    return nm_assert_unreachable_val(FALSE);
+}
+
+/*****************************************************************************/
+
+typedef struct _NML3IPv4LL NML3IPv4LL;
+
+static inline gboolean
+NM_IS_L3_IPV4LL(const NML3IPv4LL *self)
+{
+    nm_assert(!self
+              || (NM_IS_L3CFG(*((NML3Cfg **) self))
+                  && (*((int *) (((char *) self) + sizeof(gpointer)))) > 0));
+    return !!self;
+}
+
+NML3IPv4LL *nm_l3_ipv4ll_new(NML3Cfg *self);
+
+NML3IPv4LL *nm_l3_ipv4ll_ref(NML3IPv4LL *self);
+void        nm_l3_ipv4ll_unref(NML3IPv4LL *self);
+
+NM_AUTO_DEFINE_FCN0(NML3IPv4LL *, _nm_auto_unref_l3ipv4ll, nm_l3_ipv4ll_unref);
+#define nm_auto_unref_l3ipv4ll nm_auto(_nm_auto_unref_l3ipv4ll)
+
+/*****************************************************************************/
+
+NML3Cfg *nm_l3_ipv4ll_get_l3cfg(NML3IPv4LL *self);
+
+int nm_l3_ipv4ll_get_ifindex(NML3IPv4LL *self);
+
+NMPlatform *nm_l3_ipv4ll_get_platform(NML3IPv4LL *self);
+
+/*****************************************************************************/
+
+/* By default, NML3IPv4LL is disabled. You also need to register (enable) it.
+ * The intent of this API is that multiple users can enable/register their own
+ * settings, and NML3IPv4LL will mediate the different requests.
+ *
+ * Also, by setting timeout_msec to zero, NML3IPv4LL is disabled again (zero
+ * wins over all timeouts). This is useful if you do DHCP and IPv4LL on the
+ * same interface. You possibly want to disable IPv4LL if you have a valid
+ * DHCP lease. By registering a timeout_msec to zero, you can disable IPv4LL.
+ *
+ * Also, a registration keeps the NML3IPv4LL instance alive (it also takes
+ * a reference).  */
+
+typedef struct _NML3IPv4LLRegistration NML3IPv4LLRegistration;
+
+NML3IPv4LLRegistration *nm_l3_ipv4ll_register_new(NML3IPv4LL *self, guint timeout_msec);
+
+NML3IPv4LLRegistration *nm_l3_ipv4ll_register_update(NML3IPv4LLRegistration *reg,
+                                                     guint                   timeout_msec);
+
+NML3IPv4LLRegistration *nm_l3_ipv4ll_register_remove(NML3IPv4LLRegistration *reg);
+
+NM_AUTO_DEFINE_FCN0(NML3IPv4LLRegistration *,
+                    _nm_auto_remove_l3ipv4ll_registration,
+                    nm_l3_ipv4ll_register_remove);
+#define nm_auto_remove_l3ipv4ll_registration nm_auto(_nm_auto_remove_l3ipv4ll_registration)
+
+static inline NML3IPv4LL *
+nm_l3_ipv4ll_register_get_instance(NML3IPv4LLRegistration *reg)
+{
+    NML3IPv4LL *ipv4ll;
+
+    if (!reg)
+        return NULL;
+    ipv4ll = *((NML3IPv4LL **) reg);
+    nm_assert(NM_IS_L3_IPV4LL(ipv4ll));
+    return ipv4ll;
+}
+
+/*****************************************************************************/
+
+NML3IPv4LLState nm_l3_ipv4ll_get_state(NML3IPv4LL *self);
+
+gboolean nm_l3_ipv4ll_is_timed_out(NML3IPv4LL *self);
+
+in_addr_t nm_l3_ipv4ll_get_addr(NML3IPv4LL *self);
+
+const NML3ConfigData *nm_l3_ipv4ll_get_l3cd(NML3IPv4LL *self);
+
+#endif /* __NM_L3_IPV4LL_H__ */
diff --git a/src/nm-l3cfg.c b/src/core/nm-l3cfg.c
index 7b1e6f22..839213ad 100644
--- a/src/nm-l3cfg.c
+++ b/src/core/nm-l3cfg.c
@@ -1,28 +1,33 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-l3cfg.h"
 
 #include <net/if.h>
 #include <linux/if_addr.h>
+#include <linux/if_ether.h>
 #include <linux/rtnetlink.h>
 
 #include "platform/nm-platform.h"
 #include "platform/nmp-object.h"
 #include "nm-netns.h"
 #include "n-acd/src/n-acd.h"
+#include "nm-l3-ipv4ll.h"
 
 /*****************************************************************************/
 
+G_STATIC_ASSERT(NM_ACD_TIMEOUT_RFC5227_MSEC == N_ACD_TIMEOUT_RFC5227);
+
 #define ACD_SUPPORTED_ETH_ALEN                  ETH_ALEN
 #define ACD_ENSURE_RATELIMIT_MSEC               ((guint32) 4000u)
 #define ACD_WAIT_PROBING_EXTRA_TIME_MSEC        ((guint32)(1000u + ACD_ENSURE_RATELIMIT_MSEC))
 #define ACD_WAIT_PROBING_EXTRA_TIME2_MSEC       ((guint32) 1000u)
-#define ACD_WAIT_PROBING_RESTART_TIME_MSEC      ((guint32) 8000u)
 #define ACD_MAX_TIMEOUT_MSEC                    ((guint32) 30000u)
 #define ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC ((guint32) 30000u)
-#define ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC     ((guint32) 20000u)
+#define ACD_WAIT_TIME_CONFLICT_RESTART_MSEC     ((guint32) 120000u)
+#define ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC     ((guint32) 30000u)
+#define ACD_DEFENDCONFLICT_INFO_RATELIMIT_MSEC  ((guint32) 30000u)
 
 static gboolean
 ACD_ADDR_SKIP(in_addr_t addr)
@@ -39,13 +44,16 @@ ACD_ADDR_SKIP(in_addr_t addr)
     ACD_TRACK_PTR2((acd_track)->l3cd, (acd_track)->obj, (acd_track)->tag)
 
 typedef enum {
-    ACD_STATE_CHANGE_MODE_INIT,
+    ACD_STATE_CHANGE_MODE_NACD_CONFLICT = N_ACD_EVENT_CONFLICT,
+    ACD_STATE_CHANGE_MODE_NACD_DEFENDED = N_ACD_EVENT_DEFENDED,
+    ACD_STATE_CHANGE_MODE_NACD_DOWN     = N_ACD_EVENT_DOWN,
+    ACD_STATE_CHANGE_MODE_NACD_READY    = N_ACD_EVENT_READY,
+    ACD_STATE_CHANGE_MODE_NACD_USED     = N_ACD_EVENT_USED,
+
+    ACD_STATE_CHANGE_MODE_INIT = _N_ACD_EVENT_N,
+    ACD_STATE_CHANGE_MODE_INIT_REAPPLY,
     ACD_STATE_CHANGE_MODE_POST_COMMIT,
 
-    ACD_STATE_CHANGE_MODE_NACD_READY,
-    ACD_STATE_CHANGE_MODE_NACD_USED,
-    ACD_STATE_CHANGE_MODE_NACD_DOWN,
-
     ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED,
     ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED,
     ACD_STATE_CHANGE_MODE_LINK_NOW_UP,
@@ -53,57 +61,41 @@ typedef enum {
     ACD_STATE_CHANGE_MODE_TIMEOUT,
 } AcdStateChangeMode;
 
-typedef struct {
-    CList                 acd_track_lst;
-    const NMPObject *     obj;
-    const NML3ConfigData *l3cd;
-    gconstpointer         tag;
-    guint32               acd_timeout_msec;
-    bool                  acd_dirty : 1;
-    bool                  acd_failed_notified : 1;
-} AcdTrackData;
-
-typedef enum _nm_packed {
-    ACD_STATE_INIT,
-    ACD_STATE_PROBING,
-    ACD_STATE_PROBE_DONE,
-    ACD_STATE_ANNOUNCING,
-} AcdState;
+G_STATIC_ASSERT(G_STRUCT_OFFSET(NML3AcdAddrInfo, addr) == 0);
 
 typedef struct {
-    in_addr_t addr;
-
-    /* This is only relevant while in state ACD_STATE_PROBING. It's the
-     * duration for how long we probe, and @probing_timestamp_msec is the
-     * timestamp when we start probing. */
-    guint32 probing_timeout_msec;
+    NML3AcdAddrInfo info;
 
     CList acd_lst;
-    CList acd_notify_complete_lst;
-    CList acd_track_lst_head;
-
-    NML3Cfg *self;
+    CList acd_event_notify_lst;
 
     NAcdProbe *nacd_probe;
 
-    GSource *acd_timeout_source;
-    gint64   acd_timeout_expiry_msec;
+    GSource *acd_data_timeout_source;
 
     /* see probing_timeout_msec. */
     gint64 probing_timestamp_msec;
 
-    /* the ACD state for this address. */
-    AcdState acd_state;
+    gint64 last_defendconflict_timestamp_msec;
+
+    guint n_track_infos_alloc;
+
+    /* This is only relevant while in state NM_L3_ACD_ADDR_STATE_PROBING. It's the
+     * duration for how long we probe, and @probing_timestamp_msec is the
+     * timestamp when we start probing. */
+    guint32 probing_timeout_msec;
 
-    /* The probe result. This is only relevant if @acd_state is ACD_STATE_PROBE_DONE.
-     * In state ACD_STATE_ANNOUNCING the @probe_result must be TRUE. */
-    bool probe_result : 1;
+    NMEtherAddr last_conflict_addr;
 
-    bool announcing_failed_is_retrying : 1;
+    NML3AcdDefendType acd_defend_type_desired : 3;
+    NML3AcdDefendType acd_defend_type_current : 3;
+    bool              acd_defend_type_is_active : 1;
 
-    bool initializing : 1;
+    bool track_infos_changed : 1;
 } AcdData;
 
+G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdData, info.addr) == 0);
+
 struct _NML3CfgCommitTypeHandle {
     CList             commit_type_lst;
     NML3CfgCommitType commit_type;
@@ -133,11 +125,12 @@ typedef struct {
         };
         guint32 default_route_penalty_x[2];
     };
-    gconstpointer tag;
-    guint64       pseudo_timestamp;
-    int           priority;
-    guint32       acd_timeout_msec;
-    bool          dirty : 1;
+    gconstpointer     tag_confdata;
+    guint64           pseudo_timestamp_confdata;
+    int               priority_confdata;
+    guint32           acd_timeout_msec_confdata;
+    NML3AcdDefendType acd_defend_type_confdata : 3;
+    bool              dirty_confdata : 1;
 } L3ConfigData;
 
 /*****************************************************************************/
@@ -155,6 +148,8 @@ typedef struct _NML3CfgPrivate {
     GArray *property_emit_list;
     GArray *l3_config_datas;
 
+    NML3IPv4LL *ipv4ll;
+
     const NML3ConfigData *combined_l3cd_merged;
 
     const NML3ConfigData *combined_l3cd_commited;
@@ -170,11 +165,14 @@ typedef struct _NML3CfgPrivate {
     GHashTable *acd_lst_hash;
     CList       acd_lst_head;
 
-    CList acd_notify_complete_lst_head;
+    CList acd_event_notify_lst_head;
 
     NAcd *   nacd;
     GSource *nacd_source;
 
+    GSource *nacd_event_down_source;
+    gint64   nacd_event_down_ratelimited_until_msec;
+
     /* This is for rate-limiting the creation of nacd instance. */
     GSource *nacd_instance_ensure_retry;
 
@@ -198,16 +196,35 @@ typedef struct _NML3CfgPrivate {
         guint externally_removed_objs_cnt_routes_x[2];
     };
 
+    union {
+        struct {
+            GPtrArray *last_addresses_6;
+            GPtrArray *last_addresses_4;
+        };
+        GPtrArray *last_addresses_x[2];
+    };
+
+    union {
+        struct {
+            GPtrArray *last_routes_6;
+            GPtrArray *last_routes_4;
+        };
+        GPtrArray *last_routes_x[2];
+    };
+
     guint routes_temporary_not_available_id;
 
+    gint8 commit_reentrant_count;
+
     bool commit_type_update_sticky : 1;
 
     bool acd_is_pending : 1;
-    bool acd_is_announcing : 1;
 
     bool nacd_acd_not_supported : 1;
     bool acd_ipv4_addresses_on_link_has : 1;
 
+    bool changed_configs_configs : 1;
+    bool changed_configs_acd_state : 1;
 } NML3CfgPrivate;
 
 struct _NML3CfgClass {
@@ -234,15 +251,16 @@ G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT)
     }                                                                         \
     G_STMT_END
 
-#define _LOGT_acd(acd_data, ...)                                \
-    G_STMT_START                                                \
-    {                                                           \
-        char _sbuf_acd[NM_UTILS_INET_ADDRSTRLEN];               \
-                                                                \
-        _LOGT("acd[%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),   \
-              _nm_utils_inet4_ntop((acd_data)->addr, _sbuf_acd) \
-                  _NM_UTILS_MACRO_REST(__VA_ARGS__));           \
-    }                                                           \
+#define _LOGT_acd(acd_data, ...)                                      \
+    G_STMT_START                                                      \
+    {                                                                 \
+        char _sbuf_acd[NM_UTILS_INET_ADDRSTRLEN];                     \
+                                                                      \
+        _LOGT("acd[%s, %s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),     \
+              _nm_utils_inet4_ntop((acd_data)->info.addr, _sbuf_acd), \
+              _l3_acd_addr_state_to_string((acd_data)->info.state)    \
+                  _NM_UTILS_MACRO_REST(__VA_ARGS__));                 \
+    }                                                                 \
     G_STMT_END
 
 /*****************************************************************************/
@@ -251,7 +269,7 @@ static void _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is
 
 static void _property_emit_notify(NML3Cfg *self, NML3CfgPropertyEmitType emit_type);
 
-static void _l3_acd_data_notify_acd_completed_all(NML3Cfg *self);
+static void _nm_l3cfg_emit_signal_notify_acd_event_all(NML3Cfg *self);
 
 static gboolean _acd_has_valid_link(const NMPObject *obj,
                                     const guint8 **  out_addr_bin,
@@ -260,12 +278,11 @@ static gboolean _acd_has_valid_link(const NMPObject *obj,
 static void
 _l3_acd_nacd_instance_reset(NML3Cfg *self, NMTernary start_timer, gboolean acd_data_notify);
 
-static void _l3_acd_data_prune(NML3Cfg *self, gboolean all);
-
 static void _l3_acd_data_state_change(NML3Cfg *          self,
                                       AcdData *          acd_data,
                                       AcdStateChangeMode mode,
-                                      NAcdEvent *        event);
+                                      const NMEtherAddr *sender,
+                                      gint64 *           p_now_msec);
 
 static AcdData *_l3_acd_data_find(NML3Cfg *self, in_addr_t addr);
 
@@ -282,7 +299,8 @@ static NM_UTILS_ENUM2STR_DEFINE(_l3_cfg_commit_type_to_string,
 static NM_UTILS_ENUM2STR_DEFINE(
     _l3_config_notify_type_to_string,
     NML3ConfigNotifyType,
-    NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED, "acd-complete"),
+    NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT, "acd-event"),
+    NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT, "ipv4ll-event"),
     NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, "platform-change"),
     NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, "platform-change-on-idle"),
     NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"),
@@ -290,6 +308,36 @@ static NM_UTILS_ENUM2STR_DEFINE(
                       "routes-temporary-not-available-expired"),
     NM_UTILS_ENUM2STR_IGNORE(_NM_L3_CONFIG_NOTIFY_TYPE_NUM), );
 
+static NM_UTILS_ENUM2STR_DEFINE(_l3_acd_defend_type_to_string,
+                                NML3AcdDefendType,
+                                NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_ALWAYS, "always"),
+                                NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_NEVER, "never"),
+                                NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_NONE, "none"),
+                                NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_ONCE, "once"), );
+
+static NM_UTILS_LOOKUP_DEFINE(_l3_acd_defend_type_to_nacd,
+                              NML3AcdDefendType,
+                              int,
+                              NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(0),
+                              NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_ALWAYS,
+                                                   N_ACD_DEFEND_ALWAYS),
+                              NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_ONCE, N_ACD_DEFEND_ONCE),
+                              NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_NEVER, N_ACD_DEFEND_NEVER),
+                              NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), );
+
+static NM_UTILS_LOOKUP_DEFINE(_l3_acd_addr_state_to_string,
+                              NML3AcdAddrState,
+                              const char *,
+                              NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(NULL),
+                              NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_CONFLICT, "conflict"),
+                              NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_READY, "ready"),
+                              NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_DEFENDING, "defending"),
+                              NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_INIT, "init"),
+                              NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_PROBING, "probing"),
+                              NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED,
+                                                   "external-removed"),
+                              NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_USED, "used"), );
+
 /*****************************************************************************/
 
 static const char *
@@ -297,9 +345,11 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data,
                                  char *                      sbuf,
                                  gsize                       sbuf_size)
 {
-    char  sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
-    char *s = sbuf;
-    gsize l = sbuf_size;
+    char      sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+    char      sbuf100[100];
+    char *    s = sbuf;
+    gsize     l = sbuf_size;
+    in_addr_t addr4;
 
     nm_assert(sbuf);
     nm_assert(sbuf_size > 0);
@@ -308,12 +358,12 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data,
     nm_utils_strbuf_seek_end(&s, &l);
 
     switch (notify_data->notify_type) {
-    case NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED:
+    case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT:
         nm_utils_strbuf_append(&s,
                                &l,
-                               ", addr=%s, probe-result=%d",
-                               _nm_utils_inet4_ntop(notify_data->acd_completed.addr, sbuf_addr),
-                               (int) notify_data->acd_completed.probe_result);
+                               ", addr=%s, state=%s",
+                               _nm_utils_inet4_ntop(notify_data->acd_event.info.addr, sbuf_addr),
+                               _l3_acd_addr_state_to_string(notify_data->acd_event.info.state));
         break;
     case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE:
         nm_utils_strbuf_append(
@@ -330,6 +380,19 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data,
                                ", obj-type-flags=0x%x",
                                notify_data->platform_change_on_idle.obj_type_flags);
         break;
+    case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT:
+        nm_assert(NM_IS_L3_IPV4LL(notify_data->ipv4ll_event.ipv4ll));
+        addr4 = nm_l3_ipv4ll_get_addr(notify_data->ipv4ll_event.ipv4ll);
+        nm_utils_strbuf_append(
+            &s,
+            &l,
+            ", ipv4ll=" NM_HASH_OBFUSCATE_PTR_FMT "%s%s, state=%s",
+            NM_HASH_OBFUSCATE_PTR(notify_data->ipv4ll_event.ipv4ll),
+            NM_PRINT_FMT_QUOTED2(addr4 != 0, ", addr=", _nm_utils_inet4_ntop(addr4, sbuf_addr), ""),
+            nm_l3_ipv4ll_state_to_string(nm_l3_ipv4ll_get_state(notify_data->ipv4ll_event.ipv4ll),
+                                         sbuf100,
+                                         sizeof(sbuf100)));
+        break;
     default:
         break;
     }
@@ -365,8 +428,9 @@ _nm_l3cfg_emit_signal_notify_simple(NML3Cfg *self, NML3ConfigNotifyType notify_t
 static void
 _l3_changed_configs_set_dirty(NML3Cfg *self)
 {
-    _LOGT("configuration changed");
-    self->priv.changed_configs = TRUE;
+    _LOGT("IP configuration changed (mark dirty)");
+    self->priv.p->changed_configs_configs   = TRUE;
+    self->priv.p->changed_configs_acd_state = TRUE;
 }
 
 /*****************************************************************************/
@@ -386,7 +450,11 @@ _l3_acd_ipv4_addresses_on_link_update(NML3Cfg * self,
         else
             self->priv.p->acd_ipv4_addresses_on_link_has = FALSE;
         if (acd_data)
-            _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED, NULL);
+            _l3_acd_data_state_change(self,
+                                      acd_data,
+                                      ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED,
+                                      NULL,
+                                      NULL);
         return;
     }
 
@@ -396,8 +464,13 @@ _l3_acd_ipv4_addresses_on_link_update(NML3Cfg * self,
      * cached addresses, and fetch them new the next time we need the information. */
     nm_clear_pointer(&self->priv.p->acd_ipv4_addresses_on_link, g_hash_table_unref);
     self->priv.p->acd_ipv4_addresses_on_link_has = FALSE;
-    if (acd_data)
-        _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED, NULL);
+    if (acd_data) {
+        _l3_acd_data_state_change(self,
+                                  acd_data,
+                                  ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED,
+                                  NULL,
+                                  NULL);
+    }
 }
 
 static gboolean
@@ -499,15 +572,15 @@ _l3cfg_externally_removed_objs_drop_unused(NML3Cfg *self)
 
     g_hash_table_iter_init(&h_iter, self->priv.p->externally_removed_objs_hash);
     while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj, NULL)) {
-        if (!nm_l3_config_data_lookup_route_obj(self->priv.p->combined_l3cd_commited, obj)) {
+        if (!nm_l3_config_data_lookup_obj(self->priv.p->combined_l3cd_commited, obj)) {
             /* The object is no longer tracked in the configuration.
              * The externally_removed_objs_hash is to prevent adding entires that were
              * removed externally, so if we don't plan to add the entry, we no longer need to track
              * it. */
-            (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))--;
-            g_hash_table_iter_remove(&h_iter);
             _LOGD("externally-removed: untrack %s",
                   nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+            (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))--;
+            g_hash_table_iter_remove(&h_iter);
         }
     }
 }
@@ -525,10 +598,17 @@ _l3cfg_externally_removed_objs_track(NML3Cfg *self, const NMPObject *obj, gboole
     if (!is_removed) {
         /* the object is still (or again) present. It no longer gets hidden. */
         if (self->priv.p->externally_removed_objs_hash) {
-            if (g_hash_table_remove(self->priv.p->externally_removed_objs_hash, obj)) {
-                (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))--;
+            const NMPObject *obj2;
+            gpointer         x_val;
+
+            if (g_hash_table_steal_extended(self->priv.p->externally_removed_objs_hash,
+                                            obj,
+                                            (gpointer *) &obj2,
+                                            &x_val)) {
+                (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj2))))--;
                 _LOGD("externally-removed: untrack %s",
-                      nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+                      nmp_object_to_string(obj2, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+                nmp_object_unref(obj2);
             }
         }
         return;
@@ -558,26 +638,24 @@ _l3cfg_externally_removed_objs_track(NML3Cfg *self, const NMPObject *obj, gboole
 static void
 _l3cfg_externally_removed_objs_pickup(NML3Cfg *self, int addr_family)
 {
-    const gboolean   IS_IPv4 = NM_IS_IPv4(addr_family);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
     NMDedupMultiIter iter;
     const NMPObject *obj;
 
     if (!self->priv.p->combined_l3cd_commited)
         return;
 
-    nm_l3_config_data_iter_obj_for_each(&iter,
-                                        self->priv.p->combined_l3cd_commited,
-                                        &obj,
-                                        NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4))
-    {
+    nm_l3_config_data_iter_obj_for_each (&iter,
+                                         self->priv.p->combined_l3cd_commited,
+                                         &obj,
+                                         NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
         if (!nm_platform_lookup_entry(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj))
             _l3cfg_externally_removed_objs_track(self, obj, TRUE);
     }
-    nm_l3_config_data_iter_obj_for_each(&iter,
-                                        self->priv.p->combined_l3cd_commited,
-                                        &obj,
-                                        NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4))
-    {
+    nm_l3_config_data_iter_obj_for_each (&iter,
+                                         self->priv.p->combined_l3cd_commited,
+                                         &obj,
+                                         NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
         if (!nm_platform_lookup_entry(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj))
             _l3cfg_externally_removed_objs_track(self, obj, TRUE);
     }
@@ -590,7 +668,11 @@ _l3cfg_externally_removed_objs_filter(/* const NMDedupMultiObj * */ gconstpointe
     const NMPObject *obj                          = o;
     GHashTable *     externally_removed_objs_hash = user_data;
 
-    return !g_hash_table_contains(externally_removed_objs_hash, obj);
+    if (NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ADDRESS
+        && NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready)
+        return FALSE;
+
+    return !nm_g_hash_table_contains(externally_removed_objs_hash, obj);
 }
 
 /*****************************************************************************/
@@ -664,9 +746,16 @@ _load_link(NML3Cfg *self, gboolean initial)
         _l3_acd_nacd_instance_reset(self, NM_TERNARY_FALSE, TRUE);
     } else if (nacd_link_now_up) {
         if (!c_list_is_empty(&self->priv.p->acd_lst_head)) {
+            gint64 now_msec = 0;
+
             _LOGT("acd: link up requires are re-initialize of ACD probes");
-            c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
-                _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_LINK_NOW_UP, NULL);
+            c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
+                _l3_acd_data_state_change(self,
+                                          acd_data,
+                                          ACD_STATE_CHANGE_MODE_LINK_NOW_UP,
+                                          NULL,
+                                          &now_msec);
+            }
         }
     }
 }
@@ -687,7 +776,7 @@ _nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint32 obj_type_flags)
     };
     _nm_l3cfg_emit_signal_notify(self, &notify_data);
 
-    _l3_acd_data_notify_acd_completed_all(self);
+    _nm_l3cfg_emit_signal_notify_acd_event_all(self);
 
     if (NM_FLAGS_ANY(obj_type_flags, nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP4_ROUTE)))
         _property_emit_notify(self, NM_L3CFG_PROPERTY_EMIT_TYPE_IP4_ROUTE);
@@ -891,84 +980,77 @@ nm_l3cfg_get_acd_is_pending(NML3Cfg *self)
 }
 
 static gboolean
-_acd_track_data_is_not_dirty(const AcdTrackData *acd_track)
+_acd_track_data_is_not_dirty(const NML3AcdAddrTrackInfo *acd_track)
 {
-    return acd_track && !acd_track->acd_dirty;
+    return acd_track && !acd_track->_priv.acd_dirty_track;
 }
 
 static void
-_acd_track_data_free(AcdTrackData *acd_track)
+_acd_track_data_clear(NML3AcdAddrTrackInfo *acd_track)
 {
-    c_list_unlink_stale(&acd_track->acd_track_lst);
     nm_l3_config_data_unref(acd_track->l3cd);
     nmp_object_unref(acd_track->obj);
-    nm_g_slice_free(acd_track);
 }
 
 static void
 _acd_data_free(AcdData *acd_data)
 {
-    nm_assert(c_list_is_empty(&acd_data->acd_track_lst_head));
+    nm_assert(acd_data->info.n_track_infos == 0u);
 
     n_acd_probe_free(acd_data->nacd_probe);
-    nm_clear_g_source_inst(&acd_data->acd_timeout_source);
+    nm_clear_g_source_inst(&acd_data->acd_data_timeout_source);
     c_list_unlink_stale(&acd_data->acd_lst);
+    c_list_unlink_stale(&acd_data->acd_event_notify_lst);
+    g_free((NML3AcdAddrTrackInfo *) acd_data->info.track_infos);
     nm_g_slice_free(acd_data);
 }
 
-static gboolean
-_acd_data_probe_result_is_good(const AcdData *acd_data)
-{
-    nm_assert(acd_data);
-
-    if (acd_data->acd_state < ACD_STATE_PROBE_DONE) {
-        /* we are currently probing. Wait. */
-        return FALSE;
-    }
-
-    /* Probing is already completed. Use the probe result. */
-    return acd_data->probe_result;
-}
-
 static guint
-_acd_data_collect_tracks_data(const AcdData *acd_data,
-                              NMTernary      dirty_selector,
-                              NMTernary      acd_failed_notified_selector,
-                              guint32 *      out_best_acd_timeout_msec)
-{
-    guint32       best_acd_timeout_msec = G_MAXUINT32;
-    AcdTrackData *acd_track;
-    guint         n = 0;
+_acd_data_collect_tracks_data(const AcdData *    acd_data,
+                              NMTernary          dirty_selector,
+                              guint32 *          out_best_acd_timeout_msec,
+                              NML3AcdDefendType *out_best_acd_defend_type)
+{
+    NML3AcdDefendType best_acd_defend_type  = NM_L3_ACD_DEFEND_TYPE_NONE;
+    guint32           best_acd_timeout_msec = G_MAXUINT32;
+    guint             n                     = 0;
+    guint             i;
+
+    for (i = 0; i < acd_data->info.n_track_infos; i++) {
+        const NML3AcdAddrTrackInfo *acd_track = &acd_data->info.track_infos[i];
 
-    c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) {
         if (dirty_selector != NM_TERNARY_DEFAULT) {
-            if ((!!dirty_selector) != (!!acd_track->acd_dirty))
-                continue;
-        }
-        if (acd_failed_notified_selector != NM_TERNARY_DEFAULT) {
-            if ((!!acd_failed_notified_selector) != (!!acd_track->acd_failed_notified))
+            if ((!!dirty_selector) != (!!acd_track->_priv.acd_dirty_track))
                 continue;
         }
         n++;
-        if (best_acd_timeout_msec > acd_track->acd_timeout_msec)
-            best_acd_timeout_msec = acd_track->acd_timeout_msec;
+        if (best_acd_timeout_msec > acd_track->_priv.acd_timeout_msec_track)
+            best_acd_timeout_msec = acd_track->_priv.acd_timeout_msec_track;
+        if (best_acd_defend_type < acd_track->_priv.acd_defend_type_track)
+            best_acd_defend_type = acd_track->_priv.acd_defend_type_track;
     }
 
+    nm_assert(n == 0 || best_acd_defend_type > NM_L3_ACD_DEFEND_TYPE_NONE);
+    nm_assert(best_acd_defend_type <= NM_L3_ACD_DEFEND_TYPE_ALWAYS);
+
     NM_SET_OUT(out_best_acd_timeout_msec, n > 0 ? best_acd_timeout_msec : 0u);
+    NM_SET_OUT(out_best_acd_defend_type, best_acd_defend_type);
     return n;
 }
 
-static AcdTrackData *
+static NML3AcdAddrTrackInfo *
 _acd_data_find_track(const AcdData *       acd_data,
                      const NML3ConfigData *l3cd,
                      const NMPObject *     obj,
                      gconstpointer         tag)
 {
-    AcdTrackData *acd_track;
+    guint i;
+
+    for (i = 0; i < acd_data->info.n_track_infos; i++) {
+        const NML3AcdAddrTrackInfo *acd_track = &acd_data->info.track_infos[i];
 
-    c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) {
         if (acd_track->obj == obj && acd_track->l3cd == l3cd && acd_track->tag == tag)
-            return acd_track;
+            return (NML3AcdAddrTrackInfo *) acd_track;
     }
 
     return NULL;
@@ -993,7 +1075,7 @@ _acd_has_valid_link(const NMPObject *obj,
     link = NMP_OBJECT_CAST_LINK(obj);
 
     addr_bin = nmp_link_address_get(&link->l_address, &addr_len);
-    if (!addr_bin || addr_len != ACD_SUPPORTED_ETH_ALEN) {
+    if (addr_len != ACD_SUPPORTED_ETH_ALEN) {
         NM_SET_OUT(out_acd_not_supported, TRUE);
         return FALSE;
     }
@@ -1004,6 +1086,21 @@ _acd_has_valid_link(const NMPObject *obj,
 }
 
 static gboolean
+_l3_acd_nacd_event_down_timeout_cb(gpointer user_data)
+{
+    NML3Cfg *self = user_data;
+    AcdData *acd_data;
+    gint64   now_msec = 0;
+
+    _LOGT("acd: message possibly dropped due to device down (handle events)");
+    nm_clear_g_source_inst(&self->priv.p->nacd_event_down_source);
+    c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
+        _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_DOWN, NULL, &now_msec);
+    _nm_l3cfg_emit_signal_notify_acd_event_all(self);
+    return G_SOURCE_REMOVE;
+}
+
+static gboolean
 _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data)
 {
     NML3Cfg *self    = user_data;
@@ -1020,8 +1117,10 @@ _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data)
     }
 
     while (TRUE) {
-        AcdData *  acd_data;
-        NAcdEvent *event;
+        NMEtherAddr        sender_addr_data;
+        const NMEtherAddr *sender_addr;
+        AcdData *          acd_data;
+        NAcdEvent *        event;
 
         r = n_acd_pop_event(self->priv.p->nacd, &event);
         if (r) {
@@ -1033,77 +1132,83 @@ _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data)
             goto out;
         }
 
-#define _acd_event_payload used
-        G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NAcdEvent, _acd_event_payload)
-                             == G_STRUCT_OFFSET(NAcdEvent, defended));
-        G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NAcdEvent, _acd_event_payload)
-                             == G_STRUCT_OFFSET(NAcdEvent, conflict));
-        G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NAcdEvent, _acd_event_payload)
-                             == G_STRUCT_OFFSET(NAcdEvent, used));
-        nm_assert(&event->_acd_event_payload == &event->defended);
-        nm_assert(&event->_acd_event_payload == &event->conflict);
-        nm_assert(&event->_acd_event_payload == &event->used);
-
         switch (event->event) {
         case N_ACD_EVENT_READY:
-            n_acd_probe_get_userdata(event->_acd_event_payload.probe, (void **) &acd_data);
-            _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_READY, event);
+            n_acd_probe_get_userdata(event->ready.probe, (void **) &acd_data);
+            _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_READY, NULL, NULL);
             break;
         case N_ACD_EVENT_USED:
-            n_acd_probe_get_userdata(event->_acd_event_payload.probe, (void **) &acd_data);
-            _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_USED, event);
-            break;
         case N_ACD_EVENT_DEFENDED:
         case N_ACD_EVENT_CONFLICT:
         {
-            gs_free char *sender_str = NULL;
-            const char *  addr_str   = NULL;
-            char          sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
-
-            /* since we announce with N_ACD_DEFEND_ALWAYS, we don't actually expect any
-             * conflict reported and don't handle it. It would be complicated to de-configure
-             * the address. */
-            nm_assert(event->event == N_ACD_EVENT_DEFENDED);
-
-            n_acd_probe_get_userdata(event->_acd_event_payload.probe, (void **) &acd_data);
-            _LOGT_acd(acd_data,
-                      "address %s %s from %s",
-                      (addr_str = _nm_utils_inet4_ntop(acd_data->addr, sbuf_addr)),
-                      event->event == N_ACD_EVENT_DEFENDED ? "defended" : "conflict detected",
-                      (sender_str = nm_utils_bin2hexstr_full(event->_acd_event_payload.sender,
-                                                             event->_acd_event_payload.n_sender,
-                                                             ':',
-                                                             FALSE,
-                                                             NULL)));
-            if (event->event == N_ACD_EVENT_CONFLICT) {
-                _LOGW("IPv4 address collision detection sees conflict on interface %i%s%s%s for "
-                      "address %s from host %s",
-                      self->priv.ifindex,
-                      NM_PRINT_FMT_QUOTED(self->priv.plobj,
-                                          " (",
-                                          NMP_OBJECT_CAST_LINK(self->priv.plobj)->name,
-                                          ")",
-                                          ""),
-                      addr_str ?: _nm_utils_inet4_ntop(acd_data->addr, sbuf_addr),
-                      sender_str
-                          ?: (sender_str =
-                                  nm_utils_bin2hexstr_full(event->_acd_event_payload.sender,
-                                                           event->_acd_event_payload.n_sender,
-                                                           ':',
-                                                           FALSE,
-                                                           NULL)));
+#define _acd_event_payload_with_sender(event)          \
+    ({                                                 \
+        NAcdEvent *_event = (event);                   \
+                                                       \
+        nm_assert(event);                              \
+        nm_assert(NM_IN_SET(event->event,              \
+                            N_ACD_EVENT_USED,          \
+                            N_ACD_EVENT_DEFENDED,      \
+                            N_ACD_EVENT_CONFLICT));    \
+        nm_assert(&_event->used == &_event->defended); \
+        nm_assert(&_event->used == &_event->conflict); \
+        &_event->used;                                 \
+    })
+
+            n_acd_probe_get_userdata(_acd_event_payload_with_sender(event)->probe,
+                                     (void **) &acd_data);
+
+            if (_acd_event_payload_with_sender(event)->n_sender == ETH_ALEN) {
+                G_STATIC_ASSERT_EXPR(_nm_alignof(NMEtherAddr) == 1);
+                nm_assert(_acd_event_payload_with_sender(event)->sender);
+                memcpy(&sender_addr_data, _acd_event_payload_with_sender(event)->sender, ETH_ALEN);
+                sender_addr = &sender_addr_data;
+            } else {
+                nm_assert_not_reached();
+                sender_addr = &nm_ether_addr_zero;
             }
+
+            _l3_acd_data_state_change(self,
+                                      acd_data,
+                                      (AcdStateChangeMode) event->event,
+                                      sender_addr,
+                                      NULL);
             break;
         }
         case N_ACD_EVENT_DOWN:
-            _LOGT("acd: message possibly dropped due to device down.");
-            c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
-                _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_DOWN, NULL);
+            if (!self->priv.p->nacd_event_down_source) {
+                gint64  now_msec;
+                guint32 timeout_msec;
+
+                now_msec = nm_utils_get_monotonic_timestamp_msec();
+                if (self->priv.p->nacd_event_down_ratelimited_until_msec > 0
+                    && now_msec < self->priv.p->nacd_event_down_ratelimited_until_msec)
+                    timeout_msec = self->priv.p->nacd_event_down_ratelimited_until_msec - now_msec;
+                else {
+                    timeout_msec                                         = 0;
+                    self->priv.p->nacd_event_down_ratelimited_until_msec = now_msec + 2000;
+                }
+                _LOGT("acd: message possibly dropped due to device down (schedule handling event "
+                      "in %u msec)",
+                      timeout_msec);
+                self->priv.p->nacd_event_down_source =
+                    nm_g_timeout_source_new(timeout_msec,
+                                            G_PRIORITY_DEFAULT,
+                                            _l3_acd_nacd_event_down_timeout_cb,
+                                            self,
+                                            NULL);
+                g_source_attach(self->priv.p->nacd_event_down_source, NULL);
+            }
             break;
         default:
-            _LOGT("acd: unexpected event %u. Ignore", event->event);
+            _LOGE("acd: unexpected event %u. Ignore", event->event);
+            nm_assert_not_reached();
             break;
         }
+
+        /* We are on an idle handler, and the n-acd events are expected to be independent. So, after
+         * each event emit all queued AcdEvent signals. */
+        _nm_l3cfg_emit_signal_notify_acd_event_all(self);
     }
 
     nm_assert_not_reached();
@@ -1115,8 +1220,6 @@ out:
         _l3_acd_nacd_instance_reset(self, NM_TERNARY_TRUE, TRUE);
     }
 
-    _l3_acd_data_notify_acd_completed_all(self);
-
     return G_SOURCE_CONTINUE;
 }
 
@@ -1167,9 +1270,15 @@ _l3_acd_nacd_instance_reset(NML3Cfg *self, NMTernary start_timer, gboolean acd_d
 
     if (acd_data_notify) {
         AcdData *acd_data;
-
-        c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
-            _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_INSTANCE_RESET, NULL);
+        gint64   now_msec = 0;
+
+        c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
+            _l3_acd_data_state_change(self,
+                                      acd_data,
+                                      ACD_STATE_CHANGE_MODE_INSTANCE_RESET,
+                                      NULL,
+                                      &now_msec);
+        }
     }
 }
 
@@ -1275,30 +1384,37 @@ _l3_acd_nacd_instance_create_probe(NML3Cfg *    self,
 }
 
 static void
-_l3_acd_data_free_trackers(NML3Cfg *self, AcdData *acd_data, gboolean all /* or only dirty */)
+_l3_acd_data_prune_one(NML3Cfg *self, AcdData *acd_data, gboolean all /* or only dirty */)
 {
-    AcdTrackData *acd_track;
-    AcdTrackData *acd_track_safe;
+    NML3AcdAddrTrackInfo *acd_tracks;
+    guint                 i;
+    guint                 j;
+
+    acd_tracks = (NML3AcdAddrTrackInfo *) acd_data->info.track_infos;
+    j          = 0;
+    for (i = 0; i < acd_data->info.n_track_infos; i++) {
+        NML3AcdAddrTrackInfo *acd_track = &acd_tracks[i];
 
-    c_list_for_each_entry_safe (acd_track,
-                                acd_track_safe,
-                                &acd_data->acd_track_lst_head,
-                                acd_track_lst) {
         /* If not "all" is requested, we only delete the dirty ones
          * (and mark the survivors as dirty right away). */
-        if (!all && !acd_track->acd_dirty) {
-            acd_track->acd_dirty = TRUE;
+        if (!all && !acd_track->_priv.acd_dirty_track) {
+            acd_track->_priv.acd_dirty_track = TRUE;
+            if (j != i)
+                acd_tracks[j] = *acd_track;
+            j++;
             continue;
         }
 
         _LOGT_acd(acd_data, "untrack " ACD_TRACK_FMT "", ACD_TRACK_PTR(acd_track));
 
-        _acd_track_data_free(acd_track);
+        _acd_track_data_clear(acd_track);
     }
 
-    if (!c_list_is_empty(&acd_data->acd_track_lst_head))
+    acd_data->info.n_track_infos = j;
+    if (j > 0)
         return;
 
+    _LOGT_acd(acd_data, "removed");
     if (!g_hash_table_remove(self->priv.p->acd_lst_hash, acd_data))
         nm_assert_not_reached();
     _acd_data_free(acd_data);
@@ -1311,7 +1427,7 @@ _l3_acd_data_prune(NML3Cfg *self, gboolean all /* or only dirty */)
     AcdData *acd_data;
 
     c_list_for_each_entry_safe (acd_data, acd_data_safe, &self->priv.p->acd_lst_head, acd_lst)
-        _l3_acd_data_free_trackers(self, acd_data, all);
+        _l3_acd_data_prune_one(self, acd_data, all);
 }
 
 static AcdData *
@@ -1320,17 +1436,33 @@ _l3_acd_data_find(NML3Cfg *self, in_addr_t addr)
     return nm_g_hash_table_lookup(self->priv.p->acd_lst_hash, &addr);
 }
 
+static gboolean
+_l3_acd_data_defendconflict_warning_ratelimited(AcdData *acd_data, gint64 *p_now_msec)
+{
+    nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
+
+    if (acd_data->last_defendconflict_timestamp_msec == 0
+        || acd_data->last_defendconflict_timestamp_msec
+               > *p_now_msec - ACD_DEFENDCONFLICT_INFO_RATELIMIT_MSEC) {
+        acd_data->last_defendconflict_timestamp_msec = *p_now_msec;
+        return FALSE;
+    }
+    return TRUE;
+}
+
 static void
 _l3_acd_data_add(NML3Cfg *             self,
                  const NML3ConfigData *l3cd,
                  const NMPObject *     obj,
                  gconstpointer         tag,
+                 NML3AcdDefendType     acd_defend_type,
                  guint32               acd_timeout_msec)
 {
-    in_addr_t     addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address;
-    AcdTrackData *acd_track;
-    AcdData *     acd_data;
-    const char *  track_mode;
+    in_addr_t             addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address;
+    NML3AcdAddrTrackInfo *acd_track;
+    AcdData *             acd_data;
+    const char *          track_mode;
+    char                  sbuf100[100];
 
     if (ACD_ADDR_SKIP(addr))
         return;
@@ -1345,64 +1477,93 @@ _l3_acd_data_add(NML3Cfg *             self,
 
     if (!acd_data) {
         if (G_UNLIKELY(!self->priv.p->acd_lst_hash)) {
-            G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(AcdData, addr) == 0);
+            G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(AcdData, info.addr) == 0);
             self->priv.p->acd_lst_hash = g_hash_table_new(nm_puint32_hash, nm_puint32_equals);
         }
 
         acd_data  = g_slice_new(AcdData);
         *acd_data = (AcdData){
-            .self                    = self,
-            .addr                    = addr,
-            .acd_track_lst_head      = C_LIST_INIT(acd_data->acd_track_lst_head),
-            .acd_notify_complete_lst = C_LIST_INIT(acd_data->acd_notify_complete_lst),
-            .acd_state               = ACD_STATE_INIT,
-            .probing_timestamp_msec  = 0,
-            .probe_result            = FALSE,
-            .initializing            = TRUE,
+            .info =
+                {
+                    .l3cfg         = self,
+                    .addr          = addr,
+                    .state         = NM_L3_ACD_ADDR_STATE_INIT,
+                    .n_track_infos = 0,
+                    .track_infos   = NULL,
+                },
+            .n_track_infos_alloc       = 0,
+            .acd_event_notify_lst      = C_LIST_INIT(acd_data->acd_event_notify_lst),
+            .probing_timestamp_msec    = 0,
+            .acd_defend_type_desired   = NM_L3_ACD_DEFEND_TYPE_NONE,
+            .acd_defend_type_current   = NM_L3_ACD_DEFEND_TYPE_NONE,
+            .acd_defend_type_is_active = FALSE,
         };
         c_list_link_tail(&self->priv.p->acd_lst_head, &acd_data->acd_lst);
         if (!g_hash_table_add(self->priv.p->acd_lst_hash, acd_data))
             nm_assert_not_reached();
         acd_track = NULL;
-    } else {
+    } else
         acd_track = _acd_data_find_track(acd_data, l3cd, obj, tag);
-    }
 
     if (!acd_track) {
-        acd_track  = g_slice_new(AcdTrackData);
-        *acd_track = (AcdTrackData){
-            .l3cd             = nm_l3_config_data_ref(l3cd),
-            .obj              = nmp_object_ref(obj),
-            .tag              = tag,
-            .acd_dirty        = FALSE,
-            .acd_timeout_msec = acd_timeout_msec,
+        if (acd_data->info.n_track_infos >= acd_data->n_track_infos_alloc) {
+            acd_data->n_track_infos_alloc = NM_MAX(2u, acd_data->n_track_infos_alloc * 2u);
+            acd_data->info.track_infos =
+                g_realloc((gpointer) acd_data->info.track_infos,
+                          acd_data->n_track_infos_alloc * sizeof(acd_data->info.track_infos[0]));
+        }
+        acd_track =
+            (NML3AcdAddrTrackInfo *) &acd_data->info.track_infos[acd_data->info.n_track_infos++];
+        *acd_track = (NML3AcdAddrTrackInfo){
+            .l3cd                         = nm_l3_config_data_ref(l3cd),
+            .obj                          = nmp_object_ref(obj),
+            .tag                          = tag,
+            ._priv.acd_dirty_track        = FALSE,
+            ._priv.acd_defend_type_track  = acd_defend_type,
+            ._priv.acd_timeout_msec_track = acd_timeout_msec,
         };
-        c_list_link_tail(&acd_data->acd_track_lst_head, &acd_track->acd_track_lst);
         track_mode = "new";
     } else {
-        nm_assert(acd_track->acd_dirty);
-        acd_track->acd_dirty = FALSE;
-        if (acd_track->acd_timeout_msec != acd_timeout_msec) {
-            acd_track->acd_timeout_msec = acd_timeout_msec;
-            track_mode                  = "update";
+        nm_assert(acd_track->_priv.acd_dirty_track);
+        acd_track->_priv.acd_dirty_track = FALSE;
+        if (acd_track->_priv.acd_timeout_msec_track != acd_timeout_msec
+            || acd_track->_priv.acd_defend_type_track != acd_defend_type) {
+            acd_track->_priv.acd_defend_type_track  = acd_defend_type;
+            acd_track->_priv.acd_timeout_msec_track = acd_timeout_msec;
+            track_mode                              = "update";
         } else
-            track_mode = NULL;
+            return;
     }
 
-    if (track_mode) {
-        _LOGT_acd(acd_data,
-                  "track " ACD_TRACK_FMT " with timeout %u msec (%s)",
-                  ACD_TRACK_PTR(acd_track),
-                  acd_timeout_msec,
-                  track_mode);
-    }
+    acd_data->track_infos_changed = TRUE;
+    _LOGT_acd(acd_data,
+              "track " ACD_TRACK_FMT " with timeout %u msec, defend=%s (%s)",
+              ACD_TRACK_PTR(acd_track),
+              acd_timeout_msec,
+              _l3_acd_defend_type_to_string(acd_track->_priv.acd_defend_type_track,
+                                            sbuf100,
+                                            sizeof(sbuf100)),
+              track_mode);
 }
 
 static void
-_l3_acd_data_add_all(NML3Cfg *self, const L3ConfigData *const *infos, guint infos_len)
+_l3_acd_data_add_all(NML3Cfg *                  self,
+                     const L3ConfigData *const *infos,
+                     guint                      infos_len,
+                     gboolean                   reapply)
 {
     AcdData *acd_data;
     guint    i_info;
+    gint64   now_msec = 0;
+    guint    i;
+
+    if (NM_MORE_ASSERTS > 5) {
+        c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
+            nm_assert(acd_data->info.n_track_infos > 0u);
+            for (i = 0; i < acd_data->info.n_track_infos; i++)
+                nm_assert(acd_data->info.track_infos[i]._priv.acd_dirty_track);
+        }
+    }
 
     /* First we add/track all the relevant addresses for ACD. */
     for (i_info = 0; i_info < infos_len; i_info++) {
@@ -1410,63 +1571,54 @@ _l3_acd_data_add_all(NML3Cfg *self, const L3ConfigData *const *infos, guint info
         NMDedupMultiIter    iter;
         const NMPObject *   obj;
 
-        nm_l3_config_data_iter_obj_for_each(&iter, info->l3cd, &obj, NMP_OBJECT_TYPE_IP4_ADDRESS)
-            _l3_acd_data_add(self, info->l3cd, obj, info->tag, info->acd_timeout_msec);
+        nm_l3_config_data_iter_obj_for_each (&iter, info->l3cd, &obj, NMP_OBJECT_TYPE_IP4_ADDRESS) {
+            _l3_acd_data_add(self,
+                             info->l3cd,
+                             obj,
+                             info->tag_confdata,
+                             info->acd_defend_type_confdata,
+                             info->acd_timeout_msec_confdata);
+        }
     }
 
     /* Then we do a pre-flight check, whether some of the acd_data entries can already
      * move forward to automatically pass ACD. That is the case if acd_timeout_msec
      * is zero (to disable ACD) or if the address is already configured on the
      * interface. */
-    c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
-        _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_INIT, NULL);
+    c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
+        _l3_acd_data_state_change(self,
+                                  acd_data,
+                                  reapply ? ACD_STATE_CHANGE_MODE_INIT_REAPPLY
+                                          : ACD_STATE_CHANGE_MODE_INIT,
+                                  NULL,
+                                  &now_msec);
+    }
 }
 
 static gboolean
 _l3_acd_data_timeout_cb(gpointer user_data)
 {
     AcdData *acd_data = user_data;
-    NML3Cfg *self     = acd_data->self;
+    NML3Cfg *self     = acd_data->info.l3cfg;
 
     nm_assert(NM_IS_L3CFG(self));
 
-    nm_clear_g_source_inst(&acd_data->acd_timeout_source);
-    _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_TIMEOUT, NULL);
+    nm_clear_g_source_inst(&acd_data->acd_data_timeout_source);
+    _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_TIMEOUT, NULL, NULL);
     return G_SOURCE_REMOVE;
 }
 
 static void
-_l3_acd_data_timeout_schedule(AcdData *acd_data,
-                              gint64   now_msec,
-                              gint64   expiry_msec,
-                              gboolean msec_granularity)
+_l3_acd_data_timeout_schedule(AcdData *acd_data, gint64 timeout_msec)
 {
-    nm_assert(expiry_msec > 0);
-    nm_assert(now_msec > 0);
-
-    if (acd_data->acd_timeout_source && acd_data->acd_timeout_expiry_msec == expiry_msec)
-        return;
-
-    nm_clear_g_source_inst(&acd_data->acd_timeout_source);
-
-    acd_data->acd_timeout_expiry_msec = expiry_msec;
-
-    if (msec_granularity) {
-        acd_data->acd_timeout_source = nm_g_timeout_source_new(NM_MAX(0, expiry_msec - now_msec),
-                                                               G_PRIORITY_DEFAULT,
-                                                               _l3_acd_data_timeout_cb,
-                                                               acd_data,
-                                                               NULL);
-    } else {
-        acd_data->acd_timeout_source =
-            nm_g_timeout_source_new_seconds((NM_MAX(0, expiry_msec - now_msec) + 999) / 1000,
-                                            G_PRIORITY_DEFAULT,
-                                            _l3_acd_data_timeout_cb,
-                                            acd_data,
-                                            NULL);
-    }
-
-    g_source_attach(acd_data->acd_timeout_source, NULL);
+    nm_clear_g_source_inst(&acd_data->acd_data_timeout_source);
+    acd_data->acd_data_timeout_source =
+        nm_g_timeout_source_new(NM_CLAMP((gint64) 0, timeout_msec, (gint64) G_MAXUINT),
+                                G_PRIORITY_DEFAULT,
+                                _l3_acd_data_timeout_cb,
+                                acd_data,
+                                NULL);
+    g_source_attach(acd_data->acd_data_timeout_source, NULL);
 }
 
 static void
@@ -1477,7 +1629,7 @@ _l3_acd_data_timeout_schedule_probing_restart(AcdData *acd_data, gint64 now_msec
 
     nm_assert(acd_data);
     nm_assert(now_msec > 0);
-    nm_assert(acd_data->acd_state == ACD_STATE_PROBING);
+    nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING);
     nm_assert(!acd_data->nacd_probe);
     nm_assert(acd_data->probing_timeout_msec > 0);
     nm_assert(acd_data->probing_timestamp_msec > 0);
@@ -1486,152 +1638,168 @@ _l3_acd_data_timeout_schedule_probing_restart(AcdData *acd_data, gint64 now_msec
 
     timeout_msec = NM_MAX(0, expiry_msec - now_msec);
 
-    if (timeout_msec > 1000) {
-        /* we poll at least once per second to re-check the state. */
-        timeout_msec = 1000;
+    if (timeout_msec > 1500) {
+        /* we poll at least every 1.5 seconds to re-check the state. */
+        timeout_msec = 1500;
     }
 
-    _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec + timeout_msec, TRUE);
+    _l3_acd_data_timeout_schedule(acd_data, timeout_msec);
 }
 
 static void
-_l3_acd_data_timeout_schedule_probing_full_restart(AcdData *acd_data, gint64 now_msec)
+_nm_l3cfg_emit_signal_notify_acd_event(NML3Cfg *self, AcdData *acd_data)
 {
-    nm_assert(acd_data);
-    nm_assert(now_msec > 0);
-    nm_assert(acd_data->acd_state == ACD_STATE_PROBE_DONE);
-    nm_assert(!acd_data->probe_result);
-
-    _l3_acd_data_timeout_schedule(acd_data,
-                                  now_msec,
-                                  now_msec + ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC,
-                                  FALSE);
-}
+    gs_free NML3AcdAddrTrackInfo *track_infos_clone = NULL;
+    NML3ConfigNotifyData          notify_data;
+    NML3AcdAddrInfo *             info;
+    guint                         i;
 
-static void
-_l3_acd_data_timeout_schedule_announce_restart(AcdData *acd_data, gint64 now_msec)
-{
     nm_assert(acd_data);
-    nm_assert(now_msec > 0);
-    nm_assert(acd_data->acd_state == ACD_STATE_PROBE_DONE);
-    nm_assert(acd_data->probe_result);
-
-    _l3_acd_data_timeout_schedule(acd_data,
-                                  now_msec,
-                                  now_msec + ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC,
-                                  FALSE);
-}
+    nm_assert(acd_data->info.state > NM_L3_ACD_ADDR_STATE_INIT);
+    nm_assert(acd_data->info.n_track_infos > 0);
 
-static void
-_l3_acd_data_notify_acd_completed(NML3Cfg *self, AcdData *acd_data, gboolean force_all)
-{
-    gs_free NML3ConfigNotifyPayloadAcdFailedSource *sources_free = NULL;
-    NML3ConfigNotifyPayloadAcdFailedSource *        sources      = NULL;
-    NML3ConfigNotifyData                            notify_data;
-    AcdTrackData *                                  acd_track;
-    guint                                           i, n;
-    NMTernary                                       acd_failed_notified_selector;
-
-    nm_assert(NM_IS_L3CFG(self));
-    nm_assert(acd_data);
-    nm_assert(_acd_data_collect_tracks_data(acd_data, FALSE, NM_TERNARY_DEFAULT, NULL) == 0);
-
-    acd_failed_notified_selector = force_all ? NM_TERNARY_DEFAULT : FALSE;
+    notify_data.notify_type = NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT;
+    notify_data.acd_event   = (typeof(notify_data.acd_event)){
+        .info = acd_data->info,
+    };
 
-    n = _acd_data_collect_tracks_data(acd_data,
-                                      NM_TERNARY_DEFAULT,
-                                      acd_failed_notified_selector,
-                                      NULL);
+    /* we need to clone the track-data, because the callee is allowed to add/remove
+     * configs. This means, the event data is stale. If you need the current
+     * value, look it up with nm_l3cfg_get_acd_addr_info().  */
+    info              = &notify_data.acd_event.info;
+    info->track_infos = nm_memdup_maybe_a(300,
+                                          info->track_infos,
+                                          info->n_track_infos * sizeof(info->track_infos[0]),
+                                          &track_infos_clone);
 
-    if (n == 0)
-        return;
+    for (i = 0; i < info->n_track_infos; i++) {
+        NML3AcdAddrTrackInfo *ti = (NML3AcdAddrTrackInfo *) &info->track_infos[i];
 
-    if (!force_all) {
-        _LOGT_acd(acd_data, "state: acd probe failed earlier. Emit notification for new trackers");
+        nmp_object_ref(ti->obj);
+        nm_l3_config_data_ref(ti->l3cd);
     }
 
-    if (n * sizeof(sources[0]) > 300) {
-        sources_free = g_new(NML3ConfigNotifyPayloadAcdFailedSource, n);
-        sources      = sources_free;
-    } else
-        sources = g_newa(NML3ConfigNotifyPayloadAcdFailedSource, n);
-
-    i = 0;
-    c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) {
-        if (!force_all && acd_track->acd_failed_notified) {
-            /* already notified before. Skip. */
-            continue;
-        }
-        nm_assert(i < n);
-        acd_track->acd_failed_notified = TRUE;
-        sources[i++]                   = (NML3ConfigNotifyPayloadAcdFailedSource){
-            .obj  = nmp_object_ref(acd_track->obj),
-            .l3cd = nm_l3_config_data_ref(acd_track->l3cd),
-            .tag  = acd_track->tag,
-        };
-    }
-    nm_assert(i == n);
-
-    notify_data.notify_type   = NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED;
-    notify_data.acd_completed = (typeof(notify_data.acd_completed)){
-        .addr         = acd_data->addr,
-        .probe_result = acd_data->probe_result,
-        .sources_len  = n,
-        .sources      = sources,
-    };
     _nm_l3cfg_emit_signal_notify(self, &notify_data);
 
-    for (i = 0; i < n; i++) {
-        nmp_object_unref(sources[i].obj);
-        nm_l3_config_data_unref(sources[i].l3cd);
+    for (i = 0; i < info->n_track_infos; i++) {
+        NML3AcdAddrTrackInfo *ti = (NML3AcdAddrTrackInfo *) &info->track_infos[i];
+
+        nmp_object_unref(ti->obj);
+        nm_l3_config_data_unref(ti->l3cd);
     }
 }
 
 static void
-_l3_acd_data_notify_acd_completed_queue(NML3Cfg *self, AcdData *acd_data)
+_nm_l3cfg_emit_signal_notify_acd_event_queue(NML3Cfg *self, AcdData *acd_data)
 {
-    if (!c_list_is_empty(&acd_data->acd_notify_complete_lst)) {
-        nm_assert(c_list_contains(&self->priv.p->acd_notify_complete_lst_head,
-                                  &acd_data->acd_notify_complete_lst));
+    if (!c_list_is_empty(&acd_data->acd_event_notify_lst)) {
+        nm_assert(c_list_contains(&self->priv.p->acd_event_notify_lst_head,
+                                  &acd_data->acd_event_notify_lst));
         return;
     }
-    c_list_link_tail(&self->priv.p->acd_notify_complete_lst_head,
-                     &acd_data->acd_notify_complete_lst);
+    c_list_link_tail(&self->priv.p->acd_event_notify_lst_head, &acd_data->acd_event_notify_lst);
 }
 
 static void
-_l3_acd_data_notify_acd_completed_all(NML3Cfg *self)
+_nm_l3cfg_emit_signal_notify_acd_event_all(NML3Cfg *self)
 {
     gs_unref_object NML3Cfg *self_keep_alive = NULL;
     AcdData *                acd_data;
 
-    while ((acd_data = c_list_first_entry(&self->priv.p->acd_notify_complete_lst_head,
+    while ((acd_data = c_list_first_entry(&self->priv.p->acd_event_notify_lst_head,
                                           AcdData,
-                                          acd_notify_complete_lst))) {
+                                          acd_event_notify_lst))) {
         if (!self_keep_alive)
             self_keep_alive = g_object_ref(self);
-        c_list_unlink(&acd_data->acd_notify_complete_lst);
-        _l3_acd_data_notify_acd_completed(self, acd_data, TRUE);
+        c_list_unlink(&acd_data->acd_event_notify_lst);
+        _nm_l3cfg_emit_signal_notify_acd_event(self, acd_data);
     }
 }
 
+_nm_printf(5, 6) static void _l3_acd_data_state_set_full(NML3Cfg *        self,
+                                                         AcdData *        acd_data,
+                                                         NML3AcdAddrState state,
+                                                         gboolean         allow_commit,
+                                                         const char *     format,
+                                                         ...)
+{
+    NML3AcdAddrState old_state;
+    gboolean         changed;
+
+    if (acd_data->info.state == state)
+        return;
+
+    /* in every state we only have one timer possibly running. Resetting
+     * the states makes the previous timeout obsolete. */
+    nm_clear_g_source_inst(&acd_data->acd_data_timeout_source);
+
+    old_state            = acd_data->info.state;
+    acd_data->info.state = state;
+    _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data);
+
+    if (state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED)
+        changed = FALSE;
+    else if (NM_IN_SET(old_state, NM_L3_ACD_ADDR_STATE_READY, NM_L3_ACD_ADDR_STATE_DEFENDING)
+             != NM_IN_SET(state, NM_L3_ACD_ADDR_STATE_READY, NM_L3_ACD_ADDR_STATE_DEFENDING))
+        changed = TRUE;
+    else
+        changed = FALSE;
+
+    if (format) {
+        gs_free char *msg = NULL;
+        va_list       args;
+
+        va_start(args, format);
+        msg = g_strdup_vprintf(format, args);
+        va_end(args);
+
+        _LOGT_acd(acd_data, "set state to %s (%s)", _l3_acd_addr_state_to_string(state), msg);
+    } else
+        _LOGT_acd(acd_data, "set state to %s", _l3_acd_addr_state_to_string(state));
+
+    if (changed && allow_commit) {
+        /* The availability of an address just changed (and we are instructed to
+         * trigger a new commit). Do it. */
+        _l3_changed_configs_set_dirty(self);
+        nm_l3cfg_commit_on_idle_schedule(self);
+    }
+}
+
+static void
+_l3_acd_data_state_set(NML3Cfg *        self,
+                       AcdData *        acd_data,
+                       NML3AcdAddrState state,
+                       gboolean         allow_commit)
+{
+    _l3_acd_data_state_set_full(self, acd_data, state, allow_commit, NULL);
+}
+
 static void
 _l3_acd_data_state_change(NML3Cfg *          self,
                           AcdData *          acd_data,
                           AcdStateChangeMode state_change_mode,
-                          NAcdEvent *        event)
+                          const NMEtherAddr *sender_addr,
+                          gint64 *           p_now_msec)
+
 {
-    guint32     acd_timeout_msec;
-    gint64      now_msec = 0;
-    const char *log_reason;
-    gboolean    was_probing;
+    guint32           acd_timeout_msec;
+    NML3AcdDefendType acd_defend_type;
+    gint64            now_msec;
+    const char *      log_reason;
+    char              sbuf256[256];
+    char              sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+
+    if (!p_now_msec) {
+        now_msec   = 0;
+        p_now_msec = &now_msec;
+    }
 
     /* Keeping track of ACD inevitably requires keeping (and mutating) state. Then a multitude of
      * things can happen, and depending on the state, we need to do something.
      *
      * Here, all the state for one address that we probe/announce is tracked in AcdData/acd_data.
      *
-     * The acd_data has a list of AcdTrackData/acd_track_lst_head, which are configuration items
+     * The acd_data has a list of NML3AcdAddrTrackInfo/acd_track_lst_head, which are configuration items
      * that are interested in configuring this address. The "owners" of the ACD check for a certain
      * address.
      *
@@ -1644,415 +1812,533 @@ _l3_acd_data_state_change(NML3Cfg *          self,
 
     nm_assert(NM_IS_L3CFG(self));
     nm_assert(acd_data);
-    nm_assert(!c_list_is_empty(&acd_data->acd_track_lst_head));
-
-    was_probing = acd_data->acd_state < ACD_STATE_PROBE_DONE;
+    nm_assert(acd_data->info.n_track_infos);
+    nm_assert(NM_IN_SET(acd_data->info.state,
+                        NM_L3_ACD_ADDR_STATE_CONFLICT,
+                        NM_L3_ACD_ADDR_STATE_READY,
+                        NM_L3_ACD_ADDR_STATE_DEFENDING,
+                        NM_L3_ACD_ADDR_STATE_INIT,
+                        NM_L3_ACD_ADDR_STATE_PROBING,
+                        NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED,
+                        NM_L3_ACD_ADDR_STATE_USED));
+    nm_assert(!acd_data->track_infos_changed
+              || NM_IN_SET(state_change_mode,
+                           ACD_STATE_CHANGE_MODE_INIT,
+                           ACD_STATE_CHANGE_MODE_INIT_REAPPLY,
+                           ACD_STATE_CHANGE_MODE_POST_COMMIT,
+                           ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED,
+                           ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED));
+    nm_assert((!!sender_addr)
+              == NM_IN_SET(state_change_mode,
+                           ACD_STATE_CHANGE_MODE_NACD_USED,
+                           ACD_STATE_CHANGE_MODE_NACD_CONFLICT,
+                           ACD_STATE_CHANGE_MODE_NACD_DEFENDED));
+
+    if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED) {
+        /* once remove, the state can only change by external added or during
+         * the POST-COMMIT check. */
+        if (!NM_IN_SET(state_change_mode,
+                       ACD_STATE_CHANGE_MODE_POST_COMMIT,
+                       ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED))
+            return;
+    }
 
     switch (state_change_mode) {
     case ACD_STATE_CHANGE_MODE_INIT:
-    {
-        AcdTrackData *acd_track;
-        gboolean      any_no_timeout;
-
-        /* we are called from _l3_acd_data_add_all(), and we do a fast check whether
-         * newly tracked entries already passed ACD so that we can use the address
-         * right away. */
-
-        if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->addr)) {
-            /* the address is already configured on the link. It is an automatic pass. */
-            if (_acd_data_collect_tracks_data(acd_data, FALSE, NM_TERNARY_DEFAULT, NULL) <= 0) {
-                /* The entry has no non-dirty trackers, that means, it's no longer referenced
-                 * and will be removed during the next _l3_acd_data_prune(). We can ignore
-                 * this entry. */
+    case ACD_STATE_CHANGE_MODE_INIT_REAPPLY:
+
+        /* We are called right before commit. We check whether we have a acd_data
+         * in INIT or PROBING state. In that case, maybe the new configuration
+         * disables ACD, or we have the address already configured (which also let's
+         * us skip/cancel the probing). The point is that if the address would be ready
+         * already, we want to commit it right away. */
+
+        switch (acd_data->info.state) {
+        case NM_L3_ACD_ADDR_STATE_PROBING:
+        case NM_L3_ACD_ADDR_STATE_INIT:
+        case NM_L3_ACD_ADDR_STATE_USED:
+            goto handle_init;
+        case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+        case NM_L3_ACD_ADDR_STATE_CONFLICT:
+        case NM_L3_ACD_ADDR_STATE_READY:
+        case NM_L3_ACD_ADDR_STATE_DEFENDING:
+            if (state_change_mode != ACD_STATE_CHANGE_MODE_INIT_REAPPLY)
                 return;
+            goto handle_init;
+        }
+        nm_assert_not_reached();
+        return;
+
+handle_init:
+        if (_acd_data_collect_tracks_data(acd_data,
+                                          NM_TERNARY_FALSE,
+                                          &acd_timeout_msec,
+                                          &acd_defend_type)
+            <= 0u) {
+            /* the acd_data has no active trackers. It will soon be pruned. */
+            return;
+        }
+
+        if (acd_timeout_msec == 0u)
+            log_reason = "acd disabled by configuration";
+        else if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr))
+            log_reason = "address already configured";
+        else {
+            if (state_change_mode == ACD_STATE_CHANGE_MODE_INIT_REAPPLY) {
+                /* during a reapply, we forget all the state and start from scratch. */
+                _LOGT_acd(acd_data, "reset state for reapply");
+                acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+                _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_INIT, FALSE);
             }
-            log_reason = "address initially already configured";
-            goto handle_probing_acd_good;
+            return;
         }
 
-        /* we are called at the end of _l3_acd_data_add_all(). We updated the list of a
-         * all tracked IP addresses before we actually collect the addresses that are
-         * ready. We don't do regular handling of ACD states at this point, however,
-         * we check whether ACD for new elements is disabled entirely, so we can signal
-         * the address are ready right away (without going through another hop). */
+        _LOGT_acd(acd_data,
+                  "%s probing (%s, during pre-check)",
+                  acd_data->info.state == NM_L3_ACD_ADDR_STATE_INIT ? "skip" : "cancel",
+                  log_reason);
+        acd_data->nacd_probe              = n_acd_probe_free(acd_data->nacd_probe);
+        acd_data->acd_defend_type_desired = acd_defend_type;
+        _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_READY, FALSE);
+        return;
 
-        if (acd_data->acd_state != ACD_STATE_INIT) {
-            /* this element is not new and we don't perform the quick-check. */
+    case ACD_STATE_CHANGE_MODE_POST_COMMIT:
+
+        if (acd_data->track_infos_changed) {
+            acd_data->track_infos_changed = FALSE;
+            _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data);
+        }
+
+        if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) {
+            log_reason = "address already configured";
+            goto handle_probing_done;
+        }
+
+        if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED)
             return;
+
+        /* we just did a commit of the IP configuration and now visit all ACD states
+         * and kick off the necessary actions... */
+        if (_acd_data_collect_tracks_data(acd_data,
+                                          NM_TERNARY_TRUE,
+                                          &acd_timeout_msec,
+                                          &acd_defend_type)
+            <= 0)
+            nm_assert_not_reached();
+
+        acd_data->acd_defend_type_desired = acd_defend_type;
+
+        if (acd_timeout_msec <= 0) {
+            log_reason = "acd disabled by configuration";
+            goto handle_probing_done;
         }
 
-        any_no_timeout = FALSE;
-        c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) {
-            /* There should be no dirty trackers, because the element is in init-state. */
-            nm_assert(!acd_track->acd_dirty);
-            if (acd_track->acd_timeout_msec <= 0) {
-                /* ACD for this element is disabled. We can process is right away. */
-                any_no_timeout = TRUE;
-                break;
+        switch (acd_data->info.state) {
+        case NM_L3_ACD_ADDR_STATE_INIT:
+            nm_assert(!acd_data->nacd_probe);
+            nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
+            acd_data->probing_timestamp_msec = (*p_now_msec);
+            acd_data->probing_timeout_msec   = acd_timeout_msec;
+            _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data);
+            log_reason = "initial post-commit";
+            goto handle_start_probing;
+
+        case NM_L3_ACD_ADDR_STATE_PROBING:
+        {
+            gint64 old_expiry_msec;
+            gint64 new_expiry_msec;
+
+            nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
+
+            new_expiry_msec = (*p_now_msec) + acd_timeout_msec;
+            old_expiry_msec = acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec;
+
+            if (!acd_data->nacd_probe) {
+                /* we are currently waiting for restarting a probe. At this point, at most we have
+                 * to adjust the timeout/timestamp and let the regular timeouts handle this. */
+
+                if (new_expiry_msec >= old_expiry_msec) {
+                    /* the running timeout expires before the new timeout. We don't update the timestamp/timeout,
+                     * because we don't want to prolong the overall probing time. */
+                    return;
+                }
+                /* update the timers after out timeout got reduced. Also, reschedule the timeout
+                 * so that it expires immediately. */
+                acd_data->probing_timestamp_msec = (*p_now_msec);
+                acd_data->probing_timeout_msec   = acd_timeout_msec;
+                _l3_acd_data_timeout_schedule(acd_data, 0);
+                return;
+            }
+
+            if (new_expiry_msec >= old_expiry_msec) {
+                /* we already have ACD running with a timeout that expires before the requested one. There
+                 * is nothing to do at this time. */
+                return;
             }
+
+            /* the timeout got reduced. We try to restart the probe. */
+            acd_data->probing_timestamp_msec = (*p_now_msec);
+            acd_data->probing_timeout_msec   = acd_timeout_msec;
+            log_reason                       = "post-commit timeout update";
+            goto handle_start_probing;
         }
-        if (!any_no_timeout) {
-            /* there are elements that request the address, but they all specify
-             * an ACD timeout. We cannot progress the state. */
+
+        case NM_L3_ACD_ADDR_STATE_USED:
+        case NM_L3_ACD_ADDR_STATE_CONFLICT:
+            /* we are done for now. We however scheduled a timeout to restart. This
+             * will be handled with the ACD_STATE_CHANGE_MODE_TIMEOUT event. */
             return;
-        }
 
-        /* ACD is disabled, we can artificially moving the state further to
-         * ACD_STATE_PROBE_DONE and configure the address right away. This avoids
-         * that we go through another hop.
-         */
-        log_reason = "ACD disabled by configuration from the start";
-        goto handle_probing_acd_good;
-    }
+        case NM_L3_ACD_ADDR_STATE_READY:
+        case NM_L3_ACD_ADDR_STATE_DEFENDING:
+            goto handle_start_defending;
 
-    case ACD_STATE_CHANGE_MODE_POST_COMMIT:
-        acd_data->initializing = FALSE;
-        goto handle_post_commit;
+        case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+            nm_assert_not_reached();
+            return;
+        }
+        nm_assert_not_reached();
+        return;
 
     case ACD_STATE_CHANGE_MODE_TIMEOUT:
-    {
-        if (acd_data->acd_state == ACD_STATE_PROBING && !acd_data->nacd_probe) {
-            const char *failure_reason;
-            gboolean    acd_not_supported;
 
-            nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
+        switch (acd_data->info.state) {
+        case NM_L3_ACD_ADDR_STATE_INIT:
+            nm_assert_not_reached();
+            return;
+
+        case NM_L3_ACD_ADDR_STATE_PROBING:
+            if (acd_data->nacd_probe) {
+                /* we are already probing. There is nothing to do for this timeout. */
+                return;
+            }
+
+            nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
 
             if (acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC
                     + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC
-                >= now_msec) {
+                >= (*p_now_msec)) {
+                /* hm. We failed to create a new probe too long. Something is really wrong
+                 * internally, but let's ignore the issue and assume the address is good. What
+                 * else would we do? Assume the address is USED? */
                 _LOGT_acd(acd_data,
-                          "state: probe-good (waiting for creating probe timed out. Assume good)");
-                acd_data->acd_state    = ACD_STATE_PROBE_DONE;
-                acd_data->probe_result = TRUE;
-                goto handle_probe_done;
+                          "probe-good (waiting for creating probe timed out. Assume good)");
+                goto handle_start_defending;
             }
 
-            /* try create a new probe. The timeout is always as originally requested. */
-            acd_data->nacd_probe =
-                _l3_acd_nacd_instance_create_probe(self,
-                                                   acd_data->addr,
-                                                   acd_data->probing_timeout_msec,
-                                                   acd_data,
-                                                   &acd_not_supported,
-                                                   &failure_reason);
-            if (acd_not_supported) {
-                nm_assert(!acd_data->nacd_probe);
-                _LOGT_acd(
-                    acd_data,
-                    "state: probe-good (interface does not support ACD anymore after timeout)");
-                acd_data->acd_state    = ACD_STATE_PROBE_DONE;
-                acd_data->probe_result = TRUE;
-                goto handle_probe_done;
+            log_reason = "retry probing on timeout";
+            goto handle_start_probing;
+
+        case NM_L3_ACD_ADDR_STATE_USED:
+        case NM_L3_ACD_ADDR_STATE_CONFLICT:
+
+            nm_assert(!acd_data->nacd_probe);
+
+            /* after a timeout, re-probe the address. This only happens if the caller
+             * does not deconfigure the address after USED/CONFLICT. But in that case,
+             * we eventually want to retry. */
+            if (_acd_data_collect_tracks_data(acd_data,
+                                              NM_TERNARY_TRUE,
+                                              &acd_timeout_msec,
+                                              &acd_defend_type)
+                <= 0)
+                nm_assert_not_reached();
+
+            acd_data->acd_defend_type_desired = acd_defend_type;
+
+            if (acd_timeout_msec <= 0) {
+                log_reason = "acd disabled by configuration (restart after previous conflict)";
+                goto handle_probing_done;
             }
 
-            if (!acd_data->nacd_probe) {
-                _LOGT_acd(acd_data,
-                          "state: probing not possible at this time (%s). Wait longer",
-                          failure_reason);
-                _l3_acd_data_timeout_schedule_probing_restart(acd_data, now_msec);
-                return;
+            if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) {
+                log_reason = "address already configured (restart after previous conflict)";
+                goto handle_probing_done;
             }
 
-            /* probing started (with the original timeout. Note that acd_data->probing_time*_msec
-             * no longer corresponds to the actual timeout of the nacd_probe. This is not a problem
-             * because at this point we only trust the internal timer from nacd_probe to get
-             * it right. Instead, we keep acd_data->probing_time*_msec unchanged, to remember when
-             * we originally wanted to start. */
-            _LOGT_acd(acd_data,
-                      "state: probing started (after retry, timeout %u msec)",
-                      acd_data->probing_timeout_msec);
+            nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
+            acd_data->probing_timestamp_msec = (*p_now_msec);
+            acd_data->probing_timeout_msec   = acd_timeout_msec;
+            if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_USED)
+                log_reason = "restart probing after previously used address";
+            else
+                log_reason = "restart probing after previous conflict";
+            goto handle_start_probing;
+
+        case NM_L3_ACD_ADDR_STATE_READY:
+            nm_assert_not_reached();
             return;
-        }
 
-        if (acd_data->acd_state == ACD_STATE_PROBE_DONE && !acd_data->probe_result) {
-            /* Probing is done, but previously we detected a conflict. After a restart, we retry to
-             * probe. */
+        case NM_L3_ACD_ADDR_STATE_DEFENDING:
+
             nm_assert(!acd_data->nacd_probe);
-            nm_assert(!acd_data->announcing_failed_is_retrying);
+            _LOGT_acd(acd_data, "retry announcing address");
+            goto handle_start_defending;
 
-            _LOGT_acd(acd_data, "state: restart a new probe after previous conflict");
-            acd_data->acd_state = ACD_STATE_INIT;
-            goto handle_post_commit;
+        case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+            nm_assert_not_reached();
+            return;
         }
 
-        if (acd_data->acd_state == ACD_STATE_PROBE_DONE && acd_data->probe_result
-            && !acd_data->nacd_probe && acd_data->announcing_failed_is_retrying) {
-            /* Probing is done, but previously we failed to start announcing. Retry now. */
-            nm_assert(!was_probing);
-            _LOGT_acd(acd_data, "state: retry announcing address");
-            acd_data->announcing_failed_is_retrying = FALSE;
-            goto handle_probe_done;
+        nm_assert_not_reached();
+        return;
+
+    case ACD_STATE_CHANGE_MODE_NACD_USED:
+        nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING);
+        nm_assert(acd_data->nacd_probe);
+
+        acd_data->nacd_probe         = n_acd_probe_free(acd_data->nacd_probe);
+        acd_data->last_conflict_addr = *sender_addr;
+        _l3_acd_data_state_set_full(self,
+                                    acd_data,
+                                    NM_L3_ACD_ADDR_STATE_USED,
+                                    TRUE,
+                                    "acd completed with address already in use by %s",
+                                    nm_ether_addr_to_string_a(sender_addr));
+
+        if (!acd_data->acd_data_timeout_source)
+            _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC);
+
+        if (!_l3_acd_data_defendconflict_warning_ratelimited(acd_data, p_now_msec)) {
+            _LOGI("IPv4 address %s is used on network connected to interface %d%s%s%s from "
+                  "host %s",
+                  _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr),
+                  self->priv.ifindex,
+                  NM_PRINT_FMT_QUOTED(self->priv.plobj_next,
+                                      " (",
+                                      NMP_OBJECT_CAST_LINK(self->priv.plobj_next)->name,
+                                      ")",
+                                      ""),
+                  nm_ether_addr_to_string_a(sender_addr));
         }
+        return;
 
+    case ACD_STATE_CHANGE_MODE_NACD_DEFENDED:
+        nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_DEFENDING);
+        _LOGT_acd(acd_data,
+                  "address %s defended from %s",
+                  _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr),
+                  nm_ether_addr_to_string_a(sender_addr));
+        /* we just log an info message. Nothing else to do. */
         return;
-    }
 
-    case ACD_STATE_CHANGE_MODE_NACD_READY:
-        if (acd_data->acd_state == ACD_STATE_PROBING) {
-            log_reason = "acd indicates ready";
-            goto handle_probing_acd_good;
+    case ACD_STATE_CHANGE_MODE_NACD_CONFLICT:
+        nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_DEFENDING);
+
+        _LOGT_acd(acd_data,
+                  "address conflict for %s detected with %s",
+                  _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr),
+                  nm_ether_addr_to_string_a(sender_addr));
+
+        if (!_l3_acd_data_defendconflict_warning_ratelimited(acd_data, p_now_msec)) {
+            _LOGW("IPv4 address collision detection sees conflict on interface %d%s%s%s for "
+                  "address %s from host %s",
+                  self->priv.ifindex,
+                  NM_PRINT_FMT_QUOTED(self->priv.plobj_next,
+                                      " (",
+                                      NMP_OBJECT_CAST_LINK(self->priv.plobj_next)->name,
+                                      ")",
+                                      ""),
+                  _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr),
+                  nm_ether_addr_to_string_a(sender_addr));
         }
-        if (acd_data->acd_state == ACD_STATE_ANNOUNCING) {
-            _LOGT_acd(acd_data, "state: ready to start announcing");
-            if (n_acd_probe_announce(acd_data->nacd_probe, N_ACD_DEFEND_ALWAYS) != 0)
+
+        acd_data->nacd_probe         = n_acd_probe_free(acd_data->nacd_probe);
+        acd_data->last_conflict_addr = *sender_addr;
+        _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_CONFLICT, TRUE);
+        if (!acd_data->acd_data_timeout_source)
+            _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_CONFLICT_RESTART_MSEC);
+        return;
+
+    case ACD_STATE_CHANGE_MODE_NACD_READY:
+
+        switch (acd_data->info.state) {
+        case NM_L3_ACD_ADDR_STATE_PROBING:
+            nm_assert(acd_data->nacd_probe);
+            /* we theoretically could re-use this probe for defending. But as we
+             * may not start defending right away, it makes it more complicated. */
+            acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+            log_reason           = "acd indicates ready";
+            goto handle_probing_done;
+        case NM_L3_ACD_ADDR_STATE_DEFENDING:
+            nm_assert(!acd_data->acd_defend_type_is_active);
+            acd_data->acd_defend_type_is_active = TRUE;
+            _LOGT_acd(acd_data,
+                      "start announcing (defend=%s) (after new probe ready)",
+                      _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current,
+                                                    sbuf256,
+                                                    sizeof(sbuf256)));
+            if (n_acd_probe_announce(acd_data->nacd_probe,
+                                     _l3_acd_defend_type_to_nacd(acd_data->acd_defend_type_current))
+                != 0)
                 nm_assert_not_reached();
             return;
+        case NM_L3_ACD_ADDR_STATE_INIT:
+        case NM_L3_ACD_ADDR_STATE_USED:
+        case NM_L3_ACD_ADDR_STATE_READY:
+        case NM_L3_ACD_ADDR_STATE_CONFLICT:
+        case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+            nm_assert_not_reached();
+            return;
         }
 
-        /* nacd really shouldn't call us in this state. There is a bug somewhere. */
         nm_assert_not_reached();
         return;
 
-    case ACD_STATE_CHANGE_MODE_NACD_USED:
-    {
-        gs_free char *str_to_free = NULL;
+    case ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED:
 
-        nm_assert(acd_data->acd_state == ACD_STATE_PROBING);
-        _LOGT_acd(acd_data,
-                  "state: probe-done bad (address already in use by %s)",
-                  nm_utils_bin2hexstr_a(event->_acd_event_payload.sender,
-                                        event->_acd_event_payload.n_sender,
-                                        ':',
-                                        FALSE,
-                                        &str_to_free));
-        acd_data->nacd_probe   = n_acd_probe_free(acd_data->nacd_probe);
-        acd_data->acd_state    = ACD_STATE_PROBE_DONE;
-        acd_data->probe_result = FALSE;
-        goto handle_probe_done;
-    }
+        if (self->priv.p->commit_reentrant_count > 0)
+            return;
 
-    case ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED:
-        /* the address is configured on the link. This means, ACD passed */
-        log_reason = "address configured on link";
-        goto handle_probing_acd_good;
+        _LOGT_acd(acd_data, "address was externally added");
 
-    case ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED:
-        /* The address got removed. Either we ourself removed it or it was removed externally.
-         * In either case, it's not clear what we should do about that, regardless in which
-         * ACD state we are, so ignore it. */
-        _LOGT_acd(acd_data, "state: address was externally removed. Ignore");
-        return;
+        switch (acd_data->info.state) {
+        case NM_L3_ACD_ADDR_STATE_INIT:
+            nm_assert_not_reached();
+            return;
+        case NM_L3_ACD_ADDR_STATE_READY:
+        case NM_L3_ACD_ADDR_STATE_DEFENDING:
+            goto handle_start_defending;
+        case NM_L3_ACD_ADDR_STATE_PROBING:
+        case NM_L3_ACD_ADDR_STATE_USED:
+        case NM_L3_ACD_ADDR_STATE_CONFLICT:
+        case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+            log_reason = "address configured on link";
+            goto handle_probing_done;
+        }
 
-    case ACD_STATE_CHANGE_MODE_NACD_DOWN:
-        if (acd_data->acd_state < ACD_STATE_PROBE_DONE) {
-            /* we are probing, but the probe has a problem that the link went down. Maybe
-             * we need to restart. */
+        nm_assert_not_reached();
+        return;
 
-            nm_assert(acd_data->acd_state == ACD_STATE_PROBING);
+    case ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED:
 
-            if (!acd_data->nacd_probe) {
-                /* we are in probing state, but currently not really probing. A timer is
-                 * running, and we will handle this situation that way. */
-                return;
-            }
+        if (self->priv.p->commit_reentrant_count > 0)
+            return;
 
-            /* We abort the probing, but we also schedule a timer to restart it. Let
-             * the regular re-start handling handle this. */
-            _LOGT_acd(acd_data,
-                      "state: interface-down. Probing aborted but we keep waiting to retry");
-            acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
-            _l3_acd_data_timeout_schedule_probing_restart(acd_data, now_msec);
+        if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) {
+            /* this can happen, because there might still be the same address with different
+             * plen or peer_address. */
             return;
         }
 
-        /* We already completed a probe and acted accordingly (by either configuring the address
-         * already or by rejecting it). We cannot (easily) re-evaluate what to do now. Should
-         * we later restart probing? But what about the decisions we already made??
-         * Ignore the situation. */
+        _LOGT_acd(acd_data, "address was externally removed");
+
+        acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+        _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED, FALSE);
         return;
 
+    case ACD_STATE_CHANGE_MODE_NACD_DOWN:
     case ACD_STATE_CHANGE_MODE_LINK_NOW_UP:
 
-        /* The interface just came up. */
-
-        if (acd_data->acd_state <= ACD_STATE_PROBING) {
-            nm_auto(n_acd_probe_freep) NAcdProbe *probe = NULL;
-            const char *                          failure_reason;
-            gboolean                              acd_not_supported;
-
-            /* the interface was probing. We will restart the probe. */
-            nm_assert(acd_data->acd_state == ACD_STATE_PROBING);
-
-            nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
+        switch (acd_data->info.state) {
+        case NM_L3_ACD_ADDR_STATE_INIT:
+            nm_assert_not_reached();
+            return;
+        case NM_L3_ACD_ADDR_STATE_PROBING:
 
             if (!acd_data->nacd_probe) {
-                /* We currently are waiting to restart probing. We don't handle the link-up
-                 * event here, we only trigger a timeout right away. */
+                /* we failed starting to probe before and have a timer running to
+                 * restart. We don't do anything now, but let the timer handle it.
+                 * This also implements some rate limiting for us. */
                 _LOGT_acd(acd_data,
-                          "state: ignore link up event while we are waiting to start probing");
-                _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE);
+                          "ignore link %s event while we are waiting to start probing",
+                          state_change_mode == ACD_STATE_CHANGE_MODE_NACD_DOWN ? "down" : "up");
                 return;
             }
 
-            if (acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_RESTART_TIME_MSEC >= now_msec) {
-                /* This probe was already started quite a while ago. We ignore the link-up event
-                 * and let it complete regularly. This is to avoid restarting to probing indefinitely. */
-                _LOGT_acd(acd_data, "state: ignore link up event for a probe started long ago");
-                return;
-            }
+            nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
 
-            probe = _l3_acd_nacd_instance_create_probe(self,
-                                                       acd_data->addr,
-                                                       acd_data->probing_timeout_msec,
-                                                       acd_data,
-                                                       &acd_not_supported,
-                                                       &failure_reason);
-            if (!probe) {
-                _LOGT_acd(acd_data,
-                          "state: link up event would cause to retry probing, but creating a probe "
-                          "failed (%s). Ignore and keep previous probe",
-                          failure_reason);
+            if (acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec
+                    + ACD_WAIT_PROBING_EXTRA_TIME_MSEC + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC
+                >= (*p_now_msec)) {
+                /* The probing already started quite a while ago. We ignore the link event
+                 * and let the probe come to it's natural end. */
+                _LOGT_acd(acd_data, "ignore link up event for a probe started long ago");
                 return;
             }
 
-            NM_SWAP(&probe, &acd_data->nacd_probe);
-
-            /* We just restarted probing. Note that we don't touch the original acd_data->probing_time*_msec
-             * times, otherwise a repeated link up/down cycle could extend the probing indefinitely.
-             *
-             * This is despite the new probe just started counting now. So, at this point, the
-             * timestamp/timeout of acd_data no longer corresponds to the internal timestamp of
-             * acd_data->nacd_probe. But since we don't run our own timer against the internal timer of
-             * acd_data->nacd_probe, that is not a problem.
-             */
-            _LOGT_acd(acd_data,
-                      "state: probing restarted (after link up, new timeout %u msec)",
-                      acd_data->probing_timeout_msec);
+            acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+            if (state_change_mode == ACD_STATE_CHANGE_MODE_NACD_DOWN)
+                log_reason = "restart probing after down event";
+            else
+                log_reason = "restart probing after link up";
+            goto handle_start_probing;
+
+        case NM_L3_ACD_ADDR_STATE_READY:
+        case NM_L3_ACD_ADDR_STATE_DEFENDING:
+        case NM_L3_ACD_ADDR_STATE_USED:
+        case NM_L3_ACD_ADDR_STATE_CONFLICT:
+        case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+            /* if the link was down/came up, it's no clear what we should do about these
+             * cases. Ignore the event. */
             return;
         }
-
-        /* we are already done with the ACD state. Bringing up an interface has
-         * no further consequence w.r.t. the ACD state. */
+        nm_assert_not_reached();
         return;
 
     case ACD_STATE_CHANGE_MODE_INSTANCE_RESET:
-        nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
-        if (acd_data->acd_state <= ACD_STATE_PROBING) {
-            nm_assert(acd_data->acd_state == ACD_STATE_PROBING);
-
-            _LOGT_acd(
-                acd_data,
-                "state: n-acd instance reset. Trigger a restart of the probing (was %sprobing)",
-                acd_data->nacd_probe ? "" : "not");
-            /* Just destroy the current probe (if any) and retrigger a restart right away. */
-            acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
-            _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE);
+
+        switch (acd_data->info.state) {
+        case NM_L3_ACD_ADDR_STATE_INIT:
+            nm_assert_not_reached();
             return;
-        }
+        case NM_L3_ACD_ADDR_STATE_PROBING:
+        case NM_L3_ACD_ADDR_STATE_DEFENDING:
+
+            if (!acd_data->nacd_probe) {
+                /* we failed starting to probe before and have a timer running to
+                 * restart. We don't do anything now, but let the timer handle it.
+                 * This also implements some rate limiting for us. */
+                _LOGT_acd(acd_data,
+                          "n-acd instance reset. Ignore event while restarting %s",
+                          (acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING) ? "probing"
+                                                                                 : "defending");
+                return;
+            }
 
-        if (acd_data->probe_result) {
-            _LOGT_acd(acd_data, "state: n-acd instance reset. Restart announcing");
-        } else {
             _LOGT_acd(acd_data,
-                      "state: n-acd instance reset. Reprobe the address that conflicted before");
+                      "n-acd instance reset. Trigger a restart of the %s",
+                      (acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING) ? "probing"
+                                                                             : "defending");
+            acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+            _l3_acd_data_timeout_schedule(acd_data, 0);
+            return;
+        case NM_L3_ACD_ADDR_STATE_READY:
+        case NM_L3_ACD_ADDR_STATE_USED:
+        case NM_L3_ACD_ADDR_STATE_CONFLICT:
+        case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+            nm_assert(!acd_data->nacd_probe);
+            return;
         }
-        acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
-        acd_data->acd_state  = ACD_STATE_PROBE_DONE;
-        _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE);
+        nm_assert_not_reached();
         return;
     }
 
     nm_assert_not_reached();
     return;
 
-handle_post_commit:
-    /* we just did a commit of the IP configuration and now visit all ACD states
-     * and kick off the necessary actions... */
-    if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->addr)) {
-        log_reason = "address already configured";
-        goto handle_probing_acd_good;
-    }
-    if (_acd_data_collect_tracks_data(acd_data, TRUE, NM_TERNARY_DEFAULT, &acd_timeout_msec) <= 0)
-        nm_assert_not_reached();
-    if (acd_timeout_msec <= 0) {
-        log_reason = "ACD disabled by configuration";
-        goto handle_probing_acd_good;
-    }
-
-    switch (acd_data->acd_state) {
-    case ACD_STATE_INIT:
-    {
-        const char *failure_reason;
-        gboolean    acd_not_supported;
-        NAcdProbe * probe;
-
-        nm_assert(!acd_data->nacd_probe);
-
-        probe = _l3_acd_nacd_instance_create_probe(self,
-                                                   acd_data->addr,
-                                                   acd_timeout_msec,
-                                                   acd_data,
-                                                   &acd_not_supported,
-                                                   &failure_reason);
-        if (acd_not_supported) {
-            nm_assert(!probe);
-            _LOGT_acd(acd_data, "state: probe-good (interface does not support ACD)");
-            acd_data->acd_state    = ACD_STATE_PROBE_DONE;
-            acd_data->probe_result = TRUE;
-            goto handle_probe_done;
-        }
-
-        if (!probe) {
-            _LOGT_acd(acd_data,
-                      "state: probing currently not possible (timeout %u msec; %s)",
-                      acd_timeout_msec,
-                      failure_reason);
-            acd_data->acd_state            = ACD_STATE_PROBING;
-            acd_data->probing_timeout_msec = acd_timeout_msec;
-            acd_data->probing_timestamp_msec =
-                nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
-            _l3_acd_data_timeout_schedule_probing_restart(acd_data, now_msec);
-            return;
-        }
-
-        _LOGT_acd(acd_data, "state: start probing (timeout %u msec)", acd_timeout_msec);
-        acd_data->acd_state              = ACD_STATE_PROBING;
-        acd_data->nacd_probe             = probe;
-        acd_data->probing_timeout_msec   = acd_timeout_msec;
-        acd_data->probing_timestamp_msec = nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
-        return;
-    }
-
-    case ACD_STATE_PROBING:
-    {
-        nm_auto(n_acd_probe_freep) NAcdProbe *probe = NULL;
+handle_start_probing:
+    if (TRUE) {
+        const NML3AcdAddrState                orig_state = acd_data->info.state;
+        nm_auto(n_acd_probe_freep) NAcdProbe *probe      = NULL;
         const char *                          failure_reason;
         gboolean                              acd_not_supported;
-        gint64                                old_expiry_msec;
-        gint64                                new_expiry_msec;
-
-        nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
-
-        new_expiry_msec = now_msec + acd_timeout_msec;
-        old_expiry_msec = acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec;
 
-        if (!acd_data->nacd_probe) {
-            /* we are currently waiting for restarting a probe. At this point, at most we have
-             * to adjust the timeout/timestamp and let the regular timeouts handle this. */
-
-            if (new_expiry_msec >= old_expiry_msec) {
-                /* the running timeout expires before the new timeout. We don't update the timestamp/timerout,
-                 * because we don't want to prolong the overall probing time. */
-                return;
-            }
-            /* update the timers after out timeout got reduced. Also, reschedule the timeout
-             * so that it expires immediately. */
-            acd_data->probing_timestamp_msec = now_msec;
-            acd_data->probing_timeout_msec   = acd_timeout_msec;
-            _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE);
-            return;
-        }
+        nm_assert(NM_IN_SET(acd_data->info.state,
+                            NM_L3_ACD_ADDR_STATE_INIT,
+                            NM_L3_ACD_ADDR_STATE_PROBING,
+                            NM_L3_ACD_ADDR_STATE_USED,
+                            NM_L3_ACD_ADDR_STATE_CONFLICT));
 
-        if (new_expiry_msec >= old_expiry_msec) {
-            /* we already have ACD running with a timeout that expires before the requested one. There
-             * is nothing to do at this time. */
-            return;
-        }
+        /* note that we reach this line also during a ACD_STATE_CHANGE_MODE_TIMEOUT, when
+         * or when we restart the probing (with a new timeout). In all cases, we still
+         * give the original timeout (acd_data->probing_timeout_msec), and not the remaining
+         * time. That means, the probing step might take longer then originally planned
+         * (e.g. if we initially cannot start probing right away). */
 
-        /* the timeout got reduced. We try to restart the probe. */
         probe = _l3_acd_nacd_instance_create_probe(self,
-                                                   acd_data->addr,
-                                                   acd_timeout_msec,
+                                                   acd_data->info.addr,
+                                                   acd_data->probing_timeout_msec,
                                                    acd_data,
                                                    &acd_not_supported,
                                                    &failure_reason);
@@ -2060,143 +2346,144 @@ handle_post_commit:
 
         if (acd_not_supported) {
             nm_assert(!acd_data->nacd_probe);
-            _LOGT_acd(acd_data, "state: probe-good (interface does not support ACD anymore)");
-            acd_data->acd_state    = ACD_STATE_PROBE_DONE;
-            acd_data->probe_result = TRUE;
-            goto handle_probe_done;
+            _LOGT_acd(acd_data,
+                      "probe-good (interface does not support acd%s, %s)",
+                      orig_state == NM_L3_ACD_ADDR_STATE_INIT ? ""
+                      : (state_change_mode != ACD_STATE_CHANGE_MODE_TIMEOUT)
+                          ? " anymore"
+                          : " anymore after timeout",
+                      log_reason);
+            goto handle_start_defending;
         }
 
+        _l3_acd_data_state_set(self,
+                               acd_data,
+                               NM_L3_ACD_ADDR_STATE_PROBING,
+                               !NM_IN_SET(state_change_mode,
+                                          ACD_STATE_CHANGE_MODE_INIT,
+                                          ACD_STATE_CHANGE_MODE_INIT_REAPPLY,
+                                          ACD_STATE_CHANGE_MODE_POST_COMMIT));
+
         if (!acd_data->nacd_probe) {
             _LOGT_acd(acd_data,
-                      "state: probing currently still not possible (timeout %u msec; %s)",
-                      acd_timeout_msec,
-                      failure_reason);
-            acd_data->acd_state              = ACD_STATE_PROBING;
-            acd_data->probing_timeout_msec   = acd_timeout_msec;
-            acd_data->probing_timestamp_msec = now_msec;
-            _l3_acd_data_timeout_schedule_probing_restart(acd_data, now_msec);
+                      "probing currently %snot possible (timeout %u msec; %s, %s)",
+                      orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : " still",
+                      acd_data->probing_timeout_msec,
+                      failure_reason,
+                      log_reason);
+            _l3_acd_data_timeout_schedule_probing_restart(acd_data, (*p_now_msec));
             return;
         }
 
-        /* We update the timestamps (after also restarting the probe).
-         *
-         * Note that we only reduced the overall expiry. */
-        acd_data->probing_timestamp_msec = now_msec;
-        acd_data->probing_timeout_msec   = acd_timeout_msec;
-        _LOGT_acd(acd_data, "state: restart probing (timeout %u msec)", acd_timeout_msec);
+        _LOGT_acd(acd_data,
+                  "%sstart probing (timeout %u msec, %s)",
+                  orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : "re",
+                  acd_data->probing_timeout_msec,
+                  log_reason);
         return;
     }
 
-    case ACD_STATE_PROBE_DONE:
-    case ACD_STATE_ANNOUNCING:
-        goto handle_probe_done;
-    }
-    nm_assert_not_reached();
-    return;
-
-handle_probing_acd_good:
-    switch (acd_data->acd_state) {
-    case ACD_STATE_INIT:
-        _LOGT_acd(acd_data, "state: probe-done good (%s, initializing)", log_reason);
-        acd_data->acd_state    = ACD_STATE_PROBE_DONE;
-        acd_data->probe_result = TRUE;
-        goto handle_probe_done;
-    case ACD_STATE_PROBING:
-        _LOGT_acd(acd_data, "state: probe-done good (%s, probing done)", log_reason);
+handle_probing_done:
+    switch (acd_data->info.state) {
+    case NM_L3_ACD_ADDR_STATE_INIT:
+        _LOGT_acd(acd_data, "probe-done good (%s, initializing)", log_reason);
+        goto handle_start_defending;
+    case NM_L3_ACD_ADDR_STATE_PROBING:
+        _LOGT_acd(acd_data, "probe-done good (%s, probing done)", log_reason);
         if (state_change_mode != ACD_STATE_CHANGE_MODE_NACD_READY)
             acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
-        acd_data->acd_state    = ACD_STATE_PROBE_DONE;
-        acd_data->probe_result = TRUE;
-        goto handle_probe_done;
-    case ACD_STATE_PROBE_DONE:
-        if (!acd_data->probe_result) {
-            nm_assert(!acd_data->nacd_probe);
-            _LOGT_acd(acd_data, "state: probe-done good (%s, after probe failed)", log_reason);
-            acd_data->probe_result = TRUE;
-        }
-        goto handle_probe_done;
-    case ACD_STATE_ANNOUNCING:
-        nm_assert(acd_data->probe_result);
-        goto handle_probe_done;
-    }
-    nm_assert_not_reached();
-    return;
-
-handle_probe_done:
-    nm_assert(NM_IN_SET(acd_data->acd_state, ACD_STATE_PROBE_DONE, ACD_STATE_ANNOUNCING));
-
-    if (acd_data->initializing)
+        goto handle_start_defending;
+    case NM_L3_ACD_ADDR_STATE_USED:
+        _LOGT_acd(acd_data, "probe-done good (%s, after probe failed)", log_reason);
+        goto handle_start_defending;
+    case NM_L3_ACD_ADDR_STATE_READY:
+    case NM_L3_ACD_ADDR_STATE_DEFENDING:
+        goto handle_start_defending;
+    case NM_L3_ACD_ADDR_STATE_CONFLICT:
         return;
-
-    if (acd_data->acd_state >= ACD_STATE_ANNOUNCING) {
-        nm_assert(acd_data->nacd_probe);
-        nm_assert(acd_data->probe_result);
+    case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+        nm_assert_not_reached();
         return;
     }
+    nm_assert_not_reached();
+    return;
 
-    if (!acd_data->probe_result) {
-        nm_assert(acd_data->acd_state == ACD_STATE_PROBE_DONE);
-        nm_assert(!acd_data->nacd_probe);
-        /* we just completed probing with negative result.
-         * Emit a signal, but also reschedule a timer to restart. */
-        if (was_probing) {
-            _LOGT_acd(acd_data, "state: acd probe failed; signal failure");
-            acd_data->probing_timestamp_msec =
-                nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
-            _l3_acd_data_timeout_schedule_probing_full_restart(acd_data, now_msec);
+handle_start_defending:
+    if (!_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) {
+        if (acd_data->info.state != NM_L3_ACD_ADDR_STATE_READY) {
+            _l3_acd_data_state_set_full(self,
+                                        acd_data,
+                                        NM_L3_ACD_ADDR_STATE_READY,
+                                        !NM_IN_SET(state_change_mode,
+                                                   ACD_STATE_CHANGE_MODE_INIT,
+                                                   ACD_STATE_CHANGE_MODE_INIT_REAPPLY,
+                                                   ACD_STATE_CHANGE_MODE_POST_COMMIT),
+                                        "probe is ready, waiting for address to be configured");
         }
-        _l3_acd_data_notify_acd_completed_queue(self, acd_data);
         return;
     }
 
-    if (was_probing && acd_data->probe_result) {
-        /* probing just completed. Schedule handling the change. */
-        _LOGT_acd(acd_data, "state: acd probe succeed");
-        _l3_acd_data_notify_acd_completed_queue(self, acd_data);
-        if (state_change_mode != ACD_STATE_CHANGE_MODE_POST_COMMIT)
-            _l3_changed_configs_set_dirty(self);
-        nm_l3cfg_commit_on_idle_schedule(self);
+    _l3_acd_data_state_set(self,
+                           acd_data,
+                           NM_L3_ACD_ADDR_STATE_DEFENDING,
+                           !NM_IN_SET(state_change_mode,
+                                      ACD_STATE_CHANGE_MODE_INIT,
+                                      ACD_STATE_CHANGE_MODE_INIT_REAPPLY,
+                                      ACD_STATE_CHANGE_MODE_POST_COMMIT));
+
+    nm_assert(acd_data->acd_defend_type_desired > NM_L3_ACD_DEFEND_TYPE_NONE);
+    nm_assert(acd_data->acd_defend_type_desired <= NM_L3_ACD_DEFEND_TYPE_ALWAYS);
+
+    if (acd_data->acd_defend_type_desired != acd_data->acd_defend_type_current) {
+        acd_data->acd_defend_type_current = acd_data->acd_defend_type_desired;
+        acd_data->nacd_probe              = n_acd_probe_free(acd_data->nacd_probe);
     }
 
     if (!acd_data->nacd_probe) {
         const char *failure_reason;
         NAcdProbe * probe;
 
-        if (acd_data->announcing_failed_is_retrying) {
+        if (acd_data->acd_data_timeout_source) {
             /* we already failed to create a probe. We are ratelimited to retry, but
              * we have a timer pending... */
             return;
         }
 
         probe = _l3_acd_nacd_instance_create_probe(self,
-                                                   acd_data->addr,
+                                                   acd_data->info.addr,
                                                    0,
                                                    acd_data,
                                                    NULL,
                                                    &failure_reason);
         if (!probe) {
-            /* we failed to create a probe for announcing the address. We log a (rate limited)
-             * warning and start a timer to retry. */
-            _LOGT_acd(acd_data,
-                      "state: start announcing failed to create probe (%s)",
-                      failure_reason);
-            acd_data->announcing_failed_is_retrying = TRUE;
-            acd_data->probing_timestamp_msec =
-                nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
-            _l3_acd_data_timeout_schedule_announce_restart(acd_data, now_msec);
+            /* we failed to create a probe for announcing the address. We log a
+             * warning and start a timer to retry. This way (of having a timer pending)
+             * we also back off and are rate limited from retrying too frequently. */
+            _LOGT_acd(acd_data, "start announcing failed to create probe (%s)", failure_reason);
+            _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC);
             return;
         }
 
-        _LOGT_acd(acd_data, "state: start announcing (with new probe)");
-        acd_data->nacd_probe = probe;
-        acd_data->acd_state  = ACD_STATE_ANNOUNCING;
+        _LOGT_acd(acd_data,
+                  "start announcing (defend=%s) (probe created)",
+                  _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current,
+                                                sbuf256,
+                                                sizeof(sbuf256)));
+        acd_data->acd_defend_type_is_active = FALSE;
+        acd_data->nacd_probe                = probe;
         return;
     }
 
-    if (acd_data->acd_state == ACD_STATE_PROBE_DONE) {
-        _LOGT_acd(acd_data, "state: start announcing (with existing probe)");
-        acd_data->acd_state = ACD_STATE_ANNOUNCING;
-        if (n_acd_probe_announce(acd_data->nacd_probe, N_ACD_DEFEND_ALWAYS) != 0)
+    if (!acd_data->acd_defend_type_is_active) {
+        acd_data->acd_defend_type_is_active = TRUE;
+        _LOGT_acd(acd_data,
+                  "start announcing (defend=%s) (with existing probe)",
+                  _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current,
+                                                sbuf256,
+                                                sizeof(sbuf256)));
+        if (n_acd_probe_announce(acd_data->nacd_probe,
+                                 _l3_acd_defend_type_to_nacd(acd_data->acd_defend_type_current))
+            != 0)
             nm_assert_not_reached();
         return;
     }
@@ -2205,28 +2492,47 @@ handle_probe_done:
 static void
 _l3_acd_data_process_changes(NML3Cfg *self)
 {
-    gboolean acd_is_announcing = FALSE;
-    gboolean acd_is_pending    = FALSE;
+    gboolean acd_is_pending = FALSE;
+    gboolean acd_busy       = FALSE;
     AcdData *acd_data;
+    gint64   now_msec = 0;
 
     _l3_acd_data_prune(self, FALSE);
 
     c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
-        _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_POST_COMMIT, NULL);
-        if (acd_data->acd_state < ACD_STATE_PROBE_DONE)
+        _l3_acd_data_state_change(self,
+                                  acd_data,
+                                  ACD_STATE_CHANGE_MODE_POST_COMMIT,
+                                  NULL,
+                                  &now_msec);
+        if (acd_data->info.state <= NM_L3_ACD_ADDR_STATE_PROBING)
             acd_is_pending = TRUE;
-        else if (acd_data->acd_state >= ACD_STATE_ANNOUNCING
-                 || (acd_data->acd_state >= ACD_STATE_PROBE_DONE && acd_data->probe_result))
-            acd_is_announcing = TRUE;
+        if (acd_data->nacd_probe)
+            acd_busy = TRUE;
     }
 
-    self->priv.p->acd_is_pending    = acd_is_pending;
-    self->priv.p->acd_is_announcing = acd_is_announcing;
+    self->priv.p->acd_is_pending = acd_is_pending;
 
-    if (!acd_is_pending && !acd_is_announcing)
+    if (!acd_busy)
         _l3_acd_nacd_instance_reset(self, NM_TERNARY_DEFAULT, FALSE);
 
-    _l3_acd_data_notify_acd_completed_all(self);
+    _nm_l3cfg_emit_signal_notify_acd_event_all(self);
+}
+
+/*****************************************************************************/
+
+const NML3AcdAddrInfo *
+nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr)
+{
+    AcdData *acd_data;
+
+    nm_assert(NM_IS_L3CFG(self));
+
+    acd_data = _l3_acd_data_find(self, addr);
+    if (!acd_data)
+        return NULL;
+
+    return &acd_data->info;
 }
 
 /*****************************************************************************/
@@ -2238,7 +2544,7 @@ _l3_commit_on_idle_cb(gpointer user_data)
 
     nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source);
 
-    _LOGT("platform commit on idle");
+    _LOGT("commit on idle");
     _l3_commit(self, NM_L3_CFG_COMMIT_TYPE_AUTO, TRUE);
     return G_SOURCE_REMOVE;
 }
@@ -2251,6 +2557,7 @@ nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self)
     if (self->priv.p->commit_on_idle_source)
         return;
 
+    _LOGT("commit on idle (scheduled)");
     self->priv.p->commit_on_idle_source =
         nm_g_idle_source_new(G_PRIORITY_DEFAULT, _l3_commit_on_idle_cb, self, NULL);
     g_source_attach(self->priv.p->commit_on_idle_source, NULL);
@@ -2275,7 +2582,7 @@ _l3_config_datas_find_next(GArray *              l3_config_datas,
     for (i = start_idx; i < l3_config_datas->len; i++) {
         const L3ConfigData *l3_config_data = _l3_config_datas_at(l3_config_datas, i);
 
-        if (NM_IN_SET(needle_tag, NULL, l3_config_data->tag)
+        if (NM_IN_SET(needle_tag, NULL, l3_config_data->tag_confdata)
             && NM_IN_SET(needle_l3cd, NULL, l3_config_data->l3cd))
             return i;
     }
@@ -2294,54 +2601,15 @@ _l3_config_datas_get_sorted_cmp(gconstpointer p_a, gconstpointer p_b, gpointer u
 
     /* we sort the entries with higher priority (more important, lower numerical value)
      * first. */
-    NM_CMP_FIELD(a, b, priority);
+    NM_CMP_FIELD(a, b, priority_confdata);
 
     /* if the priority is not unique, we sort them in the order they were added,
      * with the oldest first (lower numerical value). */
-    NM_CMP_FIELD(a, b, pseudo_timestamp);
+    NM_CMP_FIELD(a, b, pseudo_timestamp_confdata);
 
     return nm_assert_unreachable_val(0);
 }
 
-#define _l3_config_datas_get_sorted_a(l3_config_datas, out_infos, out_infos_len, out_infos_free) \
-    G_STMT_START                                                                                 \
-    {                                                                                            \
-        GArray *const                     _l3_config_datas = (l3_config_datas);                  \
-        const L3ConfigData *const **const _out_infos       = (out_infos);                        \
-        guint *const                      _out_infos_len   = (out_infos_len);                    \
-        const L3ConfigData ***const       _out_infos_free  = (out_infos_free);                   \
-        gs_free const L3ConfigData **_infos_free           = NULL;                               \
-        const L3ConfigData **        _infos;                                                     \
-        guint                        _l3_config_datas_len;                                       \
-        guint                        _i;                                                         \
-                                                                                                 \
-        _l3_config_datas_len = nm_g_array_len(_l3_config_datas);                                 \
-                                                                                                 \
-        if (_l3_config_datas_len == 0)                                                           \
-            _infos = NULL;                                                                       \
-        else if (_l3_config_datas_len < 300 / sizeof(_infos[0]))                                 \
-            _infos = g_alloca(_l3_config_datas_len * sizeof(_infos[0]));                         \
-        else {                                                                                   \
-            _infos_free = g_new(const L3ConfigData *, _l3_config_datas_len);                     \
-            _infos      = _infos_free;                                                           \
-        }                                                                                        \
-        for (_i = 0; _i < _l3_config_datas_len; _i++)                                            \
-            _infos[_i] = _l3_config_datas_at(_l3_config_datas, _i);                              \
-                                                                                                 \
-        if (_l3_config_datas_len > 1) {                                                          \
-            g_qsort_with_data(_infos,                                                            \
-                              _l3_config_datas_len,                                              \
-                              sizeof(_infos[0]),                                                 \
-                              _l3_config_datas_get_sorted_cmp,                                   \
-                              NULL);                                                             \
-        }                                                                                        \
-                                                                                                 \
-        *_out_infos      = _infos;                                                               \
-        *_out_infos_len  = _l3_config_datas_len;                                                 \
-        *_out_infos_free = g_steal_pointer(&_infos_free);                                        \
-    }                                                                                            \
-    G_STMT_END
-
 static void
 _l3_config_datas_remove_index_fast(GArray *arr, guint idx)
 {
@@ -2376,7 +2644,7 @@ nm_l3cfg_mark_config_dirty(NML3Cfg *self, gconstpointer tag, gboolean dirty)
         if (idx < 0)
             return;
 
-        _l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty = dirty;
+        _l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty_confdata = dirty;
         idx++;
     }
 }
@@ -2393,6 +2661,7 @@ nm_l3cfg_add_config(NML3Cfg *             self,
                     guint32               default_route_metric_6,
                     guint32               default_route_penalty_4,
                     guint32               default_route_penalty_6,
+                    NML3AcdDefendType     acd_defend_type,
                     guint32               acd_timeout_msec,
                     NML3ConfigMergeFlags  merge_flags)
 {
@@ -2405,6 +2674,14 @@ nm_l3cfg_add_config(NML3Cfg *             self,
     nm_assert(l3cd);
     nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex);
 
+    if (acd_timeout_msec > ACD_MAX_TIMEOUT_MSEC)
+        acd_timeout_msec = ACD_MAX_TIMEOUT_MSEC;
+
+    nm_assert(NM_IN_SET(acd_defend_type,
+                        NM_L3_ACD_DEFEND_TYPE_NEVER,
+                        NM_L3_ACD_DEFEND_TYPE_ONCE,
+                        NM_L3_ACD_DEFEND_TYPE_ALWAYS));
+
     nm_assert(default_route_metric_6 != 0u); /* IPv6 default route metric cannot be zero. */
 
     if (default_route_table_4 == 0u)
@@ -2434,12 +2711,11 @@ nm_l3cfg_add_config(NML3Cfg *             self,
             if (l3_config_data->l3cd == l3cd) {
                 nm_assert(idx == -1);
                 idx = idx2;
-                continue;
+                idx2++;
+            } else {
+                changed = TRUE;
+                _l3_config_datas_remove_index_fast(self->priv.p->l3_config_datas, idx2);
             }
-
-            changed = TRUE;
-            _l3_config_datas_remove_index_fast(self->priv.p->l3_config_datas, idx2);
-
             idx2 = _l3_config_datas_find_next(self->priv.p->l3_config_datas, idx2, tag, NULL);
             if (idx2 < 0)
                 break;
@@ -2449,29 +2725,30 @@ nm_l3cfg_add_config(NML3Cfg *             self,
     if (idx < 0) {
         l3_config_data  = nm_g_array_append_new(self->priv.p->l3_config_datas, L3ConfigData);
         *l3_config_data = (L3ConfigData){
-            .tag                     = tag,
-            .l3cd                    = nm_l3_config_data_ref_and_seal(l3cd),
-            .merge_flags             = merge_flags,
-            .default_route_table_4   = default_route_table_4,
-            .default_route_table_6   = default_route_table_6,
-            .default_route_metric_4  = default_route_metric_4,
-            .default_route_metric_6  = default_route_metric_6,
-            .default_route_penalty_4 = default_route_penalty_4,
-            .default_route_penalty_6 = default_route_penalty_6,
-            .acd_timeout_msec        = acd_timeout_msec,
-            .priority                = priority,
-            .pseudo_timestamp        = ++self->priv.p->pseudo_timestamp_counter,
-            .dirty                   = FALSE,
+            .tag_confdata              = tag,
+            .l3cd                      = nm_l3_config_data_ref_and_seal(l3cd),
+            .merge_flags               = merge_flags,
+            .default_route_table_4     = default_route_table_4,
+            .default_route_table_6     = default_route_table_6,
+            .default_route_metric_4    = default_route_metric_4,
+            .default_route_metric_6    = default_route_metric_6,
+            .default_route_penalty_4   = default_route_penalty_4,
+            .default_route_penalty_6   = default_route_penalty_6,
+            .acd_defend_type_confdata  = acd_defend_type,
+            .acd_timeout_msec_confdata = acd_timeout_msec,
+            .priority_confdata         = priority,
+            .pseudo_timestamp_confdata = ++self->priv.p->pseudo_timestamp_counter,
+            .dirty_confdata            = FALSE,
         };
         changed = TRUE;
     } else {
-        l3_config_data        = _l3_config_datas_at(self->priv.p->l3_config_datas, idx);
-        l3_config_data->dirty = FALSE;
-        nm_assert(l3_config_data->tag == tag);
+        l3_config_data                 = _l3_config_datas_at(self->priv.p->l3_config_datas, idx);
+        l3_config_data->dirty_confdata = FALSE;
+        nm_assert(l3_config_data->tag_confdata == tag);
         nm_assert(l3_config_data->l3cd == l3cd);
-        if (l3_config_data->priority != priority) {
-            l3_config_data->priority = priority;
-            changed                  = TRUE;
+        if (l3_config_data->priority_confdata != priority) {
+            l3_config_data->priority_confdata = priority;
+            changed                           = TRUE;
         }
         if (l3_config_data->merge_flags != merge_flags) {
             l3_config_data->merge_flags = merge_flags;
@@ -2501,12 +2778,18 @@ nm_l3cfg_add_config(NML3Cfg *             self,
             l3_config_data->default_route_penalty_6 = default_route_penalty_6;
             changed                                 = TRUE;
         }
-        if (l3_config_data->acd_timeout_msec != acd_timeout_msec) {
-            l3_config_data->acd_timeout_msec = acd_timeout_msec;
-            changed                          = TRUE;
+        if (l3_config_data->acd_defend_type_confdata != acd_defend_type) {
+            l3_config_data->acd_defend_type_confdata = acd_defend_type;
+            changed                                  = TRUE;
+        }
+        if (l3_config_data->acd_timeout_msec_confdata != acd_timeout_msec) {
+            l3_config_data->acd_timeout_msec_confdata = acd_timeout_msec;
+            changed                                   = TRUE;
         }
     }
 
+    nm_assert(l3_config_data->acd_defend_type_confdata == acd_defend_type);
+
     if (changed)
         _l3_changed_configs_set_dirty(self);
 
@@ -2537,7 +2820,8 @@ _l3cfg_remove_config(NML3Cfg *             self,
         if (idx < 0)
             break;
 
-        if (only_dirty && !_l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty) {
+        if (only_dirty
+            && !_l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty_confdata) {
             idx++;
             continue;
         }
@@ -2582,12 +2866,18 @@ typedef struct {
 } L3ConfigMergeHookAddObjData;
 
 static gboolean
-_l3_hook_add_addr_cb(const NML3ConfigData *l3cd, const NMPObject *obj, gpointer user_data)
+_l3_hook_add_addr_cb(const NML3ConfigData *l3cd,
+                     const NMPObject *     obj,
+                     NMTernary *           out_ip4acd_not_ready,
+                     gpointer              user_data)
 {
     const L3ConfigMergeHookAddObjData *hook_data = user_data;
     NML3Cfg *                          self      = hook_data->self;
     AcdData *                          acd_data;
     in_addr_t                          addr;
+    gboolean                           acd_bad = FALSE;
+
+    nm_assert(out_ip4acd_not_ready && *out_ip4acd_not_ready == NM_TERNARY_DEFAULT);
 
     if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP4_ADDRESS)
         return TRUE;
@@ -2595,46 +2885,91 @@ _l3_hook_add_addr_cb(const NML3ConfigData *l3cd, const NMPObject *obj, gpointer
     addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address;
 
     if (ACD_ADDR_SKIP(addr))
-        return TRUE;
+        goto out;
 
     acd_data = _l3_acd_data_find(self, addr);
-    nm_assert(acd_data);
+
+    if (!acd_data) {
+        /* we don't yet track an ACD state for this address. That can only
+         * happend during _l3cfg_update_combined_config() with !to_commit,
+         * where we didn't update the ACD state.
+         *
+         * This means, unless you actually commit, nm_l3cfg_get_combined_l3cd(self, get_commited = FALSE)
+         * won't consider IPv4 addresses ready, that have no known ACD state yet. */
+        nm_assert(self->priv.p->changed_configs_acd_state);
+        acd_bad = TRUE;
+        goto out;
+    }
+
     nm_assert(
         _acd_track_data_is_not_dirty(_acd_data_find_track(acd_data, l3cd, obj, hook_data->tag)));
-    return _acd_data_probe_result_is_good(acd_data);
+    if (!NM_IN_SET(acd_data->info.state,
+                   NM_L3_ACD_ADDR_STATE_READY,
+                   NM_L3_ACD_ADDR_STATE_DEFENDING))
+        acd_bad = TRUE;
+
+out:
+    *out_ip4acd_not_ready = acd_bad ? NM_TERNARY_TRUE : NM_TERNARY_FALSE;
+    return TRUE;
 }
 
 static void
 _l3cfg_update_combined_config(NML3Cfg *              self,
                               gboolean               to_commit,
+                              gboolean               reapply,
                               const NML3ConfigData **out_old /* transfer reference */,
-                              gboolean *             out_changed_configs,
                               gboolean *             out_changed_combined_l3cd)
 {
-    nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL;
-    nm_auto_unref_l3cd_init NML3ConfigData *l3cd      = NULL;
-    gs_free const L3ConfigData **l3_config_datas_free = NULL;
-    const L3ConfigData *const *  l3_config_datas_sorted;
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd_commited_old = NULL;
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd_old          = NULL;
+    nm_auto_unref_l3cd_init NML3ConfigData *l3cd               = NULL;
+    gs_free const L3ConfigData **l3_config_datas_free          = NULL;
+    const L3ConfigData **        l3_config_datas_arr;
     guint                        l3_config_datas_len;
     guint                        i;
+    gboolean                     merged_changed   = FALSE;
+    gboolean                     commited_changed = FALSE;
 
     nm_assert(NM_IS_L3CFG(self));
-
     nm_assert(!out_old || !*out_old);
-    NM_SET_OUT(out_changed_configs, self->priv.changed_configs);
+
     NM_SET_OUT(out_changed_combined_l3cd, FALSE);
 
-    if (!self->priv.changed_configs)
-        goto out;
+    if (!self->priv.p->changed_configs_configs) {
+        if (!self->priv.p->changed_configs_acd_state)
+            goto out;
+        if (!to_commit) {
+            /* since we are not going to commit, we don't care about the
+             * ACD state. */
+            goto out;
+        }
+    }
+
+    self->priv.p->changed_configs_configs = FALSE;
 
-    self->priv.changed_configs = FALSE;
+    l3_config_datas_len = nm_g_array_len(self->priv.p->l3_config_datas);
+    l3_config_datas_arr = nm_malloc_maybe_a(300,
+                                            l3_config_datas_len * sizeof(l3_config_datas_arr[0]),
+                                            &l3_config_datas_free);
+    for (i = 0; i < l3_config_datas_len; i++)
+        l3_config_datas_arr[i] = _l3_config_datas_at(self->priv.p->l3_config_datas, i);
 
-    _l3_config_datas_get_sorted_a(self->priv.p->l3_config_datas,
-                                  &l3_config_datas_sorted,
-                                  &l3_config_datas_len,
-                                  &l3_config_datas_free);
+    if (l3_config_datas_len > 1) {
+        g_qsort_with_data(l3_config_datas_arr,
+                          l3_config_datas_len,
+                          sizeof(l3_config_datas_arr[0]),
+                          _l3_config_datas_get_sorted_cmp,
+                          NULL);
+    }
 
-    _l3_acd_data_add_all(self, l3_config_datas_sorted, l3_config_datas_len);
+    if (!to_commit) {
+        /* we are not going to commit these changes. Hence, we don't update the
+         * ACD states, but we need to remember that we have to on the next commit. */
+        self->priv.p->changed_configs_acd_state = TRUE;
+    } else {
+        _l3_acd_data_add_all(self, l3_config_datas_arr, l3_config_datas_len, reapply);
+        self->priv.p->changed_configs_acd_state = FALSE;
+    }
 
     if (l3_config_datas_len > 0) {
         L3ConfigMergeHookAddObjData hook_data = {
@@ -2645,12 +2980,12 @@ _l3cfg_update_combined_config(NML3Cfg *              self,
                                      self->priv.ifindex);
 
         for (i = 0; i < l3_config_datas_len; i++) {
-            const L3ConfigData *l3cd_data = l3_config_datas_sorted[i];
+            const L3ConfigData *l3cd_data = l3_config_datas_arr[i];
 
             if (NM_FLAGS_HAS(l3cd_data->merge_flags, NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD))
                 continue;
 
-            hook_data.tag = l3cd_data->tag;
+            hook_data.tag = l3cd_data->tag_confdata;
             nm_l3_config_data_merge(l3cd,
                                     l3cd_data->l3cd,
                                     l3cd_data->merge_flags,
@@ -2672,24 +3007,43 @@ _l3cfg_update_combined_config(NML3Cfg *              self,
 
     l3cd_old                           = g_steal_pointer(&self->priv.p->combined_l3cd_merged);
     self->priv.p->combined_l3cd_merged = nm_l3_config_data_seal(g_steal_pointer(&l3cd));
-
+    merged_changed                     = TRUE;
     if (!to_commit) {
         NM_SET_OUT(out_old, g_steal_pointer(&l3cd_old));
         NM_SET_OUT(out_changed_combined_l3cd, TRUE);
-        _LOGT("desired IP configuration changed");
     }
 
 out:
     if (to_commit && self->priv.p->combined_l3cd_commited != self->priv.p->combined_l3cd_merged) {
-        nm_auto_unref_l3cd const NML3ConfigData *l3cd_commited_old = NULL;
-
         l3cd_commited_old = g_steal_pointer(&self->priv.p->combined_l3cd_commited);
         self->priv.p->combined_l3cd_commited =
             nm_l3_config_data_ref(self->priv.p->combined_l3cd_merged);
-
+        commited_changed = TRUE;
         NM_SET_OUT(out_old, g_steal_pointer(&l3cd_commited_old));
         NM_SET_OUT(out_changed_combined_l3cd, TRUE);
-        _LOGT("desired IP configuration changed for commit");
+    }
+
+    if ((merged_changed || commited_changed) && _LOGT_ENABLED()) {
+        char sbuf256[256];
+        char sbuf30[30];
+
+        _LOGT("IP configuration changed (merged=%c%s, commited=%c%s)",
+              merged_changed ? '>' : '=',
+              NM_HASH_OBFUSCATE_PTR_STR(self->priv.p->combined_l3cd_merged, sbuf256),
+              commited_changed ? '>' : '=',
+              NM_HASH_OBFUSCATE_PTR_STR(self->priv.p->combined_l3cd_commited, sbuf30));
+
+        if (merged_changed) {
+            nm_l3_config_data_log(self->priv.p->combined_l3cd_merged,
+                                  NULL,
+                                  nm_sprintf_buf(sbuf256,
+                                                 "l3cfg[" NM_HASH_OBFUSCATE_PTR_FMT
+                                                 ",ifindex=%d]:    ",
+                                                 NM_HASH_OBFUSCATE_PTR(self),
+                                                 nm_l3cfg_get_ifindex(self)),
+                                  LOGL_TRACE,
+                                  _NMLOG_DOMAIN);
+        }
     }
 }
 
@@ -2874,10 +3228,13 @@ out_prune:
 /*****************************************************************************/
 
 static gboolean
-_l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
+_l3_commit_one(NML3Cfg *             self,
+               int                   addr_family,
+               NML3CfgCommitType     commit_type,
+               gboolean              changed_combined_l3cd,
+               const NML3ConfigData *l3cd_old)
 {
-    const gboolean           IS_IPv4                                = NM_IS_IPv4(addr_family);
-    nm_auto_unref_l3cd const NML3ConfigData *l3cd_old               = NULL;
+    const int         IS_IPv4                                       = NM_IS_IPv4(addr_family);
     gs_unref_ptrarray GPtrArray *addresses                          = NULL;
     gs_unref_ptrarray GPtrArray *routes                             = NULL;
     gs_unref_ptrarray GPtrArray *addresses_prune                    = NULL;
@@ -2885,8 +3242,6 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
     gs_unref_ptrarray GPtrArray *routes_temporary_not_available_arr = NULL;
     NMIPRouteTableSyncMode       route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE;
     gboolean                     final_failure_for_temporary_not_available = FALSE;
-    gboolean                     changed_combined_l3cd;
-    gboolean                     changed_configs;
     char                         sbuf_commit_type[50];
     gboolean                     success = TRUE;
 
@@ -2902,8 +3257,6 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
           nm_utils_addr_family_to_char(addr_family),
           _l3_cfg_commit_type_to_string(commit_type, sbuf_commit_type, sizeof(sbuf_commit_type)));
 
-    _l3cfg_update_combined_config(self, TRUE, &l3cd_old, &changed_configs, &changed_combined_l3cd);
-
     if (changed_combined_l3cd) {
         /* our combined configuration changed. We may track entries in externally_removed_objs_hash,
          * which are not longer to be considered by our configuration. We need to forget about them. */
@@ -2916,29 +3269,40 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
     }
 
     if (self->priv.p->combined_l3cd_commited) {
+        GHashTable *                   externally_removed_objs_hash;
         NMDedupMultiFcnSelectPredicate predicate;
+        const NMDedupMultiHeadEntry *  head_entry;
 
         if (commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
-            && self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] > 0)
-            predicate = _l3cfg_externally_removed_objs_filter;
-        else
-            predicate = NULL;
-        addresses = nm_dedup_multi_objs_to_ptr_array_head(
-            nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
-                                          NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)),
-            predicate,
-            self->priv.p->externally_removed_objs_hash);
+            && self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] > 0) {
+            predicate                    = _l3cfg_externally_removed_objs_filter;
+            externally_removed_objs_hash = self->priv.p->externally_removed_objs_hash;
+        } else {
+            if (IS_IPv4)
+                predicate = _l3cfg_externally_removed_objs_filter;
+            else
+                predicate = NULL;
+            externally_removed_objs_hash = NULL;
+        }
+        head_entry = nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
+                                                   NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4));
+        addresses  = nm_dedup_multi_objs_to_ptr_array_head(head_entry,
+                                                          predicate,
+                                                          externally_removed_objs_hash);
 
         if (commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
-            && self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] > 0)
-            predicate = _l3cfg_externally_removed_objs_filter;
-        else
-            predicate = NULL;
-        routes = nm_dedup_multi_objs_to_ptr_array_head(
-            nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
-                                          NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)),
-            predicate,
-            self->priv.p->externally_removed_objs_hash);
+            && self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] > 0) {
+            predicate                    = _l3cfg_externally_removed_objs_filter;
+            externally_removed_objs_hash = self->priv.p->externally_removed_objs_hash;
+        } else {
+            predicate                    = NULL;
+            externally_removed_objs_hash = NULL;
+        }
+        head_entry = nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
+                                                   NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4));
+        routes     = nm_dedup_multi_objs_to_ptr_array_head(head_entry,
+                                                       predicate,
+                                                       externally_removed_objs_hash);
 
         route_table_sync =
             nm_l3_config_data_get_route_table_sync(self->priv.p->combined_l3cd_commited,
@@ -2958,22 +3322,13 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
                                                            self->priv.ifindex,
                                                            route_table_sync);
     } else if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE) {
-        /* during update, we do a cross with the previous configuration.
-         *
-         * Of course, if an entry is both to be pruned and to be added, then
-         * the latter wins. So, this works just nicely. */
-        if (l3cd_old) {
-            const NMDedupMultiHeadEntry *head_entry;
-
-            head_entry =
-                nm_l3_config_data_lookup_objs(l3cd_old, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4));
-            addresses_prune = nm_dedup_multi_objs_to_ptr_array_head(head_entry, NULL, NULL);
-
-            head_entry = nm_l3_config_data_lookup_objs(l3cd_old, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4));
-            addresses_prune = nm_dedup_multi_objs_to_ptr_array_head(head_entry, NULL, NULL);
-        }
+        addresses_prune = nm_g_ptr_array_ref(self->priv.p->last_addresses_x[IS_IPv4]);
+        routes_prune    = nm_g_ptr_array_ref(self->priv.p->last_routes_x[IS_IPv4]);
     }
 
+    nm_g_ptr_array_set(&self->priv.p->last_addresses_x[IS_IPv4], addresses);
+    nm_g_ptr_array_set(&self->priv.p->last_routes_x[IS_IPv4], routes);
+
     /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ip6_privacy(). */
     /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ndisc_*(). */
     /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ip6_mtu(). */
@@ -3008,8 +3363,10 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
 static void
 _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
 {
-    gboolean commit_type_detected = FALSE;
-    char     sbuf_ct[30];
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd_old             = NULL;
+    gboolean                                 commit_type_detected = FALSE;
+    char                                     sbuf_ct[30];
+    gboolean                                 changed_combined_l3cd;
 
     g_return_if_fail(NM_IS_L3CFG(self));
     nm_assert(NM_IN_SET(commit_type,
@@ -3018,6 +3375,7 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
                         NM_L3_CFG_COMMIT_TYPE_ASSUME,
                         NM_L3_CFG_COMMIT_TYPE_UPDATE,
                         NM_L3_CFG_COMMIT_TYPE_REAPPLY));
+    nm_assert(self->priv.p->commit_reentrant_count == 0);
 
     switch (commit_type) {
     case NM_L3_CFG_COMMIT_TYPE_AUTO:
@@ -3043,7 +3401,7 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
         break;
     }
 
-    _LOGT("platform-commit %s%s%s",
+    _LOGT("commit %s%s%s",
           _l3_cfg_commit_type_to_string(commit_type, sbuf_ct, sizeof(sbuf_ct)),
           commit_type_detected ? " (auto)" : "",
           is_idle ? " (idle handler)" : "");
@@ -3051,18 +3409,29 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
     if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE)
         return;
 
+    self->priv.p->commit_reentrant_count++;
+
     nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source);
 
     if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY)
         _l3cfg_externally_removed_objs_drop(self);
 
+    _l3cfg_update_combined_config(self,
+                                  TRUE,
+                                  commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY,
+                                  &l3cd_old,
+                                  &changed_combined_l3cd);
+
     /* FIXME(l3cfg): handle items currently not configurable in kernel. */
 
-    _l3_commit_one(self, AF_INET, commit_type);
-    _l3_commit_one(self, AF_INET6, commit_type);
+    _l3_commit_one(self, AF_INET, commit_type, changed_combined_l3cd, l3cd_old);
+    _l3_commit_one(self, AF_INET6, commit_type, changed_combined_l3cd, l3cd_old);
 
     _l3_acd_data_process_changes(self);
 
+    nm_assert(self->priv.p->commit_reentrant_count == 1);
+    self->priv.p->commit_reentrant_count--;
+
     _nm_l3cfg_emit_signal_notify_simple(self, NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT);
 }
 
@@ -3141,7 +3510,7 @@ nm_l3cfg_commit_type_register(NML3Cfg *                self,
     linked = FALSE;
     c_list_for_each_entry (h, &self->priv.p->commit_type_lst_head, commit_type_lst) {
         if (handle->commit_type >= h->commit_type) {
-            c_list_link_before(&self->priv.p->commit_type_lst_head, &handle->commit_type_lst);
+            c_list_link_before(&h->commit_type_lst, &handle->commit_type_lst);
             linked = TRUE;
             break;
         }
@@ -3178,7 +3547,7 @@ nm_l3cfg_get_combined_l3cd(NML3Cfg *self, gboolean get_commited)
     if (get_commited)
         return self->priv.p->combined_l3cd_commited;
 
-    _l3cfg_update_combined_config(self, FALSE, NULL, NULL, NULL);
+    _l3cfg_update_combined_config(self, FALSE, FALSE, NULL, NULL);
     return self->priv.p->combined_l3cd_merged;
 }
 
@@ -3220,8 +3589,7 @@ nm_l3cfg_has_commited_ip6_addresses_pending_dad(NML3Cfg *self)
 
     nmp_lookup_init_object(&plat_lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, self->priv.ifindex);
 
-    nm_platform_iter_obj_for_each(&iter, self->priv.platform, &plat_lookup, &plat_obj)
-    {
+    nm_platform_iter_obj_for_each (&iter, self->priv.platform, &plat_lookup, &plat_obj) {
         const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS(plat_obj);
         const NMDedupMultiEntry *   l3cd_entry;
 
@@ -3243,6 +3611,47 @@ nm_l3cfg_has_commited_ip6_addresses_pending_dad(NML3Cfg *self)
 
 /*****************************************************************************/
 
+NML3IPv4LL *
+nm_l3cfg_get_ipv4ll(NML3Cfg *self)
+{
+    g_return_val_if_fail(NM_IS_L3CFG(self), NULL);
+
+    return self->priv.p->ipv4ll;
+}
+
+NML3IPv4LL *
+nm_l3cfg_access_ipv4ll(NML3Cfg *self)
+{
+    g_return_val_if_fail(NM_IS_L3CFG(self), NULL);
+
+    if (self->priv.p->ipv4ll)
+        return nm_l3_ipv4ll_ref(self->priv.p->ipv4ll);
+
+    /* We return the reference. But the NML3IPv4LL instance
+     * will call _nm_l3cfg_unregister_ipv4ll() when it gets
+     * destroyed.
+     *
+     * We don't have weak references, but NML3Cfg and NML3IPv4LL
+     * cooperate to handle this reference. */
+    self->priv.p->ipv4ll = nm_l3_ipv4ll_new(self);
+    return self->priv.p->ipv4ll;
+}
+
+void
+_nm_l3cfg_unregister_ipv4ll(NML3Cfg *self)
+{
+    nm_assert(NM_IS_L3CFG(self));
+
+    /* we don't own the refernce to "self->priv.p->ipv4ll", but
+     * when that instance gets destroyed, we get called back to
+     * forget about it. Basically, it's like a weak pointer. */
+
+    nm_assert(self->priv.p->ipv4ll);
+    self->priv.p->ipv4ll = NULL;
+}
+
+/*****************************************************************************/
+
 static void
 set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
@@ -3273,7 +3682,7 @@ nm_l3cfg_init(NML3Cfg *self)
     self->priv.p = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_L3CFG, NML3CfgPrivate);
 
     c_list_init(&self->priv.p->acd_lst_head);
-    c_list_init(&self->priv.p->acd_notify_complete_lst_head);
+    c_list_init(&self->priv.p->acd_event_notify_lst_head);
     c_list_init(&self->priv.p->commit_type_lst_head);
 }
 
@@ -3310,6 +3719,7 @@ finalize(GObject *object)
     NML3Cfg *self = NM_L3CFG(object);
 
     nm_assert(!self->priv.p->l3_config_datas);
+    nm_assert(!self->priv.p->ipv4ll);
 
     nm_assert(c_list_is_empty(&self->priv.p->commit_type_lst_head));
 
@@ -3320,7 +3730,7 @@ finalize(GObject *object)
     _l3_acd_data_prune(self, TRUE);
 
     nm_assert(c_list_is_empty(&self->priv.p->acd_lst_head));
-    nm_assert(c_list_is_empty(&self->priv.p->acd_notify_complete_lst_head));
+    nm_assert(c_list_is_empty(&self->priv.p->acd_event_notify_lst_head));
     nm_assert(nm_g_hash_table_size(self->priv.p->acd_lst_hash) == 0);
 
     nm_clear_pointer(&self->priv.p->acd_lst_hash, g_hash_table_unref);
@@ -3328,6 +3738,11 @@ finalize(GObject *object)
     nm_clear_g_source_inst(&self->priv.p->nacd_source);
     nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry);
 
+    nm_clear_pointer(&self->priv.p->last_addresses_4, g_ptr_array_unref);
+    nm_clear_pointer(&self->priv.p->last_addresses_6, g_ptr_array_unref);
+    nm_clear_pointer(&self->priv.p->last_routes_4, g_ptr_array_unref);
+    nm_clear_pointer(&self->priv.p->last_routes_6, g_ptr_array_unref);
+
     nm_clear_g_source(&self->priv.p->routes_temporary_not_available_id);
     nm_clear_pointer(&self->priv.p->routes_temporary_not_available_hash, g_hash_table_unref);
 
diff --git a/src/nm-l3cfg.h b/src/core/nm-l3cfg.h
index 9f957ae1..5cfc93f6 100644
--- a/src/nm-l3cfg.h
+++ b/src/core/nm-l3cfg.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #ifndef __NM_L3CFG_H__
 #define __NM_L3CFG_H__
@@ -6,6 +6,9 @@
 #include "platform/nmp-object.h"
 #include "nm-l3-config-data.h"
 
+#define NM_L3CFG_CONFIG_PRIORITY_IPV4LL 0
+#define NM_ACD_TIMEOUT_RFC5227_MSEC     9000u
+
 #define NM_TYPE_L3CFG            (nm_l3cfg_get_type())
 #define NM_L3CFG(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_L3CFG, NML3Cfg))
 #define NM_L3CFG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_L3CFG, NML3CfgClass))
@@ -18,10 +21,76 @@
 
 #define NM_L3CFG_SIGNAL_NOTIFY "l3cfg-notify"
 
+typedef enum _nm_packed {
+    NM_L3_ACD_DEFEND_TYPE_NONE,
+    NM_L3_ACD_DEFEND_TYPE_NEVER,
+    NM_L3_ACD_DEFEND_TYPE_ONCE,
+    NM_L3_ACD_DEFEND_TYPE_ALWAYS,
+} NML3AcdDefendType;
+
+typedef enum _nm_packed {
+    NM_L3_ACD_ADDR_STATE_INIT,
+    NM_L3_ACD_ADDR_STATE_PROBING,
+    NM_L3_ACD_ADDR_STATE_USED,
+    NM_L3_ACD_ADDR_STATE_READY,
+    NM_L3_ACD_ADDR_STATE_DEFENDING,
+    NM_L3_ACD_ADDR_STATE_CONFLICT,
+    NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED,
+} NML3AcdAddrState;
+
+typedef struct {
+    const NMPObject *     obj;
+    const NML3ConfigData *l3cd;
+    gconstpointer         tag;
+
+    struct {
+        guint32           acd_timeout_msec_track;
+        NML3AcdDefendType acd_defend_type_track;
+        bool              acd_dirty_track : 1;
+        bool              acd_failed_notified_track : 1;
+    } _priv;
+
+} NML3AcdAddrTrackInfo;
+
+typedef struct {
+    in_addr_t                   addr;
+    guint                       n_track_infos;
+    NML3AcdAddrState            state;
+    NML3Cfg *                   l3cfg;
+    const NML3AcdAddrTrackInfo *track_infos;
+} NML3AcdAddrInfo;
+
+static inline const NML3AcdAddrTrackInfo *
+nm_l3_acd_addr_info_find_track_info(const NML3AcdAddrInfo *addr_info,
+                                    gconstpointer          tag,
+                                    const NML3ConfigData * l3cd,
+                                    const NMPObject *      obj)
+{
+    guint                       i;
+    const NML3AcdAddrTrackInfo *ti;
+
+    nm_assert(addr_info);
+
+    /* we always expect that the number n_track_infos is reasonably small. Hence,
+     * a naive linear search is simplest and fastest (e.g. we don't have a hash table). */
+
+    for (i = 0, ti = addr_info->track_infos; i < addr_info->n_track_infos; i++, ti++) {
+        if (l3cd && ti->l3cd != l3cd)
+            continue;
+        if (tag && ti->tag != tag)
+            continue;
+        if (obj && ti->obj != obj)
+            continue;
+        return ti;
+    }
+
+    return NULL;
+}
+
 typedef enum {
     NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED,
 
-    NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED,
+    NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT,
 
     /* emitted at the end of nm_l3cfg_platform_commit(). */
     NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT,
@@ -38,24 +107,19 @@ typedef enum {
      * notifications without also subscribing directly to the platform. */
     NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE,
 
+    NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT,
+
     _NM_L3_CONFIG_NOTIFY_TYPE_NUM,
 } NML3ConfigNotifyType;
 
-typedef struct {
-    const NMPObject *     obj;
-    const NML3ConfigData *l3cd;
-    gconstpointer         tag;
-} NML3ConfigNotifyPayloadAcdFailedSource;
+struct _NML3IPv4LL;
 
 typedef struct {
     NML3ConfigNotifyType notify_type;
     union {
         struct {
-            in_addr_t                                     addr;
-            guint                                         sources_len;
-            bool                                          probe_result : 1;
-            const NML3ConfigNotifyPayloadAcdFailedSource *sources;
-        } acd_completed;
+            NML3AcdAddrInfo info;
+        } acd_event;
 
         struct {
             const NMPObject *          obj;
@@ -65,6 +129,10 @@ typedef struct {
         struct {
             guint32 obj_type_flags;
         } platform_change_on_idle;
+
+        struct {
+            struct _NML3IPv4LL *ipv4ll;
+        } ipv4ll_event;
     };
 } NML3ConfigNotifyData;
 
@@ -79,7 +147,6 @@ struct _NML3Cfg {
         const NMPObject *       plobj;
         const NMPObject *       plobj_next;
         int                     ifindex;
-        bool                    changed_configs : 1;
     } priv;
 };
 
@@ -205,6 +272,7 @@ gboolean nm_l3cfg_add_config(NML3Cfg *             self,
                              guint32               default_route_metric_6,
                              guint32               default_route_penalty_4,
                              guint32               default_route_penalty_6,
+                             NML3AcdDefendType     acd_defend_type,
                              guint32               acd_timeout_msec,
                              NML3ConfigMergeFlags  merge_flags);
 
@@ -250,6 +318,10 @@ void nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self);
 
 /*****************************************************************************/
 
+const NML3AcdAddrInfo *nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr);
+
+/*****************************************************************************/
+
 NML3CfgCommitType nm_l3cfg_commit_type_get(NML3Cfg *self);
 
 typedef struct _NML3CfgCommitTypeHandle NML3CfgCommitTypeHandle;
@@ -273,4 +345,12 @@ gboolean nm_l3cfg_has_commited_ip6_addresses_pending_dad(NML3Cfg *self);
 
 /*****************************************************************************/
 
+struct _NML3IPv4LL *nm_l3cfg_get_ipv4ll(NML3Cfg *self);
+
+struct _NML3IPv4LL *nm_l3cfg_access_ipv4ll(NML3Cfg *self);
+
+void _nm_l3cfg_unregister_ipv4ll(NML3Cfg *self);
+
+/*****************************************************************************/
+
 #endif /* __NM_L3CFG_H__ */
diff --git a/src/nm-manager.c b/src/core/nm-manager.c
index 46f81c7c..5a6e05a9 100644
--- a/src/nm-manager.c
+++ b/src/core/nm-manager.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2007 - 2009 Novell, Inc.
  * Copyright (C) 2007 - 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-manager.h"
 
@@ -102,6 +102,7 @@ enum {
     ACTIVE_CONNECTION_ADDED,
     ACTIVE_CONNECTION_REMOVED,
     CONFIGURE_QUIT,
+    DEVICE_IFINDEX_CHANGED,
 
     LAST_SIGNAL
 };
@@ -142,7 +143,7 @@ typedef struct {
 
     GArray *capabilities;
 
-    CList               active_connections_lst_head;
+    CList               active_connections_lst_head; /* Oldest ACs at the beginning */
     CList               async_op_lst_head;
     guint               ac_cleanup_id;
     NMActiveConnection *primary_connection;
@@ -941,7 +942,7 @@ active_connection_add(NMManager *self, NMActiveConnection *active)
     nm_assert(NM_IS_ACTIVE_CONNECTION(active));
     nm_assert(!c_list_is_linked(&active->active_connections_lst));
 
-    c_list_link_front(&priv->active_connections_lst_head, &active->active_connections_lst);
+    c_list_link_tail(&priv->active_connections_lst_head, &active->active_connections_lst);
     g_object_ref(active);
 
     g_signal_connect(active,
@@ -987,7 +988,7 @@ active_connection_find(
     nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn));
     nm_assert(!out_all_matching || !*out_all_matching);
 
-    c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
+    c_list_for_each_entry_prev (ac, &priv->active_connections_lst_head, active_connections_lst) {
         NMSettingsConnection *ac_conn;
 
         ac_conn = nm_active_connection_get_settings_connection(ac);
@@ -1013,8 +1014,37 @@ active_connection_find(
         g_ptr_array_add(all, g_object_ref(ac));
     }
 
-    if (!best_ac)
-        return NULL;
+    if (!best_ac) {
+        AsyncOpData *async_op_data;
+
+        c_list_for_each_entry (async_op_data, &priv->async_op_lst_head, async_op_lst) {
+            NMSettingsConnection *ac_conn;
+
+            ac      = async_op_data->ac_auth.active;
+            ac_conn = nm_active_connection_get_settings_connection(ac);
+            if (sett_conn && sett_conn != ac_conn)
+                continue;
+            if (uuid && !nm_streq0(uuid, nm_settings_connection_get_uuid(ac_conn)))
+                continue;
+
+            if (!out_all_matching)
+                return ac;
+
+            if (!best_ac) {
+                best_ac = ac;
+                continue;
+            }
+
+            if (!all) {
+                all = g_ptr_array_new_with_free_func(g_object_unref);
+                g_ptr_array_add(all, g_object_ref(best_ac));
+            }
+            g_ptr_array_add(all, g_object_ref(ac));
+        }
+
+        if (!best_ac)
+            return NULL;
+    }
 
     /* as an optimization, we only allocate out_all_matching, if there are more
      * than one result. If there is only one result, we only return the single
@@ -2495,16 +2525,7 @@ nm_manager_device_auth_request(NMManager *                    self,
                                                 &error))
         goto fail_on_idle;
 
-    /* Validate the request */
     chain = nm_auth_chain_new_subject(subject, context, device_auth_done_cb, self);
-    if (!chain) {
-        g_set_error(&error,
-                    NM_MANAGER_ERROR,
-                    NM_MANAGER_ERROR_PERMISSION_DENIED,
-                    NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
-        goto fail_on_idle;
-    }
-
     if (cancellable)
         nm_auth_chain_set_cancellable(chain, cancellable);
 
@@ -4907,11 +4928,11 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **
                 if (master_device) {
                     g_prefix_error(error,
                                    "Master device '%s' can't be activated: ",
-                                   nm_device_get_ip_iface(device));
+                                   nm_device_get_ip_iface(master_device));
                 } else {
                     g_prefix_error(error,
                                    "Master connection '%s' can't be activated: ",
-                                   nm_settings_connection_get_id(sett_conn));
+                                   nm_settings_connection_get_id(master_connection));
                 }
                 return FALSE;
             }
@@ -5184,6 +5205,7 @@ _internal_activation_auth_done(NMManager *         self,
         return;
 
 fail:
+    _delete_volatile_connection_do(self, nm_active_connection_get_settings_connection(active));
     nm_assert(error_desc || error);
     nm_active_connection_set_state_fail(active,
                                         NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
@@ -5452,6 +5474,8 @@ _activation_auth_done(NMManager *            self,
     return;
 
 fail:
+    _delete_volatile_connection_do(self, connection);
+
     nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ACTIVATE,
                                connection,
                                FALSE,
@@ -6038,15 +6062,7 @@ impl_manager_deactivate_connection(NMDBusObject *                     obj,
                                              &error))
         goto done;
 
-    /* Validate the user request */
     chain = nm_auth_chain_new_subject(subject, invocation, deactivate_net_auth_done_cb, self);
-    if (!chain) {
-        error = g_error_new_literal(NM_MANAGER_ERROR,
-                                    NM_MANAGER_ERROR_PERMISSION_DENIED,
-                                    NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
-        goto done;
-    }
-
     c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain));
     nm_auth_chain_set_data(chain, "path", g_strdup(active_path), g_free);
     nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE);
@@ -7435,10 +7451,11 @@ auth_mgr_changed(NMAuthManager *auth_manager, gpointer user_data)
 #define KERN_RFKILL_TYPE_WLAN     1
 #define KERN_RFKILL_TYPE_WWAN     5
 struct rfkill_event {
-    __u32 idx;
-    __u8  type;
-    __u8  op;
-    __u8  soft, hard;
+    uint32_t idx;
+    uint8_t  type;
+    uint8_t  op;
+    uint8_t  soft;
+    uint8_t  hard;
 } _nm_packed;
 
 static void
@@ -7566,6 +7583,14 @@ periodic_update_active_connection_timestamps(gpointer user_data)
     return G_SOURCE_CONTINUE;
 }
 
+void
+nm_manager_unblock_failed_ovs_interfaces(NMManager *self)
+{
+    NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self);
+
+    nm_policy_unblock_failed_ovs_interfaces(priv->policy);
+}
+
 /*****************************************************************************/
 
 void
@@ -7598,6 +7623,12 @@ nm_manager_set_capability(NMManager *self, NMCapability cap)
     _notify(self, PROP_CAPABILITIES);
 }
 
+void
+nm_manager_emit_device_ifindex_changed(NMManager *self, NMDevice *device)
+{
+    g_signal_emit(self, signals[DEVICE_IFINDEX_CHANGED], 0, device);
+}
+
 /*****************************************************************************/
 
 NM_DEFINE_SINGLETON_REGISTER(NMManager);
@@ -7857,7 +7888,9 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
         break;
     case PROP_ACTIVE_CONNECTIONS:
         ptrarr = g_ptr_array_new();
-        c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
+        c_list_for_each_entry_prev (ac,
+                                    &priv->active_connections_lst_head,
+                                    active_connections_lst) {
             path = nm_dbus_object_get_path(NM_DBUS_OBJECT(ac));
             if (path)
                 g_ptr_array_add(ptrarr, g_strdup(path));
@@ -8649,4 +8682,15 @@ nm_manager_class_init(NMManagerClass *manager_class)
                                            NULL,
                                            G_TYPE_NONE,
                                            0);
+
+    signals[DEVICE_IFINDEX_CHANGED] = g_signal_new(NM_MANAGER_DEVICE_IFINDEX_CHANGED,
+                                                   G_OBJECT_CLASS_TYPE(object_class),
+                                                   G_SIGNAL_RUN_FIRST,
+                                                   0,
+                                                   NULL,
+                                                   NULL,
+                                                   NULL,
+                                                   G_TYPE_NONE,
+                                                   1,
+                                                   NM_TYPE_DEVICE);
 }
diff --git a/src/nm-manager.h b/src/core/nm-manager.h
index 75f8a673..7a8b3a1e 100644
--- a/src/nm-manager.h
+++ b/src/core/nm-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2007 - 2008 Novell, Inc.
  * Copyright (C) 2007 - 2010 Red Hat, Inc.
@@ -50,6 +50,7 @@
 /* Signals */
 #define NM_MANAGER_DEVICE_ADDED             "device-added"
 #define NM_MANAGER_DEVICE_REMOVED           "device-removed"
+#define NM_MANAGER_DEVICE_IFINDEX_CHANGED   "device-ifindex-changed"
 #define NM_MANAGER_USER_PERMISSIONS_CHANGED "user-permissions-changed"
 
 #define NM_MANAGER_ACTIVE_CONNECTION_ADDED   "active-connection-added"
@@ -72,6 +73,7 @@ NMState  nm_manager_get_state(NMManager *manager);
 
 const CList *nm_manager_get_active_connections(NMManager *manager);
 
+/* From least recently activated */
 #define nm_manager_for_each_active_connection(manager, iter, tmp_list)                        \
     for (tmp_list = nm_manager_get_active_connections(manager),                               \
         iter      = c_list_entry(tmp_list->next, NMActiveConnection, active_connections_lst); \
@@ -86,6 +88,22 @@ const CList *nm_manager_get_active_connections(NMManager *manager);
                              NMActiveConnection,                                              \
                              active_connections_lst))
 
+/* From most recently activated */
+#define nm_manager_for_each_active_connection_prev(manager, iter, tmp_list)                   \
+    for (tmp_list = nm_manager_get_active_connections(manager),                               \
+        iter      = c_list_entry(tmp_list->prev, NMActiveConnection, active_connections_lst); \
+         ({                                                                                   \
+             const gboolean _has_prev = (&iter->active_connections_lst != tmp_list);          \
+                                                                                              \
+             if (!_has_prev)                                                                  \
+                 iter = NULL;                                                                 \
+             _has_prev;                                                                       \
+         });                                                                                  \
+         iter = c_list_entry(iter->active_connections_lst.prev,                               \
+                             NMActiveConnection,                                              \
+                             active_connections_lst))
+
+/* From least recently activated */
 #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) {                                                         \
@@ -163,6 +181,7 @@ gboolean nm_manager_deactivate_connection(NMManager *         manager,
                                           GError **           error);
 
 void nm_manager_set_capability(NMManager *self, NMCapability cap);
+void nm_manager_emit_device_ifindex_changed(NMManager *self, NMDevice *device);
 
 NMDevice *nm_manager_get_device(NMManager *self, const char *ifname, NMDeviceType device_type);
 gboolean  nm_manager_remove_device(NMManager *self, const char *ifname, NMDeviceType device_type);
@@ -192,4 +211,6 @@ void nm_manager_device_auth_request(NMManager *                    self,
                                     NMManagerDeviceAuthRequestFunc callback,
                                     gpointer                       user_data);
 
+void nm_manager_unblock_failed_ovs_interfaces(NMManager *self);
+
 #endif /* __NETWORKMANAGER_MANAGER_H__ */
diff --git a/src/nm-netns.c b/src/core/nm-netns.c
index 2970e45c..88ea09b6 100644
--- a/src/nm-netns.c
+++ b/src/core/nm-netns.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-netns.h"
 
@@ -14,7 +14,7 @@
 #include "nm-core-internal.h"
 #include "nm-l3cfg.h"
 #include "platform/nm-platform.h"
-#include "platform/nmp-netns.h"
+#include "nm-platform/nmp-netns.h"
 #include "platform/nmp-rules-manager.h"
 
 /*****************************************************************************/
@@ -126,6 +126,19 @@ _l3cfg_weak_notify(gpointer data, GObject *where_the_object_was)
 }
 
 NML3Cfg *
+nm_netns_get_l3cfg(NMNetns *self, int ifindex)
+{
+    NMNetnsPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_NETNS(self), NULL);
+    g_return_val_if_fail(ifindex > 0, NULL);
+
+    priv = NM_NETNS_GET_PRIVATE(self);
+
+    return g_hash_table_lookup(priv->l3cfgs, &ifindex);
+}
+
+NML3Cfg *
 nm_netns_access_l3cfg(NMNetns *self, int ifindex)
 {
     NMNetnsPrivate *priv;
@@ -453,7 +466,7 @@ dispose(GObject *object)
         g_signal_handlers_disconnect_by_data(priv->platform, &priv->_self_signal_user_data);
 
     g_clear_object(&priv->platform);
-    g_clear_pointer(&priv->l3cfgs, g_hash_table_unref);
+    nm_clear_pointer(&priv->l3cfgs, g_hash_table_unref);
 
     nm_clear_pointer(&priv->rules_manager, nmp_rules_manager_unref);
 
diff --git a/src/nm-netns.h b/src/core/nm-netns.h
index bb0c955e..aab3b520 100644
--- a/src/nm-netns.h
+++ b/src/core/nm-netns.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
@@ -6,6 +6,8 @@
 #ifndef __NM_NETNS_H__
 #define __NM_NETNS_H__
 
+#include "nm-platform/nmp-base.h"
+
 #define NM_TYPE_NETNS            (nm_netns_get_type())
 #define NM_NETNS(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_NETNS, NMNetns))
 #define NM_NETNS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_NETNS, NMNetnsClass))
@@ -31,6 +33,8 @@ struct _NMDedupMultiIndex *nm_netns_get_multi_idx(NMNetns *self);
 
 #define NM_NETNS_GET (nm_netns_get())
 
+NML3Cfg *nm_netns_get_l3cfg(NMNetns *self, int ifindex);
+
 NML3Cfg *nm_netns_access_l3cfg(NMNetns *netns, int ifindex);
 
 /*****************************************************************************/
diff --git a/src/nm-pacrunner-manager.c b/src/core/nm-pacrunner-manager.c
index 0fb31984..b58aef06 100644
--- a/src/nm-pacrunner-manager.c
+++ b/src/core/nm-pacrunner-manager.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Atul Anand <atulhjp@gmail.com>.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-pacrunner-manager.h"
 
@@ -126,11 +126,13 @@ NM_AUTO_DEFINE_FCN0(NMPacrunnerConfId *, _nm_auto_unref_conf_id, conf_id_unref);
 static void
 get_ip_domains(GPtrArray *domains, NMIPConfig *ip_config)
 {
-    NMDedupMultiIter ipconf_iter;
-    char *           cidr;
-    guint            i, num;
-    char             sbuf[NM_UTILS_INET_ADDRSTRLEN];
-    int              addr_family;
+    NMDedupMultiIter           ipconf_iter;
+    char *                     cidr;
+    guint                      i, num;
+    char                       sbuf[NM_UTILS_INET_ADDRSTRLEN];
+    int                        addr_family;
+    const NMPlatformIPAddress *address;
+    const NMPlatformIPRoute *  routes;
 
     if (!ip_config)
         return;
@@ -145,47 +147,20 @@ get_ip_domains(GPtrArray *domains, NMIPConfig *ip_config)
     for (i = 0; i < num; i++)
         g_ptr_array_add(domains, g_strdup(nm_ip_config_get_domain(ip_config, i)));
 
-    if (addr_family == AF_INET) {
-        const NMPlatformIP4Address *address;
-
-        nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, (NMIP4Config *) ip_config, &address) {
-            cidr = g_strdup_printf("%s/%u",
-                                   _nm_utils_inet4_ntop(address->address, sbuf),
-                                   address->plen);
-            g_ptr_array_add(domains, cidr);
-        }
-    } else {
-        const NMPlatformIP6Address *address;
-
-        nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, (NMIP6Config *) ip_config, &address) {
-            cidr = g_strdup_printf("%s/%u",
-                                   _nm_utils_inet6_ntop(&address->address, sbuf),
-                                   address->plen);
-            g_ptr_array_add(domains, cidr);
-        }
+    nm_ip_config_iter_ip_address_for_each (&ipconf_iter, ip_config, &address) {
+        cidr = g_strdup_printf("%s/%u",
+                               nm_utils_inet_ntop(addr_family, address->address_ptr, sbuf),
+                               address->plen);
+        g_ptr_array_add(domains, cidr);
     }
 
-    if (addr_family == AF_INET) {
-        const NMPlatformIP4Route *routes;
-
-        nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, (NMIP4Config *) ip_config, &routes) {
-            if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(routes))
-                continue;
-            cidr =
-                g_strdup_printf("%s/%u", _nm_utils_inet4_ntop(routes->network, sbuf), routes->plen);
-            g_ptr_array_add(domains, cidr);
-        }
-    } else {
-        const NMPlatformIP6Route *routes;
-
-        nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, (NMIP6Config *) ip_config, &routes) {
-            if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(routes))
-                continue;
-            cidr = g_strdup_printf("%s/%u",
-                                   _nm_utils_inet6_ntop(&routes->network, sbuf),
-                                   routes->plen);
-            g_ptr_array_add(domains, cidr);
-        }
+    nm_ip_config_iter_ip_route_for_each (&ipconf_iter, ip_config, &routes) {
+        if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(routes))
+            continue;
+        cidr = g_strdup_printf("%s/%u",
+                               nm_utils_inet_ntop(addr_family, routes->network_ptr, sbuf),
+                               routes->plen);
+        g_ptr_array_add(domains, cidr);
     }
 }
 
diff --git a/src/nm-pacrunner-manager.h b/src/core/nm-pacrunner-manager.h
index b3c3bdb7..fd40f120 100644
--- a/src/nm-pacrunner-manager.h
+++ b/src/core/nm-pacrunner-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Atul Anand <atulhjp@gmail.com>.
  * Copyright (C) 2016 - 2017 Red Hat, Inc.
diff --git a/src/nm-policy.c b/src/core/nm-policy.c
index 038b89cf..db4983f8 100644
--- a/src/nm-policy.c
+++ b/src/core/nm-policy.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2013 Red Hat, Inc.
  * Copyright (C) 2007 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-policy.h"
 
@@ -64,12 +64,6 @@ typedef struct {
     NMActiveConnection *default_ac4, *activating_ac4;
     NMActiveConnection *default_ac6, *activating_ac6;
 
-    struct {
-        GInetAddress *addr;
-        GResolver *   resolver;
-        GCancellable *cancellable;
-    } lookup;
-
     NMDnsManager *dns_manager;
     gulong        config_changed_id;
 
@@ -82,8 +76,10 @@ typedef struct {
     char *               cur_hostname;  /* hostname we want to assign */
     char *
         last_hostname; /* last hostname NM set (to detect if someone else changed it in the meanwhile) */
-    gboolean changing_hostname; /* hostname set operation still in progress */
-    gboolean dhcp_hostname;     /* current hostname was set from dhcp */
+
+    bool changing_hostname : 1; /* hostname set operation in progress */
+    bool dhcp_hostname : 1;     /* current hostname was set from dhcp */
+    bool updating_dns : 1;
 
     GArray *ip6_prefix_delegations; /* pool of ip6 prefixes delegated to all devices */
 } NMPolicyPrivate;
@@ -117,6 +113,8 @@ _PRIV_TO_SELF(NMPolicyPrivate *priv)
 /*****************************************************************************/
 
 #define _NMLOG_PREFIX_NAME "policy"
+#undef _NMLOG_ENABLED
+#define _NMLOG_ENABLED(level, domain) (nm_logging_enabled((level), (domain)))
 #define _NMLOG(level, domain, ...)                                         \
     G_STMT_START                                                           \
     {                                                                      \
@@ -131,6 +129,7 @@ _PRIV_TO_SELF(NMPolicyPrivate *priv)
 
 /*****************************************************************************/
 
+static void      update_system_hostname(NMPolicy *self, const char *msg);
 static void      schedule_activate_all(NMPolicy *self);
 static void      schedule_activate_check(NMPolicy *self, NMDevice *device);
 static NMDevice *get_default_device(NMPolicy *self, int addr_family);
@@ -359,11 +358,11 @@ device_ip6_prefix_delegated(NMDevice *device, NMPlatformIP6Address *prefix, gpoi
     delegation->device = device;
     delegation->prefix = *prefix;
 
-    /* The newly activated connections are added to the list beginning,
-     * so traversing it from the beginning makes it likely for newly
+    /* The newly activated connections are added to the end of the list,
+     * so traversing it from the end makes it likely for newly
      * activated connections that have no subnet assigned to be served
      * first. That is a simple yet fair policy, which is good. */
-    nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
+    nm_manager_for_each_active_connection_prev (priv->manager, ac, tmp_list) {
         NMDevice *to_device;
 
         to_device = nm_active_connection_get_device(ac);
@@ -588,12 +587,6 @@ _set_hostname(NMPolicy *self, const char *new_hostname, const char *msg)
      * there was no valid hostname to start with.
      */
 
-    /* Clear lookup addresses if we have a hostname, so that we don't
-     * restart the reverse lookup thread later.
-     */
-    if (new_hostname)
-        g_clear_object(&priv->lookup.addr);
-
     /* Update the DNS only if the hostname is actually
      * going to change.
      */
@@ -602,11 +595,14 @@ _set_hostname(NMPolicy *self, const char *new_hostname, const char *msg)
         priv->cur_hostname = g_strdup(new_hostname);
 
         /* Notify the DNS manager of the hostname change so that the domain part, if
-         * present, can be added to the search list.
+         * present, can be added to the search list. Set the @updating_dns flag
+         * so that dns_config_changed() doesn't try again to restart DNS lookup.
          */
+        priv->updating_dns = TRUE;
         nm_dns_manager_set_hostname(priv->dns_manager,
                                     priv->cur_hostname,
                                     all_devices_not_active(self));
+        priv->updating_dns = FALSE;
     }
 
     /* Finally, set kernel hostname */
@@ -639,57 +635,176 @@ _set_hostname(NMPolicy *self, const char *new_hostname, const char *msg)
                                                g_object_ref(self));
 }
 
-static void
-lookup_callback(GObject *source, GAsyncResult *result, gpointer user_data)
+typedef struct {
+    NMDevice *device;
+    int       priority;
+    bool      from_dhcp : 1;
+    bool      from_dns : 1;
+    bool      IS_IPv4 : 1;
+    bool      is_default : 1;
+} DeviceHostnameInfo;
+
+static int
+device_hostname_info_compare(gconstpointer a, gconstpointer b)
 {
-    NMPolicy *       self;
-    NMPolicyPrivate *priv;
-    gs_free char *   hostname   = NULL;
-    gs_free_error GError *error = NULL;
+    const DeviceHostnameInfo *info1 = a;
+    const DeviceHostnameInfo *info2 = b;
 
-    hostname = g_resolver_lookup_by_address_finish(G_RESOLVER(source), result, &error);
-    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-        return;
+    NM_CMP_FIELD(info1, info2, priority);
+    NM_CMP_FIELD_UNSAFE(info2, info1, is_default);
 
-    self = user_data;
-    priv = NM_POLICY_GET_PRIVATE(self);
+    return 0;
+}
+
+NM_CON_DEFAULT_NOP("hostname.from-dhcp");
+NM_CON_DEFAULT_NOP("hostname.from-dns-lookup");
+NM_CON_DEFAULT_NOP("hostname.only-from-default");
+
+static gboolean
+device_get_hostname_property_boolean(NMDevice *device, const char *name)
+{
+    NMSettingHostname *s_hostname;
+    char               buf[128];
+    int                value;
+    NMTernary          default_value;
 
-    g_clear_object(&priv->lookup.cancellable);
+    nm_assert(NM_IN_STRSET(name,
+                           NM_SETTING_HOSTNAME_FROM_DHCP,
+                           NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP,
+                           NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT));
 
-    if (hostname)
-        _set_hostname(self, hostname, "from address lookup");
+    s_hostname = nm_device_get_applied_setting(device, NM_TYPE_SETTING_HOSTNAME);
+
+    if (s_hostname) {
+        g_object_get(s_hostname, name, &value, NULL);
+        if (NM_IN_SET(value, NM_TERNARY_FALSE, NM_TERNARY_TRUE))
+            return value;
+    }
+
+    if (nm_streq(name, NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT))
+        default_value = NM_TERNARY_FALSE;
     else
-        _set_hostname(self, NULL, error->message);
+        default_value = NM_TERNARY_TRUE;
+
+    return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                       nm_sprintf_buf(buf, "hostname.%s", name),
+                                                       device,
+                                                       NM_TERNARY_FALSE,
+                                                       NM_TERNARY_TRUE,
+                                                       default_value);
+}
+
+static int
+device_get_hostname_priority(NMDevice *device)
+{
+    NMSettingHostname *s_hostname;
+    int                priority;
+
+    s_hostname = nm_device_get_applied_setting(device, NM_TYPE_SETTING_HOSTNAME);
+    if (s_hostname) {
+        priority = nm_setting_hostname_get_priority(s_hostname);
+        if (priority != 0)
+            return priority;
+    }
+
+    return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                       NM_CON_DEFAULT("hostname.priority"),
+                                                       device,
+                                                       G_MININT,
+                                                       G_MAXINT,
+                                                       100);
+}
+
+static GArray *
+build_device_hostname_infos(NMPolicy *self)
+{
+    NMPolicyPrivate *   priv = NM_POLICY_GET_PRIVATE(self);
+    const CList *       tmp_clist;
+    NMActiveConnection *ac;
+    GArray *            array = NULL;
+
+    nm_manager_for_each_active_connection (priv->manager, ac, tmp_clist) {
+        DeviceHostnameInfo *info;
+        NMDevice *          device;
+        gboolean            only_from_default;
+        gboolean            is_default;
+        int                 IS_IPv4;
+
+        device = nm_active_connection_get_device(ac);
+        if (!device)
+            continue;
+
+        only_from_default =
+            device_get_hostname_property_boolean(device, NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT);
+
+        for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
+            is_default = (ac == (IS_IPv4 ? priv->default_ac4 : priv->default_ac6));
+            if (only_from_default && !is_default)
+                continue;
+
+            if (!array)
+                array = g_array_sized_new(FALSE, FALSE, sizeof(DeviceHostnameInfo), 4);
+
+            info  = nm_g_array_append_new(array, DeviceHostnameInfo);
+            *info = (DeviceHostnameInfo){
+                .device   = device,
+                .priority = device_get_hostname_priority(device),
+                .from_dhcp =
+                    device_get_hostname_property_boolean(device, NM_SETTING_HOSTNAME_FROM_DHCP),
+                .from_dns =
+                    device_get_hostname_property_boolean(device,
+                                                         NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP),
+                .IS_IPv4    = IS_IPv4,
+                .is_default = is_default,
+            };
+        }
+    }
+
+    if (array && array->len > 1) {
+        const DeviceHostnameInfo *info0;
+        guint                     i;
+
+        g_array_sort(array, device_hostname_info_compare);
+
+        info0 = &g_array_index(array, DeviceHostnameInfo, 0);
+        if (info0->priority < 0) {
+            for (i = 1; i < array->len; i++) {
+                const DeviceHostnameInfo *info = &g_array_index(array, DeviceHostnameInfo, i);
+
+                if (info->priority > info0->priority) {
+                    g_array_set_size(array, i);
+                    break;
+                }
+            }
+        }
+    }
+
+    return array;
 }
 
 static void
-lookup_by_address(NMPolicy *self)
+device_dns_lookup_done(NMDevice *device, gpointer user_data)
 {
-    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    NMPolicy *self = user_data;
 
-    nm_clear_g_cancellable(&priv->lookup.cancellable);
-    priv->lookup.cancellable = g_cancellable_new();
-    g_resolver_lookup_by_address_async(priv->lookup.resolver,
-                                       priv->lookup.addr,
-                                       priv->lookup.cancellable,
-                                       lookup_callback,
-                                       self);
+    g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self);
+
+    update_system_hostname(self, "lookup finished");
 }
 
 static void
 update_system_hostname(NMPolicy *self, const char *msg)
 {
-    NMPolicyPrivate *           priv = NM_POLICY_GET_PRIVATE(self);
-    const char *                configured_hostname;
-    gs_free char *              temp_hostname = NULL;
-    const char *                dhcp_hostname, *p;
-    NMIP4Config *               ip4_config;
-    NMIP6Config *               ip6_config;
-    gboolean                    external_hostname = FALSE;
-    const NMPlatformIP4Address *addr4;
-    const NMPlatformIP6Address *addr6;
-    NMDevice *                  device;
-    NMDhcpConfig *              dhcp_config;
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    const char *     configured_hostname;
+    gs_free char *   temp_hostname = NULL;
+    const char *     dhcp_hostname, *p;
+    gboolean         external_hostname = FALSE;
+    NMDhcpConfig *   dhcp_config;
+    gs_unref_array GArray *infos = NULL;
+    DeviceHostnameInfo *   info;
+    guint                  i;
+    int                    addr_family;
 
     g_return_if_fail(self != NULL);
 
@@ -700,8 +815,6 @@ update_system_hostname(NMPolicy *self, const char *msg)
 
     _LOGT(LOGD_DNS, "set-hostname: updating hostname (%s)", msg);
 
-    nm_clear_g_cancellable(&priv->lookup.cancellable);
-
     /* Check if the hostname was set externally to NM, so that in that case
      * we can avoid to fallback to the one we got when we started.
      * Consider "not specific" hostnames as equal. */
@@ -729,10 +842,9 @@ update_system_hostname(NMPolicy *self, const char *msg)
     /* Hostname precedence order:
      *
      * 1) a configured hostname (from settings)
-     * 2) automatic hostname from the default device's config (DHCP, VPN, etc)
-     * 3) the last hostname set outside NM
-     * 4) reverse-DNS of the best device's IPv4 address
-     *
+     * 2) automatic hostname from DHCP of eligible interfaces
+     * 3) reverse-DNS lookup of the first address on eligible interfaces
+     * 4) the last hostname set outside NM
      */
 
     /* Try a persistent hostname first */
@@ -743,40 +855,67 @@ update_system_hostname(NMPolicy *self, const char *msg)
         return;
     }
 
-    if (priv->default_ac4) {
-        /* Grab a hostname out of the device's DHCP4 config */
-        dhcp_config = nm_device_get_dhcp_config(get_default_device(self, AF_INET), AF_INET);
-        if (dhcp_config) {
-            dhcp_hostname = nm_dhcp_config_get_option(dhcp_config, "host_name");
-            if (dhcp_hostname && dhcp_hostname[0]) {
-                p = nm_str_skip_leading_spaces(dhcp_hostname);
-                if (p[0]) {
-                    _set_hostname(self, p, "from DHCPv4");
-                    priv->dhcp_hostname = TRUE;
-                    return;
+    infos = build_device_hostname_infos(self);
+
+    if (infos && _LOGT_ENABLED(LOGD_DNS)) {
+        _LOGT(LOGD_DNS, "device hostname info:");
+        for (i = 0; i < infos->len; i++) {
+            info = &g_array_index(infos, DeviceHostnameInfo, i);
+            _LOGT(LOGD_DNS,
+                  "  - prio:%5d ipv%c%s %s %s dev:%s",
+                  info->priority,
+                  info->IS_IPv4 ? '4' : '6',
+                  info->is_default ? " (def)" : "      ",
+                  info->from_dhcp ? "dhcp " : "     ",
+                  info->from_dns ? "dns " : "    ",
+                  nm_device_get_iface(info->device));
+        }
+    }
+
+    for (i = 0; infos && i < infos->len; i++) {
+        info        = &g_array_index(infos, DeviceHostnameInfo, i);
+        addr_family = info->IS_IPv4 ? AF_INET : AF_INET6;
+        g_signal_handlers_disconnect_by_func(info->device, device_dns_lookup_done, self);
+
+        if (info->from_dhcp) {
+            dhcp_config = nm_device_get_dhcp_config(info->device, addr_family);
+            if (dhcp_config) {
+                dhcp_hostname =
+                    nm_dhcp_config_get_option(dhcp_config,
+                                              info->IS_IPv4 ? "host_name" : "fqdn_fqdn");
+                if (dhcp_hostname && dhcp_hostname[0]) {
+                    p = nm_str_skip_leading_spaces(dhcp_hostname);
+                    if (p[0]) {
+                        _set_hostname(self, p, info->IS_IPv4 ? "from DHCPv4" : "from DHCPv6");
+                        priv->dhcp_hostname = TRUE;
+                        return;
+                    }
+                    _LOGW(LOGD_DNS,
+                          "set-hostname: DHCPv%c-provided hostname '%s' looks invalid; "
+                          "ignoring it",
+                          nm_utils_addr_family_to_char(addr_family),
+                          dhcp_hostname);
                 }
-                _LOGW(LOGD_DNS,
-                      "set-hostname: DHCPv4-provided hostname '%s' looks invalid; ignoring it",
-                      dhcp_hostname);
             }
         }
-    }
 
-    if (priv->default_ac6) {
-        /* Grab a hostname out of the device's DHCP6 config */
-        dhcp_config = nm_device_get_dhcp_config(get_default_device(self, AF_INET6), AF_INET6);
-        if (dhcp_config) {
-            dhcp_hostname = nm_dhcp_config_get_option(dhcp_config, "fqdn_fqdn");
-            if (dhcp_hostname && dhcp_hostname[0]) {
-                p = nm_str_skip_leading_spaces(dhcp_hostname);
-                if (p[0]) {
-                    _set_hostname(self, p, "from DHCPv6");
-                    priv->dhcp_hostname = TRUE;
+        if (priv->hostname_mode != NM_POLICY_HOSTNAME_MODE_DHCP) {
+            if (info->from_dns) {
+                const char *result;
+                gboolean    wait = FALSE;
+
+                result = nm_device_get_hostname_from_dns_lookup(info->device, addr_family, &wait);
+                if (result) {
+                    _set_hostname(self, result, "from address lookup");
+                    return;
+                }
+                if (wait) {
+                    g_signal_connect(info->device,
+                                     NM_DEVICE_DNS_LOOKUP_DONE,
+                                     (GCallback) device_dns_lookup_done,
+                                     self);
                     return;
                 }
-                _LOGW(LOGD_DNS,
-                      "set-hostname: DHCPv6-provided hostname '%s' looks invalid; ignoring it",
-                      dhcp_hostname);
             }
         }
     }
@@ -800,14 +939,6 @@ update_system_hostname(NMPolicy *self, const char *msg)
 
     priv->dhcp_hostname = FALSE;
 
-    if (!priv->default_ac4 && !priv->default_ac6) {
-        /* No best device; fall back to the last hostname set externally
-         * to NM or if there wasn't one, 'localhost.localdomain'
-         */
-        _set_hostname(self, priv->orig_hostname, "no default device");
-        return;
-    }
-
     /* If no automatically-configured hostname, try using the last hostname
      * set externally to NM
      */
@@ -816,30 +947,7 @@ update_system_hostname(NMPolicy *self, const char *msg)
         return;
     }
 
-    /* No configured hostname, no automatically determined hostname, and no
-     * bootup hostname. Start reverse DNS of the current IPv4 or IPv6 address.
-     */
-    device     = get_default_device(self, AF_INET);
-    ip4_config = device ? nm_device_get_ip4_config(device) : NULL;
-
-    device     = get_default_device(self, AF_INET6);
-    ip6_config = device ? nm_device_get_ip6_config(device) : NULL;
-
-    if (ip4_config && (addr4 = nm_ip4_config_get_first_address(ip4_config))) {
-        g_clear_object(&priv->lookup.addr);
-        priv->lookup.addr =
-            g_inet_address_new_from_bytes((guint8 *) &addr4->address, G_SOCKET_FAMILY_IPV4);
-    } else if (ip6_config && (addr6 = nm_ip6_config_get_first_address(ip6_config))) {
-        g_clear_object(&priv->lookup.addr);
-        priv->lookup.addr =
-            g_inet_address_new_from_bytes((guint8 *) &addr6->address, G_SOCKET_FAMILY_IPV6);
-    } else {
-        /* No valid IP config; fall back to localhost.localdomain */
-        _set_hostname(self, NULL, "no IP config");
-        return;
-    }
-
-    lookup_by_address(self);
+    _set_hostname(self, NULL, "no hostname found");
 }
 
 static void
@@ -1433,6 +1541,30 @@ hostname_changed(NMHostnameManager *hostname_manager, GParamSpec *pspec, gpointe
     update_system_hostname(self, "hostname changed");
 }
 
+void
+nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self)
+{
+    NMPolicyPrivate *            priv        = NM_POLICY_GET_PRIVATE(self);
+    NMSettingsConnection *const *connections = NULL;
+    guint                        i;
+
+    _LOGT(LOGD_DEVICE, "unblocking failed OVS interfaces");
+
+    connections = nm_settings_get_connections(priv->settings, NULL);
+    for (i = 0; connections[i]; i++) {
+        NMSettingsConnection *sett_conn  = connections[i];
+        NMConnection *        connection = nm_settings_connection_get_connection(sett_conn);
+
+        if (nm_connection_get_setting_ovs_interface(connection)) {
+            nm_settings_connection_autoconnect_retries_reset(sett_conn);
+            nm_settings_connection_autoconnect_blocked_reason_set(
+                sett_conn,
+                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+                FALSE);
+        }
+    }
+}
+
 static gboolean
 reset_autoconnect_all(
     NMPolicy *self,
@@ -1801,6 +1933,8 @@ device_state_changed(NMDevice *          device,
 
     switch (new_state) {
     case NM_DEVICE_STATE_FAILED:
+        g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self);
+
         /* Mark the connection invalid if it failed during activation so that
          * it doesn't get automatically chosen over and over and over again.
          */
@@ -1939,6 +2073,8 @@ device_state_changed(NMDevice *          device,
         ip6_remove_device_prefix_delegations(self, device);
         break;
     case NM_DEVICE_STATE_DISCONNECTED:
+        g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self);
+
         /* Reset retry counts for a device's connections when carrier on; if cable
          * was unplugged and plugged in again, we should try to reconnect.
          */
@@ -2416,33 +2552,19 @@ dns_config_changed(NMDnsManager *dns_manager, gpointer user_data)
 {
     NMPolicy *       self = (NMPolicy *) user_data;
     NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    NMDevice *       device;
+    const CList *    tmp_lst;
 
-    /* Restart a thread for reverse-DNS lookup after we are signalled that
-     * DNS changed. Because the result from a previous run may not be right
-     * (race in updating DNS and doing the reverse lookup).
-     */
-
-    nm_clear_g_cancellable(&priv->lookup.cancellable);
-
-    /* Re-start the hostname lookup thread if we don't have hostname yet. */
-    if (priv->lookup.addr) {
-        char *        str      = NULL;
-        gs_free char *hostname = NULL;
-
-        /* Check if the hostname was externally set */
-        if ((hostname = _get_hostname(self)) && nm_utils_is_specific_hostname(hostname)
-            && !nm_streq0(hostname, priv->last_hostname)) {
-            g_clear_object(&priv->lookup.addr);
-            return;
-        }
-
-        _LOGD(LOGD_DNS,
-              "restarting reverse-lookup thread for address %s",
-              (str = g_inet_address_to_string(priv->lookup.addr)));
-        g_free(str);
+    /* We are currently updating the hostname in the DNS manager.
+     * This doesn't warrant a new DNS lookup.*/
+    if (priv->updating_dns)
+        return;
 
-        lookup_by_address(self);
+    nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+        nm_device_clear_dns_lookup_data(device);
     }
+
+    update_system_hostname(self, "DNS configuration changed");
 }
 
 static void
@@ -2682,8 +2804,6 @@ constructed(GObject *object)
                                                G_CALLBACK(dns_config_changed),
                                                self);
 
-    priv->lookup.resolver = g_resolver_get_default();
-
     g_signal_connect(priv->hostname_manager,
                      "notify::" NM_HOSTNAME_MANAGER_HOSTNAME,
                      (GCallback) hostname_changed,
@@ -2764,10 +2884,6 @@ dispose(GObject *object)
     NMDevice *       device;
     ActivateData *   data, *data_safe;
 
-    nm_clear_g_cancellable(&priv->lookup.cancellable);
-    g_clear_object(&priv->lookup.addr);
-    g_clear_object(&priv->lookup.resolver);
-
     nm_clear_g_object(&priv->default_ac4);
     nm_clear_g_object(&priv->default_ac6);
     nm_clear_g_object(&priv->activating_ac4);
diff --git a/src/nm-policy.h b/src/core/nm-policy.h
index feac9ef7..fa216db3 100644
--- a/src/nm-policy.h
+++ b/src/core/nm-policy.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2004 - 2010 Red Hat, Inc.
  * Copyright (C) 2007 - 2008 Novell, Inc.
@@ -32,6 +32,8 @@ NMActiveConnection *nm_policy_get_default_ip6_ac(NMPolicy *policy);
 NMActiveConnection *nm_policy_get_activating_ip4_ac(NMPolicy *policy);
 NMActiveConnection *nm_policy_get_activating_ip6_ac(NMPolicy *policy);
 
+void nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self);
+
 /**
  * NMPolicyHostnameMode
  * @NM_POLICY_HOSTNAME_MODE_NONE: never update the transient hostname.
diff --git a/src/nm-proxy-config.c b/src/core/nm-proxy-config.c
index 8e8c9030..f41601c9 100644
--- a/src/nm-proxy-config.c
+++ b/src/core/nm-proxy-config.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Atul Anand <atulhjp@gmail.com>.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-proxy-config.h"
 
@@ -148,7 +148,7 @@ nm_proxy_config_init(NMProxyConfig *config)
 NMProxyConfig *
 nm_proxy_config_new(void)
 {
-    return NM_PROXY_CONFIG(g_object_new(NM_TYPE_PROXY_CONFIG, NULL));
+    return g_object_new(NM_TYPE_PROXY_CONFIG, NULL);
 }
 
 static void
diff --git a/src/nm-proxy-config.h b/src/core/nm-proxy-config.h
index 305e2de6..7aa587d0 100644
--- a/src/nm-proxy-config.h
+++ b/src/core/nm-proxy-config.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Atul Anand <atulhjp@gmail.com>.
  */
diff --git a/src/nm-rfkill-manager.c b/src/core/nm-rfkill-manager.c
index 4b3e18d7..f032851e 100644
--- a/src/nm-rfkill-manager.c
+++ b/src/core/nm-rfkill-manager.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 - 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-rfkill-manager.h"
 
@@ -367,7 +367,7 @@ nm_rfkill_manager_init(NMRfkillManager *self)
     for (i = 0; i < RFKILL_TYPE_MAX; i++)
         priv->rfkill_states[i] = RFKILL_UNBLOCKED;
 
-    priv->udev_client = nm_udev_client_new((const char *[]){"rfkill", NULL}, handle_uevent, self);
+    priv->udev_client = nm_udev_client_new(NM_MAKE_STRV("rfkill"), handle_uevent, self);
 
     enumerate = nm_udev_client_enumerate_new(priv->udev_client);
     udev_enumerate_scan_devices(enumerate);
@@ -391,7 +391,7 @@ nm_rfkill_manager_init(NMRfkillManager *self)
 NMRfkillManager *
 nm_rfkill_manager_new(void)
 {
-    return NM_RFKILL_MANAGER(g_object_new(NM_TYPE_RFKILL_MANAGER, NULL));
+    return g_object_new(NM_TYPE_RFKILL_MANAGER, NULL);
 }
 
 static void
@@ -405,7 +405,7 @@ dispose(GObject *object)
         priv->killswitches = NULL;
     }
 
-    priv->udev_client = nm_udev_client_unref(priv->udev_client);
+    priv->udev_client = nm_udev_client_destroy(priv->udev_client);
 
     G_OBJECT_CLASS(nm_rfkill_manager_parent_class)->dispose(object);
 }
diff --git a/src/nm-rfkill-manager.h b/src/core/nm-rfkill-manager.h
index a49620bd..d3f66349 100644
--- a/src/nm-rfkill-manager.h
+++ b/src/core/nm-rfkill-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2007 - 2008 Novell, Inc.
  * Copyright (C) 2007 - 2013 Red Hat, Inc.
diff --git a/src/nm-session-monitor.c b/src/core/nm-session-monitor.c
index edd89b6b..88de1a00 100644
--- a/src/nm-session-monitor.c
+++ b/src/core/nm-session-monitor.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2015 Red Hat, Inc.
  * Author: David Zeuthen <davidz@redhat.com>
@@ -6,7 +6,7 @@
  * Author: Matthias Clasen
  * Author: Pavel Å imerda <psimerda@redhat.com>
  */
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-session-monitor.h"
 
diff --git a/src/nm-session-monitor.h b/src/core/nm-session-monitor.h
index e42311d8..63a3136b 100644
--- a/src/nm-session-monitor.h
+++ b/src/core/nm-session-monitor.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2010 Red Hat, Inc.
  * Author: David Zeuthen <davidz@redhat.com>
diff --git a/src/nm-sleep-monitor.c b/src/core/nm-sleep-monitor.c
index 2a10f92c..ffb4db4e 100644
--- a/src/nm-sleep-monitor.c
+++ b/src/core/nm-sleep-monitor.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 - 2016 Red Hat, Inc.
  * Author: Matthias Clasen <mclasen@redhat.com>
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-sleep-monitor.h"
 
diff --git a/src/nm-sleep-monitor.h b/src/core/nm-sleep-monitor.h
index a7c1008d..7d45065b 100644
--- a/src/nm-sleep-monitor.h
+++ b/src/core/nm-sleep-monitor.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 - 2016 Red Hat, Inc.
  * Author: Matthias Clasen <mclasen@redhat.com>
diff --git a/src/nm-test-utils-core.h b/src/core/nm-test-utils-core.h
index 0c9cfa9e..e655ce22 100644
--- a/src/nm-test-utils-core.h
+++ b/src/core/nm-test-utils-core.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014 - 2016 Red Hat, Inc.
  */
@@ -7,7 +7,7 @@
 #define __NM_TEST_UTILS_CORE_H__
 
 #include "NetworkManagerUtils.h"
-#include "nm-keyfile/nm-keyfile-internal.h"
+#include "nm-keyfile-internal.h"
 
 #define _NMTST_INSIDE_CORE 1
 
diff --git a/src/nm-types.h b/src/core/nm-types.h
index d24031ea..ab231459 100644
--- a/src/nm-types.h
+++ b/src/core/nm-types.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 - 2018 Red Hat, Inc.
  */
@@ -77,41 +77,6 @@ typedef enum {
     NM_ACTIVATION_REASON_USER_REQUEST,
 } NMActivationReason;
 
-typedef enum {
-    /* In priority order; higher number == higher priority */
-
-    NM_IP_CONFIG_SOURCE_UNKNOWN = 0,
-
-    /* for routes, the source is mapped to the uint8 field rtm_protocol.
-     * Reserve the range [1,0x100] for native RTPROT values. */
-
-    NM_IP_CONFIG_SOURCE_RTPROT_UNSPEC   = 1 + 0,
-    NM_IP_CONFIG_SOURCE_RTPROT_REDIRECT = 1 + 1,
-    NM_IP_CONFIG_SOURCE_RTPROT_KERNEL   = 1 + 2,
-    NM_IP_CONFIG_SOURCE_RTPROT_BOOT     = 1 + 3,
-    NM_IP_CONFIG_SOURCE_RTPROT_STATIC   = 1 + 4,
-    NM_IP_CONFIG_SOURCE_RTPROT_RA       = 1 + 9,
-    NM_IP_CONFIG_SOURCE_RTPROT_DHCP     = 1 + 16,
-    _NM_IP_CONFIG_SOURCE_RTPROT_LAST    = 1 + 0xFF,
-
-    NM_IP_CONFIG_SOURCE_KERNEL,
-    NM_IP_CONFIG_SOURCE_SHARED,
-    NM_IP_CONFIG_SOURCE_IP4LL,
-    NM_IP_CONFIG_SOURCE_IP6LL,
-    NM_IP_CONFIG_SOURCE_PPP,
-    NM_IP_CONFIG_SOURCE_WWAN,
-    NM_IP_CONFIG_SOURCE_VPN,
-    NM_IP_CONFIG_SOURCE_DHCP,
-    NM_IP_CONFIG_SOURCE_NDISC,
-    NM_IP_CONFIG_SOURCE_USER,
-} NMIPConfigSource;
-
-static inline gboolean
-NM_IS_IP_CONFIG_SOURCE_RTPROT(NMIPConfigSource source)
-{
-    return source > NM_IP_CONFIG_SOURCE_UNKNOWN && source <= _NM_IP_CONFIG_SOURCE_RTPROT_LAST;
-}
-
 /* platform */
 typedef struct _NMPlatform               NMPlatform;
 typedef struct _NMPlatformObject         NMPlatformObject;
@@ -121,7 +86,6 @@ typedef struct _NMPlatformIP4Route       NMPlatformIP4Route;
 typedef struct _NMPlatformIP6Address     NMPlatformIP6Address;
 typedef struct _NMPlatformIP6Route       NMPlatformIP6Route;
 typedef struct _NMPlatformLink           NMPlatformLink;
-typedef struct _NMPNetns                 NMPNetns;
 typedef struct _NMPObject                NMPObject;
 
 typedef enum {
diff --git a/src/org.freedesktop.NetworkManager.conf b/src/core/org.freedesktop.NetworkManager.conf
index 5c2af2e0..5c2af2e0 100644
--- a/src/org.freedesktop.NetworkManager.conf
+++ b/src/core/org.freedesktop.NetworkManager.conf
diff --git a/src/platform/linux/nl802154.h b/src/core/platform/linux/nl802154.h
index c21bd079..c21bd079 100644
--- a/src/platform/linux/nl802154.h
+++ b/src/core/platform/linux/nl802154.h
diff --git a/src/platform/nm-fake-platform.c b/src/core/platform/nm-fake-platform.c
index 5bb0b426..81c9f06c 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/core/platform/nm-fake-platform.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 - 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-fake-platform.h"
 
@@ -16,7 +16,7 @@
 #include "nm-utils.h"
 
 #include "nm-core-utils.h"
-#include "nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "nm-platform-private.h"
 #include "nmp-object.h"
 
diff --git a/src/platform/nm-fake-platform.h b/src/core/platform/nm-fake-platform.h
index c3ec8921..a1d44f8f 100644
--- a/src/platform/nm-fake-platform.h
+++ b/src/core/platform/nm-fake-platform.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 Red Hat, Inc.
  */
diff --git a/src/platform/nm-linux-platform.c b/src/core/platform/nm-linux-platform.c
index b377c85e..7bd42bba 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/core/platform/nm-linux-platform.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-linux-platform.h"
 
@@ -12,17 +12,20 @@
 #include <endian.h>
 #include <fcntl.h>
 #include <libudev.h>
+#include <net/ethernet.h>
 #include <linux/fib_rules.h>
 #include <linux/ip.h>
-#include <linux/if_arp.h>
+#include <linux/if.h>
 #include <linux/if_bridge.h>
 #include <linux/if_link.h>
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
+#include <linux/if_vlan.h>
 #include <linux/ip6_tunnel.h>
 #include <linux/tc_act/tc_mirred.h>
 #include <netinet/icmp6.h>
 #include <netinet/in.h>
+#include <net/if_arp.h>
 #include <poll.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
@@ -37,11 +40,11 @@
 
 #include "nm-glib-aux/nm-secret-utils.h"
 #include "nm-glib-aux/nm-c-list.h"
-#include "nm-netlink.h"
+#include "nm-platform/nm-netlink.h"
 #include "nm-core-utils.h"
 #include "nmp-object.h"
-#include "nmp-netns.h"
-#include "nm-platform-utils.h"
+#include "nm-platform/nmp-netns.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "nm-platform-private.h"
 #include "wifi/nm-wifi-utils.h"
 #include "wifi/nm-wifi-utils-wext.h"
@@ -2786,7 +2789,7 @@ 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_allowed_ips);
     _nla_nest_end(msg, nest_curr_peer);
     _nla_nest_end(msg, nest_peers);
     goto send;
@@ -7650,7 +7653,7 @@ static void
 link_set_sriov_params_async(NMPlatform *            platform,
                             int                     ifindex,
                             guint                   num_vfs,
-                            NMTernary               autoprobe,
+                            NMOptionBool            autoprobe,
                             NMPlatformAsyncCallback callback,
                             gpointer                data,
                             GCancellable *          cancellable)
@@ -7723,10 +7726,11 @@ link_set_sriov_params_async(NMPlatform *            platform,
     }
 
     if (current_num == num_vfs
-        && (autoprobe == NM_TERNARY_DEFAULT || current_autoprobe == autoprobe))
+        && (autoprobe == NM_OPTION_BOOL_DEFAULT || current_autoprobe == autoprobe))
         goto out_idle;
 
-    if (NM_IN_SET(autoprobe, NM_TERNARY_TRUE, NM_TERNARY_FALSE) && current_autoprobe != autoprobe
+    if (NM_IN_SET(autoprobe, NM_OPTION_BOOL_TRUE, NM_OPTION_BOOL_FALSE)
+        && current_autoprobe != autoprobe
         && !nm_platform_sysctl_set(
             NM_PLATFORM_GET,
             NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_drivers_autoprobe"),
@@ -8281,13 +8285,6 @@ wifi_get_capabilities(NMPlatform *platform, int ifindex, NMDeviceWifiCapabilitie
     return TRUE;
 }
 
-static gboolean
-wifi_get_bssid(NMPlatform *platform, int ifindex, guint8 *bssid)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
-    return nm_wifi_utils_get_bssid(wifi_data, bssid);
-}
-
 static guint32
 wifi_get_frequency(NMPlatform *platform, int ifindex)
 {
@@ -8296,17 +8293,14 @@ wifi_get_frequency(NMPlatform *platform, int ifindex)
 }
 
 static gboolean
-wifi_get_quality(NMPlatform *platform, int ifindex)
+wifi_get_station(NMPlatform * platform,
+                 int          ifindex,
+                 NMEtherAddr *out_bssid,
+                 int *        out_quality,
+                 guint32 *    out_rate)
 {
     WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
-    return nm_wifi_utils_get_qual(wifi_data);
-}
-
-static guint32
-wifi_get_rate(NMPlatform *platform, int ifindex)
-{
-    WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
-    return nm_wifi_utils_get_rate(wifi_data);
+    return nm_wifi_utils_get_station(wifi_data, out_bssid, out_quality, out_rate);
 }
 
 static NM80211Mode
@@ -8641,16 +8635,9 @@ ip_route_add(NMPlatform *             platform,
     nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
     NMPObject                    obj;
 
-    switch (addr_family) {
-    case AF_INET:
-        nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP4_ROUTE, (const NMPlatformObject *) route);
-        break;
-    case AF_INET6:
-        nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP6_ROUTE, (const NMPlatformObject *) route);
-        break;
-    default:
-        nm_assert_not_reached();
-    }
+    nmp_object_stackinit(&obj,
+                         NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)),
+                         (const NMPlatformObject *) route);
 
     nm_platform_ip_route_normalize(addr_family, NMP_OBJECT_CAST_IP_ROUTE(&obj));
 
@@ -9606,7 +9593,7 @@ finalize(GObject *object)
         g_hash_table_destroy(priv->sysctl_get_prev_values);
     }
 
-    priv->udev_client = nm_udev_client_unref(priv->udev_client);
+    priv->udev_client = nm_udev_client_destroy(priv->udev_client);
 
     G_OBJECT_CLASS(nm_linux_platform_parent_class)->finalize(object);
 }
@@ -9669,10 +9656,8 @@ nm_linux_platform_class_init(NMLinuxPlatformClass *klass)
     platform_class->infiniband_partition_delete = infiniband_partition_delete;
 
     platform_class->wifi_get_capabilities            = wifi_get_capabilities;
-    platform_class->wifi_get_bssid                   = wifi_get_bssid;
     platform_class->wifi_get_frequency               = wifi_get_frequency;
-    platform_class->wifi_get_quality                 = wifi_get_quality;
-    platform_class->wifi_get_rate                    = wifi_get_rate;
+    platform_class->wifi_get_station                 = wifi_get_station;
     platform_class->wifi_get_mode                    = wifi_get_mode;
     platform_class->wifi_set_mode                    = wifi_set_mode;
     platform_class->wifi_set_powersave               = wifi_set_powersave;
diff --git a/src/platform/nm-linux-platform.h b/src/core/platform/nm-linux-platform.h
index 98935b98..6837e64a 100644
--- a/src/platform/nm-linux-platform.h
+++ b/src/core/platform/nm-linux-platform.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 Red Hat, Inc.
  */
diff --git a/src/platform/nm-platform-private.h b/src/core/platform/nm-platform-private.h
index d99e330b..cf805689 100644
--- a/src/platform/nm-platform-private.h
+++ b/src/core/platform/nm-platform-private.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/src/platform/nm-platform.c b/src/core/platform/nm-platform.c
index 7969206c..0e5f8ab5 100644
--- a/src/platform/nm-platform.c
+++ b/src/core/platform/nm-platform.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2012 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-platform.h"
 
@@ -29,10 +29,10 @@
 #include "nm-glib-aux/nm-secret-utils.h"
 
 #include "nm-core-utils.h"
-#include "nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "nm-platform-private.h"
 #include "nmp-object.h"
-#include "nmp-netns.h"
+#include "nm-platform/nmp-netns.h"
 
 /*****************************************************************************/
 
@@ -1842,7 +1842,7 @@ nm_platform_link_supports_sriov(NMPlatform *self, int ifindex)
  * @ifindex: the index of the interface to change
  * @num_vfs: the number of VFs to create
  * @autoprobe: the new autoprobe-drivers value (pass
- *     %NM_TERNARY_DEFAULT to keep current value)
+ *     %NM_OPTION_BOOL_DEFAULT to keep current value)
  * @callback: called when the operation finishes
  * @callback_data: data passed to @callback
  * @cancellable: cancellable to abort the operation
@@ -1855,7 +1855,7 @@ void
 nm_platform_link_set_sriov_params_async(NMPlatform *            self,
                                         int                     ifindex,
                                         guint                   num_vfs,
-                                        NMTernary               autoprobe,
+                                        NMOptionBool            autoprobe,
                                         NMPlatformAsyncCallback callback,
                                         gpointer                callback_data,
                                         GCancellable *          cancellable)
@@ -3051,16 +3051,6 @@ nm_platform_wifi_get_capabilities(NMPlatform *self, int ifindex, NMDeviceWifiCap
     return klass->wifi_get_capabilities(self, ifindex, caps);
 }
 
-gboolean
-nm_platform_wifi_get_bssid(NMPlatform *self, int ifindex, guint8 *bssid)
-{
-    _CHECK_SELF(self, klass, FALSE);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    return klass->wifi_get_bssid(self, ifindex, bssid);
-}
-
 guint32
 nm_platform_wifi_get_frequency(NMPlatform *self, int ifindex)
 {
@@ -3071,24 +3061,18 @@ nm_platform_wifi_get_frequency(NMPlatform *self, int ifindex)
     return klass->wifi_get_frequency(self, ifindex);
 }
 
-int
-nm_platform_wifi_get_quality(NMPlatform *self, int ifindex)
-{
-    _CHECK_SELF(self, klass, 0);
-
-    g_return_val_if_fail(ifindex > 0, 0);
-
-    return klass->wifi_get_quality(self, ifindex);
-}
-
-guint32
-nm_platform_wifi_get_rate(NMPlatform *self, int ifindex)
+gboolean
+nm_platform_wifi_get_station(NMPlatform * self,
+                             int          ifindex,
+                             NMEtherAddr *out_bssid,
+                             int *        out_quality,
+                             guint32 *    out_rate)
 {
-    _CHECK_SELF(self, klass, 0);
+    _CHECK_SELF(self, klass, FALSE);
 
-    g_return_val_if_fail(ifindex > 0, 0);
+    g_return_val_if_fail(ifindex > 0, FALSE);
 
-    return klass->wifi_get_rate(self, ifindex);
+    return klass->wifi_get_station(self, ifindex, out_bssid, out_quality, out_rate);
 }
 
 NM80211Mode
@@ -3298,10 +3282,10 @@ _to_string_ifa_flags(guint32 ifa_flags, char *buf, gsize size)
 /*****************************************************************************/
 
 gboolean
-nm_platform_ethtool_set_wake_on_lan(NMPlatform *            self,
-                                    int                     ifindex,
-                                    NMSettingWiredWakeOnLan wol,
-                                    const char *            wol_password)
+nm_platform_ethtool_set_wake_on_lan(NMPlatform *             self,
+                                    int                      ifindex,
+                                    _NMSettingWiredWakeOnLan wol,
+                                    const char *             wol_password)
 {
     _CHECK_SELF_NETNS(self, klass, netns, FALSE);
 
@@ -3338,13 +3322,6 @@ nm_platform_ethtool_get_link_settings(NMPlatform *              self,
     return nmp_utils_ethtool_get_link_settings(ifindex, out_autoneg, out_speed, out_duplex);
 }
 
-NM_UTILS_LOOKUP_STR_DEFINE(nm_platform_link_duplex_type_to_string,
-                           NMPlatformLinkDuplexType,
-                           NM_UTILS_LOOKUP_DEFAULT_WARN(NULL),
-                           NM_UTILS_LOOKUP_STR_ITEM(NM_PLATFORM_LINK_DUPLEX_UNKNOWN, "unknown"),
-                           NM_UTILS_LOOKUP_STR_ITEM(NM_PLATFORM_LINK_DUPLEX_FULL, "full"),
-                           NM_UTILS_LOOKUP_STR_ITEM(NM_PLATFORM_LINK_DUPLEX_HALF, "half"), );
-
 /*****************************************************************************/
 
 NMEthtoolFeatureStates *
@@ -3362,8 +3339,8 @@ nm_platform_ethtool_set_features(
     NMPlatform *                  self,
     int                           ifindex,
     const NMEthtoolFeatureStates *features,
-    const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
-    gboolean         do_set /* or reset */)
+    const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+    gboolean            do_set /* or reset */)
 {
     _CHECK_SELF_NETNS(self, klass, netns, FALSE);
 
@@ -3746,7 +3723,7 @@ _addr_array_clean_expired(int          addr_family,
         }
 #endif
 
-        if (addr_family == AF_INET6 && NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TEMPORARY)) {
+        if (!NM_IS_IPv4(addr_family) && NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TEMPORARY)) {
             /* temporary addresses are never added explicitly by NetworkManager but
              * kernel adds them via mngtempaddr flag.
              *
@@ -3987,7 +3964,7 @@ nm_platform_ip_address_sync(NMPlatform *self,
                             GPtrArray * addresses_prune)
 {
     const gint32       now                             = nm_utils_get_monotonic_timestamp_sec();
-    const gboolean     IS_IPv4                         = NM_IS_IPv4(addr_family);
+    const int          IS_IPv4                         = NM_IS_IPv4(addr_family);
     gs_unref_hashtable GHashTable *known_addresses_idx = NULL;
     GPtrArray *                    plat_addresses;
     GHashTable *                   known_subnets = NULL;
@@ -4336,7 +4313,7 @@ nm_platform_ip_address_get_prune_list(NMPlatform *self,
                                       int         ifindex,
                                       gboolean    exclude_ipv6_temporary_addrs)
 {
-    const gboolean               IS_IPv4 = NM_IS_IPv4(addr_family);
+    const int                    IS_IPv4 = NM_IS_IPv4(addr_family);
     const NMDedupMultiHeadEntry *head_entry;
     NMPLookup                    lookup;
     GPtrArray *                  result;
@@ -4388,10 +4365,7 @@ nm_platform_ip_route_get_prune_list(NMPlatform *           self,
                         NM_IP_ROUTE_TABLE_SYNC_MODE_FULL,
                         NM_IP_ROUTE_TABLE_SYNC_MODE_ALL));
 
-    nmp_lookup_init_object(&lookup,
-                           addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ROUTE
-                                                  : NMP_OBJECT_TYPE_IP6_ROUTE,
-                           ifindex);
+    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)), ifindex);
     head_entry = nm_platform_lookup(self, &lookup);
     if (!head_entry)
         return NULL;
@@ -4447,6 +4421,7 @@ nm_platform_ip_route_sync(NMPlatform *self,
                           GPtrArray * routes_prune,
                           GPtrArray **out_temporary_not_available)
 {
+    const int                    IS_IPv4 = NM_IS_IPv4(addr_family);
     const NMPlatformVTableRoute *vt;
     gs_unref_hashtable GHashTable *routes_idx = NULL;
     const NMPObject *              conf_o;
@@ -4456,10 +4431,8 @@ 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)];
-    const gboolean                 IS_IPv4 = (addr_family == AF_INET);
 
     nm_assert(NM_IS_PLATFORM(self));
-    nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
     nm_assert(ifindex > 0);
 
     vt = &nm_platform_vtable_route.vx[IS_IPv4];
@@ -4671,9 +4644,9 @@ sync_route_add:
 
             prune_o = routes_prune->pdata[i];
 
-            nm_assert((addr_family == AF_INET
+            nm_assert((NM_IS_IPv4(addr_family)
                        && NMP_OBJECT_GET_TYPE(prune_o) == NMP_OBJECT_TYPE_IP4_ROUTE)
-                      || (addr_family == AF_INET6
+                      || (!NM_IS_IPv4(addr_family)
                           && NMP_OBJECT_GET_TYPE(prune_o) == NMP_OBJECT_TYPE_IP6_ROUTE));
 
             if (routes_idx && g_hash_table_lookup(routes_idx, prune_o))
@@ -4816,8 +4789,8 @@ _ip_route_add(NMPlatform *self, NMPNlmFlags flags, int addr_family, gconstpointe
     _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)));
+           NM_IS_IPv4(addr_family) ? 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);
 }
@@ -5647,7 +5620,7 @@ nm_platform_lnk_bridge_to_string(const NMPlatformLnkBridge *lnk, char *buf, gsiz
                lnk->vlan_protocol,
                (int) lnk->vlan_stats_enabled,
                lnk->group_fwd_mask,
-               NM_ETHER_ADDR_FORMAT_VAL(lnk->group_addr),
+               NM_ETHER_ADDR_FORMAT_VAL(&lnk->group_addr),
                (int) lnk->mcast_snooping,
                lnk->mcast_router,
                (int) lnk->mcast_query_use_ifaddr,
@@ -6272,11 +6245,16 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf
         "%s" /* label */
         " src %s"
         "%s" /* external */
+        "%s" /* ip4acd_not_ready */
         "",
         s_address,
         address->plen,
-        broadcast_address ? " brd " : "",
-        broadcast_address ? _nm_utils_inet4_ntop(broadcast_address, str_broadcast) : "",
+        broadcast_address != 0u || address->use_ip4_broadcast_address
+            ? (address->use_ip4_broadcast_address ? " brd " : " brd* ")
+            : "",
+        broadcast_address != 0u || address->use_ip4_broadcast_address
+            ? _nm_utils_inet4_ntop(broadcast_address, str_broadcast)
+            : "",
         str_lft_p,
         str_pref_p,
         str_time_p,
@@ -6285,7 +6263,8 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf
         _to_string_ifa_flags(address->n_ifa_flags, s_flags, sizeof(s_flags)),
         str_label,
         nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)),
-        address->external ? " ext" : "");
+        address->external ? " ext" : "",
+        address->ip4acd_not_ready ? " ip4acd-not-ready" : "");
     g_free(str_peer);
     return buf;
 }
@@ -6515,7 +6494,9 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz
         route->plen,
         s_gateway,
         str_dev,
-        route->metric_any ? "??" : nm_sprintf_buf(str_metric, "%u", route->metric),
+        route->metric_any
+            ? (route->metric ? nm_sprintf_buf(str_metric, "??+%u", route->metric) : "??")
+            : nm_sprintf_buf(str_metric, "%u", route->metric),
         route->mss,
         nmp_utils_ip_config_source_to_string(route->rt_source, s_source, sizeof(s_source)),
         _rtm_flags_to_string_full(str_rtm_flags, sizeof(str_rtm_flags), route->r_rtm_flags),
@@ -6639,7 +6620,9 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
         route->plen,
         s_gateway,
         str_dev,
-        route->metric_any ? "??" : nm_sprintf_buf(str_metric, "%u", route->metric),
+        route->metric_any
+            ? (route->metric ? nm_sprintf_buf(str_metric, "??+%u", route->metric) : "??")
+            : nm_sprintf_buf(str_metric, "%u", route->metric),
         route->mss,
         nmp_utils_ip_config_source_to_string(route->rt_source, s_source, sizeof(s_source)),
         route->src_plen || !IN6_IS_ADDR_UNSPECIFIED(&route->src)
@@ -7841,7 +7824,7 @@ nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState
     nm_hash_update_vals(h,
                         obj->ifindex,
                         obj->addr_source,
-                        nm_platform_ip4_broadcast_address_from_addr(obj),
+                        obj->use_ip4_broadcast_address ? obj->broadcast_address : ((in_addr_t) 0u),
                         obj->timestamp,
                         obj->lifetime,
                         obj->preferred,
@@ -7849,7 +7832,10 @@ nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState
                         obj->plen,
                         obj->address,
                         obj->peer_address,
-                        NM_HASH_COMBINE_BOOLS(guint8, obj->external));
+                        NM_HASH_COMBINE_BOOLS(guint8,
+                                              obj->external,
+                                              obj->use_ip4_broadcast_address,
+                                              obj->ip4acd_not_ready));
     nm_hash_update_strarr(h, obj->label);
 }
 
@@ -7861,8 +7847,9 @@ nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Ad
     NM_CMP_FIELD(a, b, address);
     NM_CMP_FIELD(a, b, plen);
     NM_CMP_FIELD(a, b, peer_address);
-    NM_CMP_DIRECT(nm_platform_ip4_broadcast_address_from_addr(a),
-                  nm_platform_ip4_broadcast_address_from_addr(b));
+    NM_CMP_FIELD_UNSAFE(a, b, use_ip4_broadcast_address);
+    if (a->use_ip4_broadcast_address)
+        NM_CMP_FIELD(a, b, broadcast_address);
     NM_CMP_FIELD(a, b, addr_source);
     NM_CMP_FIELD(a, b, timestamp);
     NM_CMP_FIELD(a, b, lifetime);
@@ -7870,6 +7857,7 @@ nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Ad
     NM_CMP_FIELD(a, b, n_ifa_flags);
     NM_CMP_FIELD_STR(a, b, label);
     NM_CMP_FIELD_UNSAFE(a, b, external);
+    NM_CMP_FIELD_UNSAFE(a, b, ip4acd_not_ready);
     return 0;
 }
 
@@ -7922,7 +7910,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
             nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
             nm_utils_ip4_address_clear_host_address(obj->network, obj->plen),
             obj->plen,
-            nm_platform_ip4_route_get_effective_metric(obj),
+            obj->metric,
             obj->tos,
             NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any));
         break;
@@ -7933,7 +7921,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
             nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
             nm_utils_ip4_address_clear_host_address(obj->network, obj->plen),
             obj->plen,
-            nm_platform_ip4_route_get_effective_metric(obj),
+            obj->metric,
             obj->tos,
             /* on top of WEAK_ID: */
             obj->ifindex,
@@ -7965,7 +7953,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
             obj->ifindex,
             nm_utils_ip4_address_clear_host_address(obj->network, obj->plen),
             obj->plen,
-            nm_platform_ip4_route_get_effective_metric(obj),
+            obj->metric,
             obj->gateway,
             nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source),
             _ip_route_scope_inv_get_normalized(obj),
@@ -7994,7 +7982,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
                             obj->ifindex,
                             obj->network,
                             obj->plen,
-                            nm_platform_ip4_route_get_effective_metric(obj),
+                            obj->metric,
                             obj->gateway,
                             obj->rt_source,
                             obj->scope_inv,
@@ -8034,8 +8022,7 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
         NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a->network, b->network, MIN(a->plen, b->plen));
         NM_CMP_FIELD(a, b, plen);
         NM_CMP_FIELD_UNSAFE(a, b, metric_any);
-        if (!a->metric_any)
-            NM_CMP_FIELD(a, b, metric);
+        NM_CMP_FIELD(a, b, metric);
         NM_CMP_FIELD(a, b, tos);
         if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
             NM_CMP_FIELD(a, b, ifindex);
@@ -8076,8 +8063,7 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
             NM_CMP_FIELD(a, b, network);
         NM_CMP_FIELD(a, b, plen);
         NM_CMP_FIELD_UNSAFE(a, b, metric_any);
-        if (!a->metric_any)
-            NM_CMP_FIELD(a, b, metric);
+        NM_CMP_FIELD(a, b, metric);
         NM_CMP_FIELD(a, b, gateway);
         if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
             NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source),
@@ -8125,7 +8111,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
             nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
             *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen),
             obj->plen,
-            nm_platform_ip6_route_get_effective_metric(obj),
+            obj->metric,
             *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen),
             obj->src_plen,
             NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any));
@@ -8137,7 +8123,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
             nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
             *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen),
             obj->plen,
-            nm_platform_ip6_route_get_effective_metric(obj),
+            obj->metric,
             *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen),
             obj->src_plen,
             NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any),
@@ -8153,7 +8139,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
             obj->ifindex,
             *nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen),
             obj->plen,
-            nm_platform_ip6_route_get_effective_metric(obj),
+            obj->metric,
             obj->gateway,
             obj->pref_src,
             *nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen),
@@ -8182,7 +8168,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
                             obj->table_coerced,
                             obj->ifindex,
                             obj->network,
-                            nm_platform_ip6_route_get_effective_metric(obj),
+                            obj->metric,
                             obj->gateway,
                             obj->pref_src,
                             obj->src,
@@ -8223,8 +8209,7 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
         NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->network, &b->network, MIN(a->plen, b->plen));
         NM_CMP_FIELD(a, b, plen);
         NM_CMP_FIELD_UNSAFE(a, b, metric_any);
-        if (!a->metric_any)
-            NM_CMP_FIELD(a, b, metric);
+        NM_CMP_FIELD(a, b, metric);
         NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->src, &b->src, MIN(a->src_plen, b->src_plen));
         NM_CMP_FIELD(a, b, src_plen);
         if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
@@ -8249,8 +8234,7 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
             NM_CMP_FIELD_IN6ADDR(a, b, network);
         NM_CMP_FIELD(a, b, plen);
         NM_CMP_FIELD_UNSAFE(a, b, metric_any);
-        if (!a->metric_any)
-            NM_CMP_FIELD(a, b, metric);
+        NM_CMP_FIELD(a, b, metric);
         NM_CMP_FIELD_IN6ADDR(a, b, gateway);
         NM_CMP_FIELD_IN6ADDR(a, b, pref_src);
         if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
diff --git a/src/platform/nm-platform.h b/src/core/platform/nm-platform.h
index a1314000..d400c568 100644
--- a/src/platform/nm-platform.h
+++ b/src/core/platform/nm-platform.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 - 2018 Red Hat, Inc.
  */
@@ -9,6 +9,9 @@
 #include "nm-dbus-interface.h"
 #include "nm-core-types-internal.h"
 
+#include "nm-platform/nmp-base.h"
+#include "nm-base/nm-base.h"
+
 #include "nm-core-utils.h"
 #include "nm-setting-vlan.h"
 #include "nm-setting-wired.h"
@@ -322,10 +325,15 @@ typedef enum {
     guint8 plen;                                                                             \
                                                                                              \
     /* FIXME(l3cfg): the external marker won't be necessary anymore, because we only
-     *   merge addresses we care about, and ignore (don't remove) external addresses. */         \
+     * merge addresses we care about, and ignore (don't remove) external addresses. */         \
     bool external : 1;                                                                       \
                                                                                              \
     bool use_ip4_broadcast_address : 1;                                                      \
+                                                                                             \
+    /* Whether the address is ready to be configured. By default, an address is, but this
+     * flag may indicate that the address is just for tracking purpose only, but the ACD
+     * state is not yet ready for the address to be configured. */    \
+    bool ip4acd_not_ready : 1;                                                               \
     ;
 
 /**
@@ -410,9 +418,9 @@ typedef union {
  * configures addresses. */
 #define NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE ((guint32) 0u)
 
-#define __NMPlatformIPRoute_COMMON                                                       \
-    __NMPlatformObjWithIfindex_COMMON;                                                   \
-                                                                                         \
+#define __NMPlatformIPRoute_COMMON                                                        \
+    __NMPlatformObjWithIfindex_COMMON;                                                    \
+                                                                                          \
     /* The NMIPConfigSource. For routes that we receive from cache this corresponds
      * to the rtm_protocol field (and is one of the NM_IP_CONFIG_SOURCE_RTPROT_* values).
      * When adding a route, the source will be coerced to the protocol using
@@ -424,11 +432,11 @@ typedef union {
      *
      * When deleting an IPv4/IPv6 route, the rtm_protocol field must match (even
      * if it is not part of the primary key for IPv6) -- unless rtm_protocol is set
-     * to zero, in which case the first matching route (with proto ignored) is deleted. */      \
-    NMIPConfigSource rt_source;                                                          \
-                                                                                         \
-    guint8 plen;                                                                         \
-                                                                                         \
+     * to zero, in which case the first matching route (with proto ignored) is deleted. */       \
+    NMIPConfigSource rt_source;                                                           \
+                                                                                          \
+    guint8 plen;                                                                          \
+                                                                                          \
     /* RTA_METRICS:
      *
      * For IPv4 routes, these properties are part of their
@@ -439,24 +447,29 @@ typedef union {
      *
      * 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. */                                                                      \
-                                                                                         \
-    /* RTA_METRICS.RTAX_LOCK (iproute2: "lock" arguments) */                             \
-    bool lock_window : 1;                                                                \
-    bool lock_cwnd : 1;                                                                  \
-    bool lock_initcwnd : 1;                                                              \
-    bool lock_initrwnd : 1;                                                              \
-    bool lock_mtu : 1;                                                                   \
-                                                                                         \
-    /* if TRUE, the "metric" field gets ignored and can be overridden with settings from
-     * the device. This is to track routes that should be configured (e.g. from a DHCP
-     * lease), but where the actual metric is determined by NMDevice. */ \
-    bool metric_any : 1;                                                                 \
-                                                                                         \
+     * route to delete. Kernel just picks the first. See rh#1475642. */                                                                       \
+                                                                                          \
+    /* RTA_METRICS.RTAX_LOCK (iproute2: "lock" arguments) */                              \
+    bool lock_window : 1;                                                                 \
+    bool lock_cwnd : 1;                                                                   \
+    bool lock_initcwnd : 1;                                                               \
+    bool lock_initrwnd : 1;                                                               \
+    bool lock_mtu : 1;                                                                    \
+                                                                                          \
+    /* if TRUE, the "metric" field is interpreted as an offset that is added to a default
+     * metric. For example, form a DHCP lease we don't know the actually used metric, because
+     * that is determined by upper layers (the configuration). However, we have a default
+     * metric that should be used. So we set "metric_any" to %TRUE, which means to use
+     * the default metric. However, we still treat the "metric" field as an offset that
+     * will be added to the default metric. In most case, you want that "metric" is zero
+     * when setting "metric_any". */ \
+    bool metric_any : 1;                                                                  \
+                                                                                          \
     /* like "metric_any", the table is determined by other layers of the code.
-     * This field overrides "table_coerced" field. */           \
-    bool table_any : 1;                                                                  \
-                                                                                         \
+     * This field overrides "table_coerced" field. If "table_any" is true, then
+     * the "table_coerced" field is ignored (unlike for the metric). */            \
+    bool table_any : 1;                                                                   \
+                                                                                          \
     /* rtnh_flags
      *
      * Routes with rtm_flags RTM_F_CLONED are hidden by platform and
@@ -466,44 +479,46 @@ typedef union {
      * NOTE: currently we ignore all flags except RTM_F_CLONED
      * and RTNH_F_ONLINK.
      * We also may not properly consider the flags as part of the ID
-     * in route-cmp. */                                                                        \
-    unsigned r_rtm_flags;                                                                \
-                                                                                         \
-    /* RTA_METRICS.RTAX_ADVMSS (iproute2: advmss) */                                     \
-    guint32 mss;                                                                         \
-                                                                                         \
-    /* RTA_METRICS.RTAX_WINDOW (iproute2: window) */                                     \
-    guint32 window;                                                                      \
-                                                                                         \
-    /* RTA_METRICS.RTAX_CWND (iproute2: cwnd) */                                         \
-    guint32 cwnd;                                                                        \
-                                                                                         \
-    /* RTA_METRICS.RTAX_INITCWND (iproute2: initcwnd) */                                 \
-    guint32 initcwnd;                                                                    \
-                                                                                         \
-    /* RTA_METRICS.RTAX_INITRWND (iproute2: initrwnd) */                                 \
-    guint32 initrwnd;                                                                    \
-                                                                                         \
-    /* RTA_METRICS.RTAX_MTU (iproute2: mtu) */                                           \
-    guint32 mtu;                                                                         \
-                                                                                         \
-    /* RTA_PRIORITY (iproute2: metric) */                                                \
-    guint32 metric;                                                                      \
-                                                                                         \
+     * in route-cmp. */                                                                         \
+    unsigned r_rtm_flags;                                                                 \
+                                                                                          \
+    /* RTA_METRICS.RTAX_ADVMSS (iproute2: advmss) */                                      \
+    guint32 mss;                                                                          \
+                                                                                          \
+    /* RTA_METRICS.RTAX_WINDOW (iproute2: window) */                                      \
+    guint32 window;                                                                       \
+                                                                                          \
+    /* RTA_METRICS.RTAX_CWND (iproute2: cwnd) */                                          \
+    guint32 cwnd;                                                                         \
+                                                                                          \
+    /* RTA_METRICS.RTAX_INITCWND (iproute2: initcwnd) */                                  \
+    guint32 initcwnd;                                                                     \
+                                                                                          \
+    /* RTA_METRICS.RTAX_INITRWND (iproute2: initrwnd) */                                  \
+    guint32 initrwnd;                                                                     \
+                                                                                          \
+    /* RTA_METRICS.RTAX_MTU (iproute2: mtu) */                                            \
+    guint32 mtu;                                                                          \
+                                                                                          \
+    /* RTA_PRIORITY (iproute2: metric)
+     * If "metric_any" is %TRUE, then this is interpreted as an offset that will be
+     * added to a default base metric. In such cases, the offset is usually zero. */                                                    \
+    guint32 metric;                                                                       \
+                                                                                          \
     /* rtm_table, RTA_TABLE.
      *
      * This is not the original table ID. Instead, 254 (RT_TABLE_MAIN) and
      * zero (RT_TABLE_UNSPEC) are swapped, so that the default is the main
-     * table. Use nm_platform_route_table_coerce()/nm_platform_route_table_uncoerce(). */                                                             \
-    guint32 table_coerced;                                                               \
-                                                                                         \
+     * table. Use nm_platform_route_table_coerce()/nm_platform_route_table_uncoerce(). */                                                              \
+    guint32 table_coerced;                                                                \
+                                                                                          \
     /* rtm_type.
      *
      * This is not the original type, if type_coerced is 0 then
      * it means RTN_UNSPEC otherwise the type value is preserved.
-     * */                                                                         \
-    guint8 type_coerced;                                                                 \
-                                                                                         \
+     * */                                                                          \
+    guint8 type_coerced;                                                                  \
+                                                                                          \
     /*end*/
 
 typedef struct {
@@ -964,12 +979,6 @@ typedef struct {
 } NMPlatformLnkWireGuard;
 
 typedef enum {
-    NM_PLATFORM_LINK_DUPLEX_UNKNOWN,
-    NM_PLATFORM_LINK_DUPLEX_HALF,
-    NM_PLATFORM_LINK_DUPLEX_FULL,
-} NMPlatformLinkDuplexType;
-
-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),
@@ -1026,7 +1035,7 @@ _nm_platform_kernel_support_detected(NMPlatformKernelSupportType type)
     return G_LIKELY(g_atomic_int_get(&_nm_platform_kernel_support_state[type]) != 0);
 }
 
-static inline NMTernary
+static inline NMOptionBool
 nm_platform_kernel_support_get_full(NMPlatformKernelSupportType type, gboolean init_if_not_set)
 {
     int v;
@@ -1036,7 +1045,7 @@ nm_platform_kernel_support_get_full(NMPlatformKernelSupportType type, gboolean i
     v = g_atomic_int_get(&_nm_platform_kernel_support_state[type]);
     if (G_UNLIKELY(v == 0)) {
         if (!init_if_not_set)
-            return NM_TERNARY_DEFAULT;
+            return NM_OPTION_BOOL_DEFAULT;
         v = _nm_platform_kernel_support_init(type, 0);
     }
     return (v >= 0);
@@ -1045,7 +1054,7 @@ nm_platform_kernel_support_get_full(NMPlatformKernelSupportType type, gboolean i
 static inline gboolean
 nm_platform_kernel_support_get(NMPlatformKernelSupportType type)
 {
-    return nm_platform_kernel_support_get_full(type, TRUE) != NM_TERNARY_FALSE;
+    return nm_platform_kernel_support_get_full(type, TRUE) != NM_OPTION_BOOL_FALSE;
 }
 
 /*****************************************************************************/
@@ -1109,7 +1118,7 @@ typedef struct {
     void (*link_set_sriov_params_async)(NMPlatform *            self,
                                         int                     ifindex,
                                         guint                   num_vfs,
-                                        int                     autoprobe,
+                                        NMOptionBool            autoprobe,
                                         NMPlatformAsyncCallback callback,
                                         gpointer                callback_data,
                                         GCancellable *          cancellable);
@@ -1170,6 +1179,11 @@ typedef struct {
     gboolean (*wifi_get_capabilities)(NMPlatform *              self,
                                       int                       ifindex,
                                       NMDeviceWifiCapabilities *caps);
+    gboolean (*wifi_get_station)(NMPlatform * self,
+                                 int          ifindex,
+                                 NMEtherAddr *out_bssid,
+                                 int *        out_quality,
+                                 guint32 *    out_rate);
     gboolean (*wifi_get_bssid)(NMPlatform *self, int ifindex, guint8 *bssid);
     guint32 (*wifi_get_frequency)(NMPlatform *self, int ifindex);
     int (*wifi_get_quality)(NMPlatform *self, int ifindex);
@@ -1839,7 +1853,7 @@ gboolean nm_platform_link_set_name(NMPlatform *self, int ifindex, const char *na
 void nm_platform_link_set_sriov_params_async(NMPlatform *            self,
                                              int                     ifindex,
                                              guint                   num_vfs,
-                                             int                     autoprobe,
+                                             NMOptionBool            autoprobe,
                                              NMPlatformAsyncCallback callback,
                                              gpointer                callback_data,
                                              GCancellable *          cancellable);
@@ -1950,10 +1964,12 @@ gboolean nm_platform_link_tun_get_properties(NMPlatform *      self,
 
 gboolean
 nm_platform_wifi_get_capabilities(NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps);
-gboolean    nm_platform_wifi_get_bssid(NMPlatform *self, int ifindex, guint8 *bssid);
 guint32     nm_platform_wifi_get_frequency(NMPlatform *self, int ifindex);
-int         nm_platform_wifi_get_quality(NMPlatform *self, int ifindex);
-guint32     nm_platform_wifi_get_rate(NMPlatform *self, int ifindex);
+gboolean    nm_platform_wifi_get_station(NMPlatform * self,
+                                         int          ifindex,
+                                         NMEtherAddr *out_bssid,
+                                         int *        out_quality,
+                                         guint32 *    out_rate);
 NM80211Mode nm_platform_wifi_get_mode(NMPlatform *self, int ifindex);
 void        nm_platform_wifi_set_mode(NMPlatform *self, int ifindex, NM80211Mode mode);
 void        nm_platform_wifi_set_powersave(NMPlatform *self, int ifindex, guint32 powersave);
@@ -2097,18 +2113,18 @@ static inline guint32
 nm_platform_ip4_route_get_effective_metric(const NMPlatformIP4Route *r)
 {
     nm_assert(r);
-    nm_assert(!r->metric_any || r->metric == 0);
 
-    return r->metric_any ? NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4 : r->metric;
+    return r->metric_any ? nm_add_clamped_u32(NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4, r->metric)
+                         : r->metric;
 }
 
 static inline guint32
 nm_platform_ip6_route_get_effective_metric(const NMPlatformIP6Route *r)
 {
     nm_assert(r);
-    nm_assert(!r->metric_any || r->metric == 0);
 
-    return r->metric_any ? NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6 : r->metric;
+    return r->metric_any ? nm_add_clamped_u32(NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, r->metric)
+                         : r->metric;
 }
 
 static inline guint32
@@ -2306,10 +2322,10 @@ const char *nm_platform_route_scope2str(int scope, char *buf, gsize len);
 
 int nm_platform_ip_address_cmp_expiry(const NMPlatformIPAddress *a, const NMPlatformIPAddress *b);
 
-gboolean nm_platform_ethtool_set_wake_on_lan(NMPlatform *            self,
-                                             int                     ifindex,
-                                             NMSettingWiredWakeOnLan wol,
-                                             const char *            wol_password);
+gboolean nm_platform_ethtool_set_wake_on_lan(NMPlatform *             self,
+                                             int                      ifindex,
+                                             _NMSettingWiredWakeOnLan wol,
+                                             const char *             wol_password);
 gboolean nm_platform_ethtool_set_link_settings(NMPlatform *             self,
                                                int                      ifindex,
                                                gboolean                 autoneg,
@@ -2321,17 +2337,13 @@ gboolean nm_platform_ethtool_get_link_settings(NMPlatform *              self,
                                                guint32 *                 out_speed,
                                                NMPlatformLinkDuplexType *out_duplex);
 
-typedef struct _NMEthtoolFeatureStates NMEthtoolFeatureStates;
-
 NMEthtoolFeatureStates *nm_platform_ethtool_get_link_features(NMPlatform *self, int ifindex);
 gboolean                nm_platform_ethtool_set_features(
                    NMPlatform *                  self,
                    int                           ifindex,
                    const NMEthtoolFeatureStates *features,
-                   const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
-                   gboolean         do_set /* or reset */);
-
-typedef struct _NMEthtoolCoalesceState NMEthtoolCoalesceState;
+                   const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+                   gboolean            do_set /* or reset */);
 
 gboolean nm_platform_ethtool_get_link_coalesce(NMPlatform *            self,
                                                int                     ifindex,
@@ -2341,15 +2353,11 @@ gboolean nm_platform_ethtool_set_coalesce(NMPlatform *                  self,
                                           int                           ifindex,
                                           const NMEthtoolCoalesceState *coalesce);
 
-typedef struct _NMEthtoolRingState NMEthtoolRingState;
-
 gboolean nm_platform_ethtool_get_link_ring(NMPlatform *self, int ifindex, NMEthtoolRingState *ring);
 
 gboolean
 nm_platform_ethtool_set_ring(NMPlatform *self, int ifindex, const NMEthtoolRingState *ring);
 
-const char *nm_platform_link_duplex_type_to_string(NMPlatformLinkDuplexType duplex);
-
 void nm_platform_ip4_dev_route_blacklist_set(NMPlatform *self,
                                              int         ifindex,
                                              GPtrArray * ip4_dev_route_blacklist);
diff --git a/src/platform/nmp-object.c b/src/core/platform/nmp-object.c
index 6eea732f..6a26595e 100644
--- a/src/platform/nmp-object.c
+++ b/src/core/platform/nmp-object.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nmp-object.h"
 
@@ -16,7 +16,7 @@
 #include "nm-glib-aux/nm-secret-utils.h"
 
 #include "nm-core-utils.h"
-#include "nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 
 #include "wifi/nm-wifi-utils.h"
 #include "wpan/nm-wpan-utils.h"
@@ -1098,7 +1098,7 @@ _vt_cmd_obj_to_string_lnk_wireguard(const NMPObject *     obj,
         g_snprintf(buf, buf_len, __VA_ARGS__);                                            \
         return buf;                                                                       \
     }                                                                                     \
-    struct _dummy_struct_for_tailing_semicolon
+    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
 _vt_cmd_plobj_to_string_id(link, NMPlatformLink, "%d", obj->ifindex);
 
@@ -1390,7 +1390,7 @@ _vt_cmd_obj_copy_lnk_wireguard(NMPObject *dst, const NMPObject *src)
             cmd                                                                                    \
         }                                                                                          \
     }                                                                                              \
-    struct _dummy_struct_for_tailing_semicolon
+    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
 _vt_cmd_plobj_id_copy(link, NMPlatformLink, { dst->ifindex = src->ifindex; });
 
@@ -1485,7 +1485,7 @@ nmp_object_id_cmp(const NMPObject *obj1, const NMPObject *obj2)
         }                                                                 \
         return 0;                                                         \
     }                                                                     \
-    struct _dummy_struct_for_tailing_semicolon
+    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
 _vt_cmd_plobj_id_cmp(link, NMPlatformLink, { NM_CMP_FIELD(obj1, obj2, ifindex); });
 
@@ -1581,7 +1581,7 @@ nmp_object_id_hash(const NMPObject *obj)
             cmd;                                                                                  \
         }                                                                                         \
     }                                                                                             \
-    struct _dummy_struct_for_tailing_semicolon
+    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
 _vt_cmd_plobj_id_hash_update(link, NMPlatformLink, { nm_hash_update_val(h, obj->ifindex); });
 
diff --git a/src/platform/nmp-object.h b/src/core/platform/nmp-object.h
index 58f54dbe..dc2cc86b 100644
--- a/src/platform/nmp-object.h
+++ b/src/core/platform/nmp-object.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
@@ -1077,52 +1077,64 @@ nm_platform_dedup_multi_iter_next_obj(NMDedupMultiIter *ipconf_iter,
     return has_next;
 }
 
-static inline gboolean
-nm_platform_dedup_multi_iter_next_ip4_address(NMDedupMultiIter *           ipconf_iter,
-                                              const NMPlatformIP4Address **out_address)
-{
-    gboolean has_next;
-
-    has_next = nm_dedup_multi_iter_next(ipconf_iter);
-    if (out_address)
-        *out_address = has_next ? NMP_OBJECT_CAST_IP4_ADDRESS(ipconf_iter->current->obj) : NULL;
-    return has_next;
-}
-
-static inline gboolean
-nm_platform_dedup_multi_iter_next_ip4_route(NMDedupMultiIter *         ipconf_iter,
-                                            const NMPlatformIP4Route **out_route)
-{
-    gboolean has_next;
-
-    has_next = nm_dedup_multi_iter_next(ipconf_iter);
-    if (out_route)
-        *out_route = has_next ? NMP_OBJECT_CAST_IP4_ROUTE(ipconf_iter->current->obj) : NULL;
-    return has_next;
-}
-
-static inline gboolean
-nm_platform_dedup_multi_iter_next_ip6_address(NMDedupMultiIter *           ipconf_iter,
-                                              const NMPlatformIP6Address **out_address)
-{
-    gboolean has_next;
-
-    has_next = nm_dedup_multi_iter_next(ipconf_iter);
-    if (out_address)
-        *out_address = has_next ? NMP_OBJECT_CAST_IP6_ADDRESS(ipconf_iter->current->obj) : NULL;
-    return has_next;
-}
-
-static inline gboolean
-nm_platform_dedup_multi_iter_next_ip6_route(NMDedupMultiIter *         ipconf_iter,
-                                            const NMPlatformIP6Route **out_route)
-{
-    gboolean has_next;
+#define _nm_platform_dedup_multi_iter_next(ipconf_iter, out_obj, field, ...)                  \
+    ({                                                                                        \
+        NMDedupMultiIter *const                           _ipconf_iter = (ipconf_iter);       \
+        const typeof(((NMPObject *) NULL)->field) **const _out_obj     = (out_obj);           \
+        gboolean                                          _has_next;                          \
+                                                                                              \
+        if (G_LIKELY(nm_dedup_multi_iter_next(_ipconf_iter))) {                               \
+            if (_out_obj) {                                                                   \
+                *_out_obj = _NMP_OBJECT_CAST(_ipconf_iter->current->obj, field, __VA_ARGS__); \
+            } else {                                                                          \
+                nm_assert(                                                                    \
+                    NM_IN_SET(NMP_OBJECT_GET_TYPE(_ipconf_iter->current->obj), __VA_ARGS__)); \
+            }                                                                                 \
+            _has_next = TRUE;                                                                 \
+        } else {                                                                              \
+            if (_out_obj)                                                                     \
+                *_out_obj = NULL;                                                             \
+            _has_next = FALSE;                                                                \
+        }                                                                                     \
+        _has_next;                                                                            \
+    })
 
-    has_next = nm_dedup_multi_iter_next(ipconf_iter);
-    if (out_route)
-        *out_route = has_next ? NMP_OBJECT_CAST_IP6_ROUTE(ipconf_iter->current->obj) : NULL;
-    return has_next;
-}
+#define nm_platform_dedup_multi_iter_next_ip_address(ipconf_iter, out_obj) \
+    _nm_platform_dedup_multi_iter_next((ipconf_iter),                      \
+                                       (out_obj),                          \
+                                       ip_address,                         \
+                                       NMP_OBJECT_TYPE_IP4_ADDRESS,        \
+                                       NMP_OBJECT_TYPE_IP6_ADDRESS)
+
+#define nm_platform_dedup_multi_iter_next_ip4_address(ipconf_iter, out_obj) \
+    _nm_platform_dedup_multi_iter_next((ipconf_iter),                       \
+                                       (out_obj),                           \
+                                       ip4_address,                         \
+                                       NMP_OBJECT_TYPE_IP4_ADDRESS)
+
+#define nm_platform_dedup_multi_iter_next_ip6_address(ipconf_iter, out_obj) \
+    _nm_platform_dedup_multi_iter_next((ipconf_iter),                       \
+                                       (out_obj),                           \
+                                       ip6_address,                         \
+                                       NMP_OBJECT_TYPE_IP6_ADDRESS)
+
+#define nm_platform_dedup_multi_iter_next_ip_route(ipconf_iter, out_obj) \
+    _nm_platform_dedup_multi_iter_next((ipconf_iter),                    \
+                                       (out_obj),                        \
+                                       ip_route,                         \
+                                       NMP_OBJECT_TYPE_IP4_ROUTE,        \
+                                       NMP_OBJECT_TYPE_IP6_ROUTE)
+
+#define nm_platform_dedup_multi_iter_next_ip4_route(ipconf_iter, out_obj) \
+    _nm_platform_dedup_multi_iter_next((ipconf_iter),                     \
+                                       (out_obj),                         \
+                                       ip4_route,                         \
+                                       NMP_OBJECT_TYPE_IP4_ROUTE)
+
+#define nm_platform_dedup_multi_iter_next_ip6_route(ipconf_iter, out_obj) \
+    _nm_platform_dedup_multi_iter_next((ipconf_iter),                     \
+                                       (out_obj),                         \
+                                       ip6_route,                         \
+                                       NMP_OBJECT_TYPE_IP6_ROUTE)
 
 #endif /* __NMP_OBJECT_H__ */
diff --git a/src/platform/nmp-rules-manager.c b/src/core/platform/nmp-rules-manager.c
index d139c9a3..0c617b80 100644
--- a/src/platform/nmp-rules-manager.c
+++ b/src/core/platform/nmp-rules-manager.c
@@ -1,6 +1,6 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nmp-rules-manager.h"
 
diff --git a/src/platform/nmp-rules-manager.h b/src/core/platform/nmp-rules-manager.h
index 29c1295c..69cf9075 100644
--- a/src/platform/nmp-rules-manager.h
+++ b/src/core/platform/nmp-rules-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #ifndef __NMP_RULES_MANAGER_H__
 #define __NMP_RULES_MANAGER_H__
diff --git a/src/platform/tests/meson.build b/src/core/platform/tests/meson.build
index f96850cc..5d55707a 100644
--- a/src/platform/tests/meson.build
+++ b/src/core/platform/tests/meson.build
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 test_fake_c_flags = test_c_flags + ['-DSETUP=nm_fake_platform_setup']
 test_linux_c_flags = test_c_flags + ['-DSETUP=nm_linux_platform_setup']
@@ -22,7 +22,7 @@ foreach test_unit: test_units
   exe = executable(
     test_unit[0],
     test_unit[1],
-    dependencies: libnetwork_manager_test_dep,
+    dependencies: libNetworkManagerTest_dep,
     c_args: test_unit[2],
   )
   test(
@@ -38,6 +38,6 @@ name = 'monitor'
 executable(
   name,
   name + '.c',
-  dependencies: libnetwork_manager_test_dep,
+  dependencies: libNetworkManagerTest_dep,
   c_args: test_c_flags,
 )
diff --git a/src/platform/tests/monitor.c b/src/core/platform/tests/monitor.c
index a8d0fad2..abdf2cd5 100644
--- a/src/platform/tests/monitor.c
+++ b/src/core/platform/tests/monitor.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <stdlib.h>
 #include <syslog.h>
diff --git a/src/platform/tests/test-address.c b/src/core/platform/tests/test-address.c
index 5ea30a57..d4d67105 100644
--- a/src/platform/tests/test-address.c
+++ b/src/core/platform/tests/test-address.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "test-common.h"
 
diff --git a/src/platform/tests/test-cleanup.c b/src/core/platform/tests/test-cleanup.c
index c364bfa1..b54eca34 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/core/platform/tests/test-cleanup.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "test-common.h"
 
diff --git a/src/platform/tests/test-common.c b/src/core/platform/tests/test-common.c
index fb430046..87e5329a 100644
--- a/src/platform/tests/test-common.c
+++ b/src/core/platform/tests/test-common.c
@@ -1,9 +1,11 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 - 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
+
+#include "test-common.h"
 
 #include <sys/mount.h>
 #include <sched.h>
@@ -11,7 +13,7 @@
 #include <fcntl.h>
 #include <linux/if_tun.h>
 
-#include "test-common.h"
+#include "n-acd/src/n-acd.h"
 
 #define SIGNAL_DATA_FMT "'%s-%s' ifindex %d%s%s%s (%d times received)"
 #define SIGNAL_DATA_ARG(data)                                                                     \
@@ -87,7 +89,7 @@ nmtstp_platform_ip6_address_get_all(NMPlatform *self, int ifindex)
 const NMPlatformIPAddress *
 nmtstp_platform_ip_address_find(NMPlatform *self, int ifindex, int addr_family, gconstpointer addr)
 {
-    const gboolean             IS_IPv4 = NM_IS_IPv4(addr_family);
+    const int                  IS_IPv4 = NM_IS_IPv4(addr_family);
     const NMPlatformIPAddress *found   = NULL;
     NMDedupMultiIter           iter;
     const NMPObject *          obj;
@@ -99,8 +101,7 @@ nmtstp_platform_ip_address_find(NMPlatform *self, int ifindex, int addr_family,
     nm_assert(addr);
 
     nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex);
-    nm_platform_iter_obj_for_each(&iter, self, &lookup, &obj)
-    {
+    nm_platform_iter_obj_for_each (&iter, self, &lookup, &obj) {
         const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(obj);
 
         g_assert(NMP_OBJECT_GET_ADDR_FAMILY(obj) == addr_family);
@@ -1478,7 +1479,7 @@ nmtstp_link_bridge_add(NMPlatform *               platform,
             lnk->group_fwd_mask != 0
                 ? nm_sprintf_buf(sbuf_gfw, "group_fwd_mask %#x ", lnk->group_fwd_mask)
                 : "",
-            NM_ETHER_ADDR_FORMAT_VAL(lnk->group_addr),
+            NM_ETHER_ADDR_FORMAT_VAL(&lnk->group_addr),
             (int) lnk->mcast_snooping,
             lnk->mcast_router,
             (int) lnk->mcast_query_use_ifaddr,
@@ -1571,8 +1572,8 @@ nmtstp_link_veth_add(NMPlatform *platform,
                      const char *name,
                      const char *peer)
 {
-    const NMPlatformLink *pllink = NULL;
-    gboolean              success;
+    const NMPlatformLink *pllink  = NULL;
+    gboolean              success = FALSE;
 
     g_assert(nm_utils_ifname_valid_kernel(name, NULL));
 
@@ -1585,9 +1586,28 @@ nmtstp_link_veth_add(NMPlatform *platform,
         if (success) {
             pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_VETH, 100);
             nmtstp_assert_wait_for_link(platform, peer, NM_LINK_TYPE_VETH, 10);
+        } else {
+            /* iproute2 might fail in copr. See below.
+             * We accept that and try our platform implementation instead. */
+            _LOGI("iproute2 failed to add veth device. Retry with platform code.");
+            external_command = FALSE;
         }
-    } else
-        success = NMTST_NM_ERR_SUCCESS(nm_platform_link_veth_add(platform, name, peer, &pllink));
+    }
+
+    if (!external_command) {
+        int try_count = 0;
+        int r;
+
+again:
+        r = nm_platform_link_veth_add(platform, name, peer, &pllink);
+        if (r == -EPERM && try_count++ < 5) {
+            /* in copr (mock with Fedora 33 builders), this randomly fails with EPERM.
+             * Very odd. Try to work around by retrying. */
+            _LOGI("netlink failuer EPERM to add veth device. Retry.");
+            goto again;
+        }
+        success = NMTST_NM_ERR_SUCCESS(r);
+    }
 
     g_assert(success);
     _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_VETH);
@@ -2254,18 +2274,18 @@ gboolean
 nmtstp_kernel_support_get(NMPlatformKernelSupportType type)
 {
     const NMPlatformLink *pllink;
-    NMTernary             v;
+    NMOptionBool          v;
 
     v = nm_platform_kernel_support_get_full(type, FALSE);
-    if (v != NM_TERNARY_DEFAULT)
-        return v != NM_TERNARY_FALSE;
+    if (v != NM_OPTION_BOOL_DEFAULT)
+        return v != NM_OPTION_BOOL_FALSE;
 
     switch (type) {
     case NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED:
         pllink = nmtstp_link_bridge_add(NULL, -1, "br-test-11", &nm_platform_lnk_bridge_default);
         nmtstp_link_delete(NULL, -1, pllink->ifindex, NULL, TRUE);
         v = nm_platform_kernel_support_get_full(type, FALSE);
-        g_assert(v != NM_TERNARY_DEFAULT);
+        g_assert(v != NM_OPTION_BOOL_DEFAULT);
         return v;
     default:
         g_assert_not_reached();
@@ -2567,3 +2587,145 @@ main(int argc, char **argv)
     g_object_unref(NM_PLATFORM_GET);
     return result;
 }
+
+/*****************************************************************************/
+
+struct _NMTstpAcdDefender {
+    int        ifindex;
+    in_addr_t  ip_addr;
+    NAcd *     nacd;
+    NAcdProbe *probe;
+    GSource *  source;
+    gint8      announce_started;
+};
+
+static gboolean
+_l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data)
+{
+    NMTstpAcdDefender *defender = user_data;
+    int                r;
+
+    r = n_acd_dispatch(defender->nacd);
+    if (r == N_ACD_E_PREEMPTED)
+        r = 0;
+    g_assert_cmpint(r, ==, 0);
+
+    while (TRUE) {
+        NAcdEvent *event;
+
+        r = n_acd_pop_event(defender->nacd, &event);
+        g_assert_cmpint(r, ==, 0);
+        if (!event)
+            return G_SOURCE_CONTINUE;
+
+        switch (event->event) {
+        case N_ACD_EVENT_READY:
+            g_assert_cmpint(defender->announce_started, ==, 0);
+            g_assert(defender->probe == event->ready.probe);
+            defender->announce_started++;
+            _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT "]: start announcing",
+                  NM_HASH_OBFUSCATE_PTR(defender));
+            r = n_acd_probe_announce(defender->probe, N_ACD_DEFEND_ALWAYS);
+            g_assert_cmpint(r, ==, 0);
+            break;
+        case N_ACD_EVENT_DEFENDED:
+            g_assert(defender->probe == event->defended.probe);
+            g_assert_cmpint(event->defended.n_sender, ==, ETH_ALEN);
+            _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT
+                  "]: defended from " NM_ETHER_ADDR_FORMAT_STR,
+                  NM_HASH_OBFUSCATE_PTR(defender),
+                  NM_ETHER_ADDR_FORMAT_VAL((const NMEtherAddr *) event->defended.sender));
+            break;
+        case N_ACD_EVENT_USED:
+        case N_ACD_EVENT_CONFLICT:
+        case N_ACD_EVENT_DOWN:
+        default:
+            g_assert_not_reached();
+            break;
+        }
+    }
+}
+
+NMTstpAcdDefender *
+nmtstp_acd_defender_new(int ifindex, in_addr_t ip_addr, const NMEtherAddr *mac_addr)
+{
+    NMTstpAcdDefender *                                defender;
+    nm_auto(n_acd_config_freep) NAcdConfig *           config       = NULL;
+    nm_auto(n_acd_unrefp) NAcd *                       nacd         = NULL;
+    nm_auto(n_acd_probe_config_freep) NAcdProbeConfig *probe_config = NULL;
+    NAcdProbe *                                        probe        = NULL;
+    int                                                fd;
+    int                                                r;
+    char                                               sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+
+    g_assert_cmpint(ifindex, >, 0);
+    g_assert(mac_addr);
+
+    r = n_acd_config_new(&config);
+    g_assert_cmpint(r, ==, 0);
+    g_assert(config);
+
+    n_acd_config_set_ifindex(config, ifindex);
+    n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET);
+    n_acd_config_set_mac(config, (const guint8 *) mac_addr, sizeof(*mac_addr));
+
+    r = n_acd_new(&nacd, config);
+    g_assert_cmpint(r, ==, 0);
+    g_assert(nacd);
+
+    r = n_acd_probe_config_new(&probe_config);
+    g_assert_cmpint(r, ==, 0);
+    g_assert(probe_config);
+
+    n_acd_probe_config_set_ip(probe_config, (struct in_addr){ip_addr});
+    n_acd_probe_config_set_timeout(probe_config, 0);
+
+    r = n_acd_probe(nacd, &probe, probe_config);
+    g_assert_cmpint(r, ==, 0);
+    g_assert(probe);
+
+    defender  = g_slice_new(NMTstpAcdDefender);
+    *defender = (NMTstpAcdDefender){
+        .ifindex = ifindex,
+        .ip_addr = ip_addr,
+        .nacd    = g_steal_pointer(&nacd),
+        .probe   = g_steal_pointer(&probe),
+    };
+
+    _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT
+          "]: new for ifindex=%d, hwaddr=" NM_ETHER_ADDR_FORMAT_STR ", ipaddr=%s",
+          NM_HASH_OBFUSCATE_PTR(defender),
+          ifindex,
+          NM_ETHER_ADDR_FORMAT_VAL(mac_addr),
+          _nm_utils_inet4_ntop(ip_addr, sbuf_addr));
+
+    n_acd_probe_set_userdata(defender->probe, defender);
+
+    n_acd_get_fd(defender->nacd, &fd);
+    g_assert_cmpint(fd, >=, 0);
+
+    defender->source = nm_g_source_attach(nm_g_unix_fd_source_new(fd,
+                                                                  G_IO_IN,
+                                                                  G_PRIORITY_DEFAULT,
+                                                                  _l3_acd_nacd_event,
+                                                                  defender,
+                                                                  NULL),
+                                          NULL);
+
+    return defender;
+}
+
+void
+nmtstp_acd_defender_destroy(NMTstpAcdDefender *defender)
+{
+    if (!defender)
+        return;
+
+    _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT "]: destroy", NM_HASH_OBFUSCATE_PTR(defender));
+
+    nm_clear_g_source_inst(&defender->source);
+    nm_clear_pointer(&defender->nacd, n_acd_unref);
+    nm_clear_pointer(&defender->probe, n_acd_probe_free);
+
+    nm_g_slice_free(defender);
+}
diff --git a/src/platform/tests/test-common.h b/src/core/platform/tests/test-common.h
index 1d996f46..618645fe 100644
--- a/src/platform/tests/test-common.h
+++ b/src/core/platform/tests/test-common.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 - 2017 Red Hat, Inc.
  */
@@ -606,5 +606,14 @@ void nmtstp_setup_platform(void);
 
 /*****************************************************************************/
 
+typedef struct _NMTstpAcdDefender NMTstpAcdDefender;
+
+NMTstpAcdDefender *
+nmtstp_acd_defender_new(int ifindex, in_addr_t ip_addr, const NMEtherAddr *mac_addr);
+
+void nmtstp_acd_defender_destroy(NMTstpAcdDefender *defender);
+
+/*****************************************************************************/
+
 void _nmtstp_init_tests(int *argc, char ***argv);
 void _nmtstp_setup_tests(void);
diff --git a/src/platform/tests/test-link.c b/src/core/platform/tests/test-link.c
index 5fd67ec4..3aaa3759 100644
--- a/src/platform/tests/test-link.c
+++ b/src/core/platform/tests/test-link.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <sched.h>
 #include <sys/mount.h>
@@ -12,9 +12,10 @@
 #include <linux/if_tun.h>
 
 #include "nm-glib-aux/nm-io-utils.h"
+#include "nm-base/nm-ethtool-base.h"
 #include "platform/nmp-object.h"
-#include "platform/nmp-netns.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nmp-netns.h"
+#include "nm-platform/nm-platform-utils.h"
 
 #include "test-common.h"
 #include "nm-test-utils-core.h"
@@ -3676,25 +3677,28 @@ test_ethtool_features_get(void)
 
     for (i_run = 0; i_run < 5; i_run++) {
         NMEthtoolFeatureStates *features;
-        NMTernary *             requested;
+        NMOptionBool *          requested;
         gboolean                do_set = TRUE;
 
-        requested = g_new(NMTernary, _NM_ETHTOOL_ID_FEATURE_NUM);
+        requested = g_new(NMOptionBool, _NM_ETHTOOL_ID_FEATURE_NUM);
         for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++)
-            requested[i] = NM_TERNARY_DEFAULT;
+            requested[i] = NM_OPTION_BOOL_DEFAULT;
         g_ptr_array_add(gfree_keeper, requested);
 
         if (i_run == 0) {
-            requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_RX)]  = NM_TERNARY_FALSE;
-            requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TSO)] = NM_TERNARY_FALSE;
+            requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_RX)] =
+                NM_OPTION_BOOL_FALSE;
+            requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TSO)] =
+                NM_OPTION_BOOL_FALSE;
             requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION)] =
-                NM_TERNARY_FALSE;
+                NM_OPTION_BOOL_FALSE;
         } else if (i_run == 1)
             do_set = FALSE;
         else if (i_run == 2) {
-            requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TSO)] = NM_TERNARY_FALSE;
+            requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TSO)] =
+                NM_OPTION_BOOL_FALSE;
             requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION)] =
-                NM_TERNARY_TRUE;
+                NM_OPTION_BOOL_TRUE;
         } else if (i_run == 3)
             do_set = FALSE;
 
diff --git a/src/platform/tests/test-nmp-object.c b/src/core/platform/tests/test-nmp-object.c
index e61e6b09..310611ee 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/core/platform/tests/test-nmp-object.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 - 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <libudev.h>
 #include <linux/pkt_sched.h>
@@ -569,7 +569,7 @@ main(int argc, char **argv)
 
     nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT");
 
-    udev_client = nm_udev_client_new((const char *[]){"net", NULL}, NULL, NULL);
+    udev_client = nm_udev_client_new(NM_MAKE_STRV("net"), NULL, NULL);
     {
         struct udev_enumerate * enumerator;
         struct udev_list_entry *devices, *l;
@@ -611,7 +611,7 @@ main(int argc, char **argv)
         global.udev_devices = g_list_delete_link(global.udev_devices, global.udev_devices);
     }
 
-    nm_udev_client_unref(udev_client);
+    nm_udev_client_destroy(udev_client);
 
     return result;
 }
diff --git a/src/platform/tests/test-platform-general.c b/src/core/platform/tests/test-platform-general.c
index 703c886e..b426cc65 100644
--- a/src/platform/tests/test-platform-general.c
+++ b/src/core/platform/tests/test-platform-general.c
@@ -1,13 +1,13 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <linux/rtnetlink.h>
 
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "platform/nm-linux-platform.h"
 
 #include "nm-test-utils-core.h"
diff --git a/src/platform/tests/test-route.c b/src/core/platform/tests/test-route.c
index 1305e586..47587865 100644
--- a/src/platform/tests/test-route.c
+++ b/src/core/platform/tests/test-route.c
@@ -1,15 +1,15 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 - 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <linux/rtnetlink.h>
 #include <linux/fib_rules.h>
 
 #include "nm-core-utils.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "platform/nmp-rules-manager.h"
 
 #include "test-common.h"
diff --git a/src/platform/tests/test-tc.c b/src/core/platform/tests/test-tc.c
index 4cbe4f5d..d15c3d76 100644
--- a/src/platform/tests/test-tc.c
+++ b/src/core/platform/tests/test-tc.c
@@ -1,13 +1,13 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <linux/pkt_sched.h>
 
 #include "nm-test-utils-core.h"
 #include "platform/nmp-object.h"
-#include "platform/nmp-netns.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nmp-netns.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "test-common.h"
 
 static NMPObject *
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.c b/src/core/platform/wifi/nm-wifi-utils-nl80211.c
index dddfa8fa..ad067248 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/core/platform/wifi/nm-wifi-utils-nl80211.c
@@ -1,11 +1,11 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  * Copyright (C) 2011 Intel Corporation. All rights reserved.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-wifi-utils-nl80211.h"
 
@@ -15,10 +15,10 @@
 #include <linux/nl80211.h>
 #include <linux/if.h>
 
-#include "platform/nm-netlink.h"
+#include "nm-platform/nm-netlink.h"
 #include "nm-wifi-utils-private.h"
 #include "platform/nm-platform.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "nm-utils.h"
 
 #define _NMLOG_PREFIX_NAME "wifi-nl80211"
@@ -163,6 +163,7 @@ dispose(GObject *object)
 
 struct nl80211_iface_info {
     NM80211Mode mode;
+    uint32_t    freq;
 };
 
 static int
@@ -193,6 +194,9 @@ nl80211_iface_info_handler(struct nl_msg *msg, void *arg)
         break;
     }
 
+    if (tb[NL80211_ATTR_WIPHY_FREQ] != NULL)
+        info->freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]);
+
     return NL_SKIP;
 }
 
@@ -359,142 +363,19 @@ nla_put_failure:
     g_return_val_if_reached(FALSE);
 }
 
-/* @divisor: pass what value @xbm should be divided by to get dBm */
-static guint32
-nl80211_xbm_to_percent(gint32 xbm, guint32 divisor)
-{
-#define NOISE_FLOOR_DBM -90
-#define SIGNAL_MAX_DBM  -20
-
-    xbm /= divisor;
-    xbm = CLAMP(xbm, NOISE_FLOOR_DBM, SIGNAL_MAX_DBM);
-
-    return 100
-           - 70
-                 * (((float) SIGNAL_MAX_DBM - (float) xbm)
-                    / ((float) SIGNAL_MAX_DBM - (float) NOISE_FLOOR_DBM));
-}
-
-struct nl80211_bss_info {
-    guint32  freq;
-    guint8   bssid[ETH_ALEN];
-    guint8   ssid[32];
-    guint32  ssid_len;
-    guint32  beacon_signal;
-    gboolean valid;
-};
-
-#define WLAN_EID_SSID 0
-
-static void
-find_ssid(guint8 *ies, guint32 ies_len, guint8 **ssid, guint32 *ssid_len)
-{
-    *ssid     = NULL;
-    *ssid_len = 0;
-
-    while (ies_len > 2 && ies[0] != WLAN_EID_SSID) {
-        ies_len -= ies[1] + 2;
-        ies += ies[1] + 2;
-    }
-    if (ies_len < 2)
-        return;
-    if (ies_len < 2 + ies[1])
-        return;
-
-    *ssid_len = ies[1];
-    *ssid     = ies + 2;
-}
-
-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[G_N_ELEMENTS(bss_policy)];
-    guint32                  status;
-
-    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_arr(bss, tb[NL80211_ATTR_BSS], bss_policy))
-        return NL_SKIP;
-
-    if (bss[NL80211_BSS_STATUS] == NULL)
-        return NL_SKIP;
-
-    status = nla_get_u32(bss[NL80211_BSS_STATUS]);
-
-    if (status != NL80211_BSS_STATUS_ASSOCIATED && status != NL80211_BSS_STATUS_IBSS_JOINED)
-        return NL_SKIP;
-
-    if (bss[NL80211_BSS_BSSID] == NULL)
-        return NL_SKIP;
-    memcpy(info->bssid, nla_data(bss[NL80211_BSS_BSSID]), ETH_ALEN);
-
-    if (bss[NL80211_BSS_FREQUENCY])
-        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]);
-
-    if (bss[NL80211_BSS_SIGNAL_MBM])
-        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)) {
-            memcpy(info->ssid, ssid, ssid_len);
-            info->ssid_len = ssid_len;
-        }
-    }
-
-    info->valid = TRUE;
-
-    return NL_SKIP;
-}
-
-static void
-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(self, NL80211_CMD_GET_SCAN, NLM_F_DUMP);
-
-    nl80211_send_and_recv(self, msg, nl80211_bss_dump_handler, bss_info);
-}
-
 static guint32
 wifi_nl80211_get_freq(NMWifiUtils *data)
 {
-    NMWifiUtilsNl80211 *    self = (NMWifiUtilsNl80211 *) data;
-    struct nl80211_bss_info bss_info;
+    NMWifiUtilsNl80211 *         self       = (NMWifiUtilsNl80211 *) data;
+    struct nl80211_iface_info    iface_info = {};
+    nm_auto_nlmsg struct nl_msg *msg        = NULL;
 
-    nl80211_get_bss_info(self, &bss_info);
+    msg = nl80211_alloc_msg(self, NL80211_CMD_GET_INTERFACE, 0);
+
+    if (nl80211_send_and_recv(self, msg, nl80211_iface_info_handler, &iface_info) < 0)
+        return 0;
 
-    return bss_info.freq;
+    return iface_info.freq;
 }
 
 static guint32
@@ -513,21 +394,25 @@ wifi_nl80211_find_freq(NMWifiUtils *data, const guint32 *freqs)
     return 0;
 }
 
-static gboolean
-wifi_nl80211_get_bssid(NMWifiUtils *data, guint8 *out_bssid)
+/* @divisor: pass what value @xbm should be divided by to get dBm */
+static guint32
+nl80211_xbm_to_percent(gint32 xbm, guint32 divisor)
 {
-    NMWifiUtilsNl80211 *    self = (NMWifiUtilsNl80211 *) data;
-    struct nl80211_bss_info bss_info;
-
-    nl80211_get_bss_info(self, &bss_info);
+#define NOISE_FLOOR_DBM -90
+#define SIGNAL_MAX_DBM  -20
 
-    if (bss_info.valid)
-        memcpy(out_bssid, bss_info.bssid, ETH_ALEN);
+    xbm /= divisor;
+    xbm = CLAMP(xbm, NOISE_FLOOR_DBM, SIGNAL_MAX_DBM);
 
-    return bss_info.valid;
+    return 100
+           - 70
+                 * (((float) SIGNAL_MAX_DBM - (float) xbm)
+                    / ((float) SIGNAL_MAX_DBM - (float) NOISE_FLOOR_DBM));
 }
 
 struct nl80211_station_info {
+    gboolean valid;
+    guint8   bssid[ETH_ALEN];
     guint32  txrate;
     gboolean txrate_valid;
     guint8   signal;
@@ -535,19 +420,21 @@ struct nl80211_station_info {
 };
 
 static int
-nl80211_station_handler(struct nl_msg *msg, void *arg)
+nl80211_station_dump_handler(struct nl_msg *msg, void *arg)
 {
     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_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_STA_FLAGS]         = {.minlen = sizeof(struct nl80211_sta_flag_update)},
+        [NL80211_STA_INFO_BEACON_SIGNAL_AVG] = {.type = NLA_U8},
     };
     static const struct nla_policy rate_policy[] = {
         [NL80211_RATE_INFO_BITRATE]      = {.type = NLA_U16},
@@ -564,80 +451,76 @@ nl80211_station_handler(struct nl_msg *msg, void *arg)
     if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0)
         return NL_SKIP;
 
+    if (tb[NL80211_ATTR_MAC] == NULL)
+        return NL_SKIP;
+
     if (tb[NL80211_ATTR_STA_INFO] == NULL)
         return NL_SKIP;
 
     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 (sinfo[NL80211_STA_INFO_STA_FLAGS] != NULL) {
+        const struct nl80211_sta_flag_update *flags = nla_data(sinfo[NL80211_STA_INFO_STA_FLAGS]);
 
-    if (nla_parse_nested_arr(rinfo, sinfo[NL80211_STA_INFO_TX_BITRATE], rate_policy))
-        return NL_SKIP;
+        if (flags->mask & ~flags->set & (1 << NL80211_STA_FLAG_ASSOCIATED))
+            return NL_SKIP;
+    }
 
-    if (rinfo[NL80211_RATE_INFO_BITRATE] == NULL)
-        return NL_SKIP;
+    memcpy(info->bssid, nla_data(tb[NL80211_ATTR_MAC]), ETH_ALEN);
+    info->valid = TRUE;
 
-    /* convert from nl80211's units of 100kbps to NM's kbps */
-    info->txrate       = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]) * 100;
-    info->txrate_valid = TRUE;
+    if (sinfo[NL80211_STA_INFO_TX_BITRATE] != NULL
+        && !nla_parse_nested_arr(rinfo, sinfo[NL80211_STA_INFO_TX_BITRATE], rate_policy)
+        && rinfo[NL80211_RATE_INFO_BITRATE] != NULL) {
+        /* convert from nl80211's units of 100kbps to NM's kbps */
+        info->txrate       = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]) * 100;
+        info->txrate_valid = TRUE;
+    }
 
     if (sinfo[NL80211_STA_INFO_SIGNAL] != NULL) {
         info->signal =
             nl80211_xbm_to_percent((gint8) nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]), 1);
         info->signal_valid = TRUE;
+    } else if (sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG] != NULL) {
+        /* Fall back to beacon signal strength */
+        info->signal =
+            nl80211_xbm_to_percent((gint8) nla_get_u8(sinfo[NL80211_STA_INFO_BEACON_SIGNAL_AVG]),
+                                   1);
+        info->signal_valid = TRUE;
     }
 
     return NL_SKIP;
 }
 
-static void
-nl80211_get_ap_info(NMWifiUtilsNl80211 *self, struct nl80211_station_info *sta_info)
+static gboolean
+wifi_nl80211_get_station(NMWifiUtils *data,
+                         NMEtherAddr *out_bssid,
+                         int *        out_quality,
+                         guint32 *    out_rate)
 {
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-    struct nl80211_bss_info      bss_info;
-
-    memset(sta_info, 0, sizeof(*sta_info));
+    NMWifiUtilsNl80211 *         self     = (NMWifiUtilsNl80211 *) data;
+    nm_auto_nlmsg struct nl_msg *msg      = NULL;
+    struct nl80211_station_info  sta_info = {};
 
-    nl80211_get_bss_info(self, &bss_info);
-    if (!bss_info.valid)
-        return;
+    msg = nl80211_alloc_msg(self, NL80211_CMD_GET_STATION, NLM_F_DUMP);
 
-    msg = nl80211_alloc_msg(self, NL80211_CMD_GET_STATION, 0);
-    NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, bss_info.bssid);
-
-    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;
-    }
-
-    return;
-
-nla_put_failure:
-    g_return_if_reached();
-}
+    nl80211_send_and_recv(self, msg, nl80211_station_dump_handler, &sta_info);
 
-static guint32
-wifi_nl80211_get_rate(NMWifiUtils *data)
-{
-    NMWifiUtilsNl80211 *        self = (NMWifiUtilsNl80211 *) data;
-    struct nl80211_station_info sta_info;
+    if (!sta_info.valid || (out_quality && !sta_info.signal_valid)
+        || (out_rate && !sta_info.txrate_valid))
+        return FALSE;
 
-    nl80211_get_ap_info(self, &sta_info);
+    if (out_bssid)
+        memcpy(out_bssid, sta_info.bssid, ETH_ALEN);
 
-    return sta_info.txrate;
-}
+    if (out_quality)
+        *out_quality = sta_info.signal;
 
-static int
-wifi_nl80211_get_qual(NMWifiUtils *data)
-{
-    NMWifiUtilsNl80211 *        self = (NMWifiUtilsNl80211 *) data;
-    struct nl80211_station_info sta_info;
+    if (out_rate)
+        *out_rate = sta_info.txrate;
 
-    nl80211_get_ap_info(self, &sta_info);
-    return sta_info.signal;
+    return TRUE;
 }
 
 static gboolean
@@ -952,9 +835,7 @@ nm_wifi_utils_nl80211_class_init(NMWifiUtilsNl80211Class *klass)
     wifi_utils_class->set_wake_on_wlan            = wifi_nl80211_set_wake_on_wlan,
     wifi_utils_class->get_freq                    = wifi_nl80211_get_freq;
     wifi_utils_class->find_freq                   = wifi_nl80211_find_freq;
-    wifi_utils_class->get_bssid                   = wifi_nl80211_get_bssid;
-    wifi_utils_class->get_rate                    = wifi_nl80211_get_rate;
-    wifi_utils_class->get_qual                    = wifi_nl80211_get_qual;
+    wifi_utils_class->get_station                 = wifi_nl80211_get_station;
     wifi_utils_class->indicate_addressing_running = wifi_nl80211_indicate_addressing_running;
     wifi_utils_class->get_mesh_channel            = wifi_nl80211_get_mesh_channel;
     wifi_utils_class->set_mesh_channel            = wifi_nl80211_set_mesh_channel;
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.h b/src/core/platform/wifi/nm-wifi-utils-nl80211.h
index 59fc1acc..37c32ed7 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.h
+++ b/src/core/platform/wifi/nm-wifi-utils-nl80211.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 Intel Corporation. All rights reserved.
  * Copyright (C) 2018 Red Hat, Inc.
@@ -8,7 +8,7 @@
 #define __WIFI_UTILS_NL80211_H__
 
 #include "nm-wifi-utils.h"
-#include "platform/nm-netlink.h"
+#include "nm-platform/nm-netlink.h"
 
 #define NM_TYPE_WIFI_UTILS_NL80211 (nm_wifi_utils_nl80211_get_type())
 #define NM_WIFI_UTILS_NL80211(obj) \
diff --git a/src/platform/wifi/nm-wifi-utils-private.h b/src/core/platform/wifi/nm-wifi-utils-private.h
index 1a5303a6..9ed57453 100644
--- a/src/platform/wifi/nm-wifi-utils-private.h
+++ b/src/core/platform/wifi/nm-wifi-utils-private.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 - 2018 Red Hat, Inc.
  */
@@ -31,15 +31,17 @@ typedef struct {
     /* Return first supported frequency in the zero-terminated list */
     guint32 (*find_freq)(NMWifiUtils *data, const guint32 *freqs);
 
-    /* Return current bitrate in Kbps */
-    guint32 (*get_rate)(NMWifiUtils *data);
-
-    gboolean (*get_bssid)(NMWifiUtils *data, guint8 *out_bssid);
-
-    /* Return a signal strength percentage 0 - 100% for the current BSSID;
-     * return -1 on errors or if not associated.
+    /*
+     * @out_bssid: must be NULL or an ETH_ALEN-byte buffer
+     * @out_quality: receives signal strength percentage 0 - 100% for the current BSSID, if not NULL
+     * @out_rate: receives current bitrate in Kbps if not NULL
+     *
+     * Returns %TRUE on succcess, %FALSE on errors or if not associated.
      */
-    int (*get_qual)(NMWifiUtils *data);
+    gboolean (*get_station)(NMWifiUtils *data,
+                            NMEtherAddr *out_bssid,
+                            int *        out_quality,
+                            guint32 *    out_rate);
 
     /* OLPC Mesh-only functions */
 
diff --git a/src/platform/wifi/nm-wifi-utils-wext.c b/src/core/platform/wifi/nm-wifi-utils-wext.c
index 8299415d..a44fcf62 100644
--- a/src/platform/wifi/nm-wifi-utils-wext.c
+++ b/src/core/platform/wifi/nm-wifi-utils-wext.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-wifi-utils-wext.h"
 
@@ -23,8 +23,9 @@
 
 #include "nm-wifi-utils-private.h"
 #include "nm-utils.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 #include "nm-core-internal.h"
+#include "nm-core-utils.h"
 
 typedef struct {
     NMWifiUtils       parent;
@@ -266,7 +267,7 @@ wifi_wext_find_freq(NMWifiUtils *data, const guint32 *freqs)
 }
 
 static gboolean
-wifi_wext_get_bssid(NMWifiUtils *data, guint8 *out_bssid)
+wifi_wext_get_bssid(NMWifiUtils *data, NMEtherAddr *out_bssid)
 {
     NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
     struct iwreq     wrq;
@@ -453,6 +454,38 @@ wifi_wext_get_qual(NMWifiUtils *data)
     return wext_qual_to_percent(&stats.qual, &wext->max_qual);
 }
 
+static gboolean
+wifi_wext_get_station(NMWifiUtils *data,
+                      NMEtherAddr *out_bssid,
+                      int *        out_quality,
+                      guint32 *    out_rate)
+{
+    NMEtherAddr local_addr;
+
+    if (!out_bssid && !out_quality && !out_rate) {
+        /* hm, the caller requested no parameter at all?
+         * Don't simply return TRUE, but at least check that
+         * we can successfully fetch the bssid. */
+        out_bssid = &local_addr;
+    }
+
+    if (out_bssid) {
+        if (!wifi_wext_get_bssid(data, out_bssid))
+            return FALSE;
+    }
+    if (out_quality) {
+        *out_quality = wifi_wext_get_qual(data);
+        if (*out_quality < 0)
+            return FALSE;
+    }
+    if (out_rate) {
+        *out_rate = wifi_wext_get_rate(data);
+        if (*out_rate == 0)
+            return FALSE;
+    }
+    return TRUE;
+}
+
 /*****************************************************************************/
 /* OLPC Mesh-only functions */
 
@@ -677,9 +710,7 @@ nm_wifi_utils_wext_class_init(NMWifiUtilsWextClass *klass)
     wifi_utils_class->set_powersave    = wifi_wext_set_powersave;
     wifi_utils_class->get_freq         = wifi_wext_get_freq;
     wifi_utils_class->find_freq        = wifi_wext_find_freq;
-    wifi_utils_class->get_bssid        = wifi_wext_get_bssid;
-    wifi_utils_class->get_rate         = wifi_wext_get_rate;
-    wifi_utils_class->get_qual         = wifi_wext_get_qual;
+    wifi_utils_class->get_station      = wifi_wext_get_station;
     wifi_utils_class->get_mesh_channel = wifi_wext_get_mesh_channel;
     wifi_utils_class->set_mesh_channel = wifi_wext_set_mesh_channel;
     wifi_utils_class->set_mesh_ssid    = wifi_wext_set_mesh_ssid;
diff --git a/src/platform/wifi/nm-wifi-utils-wext.h b/src/core/platform/wifi/nm-wifi-utils-wext.h
index 210b87e5..d6f3453c 100644
--- a/src/platform/wifi/nm-wifi-utils-wext.h
+++ b/src/core/platform/wifi/nm-wifi-utils-wext.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2011 - 2018 Red Hat, Inc.
  */
diff --git a/src/platform/wifi/nm-wifi-utils.c b/src/core/platform/wifi/nm-wifi-utils.c
index 3f9d3a48..49ae1811 100644
--- a/src/platform/wifi/nm-wifi-utils.c
+++ b/src/core/platform/wifi/nm-wifi-utils.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-wifi-utils.h"
 
@@ -19,7 +19,7 @@
 #endif
 #include "nm-core-utils.h"
 
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 
 G_DEFINE_ABSTRACT_TYPE(NMWifiUtils, nm_wifi_utils, G_TYPE_OBJECT)
 
@@ -132,27 +132,14 @@ nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs)
 }
 
 gboolean
-nm_wifi_utils_get_bssid(NMWifiUtils *data, guint8 *out_bssid)
+nm_wifi_utils_get_station(NMWifiUtils *data,
+                          NMEtherAddr *out_bssid,
+                          int *        out_quality,
+                          guint32 *    out_rate)
 {
     g_return_val_if_fail(data != NULL, FALSE);
-    g_return_val_if_fail(out_bssid != NULL, FALSE);
 
-    memset(out_bssid, 0, ETH_ALEN);
-    return NM_WIFI_UTILS_GET_CLASS(data)->get_bssid(data, out_bssid);
-}
-
-guint32
-nm_wifi_utils_get_rate(NMWifiUtils *data)
-{
-    g_return_val_if_fail(data != NULL, 0);
-    return NM_WIFI_UTILS_GET_CLASS(data)->get_rate(data);
-}
-
-int
-nm_wifi_utils_get_qual(NMWifiUtils *data)
-{
-    g_return_val_if_fail(data != NULL, 0);
-    return NM_WIFI_UTILS_GET_CLASS(data)->get_qual(data);
+    return NM_WIFI_UTILS_GET_CLASS(data)->get_station(data, out_bssid, out_quality, out_rate);
 }
 
 gboolean
diff --git a/src/platform/wifi/nm-wifi-utils.h b/src/core/platform/wifi/nm-wifi-utils.h
index d1df2fb8..a252e43e 100644
--- a/src/platform/wifi/nm-wifi-utils.h
+++ b/src/core/platform/wifi/nm-wifi-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
@@ -11,7 +11,7 @@
 
 #include "nm-dbus-interface.h"
 #include "nm-setting-wireless.h"
-#include "platform/nm-netlink.h"
+#include "nm-platform/nm-netlink.h"
 
 typedef struct NMWifiUtils NMWifiUtils;
 
@@ -43,14 +43,17 @@ guint32 nm_wifi_utils_get_freq(NMWifiUtils *data);
  * Frequencies are specified in MHz. */
 guint32 nm_wifi_utils_find_freq(NMWifiUtils *data, const guint32 *freqs);
 
-/* out_bssid must be ETH_ALEN bytes */
-gboolean nm_wifi_utils_get_bssid(NMWifiUtils *data, guint8 *out_bssid);
-
-/* Returns current bitrate in Kbps */
-guint32 nm_wifi_utils_get_rate(NMWifiUtils *data);
-
-/* Returns quality 0 - 100% on success, or -1 on error */
-int nm_wifi_utils_get_qual(NMWifiUtils *data);
+/*
+ * @out_bssid: must be NULL or an ETH_ALEN-byte buffer
+ * @out_quality: receives signal quality in 0 - 100% range if not NULL
+ * @out_rate: receives current bitrate in Kbps if not NULL
+ *
+ * Returns %TRUE on succcess.
+ */
+gboolean nm_wifi_utils_get_station(NMWifiUtils *data,
+                                   NMEtherAddr *out_bssid,
+                                   int *        out_quality,
+                                   guint32 *    out_rate);
 
 /* Tells the driver DHCP or SLAAC is running */
 gboolean nm_wifi_utils_indicate_addressing_running(NMWifiUtils *data, gboolean running);
diff --git a/src/platform/wpan/nm-wpan-utils.c b/src/core/platform/wpan/nm-wpan-utils.c
index 6f651414..efb19979 100644
--- a/src/platform/wpan/nm-wpan-utils.c
+++ b/src/core/platform/wpan/nm-wpan-utils.c
@@ -1,17 +1,17 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-wpan-utils.h"
 
 #include <linux/if.h>
 
+#include "nm-platform/nm-netlink.h"
 #include "platform/linux/nl802154.h"
-#include "platform/nm-netlink.h"
-#include "platform/nm-platform-utils.h"
+#include "nm-platform/nm-platform-utils.h"
 
 #define _NMLOG_PREFIX_NAME "wpan-nl802154"
 #define _NMLOG(level, domain, ...)                                                                \
diff --git a/src/platform/wpan/nm-wpan-utils.h b/src/core/platform/wpan/nm-wpan-utils.h
index b30c89a6..940c2c9d 100644
--- a/src/platform/wpan/nm-wpan-utils.h
+++ b/src/core/platform/wpan/nm-wpan-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
@@ -9,7 +9,7 @@
 #include <net/ethernet.h>
 
 #include "nm-dbus-interface.h"
-#include "platform/nm-netlink.h"
+#include "nm-platform/nm-netlink.h"
 
 typedef struct NMWpanUtils NMWpanUtils;
 
diff --git a/src/ppp/meson.build b/src/core/ppp/meson.build
index 181a6bef..607b4718 100644
--- a/src/ppp/meson.build
+++ b/src/core/ppp/meson.build
@@ -1,30 +1,25 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-name = 'nm-pppd-plugin'
-
-c_flags = [
-  '-DG_LOG_DOMAIN="@0@"'.format(name),
-  '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB',
-]
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 nm_pppd_plugin = shared_module(
-  name,
+  'nm-pppd-plugin',
   name_prefix: '',
-  sources: name + '.c',
+  sources: 'nm-pppd-plugin.c',
   dependencies: libnm_core_nm_default_dep,
-  c_args: c_flags,
+  c_args: [
+    '-DG_LOG_DOMAIN="nm-pppd-plugin"',
+  ],
   install: true,
   install_dir: pppd_plugin_dir,
 )
 
-name = 'nm-ppp-plugin'
-
 linker_script = join_paths(meson.current_source_dir(), 'nm-ppp-plugin.ver')
 
 core_plugins += shared_module(
-  name,
-  sources: 'nm-ppp-manager.c',
-  dependencies: daemon_nm_default_dep,
+  'nm-ppp-plugin',
+  sources: [
+    'nm-ppp-manager.c',
+  ],
+  dependencies: core_plugin_dep,
   c_args: daemon_c_flags,
   link_args: '-Wl,--version-script,@0@'.format(linker_script),
   link_depends: linker_script,
diff --git a/src/ppp/nm-ppp-manager-call.c b/src/core/ppp/nm-ppp-manager-call.c
index 34c3d06d..f791e8b0 100644
--- a/src/ppp/nm-ppp-manager-call.c
+++ b/src/core/ppp/nm-ppp-manager-call.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-ppp-manager-call.h"
 
diff --git a/src/ppp/nm-ppp-manager-call.h b/src/core/ppp/nm-ppp-manager-call.h
index d17419ba..c831cf0d 100644
--- a/src/ppp/nm-ppp-manager-call.h
+++ b/src/core/ppp/nm-ppp-manager-call.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
diff --git a/src/ppp/nm-ppp-manager.c b/src/core/ppp/nm-ppp-manager.c
index a200334c..396a49ae 100644
--- a/src/ppp/nm-ppp-manager.c
+++ b/src/core/ppp/nm-ppp-manager.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 - 2012 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-ppp-manager.h"
 
@@ -1302,8 +1302,7 @@ _ppp_manager_new(const char *iface)
 {
     g_return_val_if_fail(iface != NULL, NULL);
 
-    return (
-        NMPPPManager *) g_object_new(NM_TYPE_PPP_MANAGER, NM_PPP_MANAGER_PARENT_IFACE, iface, NULL);
+    return g_object_new(NM_TYPE_PPP_MANAGER, NM_PPP_MANAGER_PARENT_IFACE, iface, NULL);
 }
 
 static void
diff --git a/src/ppp/nm-ppp-manager.h b/src/core/ppp/nm-ppp-manager.h
index b130608c..c41dda20 100644
--- a/src/ppp/nm-ppp-manager.h
+++ b/src/core/ppp/nm-ppp-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 - 2016 Red Hat, Inc.
diff --git a/src/ppp/nm-ppp-plugin-api.h b/src/core/ppp/nm-ppp-plugin-api.h
index a105690c..647e1c24 100644
--- a/src/ppp/nm-ppp-plugin-api.h
+++ b/src/core/ppp/nm-ppp-plugin-api.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
diff --git a/src/ppp/nm-ppp-plugin.ver b/src/core/ppp/nm-ppp-plugin.ver
index fee55787..fee55787 100644
--- a/src/ppp/nm-ppp-plugin.ver
+++ b/src/core/ppp/nm-ppp-plugin.ver
diff --git a/src/ppp/nm-ppp-status.h b/src/core/ppp/nm-ppp-status.h
index 953bc32f..c346b119 100644
--- a/src/ppp/nm-ppp-status.h
+++ b/src/core/ppp/nm-ppp-status.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 - 2016 Red Hat, Inc.
diff --git a/src/ppp/nm-pppd-plugin.c b/src/core/ppp/nm-pppd-plugin.c
index f50994a3..c9016dac 100644
--- a/src/ppp/nm-pppd-plugin.c
+++ b/src/core/ppp/nm-pppd-plugin.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 Red Hat, Inc.
@@ -20,7 +20,7 @@
 #include <pppd/eui64.h>
 #include <pppd/ipv6cp.h>
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib.h"
 
 #include "nm-dbus-interface.h"
 
diff --git a/src/ppp/nm-pppd-plugin.h b/src/core/ppp/nm-pppd-plugin.h
index 44bb79b3..b56afbeb 100644
--- a/src/ppp/nm-pppd-plugin.h
+++ b/src/core/ppp/nm-pppd-plugin.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 - 2014 Red Hat, Inc.
diff --git a/src/settings/nm-agent-manager.c b/src/core/settings/nm-agent-manager.c
index 07a3dafe..7e617909 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/core/settings/nm-agent-manager.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 - 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-agent-manager.h"
 
diff --git a/src/settings/nm-agent-manager.h b/src/core/settings/nm-agent-manager.h
index f4e3d139..b26af4ef 100644
--- a/src/settings/nm-agent-manager.h
+++ b/src/core/settings/nm-agent-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 - 2011 Red Hat, Inc.
  */
diff --git a/src/settings/nm-secret-agent.c b/src/core/settings/nm-secret-agent.c
index 743a3467..7e7c5d7d 100644
--- a/src/settings/nm-secret-agent.c
+++ b/src/core/settings/nm-secret-agent.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 - 2011 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-secret-agent.h"
 
@@ -695,7 +695,7 @@ nm_secret_agent_new(GDBusMethodInvocation *   context,
 
     dbus_owner = nm_auth_subject_get_unix_process_dbus_sender(subject);
 
-    self = (NMSecretAgent *) g_object_new(NM_TYPE_SECRET_AGENT, NULL);
+    self = g_object_new(NM_TYPE_SECRET_AGENT, NULL);
 
     priv = NM_SECRET_AGENT_GET_PRIVATE(self);
 
diff --git a/src/settings/nm-secret-agent.h b/src/core/settings/nm-secret-agent.h
index 09f60bf4..ce591425 100644
--- a/src/settings/nm-secret-agent.h
+++ b/src/core/settings/nm-secret-agent.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 - 2011 Red Hat, Inc.
  */
diff --git a/src/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c
index d21b120e..0ff07189 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/core/settings/nm-settings-connection.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 - 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-settings-connection.h"
 
diff --git a/src/settings/nm-settings-connection.h b/src/core/settings/nm-settings-connection.h
index b1665cc5..7c61dbeb 100644
--- a/src/settings/nm-settings-connection.h
+++ b/src/core/settings/nm-settings-connection.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 - 2013 Red Hat, Inc.
@@ -56,9 +56,9 @@ typedef enum {
     NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_AGENT_SECRETS = (1u << 5),
     NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS = (1u << 6),
 
-    /* if a profile was greated as default-wired connection for a device, then
+    /* if a profile was generated as default-wired connection for a device, then
      * when the user modifies it via D-Bus, the profile should become persisted
-     * to disk and it the purpose why the profile was created should be forgotten. */
+     * to disk and the initial purpose the profile was created should be forgotten. */
     NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_DEFAULT_WIRED = (1u << 7),
 
     NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT = (1u << 8),
diff --git a/src/settings/nm-settings-plugin.c b/src/core/settings/nm-settings-plugin.c
index 79451bae..9e81caab 100644
--- a/src/settings/nm-settings-plugin.c
+++ b/src/core/settings/nm-settings-plugin.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2007 - 2018 Red Hat, Inc.
  * Copyright (C) 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-settings-plugin.h"
 
diff --git a/src/settings/nm-settings-plugin.h b/src/core/settings/nm-settings-plugin.h
index d4e8d396..a9b9b2ae 100644
--- a/src/settings/nm-settings-plugin.h
+++ b/src/core/settings/nm-settings-plugin.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2007 - 2018 Red Hat, Inc.
  * Copyright (C) 2008 Novell, Inc.
diff --git a/src/settings/nm-settings-storage.c b/src/core/settings/nm-settings-storage.c
index 7fa83e21..c56ba075 100644
--- a/src/settings/nm-settings-storage.c
+++ b/src/core/settings/nm-settings-storage.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-settings-storage.h"
 
diff --git a/src/settings/nm-settings-storage.h b/src/core/settings/nm-settings-storage.h
index 8f2bc3d2..970cfd77 100644
--- a/src/settings/nm-settings-storage.h
+++ b/src/core/settings/nm-settings-storage.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/src/settings/nm-settings-utils.c b/src/core/settings/nm-settings-utils.c
index 8e032c44..775d0396 100644
--- a/src/settings/nm-settings-utils.c
+++ b/src/core/settings/nm-settings-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-settings-utils.h"
 
diff --git a/src/settings/nm-settings-utils.h b/src/core/settings/nm-settings-utils.h
index b03c14b0..d3f50ddf 100644
--- a/src/settings/nm-settings-utils.h
+++ b/src/core/settings/nm-settings-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
diff --git a/src/settings/nm-settings.c b/src/core/settings/nm-settings.c
index 6b9d5083..3d31fb8b 100644
--- a/src/settings/nm-settings.c
+++ b/src/core/settings/nm-settings.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Søren Sandmann <sandmann@daimi.au.dk>
  * Dan Williams <dcbw@redhat.com>
@@ -7,7 +7,7 @@
  * Copyright (C) 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-settings.h"
 
@@ -22,7 +22,7 @@
 
 #include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-glib-aux/nm-keyfile-aux.h"
-#include "nm-keyfile/nm-keyfile-internal.h"
+#include "nm-keyfile-internal.h"
 #include "nm-dbus-interface.h"
 #include "nm-connection.h"
 #include "nm-setting-8021x.h"
@@ -2521,17 +2521,9 @@ nm_settings_add_connection_dbus(NMSettings *                    self,
     else
         perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM;
 
-    /* Validate the user request */
     chain = nm_auth_chain_new_subject(subject, context, pk_add_cb, self);
-    if (!chain) {
-        error = g_error_new_literal(NM_SETTINGS_ERROR,
-                                    NM_SETTINGS_ERROR_PERMISSION_DENIED,
-                                    NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
-        goto done;
-    }
 
     c_list_link_tail(&priv->auth_lst_head, nm_auth_chain_parent_lst_list(chain));
-
     nm_auth_chain_set_data(chain, "perm", (gpointer) perm, NULL);
     nm_auth_chain_set_data(chain, "connection", g_object_ref(connection), g_object_unref);
     nm_auth_chain_set_data(chain, "callback", callback, NULL);
diff --git a/src/settings/nm-settings.h b/src/core/settings/nm-settings.h
index 3740f277..09a0af57 100644
--- a/src/settings/nm-settings.h
+++ b/src/core/settings/nm-settings.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Søren Sandmann <sandmann@daimi.au.dk>
  * Dan Williams <dcbw@redhat.com>
diff --git a/src/settings/plugins/ifcfg-rh/meson.build b/src/core/settings/plugins/ifcfg-rh/meson.build
index 10b93d0a..a2f11a92 100644
--- a/src/settings/plugins/ifcfg-rh/meson.build
+++ b/src/core/settings/plugins/ifcfg-rh/meson.build
@@ -1,49 +1,43 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 install_data(
   'nm-ifcfg-rh.conf',
   install_dir: dbus_conf_dir,
 )
 
-name = 'nmdbus-ifcfg-rh'
-
 dbus_sources = gnome.gdbus_codegen(
-  name,
+  'nmdbus-ifcfg-rh',
   'nm-ifcfg-rh.xml',
   interface_prefix: 'com.redhat',
   namespace: 'NMDBus',
 )
 
 libnmdbus_ifcfg_rh = static_library(
-  name,
+  'nmdbus-ifcfg-rh',
   sources: dbus_sources,
   dependencies: glib_dep,
   c_args: introspection_extra_cflags,
 )
 
-core_sources = files(
-  'nms-ifcfg-rh-reader.c',
-  'nms-ifcfg-rh-utils.c',
-  'nms-ifcfg-rh-writer.c',
-  'shvar.c',
-)
-
 libnms_ifcfg_rh_core = static_library(
   'nms-ifcfg-rh-core',
-  sources: core_sources,
-  dependencies: daemon_nm_default_dep,
+  sources: files(
+    'nms-ifcfg-rh-reader.c',
+    'nms-ifcfg-rh-utils.c',
+    'nms-ifcfg-rh-writer.c',
+    'shvar.c',
+  ),
+  dependencies: core_default_dep,
   c_args: daemon_c_flags,
 )
 
-sources = [dbus_sources] + core_sources + files(
-  'nms-ifcfg-rh-storage.c',
-  'nms-ifcfg-rh-plugin.c',
-)
-
 libnm_settings_plugin_ifcfg_rh = shared_module(
   'nm-settings-plugin-ifcfg-rh',
-  sources: sources,
-  dependencies: daemon_nm_default_dep,
+  sources: files(
+    'nms-ifcfg-rh-storage.c',
+    'nms-ifcfg-rh-plugin.c',
+  ),
+  dependencies: core_plugin_dep,
   c_args: daemon_c_flags,
   link_with: libnms_ifcfg_rh_core,
   link_args: ldflags_linker_script_settings,
@@ -54,13 +48,11 @@ libnm_settings_plugin_ifcfg_rh = shared_module(
 
 core_plugins += libnm_settings_plugin_ifcfg_rh
 
-data = [
-  'nm-ifdown',
-  'nm-ifup',
-]
-
 install_data(
-  data,
+  [
+    'nm-ifdown',
+    'nm-ifup',
+  ],
   install_dir: nm_libexecdir,
   install_mode: 'rwxr-xr-x',
 )
diff --git a/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
index c7a1730a..c7a1730a 100644
--- a/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
+++ b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
diff --git a/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml
index 033fbfaa..033fbfaa 100644
--- a/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml
+++ b/src/core/settings/plugins/ifcfg-rh/nm-ifcfg-rh.xml
diff --git a/src/settings/plugins/ifcfg-rh/nm-ifdown b/src/core/settings/plugins/ifcfg-rh/nm-ifdown
index e7bd73ae..e7bd73ae 100644
--- a/src/settings/plugins/ifcfg-rh/nm-ifdown
+++ b/src/core/settings/plugins/ifcfg-rh/nm-ifdown
diff --git a/src/settings/plugins/ifcfg-rh/nm-ifup b/src/core/settings/plugins/ifcfg-rh/nm-ifup
index 96637a92..96637a92 100644
--- a/src/settings/plugins/ifcfg-rh/nm-ifup
+++ b/src/core/settings/plugins/ifcfg-rh/nm-ifup
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h
index b986cc9a..79c67e48 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2013 Red Hat, Inc.
  */
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
index 5288f1bc..d114db0f 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -1,11 +1,11 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Dan Williams <dcbw@redhat.com>
  * Søren Sandmann <sandmann@daimi.au.dk>
  * Copyright (C) 2007 - 2011 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-ifcfg-rh-plugin.h"
 
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h
index ff713cf3..dc1a01c8 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Dan Williams <dcbw@redhat.com>
  * Søren Sandmann <sandmann@daimi.au.dk>
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 5a021a8c..209957d9 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-ifcfg-rh-reader.h"
 
@@ -16,6 +16,7 @@
 #include <sys/ioctl.h>
 #include <unistd.h>
 #include <linux/rtnetlink.h>
+#include <linux/if_ether.h>
 
 #include "nm-glib-aux/nm-secret-utils.h"
 #include "nm-connection.h"
@@ -39,7 +40,7 @@
 #include "nm-setting-generic.h"
 #include "nm-core-internal.h"
 #include "nm-utils.h"
-#include "nm-libnm-core-intern/nm-ethtool-utils.h"
+#include "nm-base/nm-ethtool-base.h"
 
 #include "platform/nm-platform.h"
 #include "NetworkManagerUtils.h"
@@ -2547,6 +2548,42 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
 }
 
 static NMSetting *
+make_hostname_setting(shvarFile *ifcfg)
+{
+    NMSetting *setting;
+    NMTernary  from_dhcp;
+    NMTernary  from_dns_lookup;
+    NMTernary  only_from_default;
+    int        priority;
+
+    priority = svGetValueInt64(ifcfg, "HOSTNAME_PRIORITY", 10, G_MININT32, G_MAXINT32, 0);
+
+    from_dhcp         = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DHCP");
+    from_dns_lookup   = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DNS_LOOKUP");
+    only_from_default = svGetValueTernary(ifcfg, "HOSTNAME_ONLY_FROM_DEFAULT");
+
+    /* Create the setting when at least one key is not default*/
+    if (priority == 0 && from_dhcp == NM_TERNARY_DEFAULT && from_dns_lookup == NM_TERNARY_DEFAULT
+        && only_from_default == NM_TERNARY_DEFAULT)
+        return NULL;
+
+    setting = nm_setting_hostname_new();
+
+    g_object_set(setting,
+                 NM_SETTING_HOSTNAME_PRIORITY,
+                 priority,
+                 NM_SETTING_HOSTNAME_FROM_DHCP,
+                 from_dhcp,
+                 NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP,
+                 from_dns_lookup,
+                 NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT,
+                 only_from_default,
+                 NULL);
+
+    return setting;
+}
+
+static NMSetting *
 make_sriov_setting(shvarFile *ifcfg)
 {
     gs_unref_hashtable GHashTable *keys = NULL;
@@ -2951,7 +2988,8 @@ make_dcb_setting(shvarFile *ifcfg, NMSetting **out_setting, GError **error)
     gboolean                      dcb_on;
     NMSettingDcbFlags             flags = NM_SETTING_DCB_FLAG_NONE;
 
-    g_return_val_if_fail(out_setting != NULL, FALSE);
+    g_return_val_if_fail(out_setting, FALSE);
+    *out_setting = NULL;
 
     dcb_on = !!svGetValueBoolean(ifcfg, "DCB", FALSE);
     if (!dcb_on)
@@ -3971,9 +4009,10 @@ make_wpa_setting(shvarFile *      ifcfg,
     gs_unref_object NMSettingWirelessSecurity *wsec  = NULL;
     gs_free char *                             value = NULL;
     const char *                               v;
-    gboolean wpa_psk = FALSE, wpa_sae = FALSE, wpa_owe = FALSE, wpa_eap = FALSE, ieee8021x = FALSE;
-    int      i_val;
-    GError * local = NULL;
+    gboolean wpa_psk = FALSE, wpa_sae = FALSE, wpa_owe = FALSE, wpa_eap = FALSE, ieee8021x = FALSE,
+             wpa3_eap = FALSE;
+    int     i_val;
+    GError *local = NULL;
 
     wsec = NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new());
 
@@ -3982,8 +4021,9 @@ make_wpa_setting(shvarFile *      ifcfg,
     wpa_sae   = nm_streq0(v, "SAE");
     wpa_owe   = nm_streq0(v, "OWE");
     wpa_eap   = nm_streq0(v, "WPA-EAP");
+    wpa3_eap  = nm_streq0(v, "WPA-EAP-SUITE-B-192");
     ieee8021x = nm_streq0(v, "IEEE8021X");
-    if (!wpa_psk && !wpa_sae && !wpa_owe && !wpa_eap && !ieee8021x)
+    if (!wpa_psk && !wpa_sae && !wpa_owe && !wpa_eap && !wpa3_eap && !ieee8021x)
         return NULL; /* Not WPA or Dynamic WEP */
 
     /* WPS */
@@ -4046,7 +4086,7 @@ make_wpa_setting(shvarFile *      ifcfg,
             g_object_set(wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL);
         }
     } else {
-        nm_assert(wpa_eap || ieee8021x || wpa_owe);
+        nm_assert(wpa_eap || wpa3_eap || ieee8021x || wpa_owe);
 
         /* Adhoc mode is mutually exclusive with any 802.1x-based authentication */
         if (adhoc) {
@@ -5339,24 +5379,31 @@ infiniband_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **er
 static void
 handle_bond_option(NMSettingBond *s_bond, const char *key, const char *value)
 {
-    char *      sanitized = NULL, *j;
-    const char *p         = value;
+    gs_free char *sanitized = NULL;
+    const char *  p         = value;
 
     /* Remove any quotes or +/- from arp_ip_target */
-    if (!g_strcmp0(key, NM_SETTING_BOND_OPTION_ARP_IP_TARGET) && value && value[0]) {
+    if (nm_streq0(key, NM_SETTING_BOND_OPTION_ARP_IP_TARGET) && value && value[0]) {
+        char *j;
+
         if (*p == '\'' || *p == '"')
             p++;
-        j = sanitized = g_malloc0(strlen(p) + 1);
+        j = sanitized = g_malloc(strlen(p) + 1);
         while (*p) {
             if (*p != '+' && *p != '-' && *p != '\'' && *p != '"')
                 *j++ = *p;
             p++;
         }
+        *j++  = '\0';
+        value = sanitized;
     }
 
-    if (!nm_setting_bond_add_option(s_bond, key, sanitized ?: value))
-        PARSE_WARNING("invalid bonding option '%s' = %s", key, sanitized ?: value);
-    g_free(sanitized);
+    if (!_nm_setting_bond_validate_option(key, value, NULL)) {
+        PARSE_WARNING("invalid bonding option '%s' = %s", key, value);
+        return;
+    }
+
+    nm_setting_bond_add_option(s_bond, key, value);
 }
 
 static NMSetting *
@@ -6236,8 +6283,9 @@ connection_from_file_full(const char *filename,
     gs_unref_object NMConnection *connection          = NULL;
     gs_free char *                type                = NULL;
     char *                        devtype, *bootproto;
-    NMSetting *                   s_ip4, *s_ip6, *s_tc, *s_proxy, *s_port, *s_dcb = NULL, *s_user;
-    NMSetting *                   s_sriov, *s_match;
+    NMSetting *                   setting;
+    NMSetting *                   s_ip4;
+    NMSetting *                   s_ip6;
     const char *                  ifcfg_name       = NULL;
     gboolean                      has_ip4_defroute = FALSE;
     gboolean                      has_complex_routes_v4;
@@ -6525,13 +6573,13 @@ connection_from_file_full(const char *filename,
                        NM_SETTING_IP_CONFIG(s_ip4),
                        NM_SETTING_IP_CONFIG(s_ip6));
 
-    s_sriov = make_sriov_setting(main_ifcfg);
-    if (s_sriov)
-        nm_connection_add_setting(connection, s_sriov);
+    setting = make_sriov_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
 
-    s_tc = make_tc_setting(main_ifcfg);
-    if (s_tc)
-        nm_connection_add_setting(connection, s_tc);
+    setting = make_tc_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
 
     /* For backwards compatibility, if IPv4 is disabled or the
      * config fails for some reason, we read DOMAIN and put the
@@ -6539,30 +6587,34 @@ connection_from_file_full(const char *filename,
      */
     check_dns_search_domains(main_ifcfg, s_ip4, s_ip6);
 
-    s_proxy = make_proxy_setting(main_ifcfg);
-    if (s_proxy)
-        nm_connection_add_setting(connection, s_proxy);
+    setting = make_proxy_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
+
+    setting = make_hostname_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
 
-    s_user = make_user_setting(main_ifcfg);
-    if (s_user)
-        nm_connection_add_setting(connection, s_user);
+    setting = make_user_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
 
-    s_match = make_match_setting(main_ifcfg);
-    if (s_match)
-        nm_connection_add_setting(connection, s_match);
+    setting = make_match_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
 
-    s_port = make_bridge_port_setting(main_ifcfg);
-    if (s_port)
-        nm_connection_add_setting(connection, s_port);
+    setting = make_bridge_port_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
 
-    s_port = make_team_port_setting(main_ifcfg);
-    if (s_port)
-        nm_connection_add_setting(connection, s_port);
+    setting = make_team_port_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
 
-    if (!make_dcb_setting(main_ifcfg, &s_dcb, error))
+    if (!make_dcb_setting(main_ifcfg, &setting, error))
         return NULL;
-    if (s_dcb)
-        nm_connection_add_setting(connection, s_dcb);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
 
     if (!nm_connection_normalize(connection, NULL, NULL, error))
         return NULL;
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
index 7bbb6d80..c7dc2240 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Red Hat, Inc.
  */
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
index 28ec893f..3f674dea 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-ifcfg-rh-storage.h"
 
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h
index b8787def..6fb9838f 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 567dc8a8..8da5de47 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-ifcfg-rh-utils.h"
 
@@ -544,6 +544,7 @@ const char *const _nm_ethtool_ifcfg_names[] = {
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD, "l2-fwd-offload"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_LOOPBACK, "loopback"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_LRO, "lro"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_MACSEC_HW_OFFLOAD, "macsec-hw-offload"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_NTUPLE, "ntuple"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX, "rx"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RXHASH, "rxhash"),
@@ -551,12 +552,15 @@ const char *const _nm_ethtool_ifcfg_names[] = {
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_ALL, "rx-all"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_FCS, "rx-fcs"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_GRO_HW, "rx-gro-hw"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_GRO_LIST, "rx-gro-list"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_UDP_GRO_FORWARDING, "rx-udp-gro-forwarding"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD, "rx-udp_tunnel-port-offload"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER, "rx-vlan-filter"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER, "rx-vlan-stag-filter"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE, "rx-vlan-stag-hw-parse"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_SG, "sg"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD, "tls-hw-record"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TLS_HW_RX_OFFLOAD, "tls-hw-rx-offload"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD, "tls-hw-tx-offload"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TSO, "tso"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX, "tx"),
@@ -570,6 +574,7 @@ const char *const _nm_ethtool_ifcfg_names[] = {
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION, "tx-fcoe-segmentation"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION, "tx-gre-csum-segmentation"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION, "tx-gre-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GSO_LIST, "tx-gso-list"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL, "tx-gso-partial"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST, "tx-gso-robust"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION, "tx-ipxip4-segmentation"),
@@ -582,6 +587,8 @@ const char *const _nm_ethtool_ifcfg_names[] = {
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION, "tx-tcp-ecn-segmentation"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, "tx-tcp-segmentation"),
+    ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION,
+              "tx-tunnel-remcsum-segmentation"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION, "tx-udp-segmentation"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"),
     ETHT_NAME(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION, "tx-udp_tnl-segmentation"),
@@ -648,6 +655,7 @@ static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
     {"l2-fwd-offload", NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD},
     {"loopback", NM_ETHTOOL_ID_FEATURE_LOOPBACK},
     {"lro", NM_ETHTOOL_ID_FEATURE_LRO},
+    {"macsec-hw-offload", NM_ETHTOOL_ID_FEATURE_MACSEC_HW_OFFLOAD},
     {"ntuple", NM_ETHTOOL_ID_FEATURE_NTUPLE},
     {"rx", NM_ETHTOOL_ID_FEATURE_RX},
     {"rx-all", NM_ETHTOOL_ID_FEATURE_RX_ALL},
@@ -655,9 +663,11 @@ static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
     {"rx-fcs", NM_ETHTOOL_ID_FEATURE_RX_FCS},
     {"rx-gro", NM_ETHTOOL_ID_FEATURE_GRO},  // kernel-only name
     {"rx-gro-hw", NM_ETHTOOL_ID_FEATURE_RX_GRO_HW},
+    {"rx-gro-list", NM_ETHTOOL_ID_FEATURE_RX_GRO_LIST},
     {"rx-hashing", NM_ETHTOOL_ID_FEATURE_RXHASH},        // kernel-only name
     {"rx-lro", NM_ETHTOOL_ID_FEATURE_LRO},               // kernel-only name
     {"rx-ntuple-filter", NM_ETHTOOL_ID_FEATURE_NTUPLE},  // kernel-only name
+    {"rx-udp-gro-forwarding", NM_ETHTOOL_ID_FEATURE_RX_UDP_GRO_FORWARDING},
     {"rx-udp_tunnel-port-offload", NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD},
     {"rx-vlan-filter", NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER},
     {"rx-vlan-hw-parse", NM_ETHTOOL_ID_FEATURE_RXVLAN},  // kernel-only name
@@ -667,6 +677,7 @@ static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
     {"rxvlan", NM_ETHTOOL_ID_FEATURE_RXVLAN},
     {"sg", NM_ETHTOOL_ID_FEATURE_SG},
     {"tls-hw-record", NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD},
+    {"tls-hw-rx-offload", NM_ETHTOOL_ID_FEATURE_TLS_HW_RX_OFFLOAD},
     {"tls-hw-tx-offload", NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD},
     {"tso", NM_ETHTOOL_ID_FEATURE_TSO},
     {"tx", NM_ETHTOOL_ID_FEATURE_TX},
@@ -680,6 +691,7 @@ static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
     {"tx-generic-segmentation", NM_ETHTOOL_ID_FEATURE_GSO},  // kernel-only name
     {"tx-gre-csum-segmentation", NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION},
     {"tx-gre-segmentation", NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION},
+    {"tx-gso-list", NM_ETHTOOL_ID_FEATURE_TX_GSO_LIST},
     {"tx-gso-partial", NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL},
     {"tx-gso-robust", NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST},
     {"tx-ipxip4-segmentation", NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION},
@@ -692,6 +704,7 @@ static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
     {"tx-tcp-mangleid-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION},
     {"tx-tcp-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION},
     {"tx-tcp6-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION},
+    {"tx-tunnel-remcsum-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION},
     {"tx-udp-segmentation", NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION},
     {"tx-udp_tnl-csum-segmentation", NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION},
     {"tx-udp_tnl-segmentation", NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION},
@@ -873,6 +886,10 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
     _KEY_TYPE("GATEWAY_PING_TIMEOUT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("GENERATE_MAC_ADDRESS_MASK", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("GVRP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("HOSTNAME_FROM_DHCP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("HOSTNAME_FROM_DNS_LOOKUP", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("HOSTNAME_ONLY_FROM_DEFAULT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("HOSTNAME_PRIORITY", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("HWADDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("HWADDR_BLACKLIST", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("IEEE_8021X_ALTSUBJECT_MATCHES", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index 16ea7f0f..36ec9225 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2017 Red Hat, Inc.
  */
@@ -7,7 +7,7 @@
 #define _UTILS_H_
 
 #include "nm-connection.h"
-#include "nm-libnm-core-intern/nm-ethtool-utils.h"
+#include "nm-base/nm-ethtool-base.h"
 
 #include "shvar.h"
 
@@ -33,7 +33,7 @@ typedef struct {
     NMSIfcfgKeyTypeFlags key_flags;
 } NMSIfcfgKeyTypeInfo;
 
-extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[243];
+extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[247];
 
 const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info(const char *key, gssize *out_idx);
 
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index 64884672..a968fce0 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 - 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-ifcfg-rh-writer.h"
 
@@ -34,8 +34,7 @@
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
-#include "nm-meta-setting.h"
-#include "nm-libnm-core-intern/nm-ethtool-utils.h"
+#include "nm-base/nm-ethtool-base.h"
 
 #include "nms-ifcfg-rh-common.h"
 #include "nms-ifcfg-rh-reader.h"
@@ -614,6 +613,9 @@ write_wireless_security_setting(NMConnection *connection,
     } else if (!strcmp(key_mgmt, "wpa-eap")) {
         svSetValueStr(ifcfg, "KEY_MGMT", "WPA-EAP");
         wpa = TRUE;
+    } else if (!strcmp(key_mgmt, "wpa-eap-suite-b-192")) {
+        svSetValueStr(ifcfg, "KEY_MGMT", "WPA-EAP-SUITE-B-192");
+        wpa = TRUE;
     }
 
     if (auth_alg) {
@@ -1051,6 +1053,28 @@ write_infiniband_setting(NMConnection *connection, shvarFile *ifcfg, GError **er
     return TRUE;
 }
 
+static void
+write_hostname_setting(NMConnection *connection, shvarFile *ifcfg)
+{
+    NMSettingHostname *s_hostname;
+    NMTernary          t;
+
+    s_hostname = _nm_connection_get_setting(connection, NM_TYPE_SETTING_HOSTNAME);
+    if (!s_hostname)
+        return;
+
+    svSetValueInt64(ifcfg, "HOSTNAME_PRIORITY", nm_setting_hostname_get_priority(s_hostname));
+
+    t = nm_setting_hostname_get_from_dhcp(s_hostname);
+    svSetValueInt64_cond(ifcfg, "HOSTNAME_FROM_DHCP", t != NM_TERNARY_DEFAULT, t);
+
+    t = nm_setting_hostname_get_from_dns_lookup(s_hostname);
+    svSetValueInt64_cond(ifcfg, "HOSTNAME_FROM_DNS_LOOKUP", t != NM_TERNARY_DEFAULT, t);
+
+    t = nm_setting_hostname_get_only_from_default(s_hostname);
+    svSetValueInt64_cond(ifcfg, "HOSTNAME_ONLY_FROM_DEFAULT", t != NM_TERNARY_DEFAULT, t);
+}
+
 static gboolean
 write_wired_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
 {
@@ -3347,7 +3371,7 @@ do_write_construct(NMConnection *                  connection,
         return FALSE;
 
     write_match_setting(connection, ifcfg);
-
+    write_hostname_setting(connection, ifcfg);
     write_sriov_setting(connection, ifcfg);
 
     if (!write_tc_setting(connection, ifcfg, error))
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
index 1c58a346..d0d8a4cd 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2009 Red Hat, Inc.
  */
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/core/settings/plugins/ifcfg-rh/shvar.c
index 8f5be2cc..386bcac7 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/core/settings/plugins/ifcfg-rh/shvar.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 1999, 2000 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "shvar.h"
 
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/core/settings/plugins/ifcfg-rh/shvar.h
index 60324be0..6965d873 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/core/settings/plugins/ifcfg-rh/shvar.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 1999 Red Hat, Inc.
  */
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/meson.build b/src/core/settings/plugins/ifcfg-rh/tests/meson.build
new file mode 100644
index 00000000..077e4782
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/meson.build
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+exe = executable(
+  'test-ifcfg-rh',
+  'test-ifcfg-rh.c',
+  dependencies: libNetworkManagerTest_dep,
+  c_args: test_c_flags,
+  link_with: libnms_ifcfg_rh_core,
+)
+
+test(
+  'ifcfg-rh/test-ifcfg-rh',
+  test_script,
+  timeout: 90,
+  args: test_args + [exe.full_path()],
+)
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected
index d81d9187..d81d9187 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-a.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected
index 2e69c5e7..2e69c5e7 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected
index 59a6f79b..59a6f79b 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected
index a48be78a..a48be78a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_User_1.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
index b288c04a..b288c04a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected
index a8ff8df3..a8ff8df3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bridge_Component.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected
index 80e96921..80e96921 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected
index 716eedc2..716eedc2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Proxy_Basic.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected
index 0c2fa035..0c2fa035 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Routing_Rules.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected
index 2df1fbb3..2df1fbb3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Infiniband_Port.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected
index ff55cefe..ff55cefe 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Team_Port.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected
index 9c2a1ff0..9c2a1ff0 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected
index ccec2b81..ccec2b81 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_AP_Mode.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected
index ddbd986f..ddbd986f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected
index 495a24d3..495a24d3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected
index aec6918a..aec6918a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected
index 9d47163f..9d47163f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected
index 43c07ddb..43c07ddb 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected
index 21f2e2de..21f2e2de 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected
index d3db19b2..d3db19b2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected
index 32db7262..32db7262 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected
index 214f5da5..214f5da5 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected
index cd8fc96f..cd8fc96f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected
index de66dcdd..de66dcdd 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
index 9499278e..9499278e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
index 44eb777c..44eb777c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0
index 4f9d645c..4f9d645c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1
index 37c0df11..37c0df11 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2
index 0c3b6a7e..0c3b6a7e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99
index 5df3f154..5df3f154 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem0:99
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1
index f6fefc0d..f6fefc0d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1
index 21971611..21971611 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem1:1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2
index 92ed461e..92ed461e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1
index ce0f23ba..ce0f23ba 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem2:1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3
index b7bdd781..b7bdd781 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1
index 5e151875..5e151875 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-aliasem3:1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected
index 56e233cc..56e233cc 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1
index ecb36c37..ecb36c37 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected
index 87493ac2..87493ac2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected
index ead3a047..ead3a047 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected
index 7bc1ae6c..7bc1ae6c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected
index b01372af..b01372af 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type
index 8d295c76..8d295c76 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-eth-type
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main
index 4c4834d5..4c4834d5 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-main
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric
index 9989f58b..9989f58b 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-mode-numeric
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave
index c0de645d..c0de645d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib
index 5bd9a3a8..5bd9a3a8 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-slave-ib
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component
index 24b51221..24b51221 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-component
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
index 1788efe2..1788efe2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp
index 7135cd42..7135cd42 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-missing-stp
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb
index 6e6589d3..6e6589d3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans
index 2f46a082..2f46a082 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-booleans
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent
index 0c5d7b88..0c5d7b88 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-percent
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints
index 8334999b..8334999b 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-bad-uints
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities
index 5bdcb63c..5bdcb63c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-default-app-priorities
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100
index 95eb7c5b..95eb7c5b 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-pgpct-not-100
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans
index fbe1cbbc..fbe1cbbc 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-booleans
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent
index 07ba6b64..07ba6b64 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-percent
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints
index 05ae39cb..05ae39cb 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dcb-short-uints
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options
index cea2471a..cea2471a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-dns-options
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric
index cde1c28d..cde1c28d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-fabric
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn
index 20c08451..20c08451 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-fcoe-vn2vn
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft
index d3e4edca..d3e4edca 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ibft
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband
index 6c36f1de..6c36f1de 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-infiniband
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
index cae51b89..cae51b89 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal
index 63bcc840..63bcc840 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-minimal
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables
index 1b10cddb..1b10cddb 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-misc-variables
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled
index a38f1f5f..a38f1f5f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized
index 58263ece..58263ece 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-nm-controlled-unrecognized
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip
index b5028daa..b5028daa 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-noip
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no
index b9491711..b9491711 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-onboot-no
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions
index 5b413aa9..5b413aa9 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-permissions
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic
index e2cf6c21..e2cf6c21 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-read-proxy-basic
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore
index a2581db6..a2581db6 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sit-ignore
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov
index 142f56e4..142f56e4 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected
index c882c479..c882c479 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy
index 2173729d..2173729d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected
index a28c5c1c..a28c5c1c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc
index d0a3c254..d0a3c254 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected
index a67ca598..a67ca598 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1
index 209447b8..209447b8 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2
index 26e448cc..26e448cc 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid
index 41c0f25c..41c0f25c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-master-invalid
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1
index 80355c26..80355c26 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2
index 4284737a..4284737a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config
index 04df6938..04df6938 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-team-port-empty-config
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized
index 18da66c1..18da66c1 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-unrecognized
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1
index af62dcfc..af62dcfc 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-variables-corner-cases-1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1
index 5ff87891..5ff87891 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2
index 2c01be71..2c01be71 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-flags-2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface
index d8d9193f..d8d9193f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-interface
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device
index 4ba06f28..4ba06f28 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-device
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid
index 622d41ec..622d41ec 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-only-vlanid
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev
index 446c2a62..446c2a62 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-physdev
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1
index 0dc53971..0dc53971 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2
index d98a9d36..d98a9d36 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-reorder-hdr-2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces
index 4a31e475..4a31e475 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-trailing-spaces
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a
index 43286111..43286111 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch
index 8905ba0b..8905ba0b 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-a-channel-mismatch
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch
index e02e21ad..e02e21ad 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-band-bg-channel-mismatch
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap
index d9c95cc2..d9c95cc2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-dynamic-wep-leap
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden
index 08b9a730..08b9a730 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-hidden
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap
index 50f4fd31..50f4fd31 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent
index 991ba67a..991ba67a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-agent
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask
index 5bb02b5c..5bb02b5c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-leap-always-ask
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always
index 9dcb5bf8..9dcb5bf8 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-always
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default
index 3709c493..3709c493 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-default
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing
index f896eb0f..f896eb0f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-missing
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never
index aa12d749..aa12d749 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-mac-random-never
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open
index b089103d..b089103d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto
index 42ee200a..42ee200a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-auto
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex
index 32d63342..32d63342 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-bad-hex
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex
index dcb46da7..dcb46da7 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-hex
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex
index 37bb085b..37bb085b 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-hex
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted
index 0b46acd6..0b46acd6 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-long-quoted
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted
index 08496bb5..08496bb5 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-open-ssid-quoted
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe
index 354046ad..354046ad 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae
index 68afbe97..68afbe97 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-sae
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep
index e1ce20d6..e1ce20d6 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii
index 250efa13..250efa13 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-104-ascii
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii
index 250efa13..250efa13 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-40-ascii
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc
index 9d0bacfc..9d0bacfc 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-adhoc
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys
index 2bc16b6e..2bc16b6e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-agent-keys
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap
index bdea6aa3..bdea6aa3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-eap-ttls-chap
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys
index cb4da43f..cb4da43f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-no-keys
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase
index 250efa13..250efa13 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wep-passphrase
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls
new file mode 100644
index 00000000..9a74bb4d
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-suite-b-192-tls
@@ -0,0 +1,22 @@
+TYPE=Wireless
+DEVICE=eth2
+HWADDR=00:16:41:11:22:33
+BOOTPROTO=dhcp
+ONBOOT=yes
+ONBOOT=yes
+USERCTL=yes
+IPV6INIT=no
+NM_CONTROLLED=yes
+PEERDNS=yes
+ESSID=blahblah
+MODE=Managed
+RATE=auto
+KEY_MGMT=WPA-EAP-SUITE-B-192
+WPA_ALLOW_WPA=yes
+WPA_ALLOW_WPA2=yes
+IEEE_8021X_EAP_METHODS=TLS
+IEEE_8021X_IDENTITY="Bill Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_CLIENT_CERT=test1_key_and_cert.pem
+IEEE_8021X_PRIVATE_KEY=test1_key_and_cert.pem
+
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls
index 92aaeeab..92aaeeab 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-tls
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls
index 42ed1d68..42ed1d68 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-eap-ttls-tls
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk
index 4ab45ae3..4ab45ae3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2
index 038b656d..038b656d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc
index c3cadbb8..c3cadbb8 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-adhoc
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex
index 2119ba8e..2119ba8e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-hex
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted
index 2119ba8e..2119ba8e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2
index 2119ba8e..2119ba8e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk-unquoted2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches
index 70d69bf5..70d69bf5 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1X-subj-matches
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw
index a5434434..a5434434 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc
index fad92772..fad92772 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-ttls-eapgtc
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2
index 27bcbbf9..27bcbbf9 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-peap-mschapv2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent
index 052ab425..052ab425 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-agent
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always
index 5deee066..5deee066 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-always
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert
index 24397473..24397473 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on
index d0b50775..d0b50775 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-auto-negotiate-on
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip
index e683db3c..e683db3c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static
index 61f8f423..61f8f423 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ctc-static
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no
index fe8b15b2..fe8b15b2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes
index 3cf4323d..3cf4323d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-defroute-no-gatewaydev-yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp
index 5d36675a..5d36675a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip
index af254f9c..af254f9c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-plus-ip
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname
index cba380db..cba380db 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp-send-hostname
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only
index de03e044..de03e044 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcp6-only
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback
index d38e762e..d38e762e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-dhcpv6-hostname-fallback
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway
index 98d91053..98d91053 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore
index bb81399e..bb81399e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-global-gateway-ignore
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1
index db09afdb..db09afdb 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2
index 6972e279..6972e279 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3
index d9065994..d9065994 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-3
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4
index 935267f7..935267f7 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv4-manual-4
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual
index 45db0e4c..45db0e4c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-manual
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only
index 94189064..94189064 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1
index 95f8a8ed..95f8a8ed 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-ipv6-only-1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default
index 12d5b5e6..12d5b5e6 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-never-default
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n
index 2190a995..2190a995 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-obsolete-gateway-n
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static
index 4719de21..4719de21 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-qeth-static
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip
index 5098aef7..5098aef7 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-shared-plus-ip
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static
index 34acf9fe..34acf9fe 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto
index a01f6558..a01f6558 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-bootproto
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16
index 07999038..07999038 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-16
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24
index 688143cd..688143cd 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-24
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8
index 0433c62a..0433c62a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-no-prefix-8
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes
index 7faf49bd..7faf49bd 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy
index 7faf49bd..7faf49bd 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-static-routes-legacy
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt
index 861dd41f..861dd41f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-unknown-ethtool-opt
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan
index 1dfc9a43..1dfc9a43 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-wake-on-lan
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1
index 2af1daf3..2af1daf3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected
index 7f154073..7f154073 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-1.expected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2
index 850149b2..850149b2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected
index 6ec53c17..6ec53c17 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-2.expected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3
index 3d198e00..3d198e00 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected
index d2b27eb3..d2b27eb3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-3.expected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
index f834464e..f834464e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
index 040ddc9d..040ddc9d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected
index 42608576..42608576 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap
index 6936f2e0..6936f2e0 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-dynamic-wep-leap
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap
index fe78177e..fe78177e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-leap
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae
index 5a9569ed..5a9569ed 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-sae
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep
index 0bd766c2..0bd766c2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii
index f5d532b2..f5d532b2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-104-ascii
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii
index b0d14708..b0d14708 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-40-ascii
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc
index 0bd766c2..0bd766c2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-adhoc
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap
index 6936f2e0..6936f2e0 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-eap-ttls-chap
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase
index d45c0ea8..d45c0ea8 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wep-passphrase
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls
index 0c3bc1e1..0c3bc1e1 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-tls
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls
index b6256b95..b6256b95 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-eap-ttls-tls
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk
index d7813b2f..d7813b2f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2
index 995721dd..995721dd 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc
index d7813b2f..d7813b2f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-adhoc
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex
index d0576a8d..d0576a8d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-hex
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted
index 9a47196a..9a47196a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2
index 347bb85e..347bb85e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wifi-wpa-psk-unquoted2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw
index d29a4289..d29a4289 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-802-1x-password-raw
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2
index 6936f2e0..6936f2e0 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/keys-test-wired-8021x-peap-mschapv2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes
index 0d6a302f..0d6a302f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-defroute-no-gatewaydev-yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway
index 7987d105..7987d105 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore
index 7987d105..7987d105 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-global-gateway-ignore
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default
index 4347405e..4347405e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy
index c9e422e7..c9e422e7 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-static-routes-legacy
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes
index 9c05417e..9c05417e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy
index 1fef7e97..1fef7e97 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual
index 8bdf0acf..8bdf0acf 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/route6-test-wired-ipv6-manual
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem
index dec9aa1b..dec9aa1b 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test1_key_and_cert.pem
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem
index ef1be20d..ef1be20d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_ca_cert.pem
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12
index edc2af75..edc2af75 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12
Binary files differdiff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index a4e9d804..59127d01 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2011 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <stdio.h>
 #include <stdarg.h>
@@ -14,6 +14,8 @@
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <linux/if_ether.h>
+#include <linux/if_infiniband.h>
 
 #include "nm-glib-aux/nm-json-aux.h"
 #include "nm-utils.h"
@@ -35,7 +37,8 @@
 #include "nm-setting-vlan.h"
 #include "nm-setting-dcb.h"
 #include "nm-core-internal.h"
-#include "nm-libnm-core-intern/nm-ethtool-utils.h"
+#include "nm-base/nm-ethtool-base.h"
+#include "nm-base/nm-ethtool-utils-base.h"
 
 #include "NetworkManagerUtils.h"
 
@@ -46,8 +49,9 @@
 
 #include "nm-test-utils-core.h"
 
-#define TEST_IFCFG_DIR       NM_BUILD_SRCDIR "/src/settings/plugins/ifcfg-rh/tests/network-scripts"
-#define TEST_SCRATCH_DIR     NM_BUILD_BUILDDIR "/src/settings/plugins/ifcfg-rh/tests/network-scripts"
+#define TEST_IFCFG_DIR NM_BUILD_SRCDIR "/src/core/settings/plugins/ifcfg-rh/tests/network-scripts"
+#define TEST_SCRATCH_DIR \
+    NM_BUILD_BUILDDIR "/src/core/settings/plugins/ifcfg-rh/tests/network-scripts"
 #define TEST_SCRATCH_DIR_TMP TEST_SCRATCH_DIR "/tmp"
 
 #define TEST_SCRATCH_ALIAS_BASE TEST_SCRATCH_DIR "/ifcfg-alias0"
@@ -3685,6 +3689,34 @@ test_read_wifi_wpa_eap_ttls_tls(void)
 }
 
 static void
+test_read_wifi_wpa_eap_suite_b_192_tls(void)
+{
+    NMConnection *             connection;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wireless_sec;
+
+    connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-eap-suite-b-192-tls",
+                                       NULL,
+                                       TYPE_WIRELESS,
+                                       NULL);
+
+    /* ===== WIRELESS SETTING ===== */
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    /* ===== WIRELESS SECURITY SETTING ===== */
+
+    s_wireless_sec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wireless_sec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wireless_sec),
+                    ==,
+                    "wpa-eap-suite-b-192");
+
+    g_object_unref(connection);
+}
+
+static void
 test_read_wifi_dynamic_wep_leap(void)
 {
     NMConnection *             connection;
@@ -11535,6 +11567,8 @@ main(int argc, char **argv)
     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);
+    g_test_add_func(TPATH "wifi/read/wpa/eap-suite-b-192/tls",
+                    test_read_wifi_wpa_eap_suite_b_192_tls);
     g_test_add_func(TPATH "wifi/read/dynamic-wep/eap/ttls/chap", test_read_wifi_wep_eap_ttls_chap);
     g_test_add_func(TPATH "wifi/read-band-a", test_read_wifi_band_a);
     g_test_add_func(TPATH "wifi/read-band-a-channel-mismatch",
diff --git a/src/settings/plugins/ifupdown/meson.build b/src/core/settings/plugins/ifupdown/meson.build
index 7e2b44fb..dd252783 100644
--- a/src/settings/plugins/ifupdown/meson.build
+++ b/src/core/settings/plugins/ifupdown/meson.build
@@ -1,21 +1,19 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-sources = files(
-  'nms-ifupdown-interface-parser.c',
-  'nms-ifupdown-parser.c',
-)
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 libnms_ifupdown_core = static_library(
   'nms-ifupdown-core',
-  sources: sources,
-  dependencies: daemon_nm_default_dep,
+  sources: files(
+    'nms-ifupdown-interface-parser.c',
+    'nms-ifupdown-parser.c',
+  ),
+  dependencies: core_default_dep,
   c_args: daemon_c_flags,
 )
 
 libnm_settings_plugin_ifupdown = shared_module(
   'nm-settings-plugin-ifupdown',
   sources: 'nms-ifupdown-plugin.c',
-  dependencies: daemon_nm_default_dep,
+  dependencies: core_plugin_dep,
   c_args: daemon_c_flags,
   link_with: libnms_ifupdown_core,
   link_args: ldflags_linker_script_settings,
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
index 219490b8..a8a9c40a 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Tom Parker <palfrey@tevp.net>
  * Copyright (C) 2004 Tom Parker
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-ifupdown-interface-parser.h"
 
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
index d8256a5b..dd0e15fb 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Tom Parker <palfrey@tevp.net>
  * Copyright (C) 2004 Tom Parker
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c
index 0e01d6c5..a4974a96 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Alexander Sack <asac@ubuntu.com>
  * Copyright (C) 2008 Canonical Ltd.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-ifupdown-parser.h"
 
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.h b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h
index c5fc2874..ced98dba 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.h
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Alexander Sack <asac@ubuntu.com>
  * Copyright (C) 2008 Canonical Ltd.
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index b6ba423c..34523f8d 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -1,11 +1,11 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Alexander Sack <asac@ubuntu.com>
  * Copyright (C) 2007, 2008 Canonical Ltd.
  * Copyright (C) 2009 - 2011 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-ifupdown-plugin.h"
 
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.h b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h
index a3b73ea1..edf1b317 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.h
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Alexander Sack <asac@ubuntu.com>
  * Copyright (C) 2008 Canonical Ltd.
diff --git a/src/core/settings/plugins/ifupdown/tests/meson.build b/src/core/settings/plugins/ifupdown/tests/meson.build
new file mode 100644
index 00000000..882287cb
--- /dev/null
+++ b/src/core/settings/plugins/ifupdown/tests/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+exe = executable(
+  'test-ifupdown',
+  'test-ifupdown.c',
+  dependencies: libNetworkManagerTest_dep,
+  c_args: test_c_flags,
+  link_with: libnms_ifupdown_core,
+)
+
+test(
+  'ifupdown/test-ifupdown',
+  test_script,
+  args: test_args + [exe.full_path()],
+)
diff --git a/src/settings/plugins/ifupdown/tests/test-ifupdown.c b/src/core/settings/plugins/ifupdown/tests/test-ifupdown.c
index d2d8558e..6e3eb0e7 100644
--- a/src/settings/plugins/ifupdown/tests/test-ifupdown.c
+++ b/src/core/settings/plugins/ifupdown/tests/test-ifupdown.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-core-internal.h"
 
@@ -12,7 +12,7 @@
 
 #include "nm-test-utils-core.h"
 
-#define TEST_DIR NM_BUILD_SRCDIR "/src/settings/plugins/ifupdown/tests"
+#define TEST_DIR NM_BUILD_SRCDIR "/src/core/settings/plugins/ifupdown/tests"
 
 /*****************************************************************************/
 
diff --git a/src/settings/plugins/ifupdown/tests/test1 b/src/core/settings/plugins/ifupdown/tests/test1
index 74c23b45..74c23b45 100644
--- a/src/settings/plugins/ifupdown/tests/test1
+++ b/src/core/settings/plugins/ifupdown/tests/test1
diff --git a/src/settings/plugins/ifupdown/tests/test11 b/src/core/settings/plugins/ifupdown/tests/test11
index 89561dd7..89561dd7 100644
--- a/src/settings/plugins/ifupdown/tests/test11
+++ b/src/core/settings/plugins/ifupdown/tests/test11
diff --git a/src/settings/plugins/ifupdown/tests/test12 b/src/core/settings/plugins/ifupdown/tests/test12
index 6096842e..6096842e 100644
--- a/src/settings/plugins/ifupdown/tests/test12
+++ b/src/core/settings/plugins/ifupdown/tests/test12
diff --git a/src/settings/plugins/ifupdown/tests/test13 b/src/core/settings/plugins/ifupdown/tests/test13
index c001f7ef..c001f7ef 100644
--- a/src/settings/plugins/ifupdown/tests/test13
+++ b/src/core/settings/plugins/ifupdown/tests/test13
diff --git a/src/settings/plugins/ifupdown/tests/test14 b/src/core/settings/plugins/ifupdown/tests/test14
index 4a153ab3..4a153ab3 100644
--- a/src/settings/plugins/ifupdown/tests/test14
+++ b/src/core/settings/plugins/ifupdown/tests/test14
diff --git a/src/settings/plugins/ifupdown/tests/test15 b/src/core/settings/plugins/ifupdown/tests/test15
index c3ceca24..c3ceca24 100644
--- a/src/settings/plugins/ifupdown/tests/test15
+++ b/src/core/settings/plugins/ifupdown/tests/test15
diff --git a/src/settings/plugins/ifupdown/tests/test16 b/src/core/settings/plugins/ifupdown/tests/test16
index f4f74fb5..f4f74fb5 100644
--- a/src/settings/plugins/ifupdown/tests/test16
+++ b/src/core/settings/plugins/ifupdown/tests/test16
diff --git a/src/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4 b/src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4
index 9e5243a7..9e5243a7 100644
--- a/src/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4
+++ b/src/core/settings/plugins/ifupdown/tests/test17-wired-static-verify-ip4
diff --git a/src/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6 b/src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6
index 29546a61..29546a61 100644
--- a/src/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6
+++ b/src/core/settings/plugins/ifupdown/tests/test18-wired-static-verify-ip6
diff --git a/src/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen b/src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen
index 441106da..441106da 100644
--- a/src/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen
+++ b/src/core/settings/plugins/ifupdown/tests/test19-wired-static-verify-ip4-plen
diff --git a/src/settings/plugins/ifupdown/tests/test2 b/src/core/settings/plugins/ifupdown/tests/test2
index 7462b352..7462b352 100644
--- a/src/settings/plugins/ifupdown/tests/test2
+++ b/src/core/settings/plugins/ifupdown/tests/test2
diff --git a/src/settings/plugins/ifupdown/tests/test20-source-stanza b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza
index 5cfe1730..5cfe1730 100644
--- a/src/settings/plugins/ifupdown/tests/test20-source-stanza
+++ b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza
diff --git a/src/settings/plugins/ifupdown/tests/test20-source-stanza.eth0 b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth0
index 81922cea..81922cea 100644
--- a/src/settings/plugins/ifupdown/tests/test20-source-stanza.eth0
+++ b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth0
diff --git a/src/settings/plugins/ifupdown/tests/test20-source-stanza.eth1 b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth1
index b8a783f5..b8a783f5 100644
--- a/src/settings/plugins/ifupdown/tests/test20-source-stanza.eth1
+++ b/src/core/settings/plugins/ifupdown/tests/test20-source-stanza.eth1
diff --git a/src/settings/plugins/ifupdown/tests/test21-source-dir-stanza b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza
index d0604ddc..d0604ddc 100644
--- a/src/settings/plugins/ifupdown/tests/test21-source-dir-stanza
+++ b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza
diff --git a/src/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0 b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0
index 81922cea..81922cea 100644
--- a/src/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0
+++ b/src/core/settings/plugins/ifupdown/tests/test21-source-dir-stanza.d/test21-source-dir-stanza.eth0
diff --git a/src/settings/plugins/ifupdown/tests/test22-duplicate-stanzas b/src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas
index c13c2e7e..c13c2e7e 100644
--- a/src/settings/plugins/ifupdown/tests/test22-duplicate-stanzas
+++ b/src/core/settings/plugins/ifupdown/tests/test22-duplicate-stanzas
diff --git a/src/settings/plugins/ifupdown/tests/test3 b/src/core/settings/plugins/ifupdown/tests/test3
index f6293bbd..f6293bbd 100644
--- a/src/settings/plugins/ifupdown/tests/test3
+++ b/src/core/settings/plugins/ifupdown/tests/test3
diff --git a/src/settings/plugins/ifupdown/tests/test4 b/src/core/settings/plugins/ifupdown/tests/test4
index 46a40bc9..46a40bc9 100644
--- a/src/settings/plugins/ifupdown/tests/test4
+++ b/src/core/settings/plugins/ifupdown/tests/test4
diff --git a/src/settings/plugins/ifupdown/tests/test5 b/src/core/settings/plugins/ifupdown/tests/test5
index b69fc42b..b69fc42b 100644
--- a/src/settings/plugins/ifupdown/tests/test5
+++ b/src/core/settings/plugins/ifupdown/tests/test5
diff --git a/src/settings/plugins/ifupdown/tests/test6 b/src/core/settings/plugins/ifupdown/tests/test6
index 50ac69bd..50ac69bd 100644
--- a/src/settings/plugins/ifupdown/tests/test6
+++ b/src/core/settings/plugins/ifupdown/tests/test6
diff --git a/src/settings/plugins/ifupdown/tests/test7 b/src/core/settings/plugins/ifupdown/tests/test7
index 03cb131a..03cb131a 100644
--- a/src/settings/plugins/ifupdown/tests/test7
+++ b/src/core/settings/plugins/ifupdown/tests/test7
diff --git a/src/settings/plugins/ifupdown/tests/test8 b/src/core/settings/plugins/ifupdown/tests/test8
index 311f7e15..311f7e15 100644
--- a/src/settings/plugins/ifupdown/tests/test8
+++ b/src/core/settings/plugins/ifupdown/tests/test8
diff --git a/src/settings/plugins/ifupdown/tests/test9 b/src/core/settings/plugins/ifupdown/tests/test9
index 7d94563a..7d94563a 100644
--- a/src/settings/plugins/ifupdown/tests/test9
+++ b/src/core/settings/plugins/ifupdown/tests/test9
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
index 48cb8caa..789bfc1a 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-keyfile-plugin.h"
 
@@ -23,7 +23,7 @@
 #include "nm-utils.h"
 #include "nm-config.h"
 #include "nm-core-internal.h"
-#include "nm-keyfile/nm-keyfile-internal.h"
+#include "nm-keyfile-internal.h"
 
 #include "systemd/nm-sd-utils-shared.h"
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.h b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.h
index b52fe9db..5d9f2da3 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.h
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 - 2011 Red Hat, Inc.
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/core/settings/plugins/keyfile/nms-keyfile-reader.c
index 669f22fd..70b047f3 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -1,15 +1,15 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-keyfile-reader.h"
 
 #include <sys/stat.h>
 
-#include "nm-keyfile/nm-keyfile-internal.h"
+#include "nm-keyfile-internal.h"
 
 #include "NetworkManagerUtils.h"
 #include "nms-keyfile-utils.h"
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.h b/src/core/settings/plugins/keyfile/nms-keyfile-reader.h
index e7853cc6..247457d8 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.h
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-reader.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 Red Hat, Inc.
diff --git a/src/settings/plugins/keyfile/nms-keyfile-storage.c b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
index 9cd62780..89be817a 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-storage.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-keyfile-storage.h"
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-storage.h b/src/core/settings/plugins/keyfile/nms-keyfile-storage.h
index 90ff6f35..008070df 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-storage.h
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-storage.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.c b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c
index 851e7815..f3dffdae 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 - 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-keyfile-utils.h"
 
@@ -11,7 +11,7 @@
 #include <sys/stat.h>
 
 #include "nm-glib-aux/nm-io-utils.h"
-#include "nm-keyfile/nm-keyfile-internal.h"
+#include "nm-keyfile-internal.h"
 #include "nm-utils.h"
 #include "nm-setting-wired.h"
 #include "nm-setting-wireless.h"
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.h b/src/core/settings/plugins/keyfile/nms-keyfile-utils.h
index 4b5b4bb5..419a17ed 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.h
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 - 2018 Red Hat, Inc.
  */
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
index 4b13cef4..0e2c94b5 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 - 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nms-keyfile-writer.h"
 
@@ -12,7 +12,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
-#include "nm-keyfile/nm-keyfile-internal.h"
+#include "nm-keyfile-internal.h"
 
 #include "nms-keyfile-utils.h"
 #include "nms-keyfile-reader.h"
@@ -323,12 +323,20 @@ _internal_write_connection(NMConnection *                  connection,
     }
 
     if (!path) {
+        gs_free char *ss = NULL;
+
         /* 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);
+                    ss = ({
+                        gs_free char *filename_escaped = NULL;
+
+                        filename_escaped = nm_keyfile_utils_create_filename(id, with_extension);
+                        g_build_filename(keyfile_dir, filename_escaped, NULL);
+                    }));
+
         return FALSE;
     }
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.h b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
index 07de6569..dffd3b04 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.h
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 - 2011 Red Hat, Inc.
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT
index 162bf72e..162bf72e 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain
index 11e1ec6f..11e1ec6f 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component
index f5463ed0..f5463ed0 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main
index fc9fd8f8..fc9fd8f8 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property
index 52b395b3..52b395b3 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property
index 3a46611d..3a46611d 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection
index 3d58fee0..3d58fee0 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection
index 3a984b77..3a984b77 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID
index 2bacb725..2bacb725 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2
index 20240251..20240251 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID
index 6d2bc0fa..6d2bc0fa 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format
index b2bf9155..b2bf9155 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format
index 9427b16c..9427b16c 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID
index 138380d0..138380d0 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags
index 330adda0..330adda0 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting
index 95d7b9a9..95d7b9a9 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name
index f27cd4a3..f27cd4a3 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names
index d9ac5864..d9ac5864 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID
index 4a3b56d2..4a3b56d2 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config
index 14280eef..14280eef 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
index f9ccc003..f9ccc003 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6
index a42dd5d2..a42dd5d2 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case
index 29aef494..29aef494 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob
index 62e6ae31..62e6ae31 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New
index 4cd86686..4cd86686 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old
index d3da598c..d3da598c 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing
index 2b39538e..2b39538e 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection
index b1949d0d..b1949d0d 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection
index 16d4b45e..16d4b45e 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1
index cac135ad..cac135ad 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2
index bbf2d8d6..bbf2d8d6 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1
index d3122d53..d3122d53 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2
index eb1cdace..eb1cdace 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3
index 7419e97d..7419e97d 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4
index 626b6f68..626b6f68 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem b/src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem
index ef1be20d..ef1be20d 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem
diff --git a/src/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem b/src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem
index dec9aa1b..dec9aa1b 100644
--- a/src/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem
diff --git a/src/core/settings/plugins/keyfile/tests/meson.build b/src/core/settings/plugins/keyfile/tests/meson.build
new file mode 100644
index 00000000..3c89bcf9
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+exe = executable(
+  'test-keyfile-settings',
+  'test-keyfile-settings.c',
+  dependencies: libNetworkManagerTest_dep,
+  c_args: test_c_flags,
+)
+
+test(
+  'test-keyfile-settings',
+  test_script,
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
+)
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
index 4e8d89b1..348b0ed6 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
+++ b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <stdio.h>
 #include <stdarg.h>
@@ -12,6 +12,8 @@
 #include <arpa/inet.h>
 #include <sys/socket.h>
 #include <linux/pkt_sched.h>
+#include <linux/if_ether.h>
+#include <linux/if_infiniband.h>
 
 #include "nm-core-internal.h"
 
@@ -21,8 +23,8 @@
 
 #include "nm-test-utils-core.h"
 
-#define TEST_KEYFILES_DIR NM_BUILD_SRCDIR "/src/settings/plugins/keyfile/tests/keyfiles"
-#define TEST_SCRATCH_DIR  NM_BUILD_BUILDDIR "/src/settings/plugins/keyfile/tests/keyfiles"
+#define TEST_KEYFILES_DIR NM_BUILD_SRCDIR "/src/core/settings/plugins/keyfile/tests/keyfiles"
+#define TEST_SCRATCH_DIR  NM_BUILD_BUILDDIR "/src/core/settings/plugins/keyfile/tests/keyfiles"
 
 /*****************************************************************************/
 
diff --git a/src/settings/plugins/meson.build b/src/core/settings/plugins/meson.build
index cde0844b..d4338a30 100644
--- a/src/settings/plugins/meson.build
+++ b/src/core/settings/plugins/meson.build
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 if enable_ifcfg_rh
   subdir('ifcfg-rh')
diff --git a/src/supplicant/nm-supplicant-config.c b/src/core/supplicant/nm-supplicant-config.c
index 9a23f185..eab494b0 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/core/supplicant/nm-supplicant-config.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2006 - 2012 Red Hat, Inc.
  * Copyright (C) 2007 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-supplicant-config.h"
 
@@ -100,7 +100,7 @@ nm_supplicant_config_add_option_with_type(NMSupplicantConfig *self,
                                           const char *        value,
                                           gint32              len,
                                           NMSupplOptType      opt_type,
-                                          const char *        hidden,
+                                          const char *        display_value,
                                           GError **           error)
 {
     NMSupplicantConfigPrivate *priv;
@@ -138,7 +138,7 @@ nm_supplicant_config_add_option_with_type(NMSupplicantConfig *self,
                         NM_SUPPLICANT_ERROR_CONFIG,
                         "key '%s' and/or value %s invalid",
                         key,
-                        hidden ?: str);
+                        display_value ?: str);
             return FALSE;
         }
     }
@@ -165,7 +165,10 @@ nm_supplicant_config_add_option_with_type(NMSupplicantConfig *self,
         char buf[255];
         memset(&buf[0], 0, sizeof(buf));
         memcpy(&buf[0], opt->value, opt->len > 254 ? 254 : opt->len);
-        nm_log_info(LOGD_SUPPLICANT, "Config: added '%s' value '%s'", key, hidden ?: &buf[0]);
+        nm_log_info(LOGD_SUPPLICANT,
+                    "Config: added '%s' value '%s'",
+                    key,
+                    display_value ?: &buf[0]);
     }
 
     g_hash_table_insert(priv->config, g_strdup(key), opt);
@@ -178,7 +181,7 @@ nm_supplicant_config_add_option(NMSupplicantConfig *self,
                                 const char *        key,
                                 const char *        value,
                                 gint32              len,
-                                const char *        hidden,
+                                const char *        display_value,
                                 GError **           error)
 {
     return nm_supplicant_config_add_option_with_type(self,
@@ -186,7 +189,7 @@ nm_supplicant_config_add_option(NMSupplicantConfig *self,
                                                      value,
                                                      len,
                                                      NM_SUPPL_OPT_TYPE_INVALID,
-                                                     hidden,
+                                                     display_value,
                                                      error);
 }
 
@@ -455,7 +458,7 @@ nm_supplicant_config_add_setting_macsec(NMSupplicantConfig *self,
                                              "mka_ckn",
                                              (char *) buffer_ckn,
                                              sizeof(buffer_ckn),
-                                             NULL,
+                                             value,
                                              error))
             return FALSE;
     }
@@ -621,7 +624,8 @@ nm_supplicant_config_add_bgscan(NMSupplicantConfig *self, NMConnection *connecti
         || ((s_wsec = nm_connection_get_setting_wireless_security(connection))
             && NM_IN_STRSET(nm_setting_wireless_security_get_key_mgmt(s_wsec),
                             "ieee8021x",
-                            "wpa-eap")))
+                            "wpa-eap",
+                            "wpa-eap-suite-b-192")))
         bgscan = "simple:30:-65:300";
 
     return nm_supplicant_config_add_option(self, "bgscan", bgscan, -1, FALSE, error);
@@ -632,7 +636,7 @@ add_string_val(NMSupplicantConfig *self,
                const char *        field,
                const char *        name,
                gboolean            ucase,
-               const char *        hidden,
+               const char *        display_value,
                GError **           error)
 {
     if (field) {
@@ -642,7 +646,12 @@ add_string_val(NMSupplicantConfig *self,
             value = g_ascii_strup(field, -1);
             field = value;
         }
-        return nm_supplicant_config_add_option(self, name, field, strlen(field), hidden, error);
+        return nm_supplicant_config_add_option(self,
+                                               name,
+                                               field,
+                                               strlen(field),
+                                               display_value,
+                                               error);
     }
     return TRUE;
 }
@@ -655,7 +664,7 @@ add_string_val(NMSupplicantConfig *self,
                             name,                                                         \
                             separator,                                                    \
                             ucase,                                                        \
-                            hidden,                                                       \
+                            display_value,                                                \
                             error)                                                        \
     ({                                                                                    \
         typeof(*(setting)) *_setting = (setting);                                         \
@@ -684,7 +693,7 @@ add_string_val(NMSupplicantConfig *self,
                                                      (name),                              \
                                                      _str->str,                           \
                                                      -1,                                  \
-                                                     (hidden),                            \
+                                                     (display_value),                     \
                                                      (error)))                            \
                     _success = FALSE;                                                     \
             }                                                                             \
@@ -833,8 +842,13 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig *
         if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
             g_string_append(key_mgmt_conf, " ft-psk");
     } else if (nm_streq(key_mgmt, "wpa-eap")) {
-        if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF))
+        if (_get_capability(priv, NM_SUPPL_CAP_TYPE_PMF)) {
             g_string_append(key_mgmt_conf, " wpa-eap-sha256");
+
+            if (_get_capability(priv, NM_SUPPL_CAP_TYPE_SUITEB192)
+                && pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)
+                g_string_append(key_mgmt_conf, " wpa-eap-suite-b-192");
+        }
         if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
             g_string_append(key_mgmt_conf, " ft-eap");
         if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT)
@@ -863,6 +877,11 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig *
     } else if (nm_streq(key_mgmt, "sae")) {
         if (_get_capability(priv, NM_SUPPL_CAP_TYPE_FT))
             g_string_append(key_mgmt_conf, " ft-sae");
+    } else if (nm_streq(key_mgmt, "wpa-eap-suite-b-192")) {
+        pmf = NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED;
+        if (!nm_supplicant_config_add_option(self, "pairwise", "GCMP-256", -1, NULL, error)
+            || !nm_supplicant_config_add_option(self, "group", "GCMP-256", -1, NULL, error))
+            return FALSE;
     }
 
     if (!add_string_val(self, key_mgmt_conf->str, "key_mgmt", TRUE, NULL, error))
@@ -932,7 +951,7 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig *
     }
 
     /* Don't try to enable PMF on non-WPA/SAE/OWE networks */
-    if (!NM_IN_STRSET(key_mgmt, "wpa-eap", "wpa-psk", "sae", "owe"))
+    if (!NM_IN_STRSET(key_mgmt, "wpa-eap", "wpa-eap-suite-b-192", "wpa-psk", "sae", "owe"))
         pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
 
     /* Check if we actually support PMF */
@@ -1050,7 +1069,7 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig *
         }
     } else {
         /* 802.1x for Dynamic WEP and WPA-Enterprise */
-        if (NM_IN_STRSET(key_mgmt, "ieee8021x", "wpa-eap")) {
+        if (NM_IN_STRSET(key_mgmt, "ieee8021x", "wpa-eap", "wpa-eap-suite-b-192")) {
             if (!setting_8021x) {
                 g_set_error(error,
                             NM_SUPPLICANT_ERROR,
@@ -1068,7 +1087,7 @@ nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig *
                 return FALSE;
         }
 
-        if (nm_streq(key_mgmt, "wpa-eap")) {
+        if (NM_IN_STRSET(key_mgmt, "wpa-eap", "wpa-eap-suite-b-192")) {
             /* When using WPA-Enterprise, we want to use Proactive Key Caching (also
              * called Opportunistic Key Caching) to avoid full EAP exchanges when
              * roaming between access points in the same mobility group.
diff --git a/src/supplicant/nm-supplicant-config.h b/src/core/supplicant/nm-supplicant-config.h
index 21f1e0dc..b5619362 100644
--- a/src/supplicant/nm-supplicant-config.h
+++ b/src/core/supplicant/nm-supplicant-config.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2006 - 2012 Red Hat, Inc.
  * Copyright (C) 2007 - 2008 Novell, Inc.
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c
index 77254c7e..6d9c604f 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/core/supplicant/nm-supplicant-interface.c
@@ -1,14 +1,15 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2006 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-supplicant-interface.h"
 
 #include <stdio.h>
+#include <linux/if_ether.h>
 
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
@@ -280,6 +281,8 @@ security_from_vardict(GVariant *security)
             flags |= NM_802_11_AP_SEC_KEY_MGMT_SAE;
         if (g_strv_contains(array, "owe"))
             flags |= NM_802_11_AP_SEC_KEY_MGMT_OWE;
+        if (g_strv_contains(array, "wpa-eap-suite-b-192"))
+            flags |= NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192;
         g_free(array);
     }
 
@@ -457,9 +460,7 @@ _notify_maybe_scanning(NMSupplicantInterface *self)
     NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self);
     gboolean                      scanning;
 
-    scanning =
-        nm_supplicant_interface_state_is_operational(priv->state)
-        && (priv->scanning_property || priv->supp_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING);
+    scanning = nm_supplicant_interface_state_is_operational(priv->state) && priv->scanning_property;
 
     if (priv->scanning_cached == scanning)
         return;
@@ -584,7 +585,7 @@ _bss_info_properties_changed(NMSupplicantInterface *self,
 
         if (v_v) {
             if (g_variant_lookup(v_v, "Type", "&s", &v_s)) {
-                p_ap_flags = NM_802_11_AP_FLAGS_WPS;
+                f = NM_802_11_AP_FLAGS_WPS;
                 if (nm_streq(v_s, "pcb"))
                     f |= NM_802_11_AP_FLAGS_WPS_PBC;
                 else if (nm_streq(v_s, "pin"))
@@ -668,19 +669,17 @@ _bss_info_properties_changed(NMSupplicantInterface *self,
         if (arr_len != 0) {
             nm_assert(arr_len == sizeof(bss_info->bssid));
             bss_info->bssid_valid = TRUE;
-            memcpy(bss_info->bssid, arr_data, sizeof(bss_info->bssid));
+            memcpy(&bss_info->bssid, arr_data, sizeof(bss_info->bssid));
         } else if (bss_info->bssid_valid) {
             bss_info->bssid_valid = FALSE;
-            memset(bss_info->bssid, 0, sizeof(bss_info->bssid));
+            memset(&bss_info->bssid, 0, sizeof(bss_info->bssid));
         }
         g_variant_unref(v_v);
     } else {
         nm_assert(!initial || !bss_info->bssid_valid);
     }
-    nm_assert(
-        (bss_info->bssid_valid && !nm_utils_memeqzero(bss_info->bssid, sizeof(bss_info->bssid)))
-        || (!bss_info->bssid_valid
-            && nm_utils_memeqzero(bss_info->bssid, sizeof(bss_info->bssid))));
+    nm_assert((!!bss_info->bssid_valid)
+              == (!nm_utils_memeqzero(&bss_info->bssid, sizeof(bss_info->bssid))));
 
     p_max_rate_has = FALSE;
     p_max_rate     = 0;
@@ -1322,6 +1321,54 @@ nm_supplicant_interface_get_capabilities(NMSupplicantInterface *self)
     return caps;
 }
 
+static void
+set_bridge_cb(GVariant *ret, GError *error, gpointer user_data)
+{
+    NMSupplicantInterface *self;
+    NMLogLevel             level;
+    gs_free const char *   bridge = NULL;
+
+    nm_utils_user_data_unpack(user_data, &self, &bridge);
+
+    if (nm_utils_error_is_cancelled(error))
+        return;
+
+    /* The supplicant supports writing the bridge property since
+     * version 2.10. Before that version, trying to set the property
+     * results in a InvalidArgs error.  Don't log a warning unless we
+     * are trying to set a non-NULL bridge. */
+    if (!error)
+        level = LOGL_DEBUG;
+    else if (bridge == NULL && g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS)) {
+        level = LOGL_DEBUG;
+    } else
+        level = LOGL_WARN;
+
+    _NMLOG(level,
+           "set bridge %s%s%s result: %s",
+           NM_PRINT_FMT_QUOTE_STRING(bridge),
+           error ? error->message : "success");
+}
+
+void
+nm_supplicant_interface_set_bridge(NMSupplicantInterface *self, const char *bridge)
+{
+    NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self);
+
+    _LOGT("set bridge %s%s%s", NM_PRINT_FMT_QUOTE_STRING(bridge));
+
+    nm_dbus_connection_call_set(priv->dbus_connection,
+                                priv->name_owner->str,
+                                priv->object_path->str,
+                                NM_WPAS_DBUS_IFACE_INTERFACE,
+                                "BridgeIfname",
+                                g_variant_new_string(bridge ?: ""),
+                                DBUS_TIMEOUT_MSEC,
+                                priv->main_cancellable,
+                                set_bridge_cb,
+                                nm_utils_user_data_pack(self, g_strdup(bridge)));
+}
+
 void
 nm_supplicant_interface_set_global_capabilities(NMSupplicantInterface *self, NMSupplCapMask value)
 {
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/core/supplicant/nm-supplicant-interface.h
index 812ea193..a62eeb62 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/core/supplicant/nm-supplicant-interface.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2006 - 2017 Red Hat, Inc.
  * Copyright (C) 2007 - 2008 Novell, Inc.
@@ -190,4 +190,6 @@ void nm_supplicant_interface_cancel_wps(NMSupplicantInterface *self);
 
 NMSupplicantAuthState nm_supplicant_interface_get_auth_state(NMSupplicantInterface *self);
 
+void nm_supplicant_interface_set_bridge(NMSupplicantInterface *self, const char *bridge);
+
 #endif /* __NM_SUPPLICANT_INTERFACE_H__ */
diff --git a/src/supplicant/nm-supplicant-manager.c b/src/core/supplicant/nm-supplicant-manager.c
index a166f619..32554187 100644
--- a/src/supplicant/nm-supplicant-manager.c
+++ b/src/core/supplicant/nm-supplicant-manager.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2006 - 2010 Red Hat, Inc.
  * Copyright (C) 2007 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-supplicant-manager.h"
 
@@ -933,6 +933,7 @@ _dbus_get_capabilities_cb(GVariant *res, GError *error, gpointer user_data)
                     _caps_set(priv, NM_SUPPL_CAP_TYPE_AP, NM_TERNARY_FALSE);
                     _caps_set(priv, NM_SUPPL_CAP_TYPE_PMF, NM_TERNARY_FALSE);
                     _caps_set(priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_FALSE);
+                    _caps_set(priv, NM_SUPPL_CAP_TYPE_SUITEB192, NM_TERNARY_FALSE);
                     if (array) {
                         for (a = array; *a; a++) {
                             if (nm_streq(*a, "ap")) {
@@ -963,6 +964,10 @@ _dbus_get_capabilities_cb(GVariant *res, GError *error, gpointer user_data)
                                 _caps_set(priv, NM_SUPPL_CAP_TYPE_MESH, NM_TERNARY_TRUE);
                                 continue;
                             }
+                            if (nm_streq(*a, "suiteb192")) {
+                                _caps_set(priv, NM_SUPPL_CAP_TYPE_SUITEB192, NM_TERNARY_TRUE);
+                                continue;
+                            }
                         }
                     }
                 }
diff --git a/src/supplicant/nm-supplicant-manager.h b/src/core/supplicant/nm-supplicant-manager.h
index aaeb41e6..3000d064 100644
--- a/src/supplicant/nm-supplicant-manager.h
+++ b/src/core/supplicant/nm-supplicant-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2006 - 2008 Red Hat, Inc.
  * Copyright (C) 2007 - 2008 Novell, Inc.
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/core/supplicant/nm-supplicant-settings-verify.c
index c7209f5f..3f0a33e0 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/core/supplicant/nm-supplicant-settings-verify.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2006 - 2012 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-supplicant-settings-verify.h"
 
@@ -63,7 +63,7 @@ static const struct Opt opt_table[] = {
     OPT_INT("fragment_size", 1, 2000),
     OPT_KEYWORD("freq_list", NULL),
     OPT_INT("frequency", 2412, 5825),
-    OPT_KEYWORD("group", NM_MAKE_STRV("CCMP", "TKIP", "WEP104", "WEP40", )),
+    OPT_KEYWORD("group", NM_MAKE_STRV("CCMP", "TKIP", "WEP104", "WEP40", "GCMP-256", )),
     OPT_BYTES("identity", 0),
     OPT_INT("ieee80211w", 0, 2),
     OPT_INT("ignore_broadcast_ssid", 0, 2),
@@ -82,6 +82,7 @@ static const struct Opt opt_table[] = {
                              "FT-FILS-SHA384",
                              "IEEE8021X",
                              "SAE",
+                             "WPA-EAP-SUITE-B-192",
                              "FT-SAE",
                              "OWE",
                              "NONE", )),
@@ -92,7 +93,7 @@ static const struct Opt opt_table[] = {
     OPT_BYTES("mka_ckn", 65536),
     OPT_BYTES("nai", 0),
     OPT_BYTES("pac_file", 0),
-    OPT_KEYWORD("pairwise", NM_MAKE_STRV("CCMP", "TKIP", "NONE", )),
+    OPT_KEYWORD("pairwise", NM_MAKE_STRV("CCMP", "TKIP", "GCMP-256", "NONE", )),
     OPT_UTF8("password", 0),
     OPT_BYTES("pcsc", 0),
     OPT_KEYWORD("phase1",
diff --git a/src/supplicant/nm-supplicant-settings-verify.h b/src/core/supplicant/nm-supplicant-settings-verify.h
index 8a0c62c2..8ba50b67 100644
--- a/src/supplicant/nm-supplicant-settings-verify.h
+++ b/src/core/supplicant/nm-supplicant-settings-verify.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2006 - 2008 Red Hat, Inc.
  */
diff --git a/src/supplicant/nm-supplicant-types.h b/src/core/supplicant/nm-supplicant-types.h
index e35059af..adcf02db 100644
--- a/src/supplicant/nm-supplicant-types.h
+++ b/src/core/supplicant/nm-supplicant-types.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2006 - 2008 Red Hat, Inc.
  */
@@ -45,6 +45,7 @@ typedef enum {
     NM_SUPPL_CAP_TYPE_MESH,
     NM_SUPPL_CAP_TYPE_FAST,
     NM_SUPPL_CAP_TYPE_WFD,
+    NM_SUPPL_CAP_TYPE_SUITEB192,
     _NM_SUPPL_CAP_TYPE_NUM,
 } NMSupplCapType;
 
@@ -158,7 +159,7 @@ typedef struct _NMSupplicantBssInfo {
 
     guint8 signal_percent;
 
-    guint8 bssid[6 /* ETH_ALEN */];
+    NMEtherAddr bssid;
 
     NM80211ApFlags ap_flags : 5;
 
diff --git a/src/supplicant/tests/certs/test-ca-cert.pem b/src/core/supplicant/tests/certs/test-ca-cert.pem
index ef1be20d..ef1be20d 100644
--- a/src/supplicant/tests/certs/test-ca-cert.pem
+++ b/src/core/supplicant/tests/certs/test-ca-cert.pem
diff --git a/src/supplicant/tests/certs/test-cert.p12 b/src/core/supplicant/tests/certs/test-cert.p12
index ae4a6830..ae4a6830 100644
--- a/src/supplicant/tests/certs/test-cert.p12
+++ b/src/core/supplicant/tests/certs/test-cert.p12
Binary files differdiff --git a/src/supplicant/tests/meson.build b/src/core/supplicant/tests/meson.build
index 63012d10..88832a9b 100644
--- a/src/supplicant/tests/meson.build
+++ b/src/core/supplicant/tests/meson.build
@@ -1,11 +1,11 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 test_unit = 'test-supplicant-config'
 
 exe = executable(
   test_unit,
   test_unit + '.c',
-  dependencies: libnetwork_manager_test_dep,
+  dependencies: libNetworkManagerTest_dep,
   c_args: test_c_flags,
 )
 
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/core/supplicant/tests/test-supplicant-config.c
index 6edcd8da..99729c18 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/core/supplicant/tests/test-supplicant-config.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2008 - 2011 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <stdio.h>
 #include <stdarg.h>
@@ -21,7 +21,7 @@
 
 #include "nm-test-utils-core.h"
 
-#define TEST_CERT_DIR NM_BUILD_SRCDIR "/src/supplicant/tests/certs"
+#define TEST_CERT_DIR NM_BUILD_SRCDIR "/src/core/supplicant/tests/certs"
 
 /*****************************************************************************/
 
@@ -600,6 +600,50 @@ generate_wifi_eap_connection(const char *                  id,
     return connection;
 }
 
+static NMConnection *
+generate_wifi_eap_suite_b_192_connection(const char *id, GBytes *ssid, const char *bssid_str)
+{
+    NMConnection *             connection = NULL;
+    NMSettingWirelessSecurity *s_wsec;
+    NMSetting8021x *           s_8021x;
+    gboolean                   success;
+    GError *                   error = NULL;
+
+    connection = new_basic_connection(id, ssid, bssid_str);
+
+    /* Wifi Security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+    g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap-suite-b-192", NULL);
+
+    /* 802-1X setting */
+    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+    nm_setting_802_1x_add_eap_method(s_8021x, "tls");
+    nm_setting_802_1x_set_client_cert(s_8021x,
+                                      TEST_CERT_DIR "/test-cert.p12",
+                                      NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                      NULL,
+                                      NULL);
+    g_assert(nm_setting_802_1x_set_ca_cert(s_8021x,
+                                           TEST_CERT_DIR "/test-ca-cert.pem",
+                                           NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                           NULL,
+                                           NULL));
+    nm_setting_802_1x_set_private_key(s_8021x,
+                                      TEST_CERT_DIR "/test-cert.p12",
+                                      NULL,
+                                      NM_SETTING_802_1X_CK_SCHEME_PATH,
+                                      NULL,
+                                      NULL);
+
+    success = nm_connection_verify(connection, &error);
+    g_assert_no_error(error);
+    g_assert(success);
+
+    return connection;
+}
+
 static void
 test_wifi_eap_locked_bssid(void)
 {
@@ -755,6 +799,47 @@ test_wifi_eap_fils_disabled(void)
     validate_opt("wifi-eap", config_dict, "bgscan", NM_SUPPL_OPT_TYPE_BYTES, bgscan);
 }
 
+static void
+test_wifi_eap_suite_b_generation(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_variant GVariant *config_dict   = NULL;
+    const unsigned char        ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44};
+    gs_unref_bytes GBytes *ssid            = g_bytes_new(ssid_data, sizeof(ssid_data));
+    const char *           bssid_str       = "11:22:33:44:55:66";
+    guint32                mtu             = 1100;
+
+    connection = generate_wifi_eap_suite_b_192_connection("EAP-TLS Suite B 192", ssid, bssid_str);
+
+    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 'pairwise' value 'GCMP-256'");
+    NMTST_EXPECT_NM_INFO("Config: added 'group' value 'GCMP-256'");
+    NMTST_EXPECT_NM_INFO("Config: added 'key_mgmt' value 'WPA-EAP-SUITE-B-192'");
+    NMTST_EXPECT_NM_INFO("Config: added 'eap' value 'TLS'");
+    NMTST_EXPECT_NM_INFO("Config: added 'fragment_size' value '1086'");
+    NMTST_EXPECT_NM_INFO("Config: added 'ca_cert' value '*/test-ca-cert.pem'");
+    NMTST_EXPECT_NM_INFO("Config: added 'private_key' value '*/test-cert.p12'");
+    NMTST_EXPECT_NM_INFO("Config: added 'proactive_key_caching' value '1'");
+    config_dict = build_supplicant_config(connection, mtu, 0, NM_SUPPL_CAP_MASK_T_PMF_YES);
+    g_test_assert_expected_messages();
+    g_assert(config_dict);
+
+    validate_opt("wifi-eap", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(1));
+    validate_opt("wifi-eap", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid);
+    validate_opt("wifi-eap", config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str);
+    validate_opt("wifi-eap",
+                 config_dict,
+                 "key_mgmt",
+                 NM_SUPPL_OPT_TYPE_KEYWORD,
+                 "WPA-EAP-SUITE-B-192");
+    validate_opt("wifi-eap", config_dict, "eap", NM_SUPPL_OPT_TYPE_KEYWORD, "TLS");
+    validate_opt("wifi-eap", config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "GCMP-256");
+    validate_opt("wifi-eap", config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "GCMP-256");
+}
+
 /*****************************************************************************/
 
 static void
@@ -818,6 +903,7 @@ main(int argc, char **argv)
     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);
     g_test_add_func("/supplicant-config/test_suppl_cap_mask", test_suppl_cap_mask);
+    g_test_add_func("/supplicant-config/wifi-eap-suite-b-192", test_wifi_eap_suite_b_generation);
 
     return g_test_run();
 }
diff --git a/src/core/systemd/meson.build b/src/core/systemd/meson.build
new file mode 100644
index 00000000..70f6b542
--- /dev/null
+++ b/src/core/systemd/meson.build
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+incs = include_directories(
+  'sd-adapt-core',
+  'src/libsystemd-network',
+  'src/libsystemd/sd-event',
+  'src/systemd',
+)
+
+libnm_systemd_core = static_library(
+  'nm-systemd-core',
+  sources: files(
+    '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',
+    'nm-sd.c',
+    'nm-sd-utils-core.c',
+    'nm-sd-utils-dhcp.c',
+    'sd-adapt-core/nm-sd-adapt-core.c',
+  ),
+  include_directories: [
+    incs,
+    src_inc,
+  ],
+  dependencies: [
+    glib_nm_default_dep,
+    libnm_core_dep,
+    libnm_systemd_shared_dep,
+  ],
+  c_args: [
+    '-DG_LOG_DOMAIN="NetworkManager"',
+  ],
+  link_with: libc_siphash,
+)
+
+libnm_systemd_core_dep = declare_dependency(
+  include_directories: incs,
+  dependencies: [
+    glib_dep,
+    libnm_core_dep,
+    libnm_systemd_shared_dep,
+  ],
+  link_with: libnm_systemd_core,
+)
diff --git a/src/core/systemd/nm-default-systemd.h b/src/core/systemd/nm-default-systemd.h
new file mode 100644
index 00000000..1979d9c9
--- /dev/null
+++ b/src/core/systemd/nm-default-systemd.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEFAULT_SYSTEMD_H__
+#define __NM_DEFAULT_SYSTEMD_H__
+
+/*****************************************************************************/
+
+#include "nm-default-daemon.h"
+
+#undef NETWORKMANAGER_COMPILATION
+#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_SYSTEMD
+
+/*****************************************************************************/
+
+#endif /* __NM_DEFAULT_SYSTEMD_H__ */
diff --git a/src/systemd/nm-sd-utils-core.c b/src/core/systemd/nm-sd-utils-core.c
index 804c7282..af68603e 100644
--- a/src/systemd/nm-sd-utils-core.c
+++ b/src/core/systemd/nm-sd-utils-core.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/systemd/nm-default-systemd.h"
 
 #include "nm-sd-utils-core.h"
 
diff --git a/src/systemd/nm-sd-utils-core.h b/src/core/systemd/nm-sd-utils-core.h
index 440e6349..ccad0029 100644
--- a/src/systemd/nm-sd-utils-core.h
+++ b/src/core/systemd/nm-sd-utils-core.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/src/systemd/nm-sd-utils-dhcp.c b/src/core/systemd/nm-sd-utils-dhcp.c
index 01be2308..b5aa315a 100644
--- a/src/systemd/nm-sd-utils-dhcp.c
+++ b/src/core/systemd/nm-sd-utils-dhcp.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/systemd/nm-default-systemd.h"
 
 #include "nm-sd-utils-dhcp.h"
 
diff --git a/src/systemd/nm-sd-utils-dhcp.h b/src/core/systemd/nm-sd-utils-dhcp.h
index e456012b..5d126d5b 100644
--- a/src/systemd/nm-sd-utils-dhcp.h
+++ b/src/core/systemd/nm-sd-utils-dhcp.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
diff --git a/src/systemd/nm-sd.c b/src/core/systemd/nm-sd.c
index 89c12337..11f3abbd 100644
--- a/src/systemd/nm-sd.c
+++ b/src/core/systemd/nm-sd.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 - 2016 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/systemd/nm-default-systemd.h"
 
 #include "nm-sd.h"
 
diff --git a/src/systemd/nm-sd.h b/src/core/systemd/nm-sd.h
index 7cea9b68..0ea4be22 100644
--- a/src/systemd/nm-sd.h
+++ b/src/core/systemd/nm-sd.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 - 2016 Red Hat, Inc.
  */
diff --git a/src/systemd/sd-adapt-core/condition.h b/src/core/systemd/sd-adapt-core/condition.h
index d3a6812a..d3a6812a 100644
--- a/src/systemd/sd-adapt-core/condition.h
+++ b/src/core/systemd/sd-adapt-core/condition.h
diff --git a/src/systemd/sd-adapt-core/conf-parser.h b/src/core/systemd/sd-adapt-core/conf-parser.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt-core/conf-parser.h
+++ b/src/core/systemd/sd-adapt-core/conf-parser.h
diff --git a/src/systemd/sd-adapt-core/device-util.h b/src/core/systemd/sd-adapt-core/device-util.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt-core/device-util.h
+++ b/src/core/systemd/sd-adapt-core/device-util.h
diff --git a/src/systemd/sd-adapt-core/khash.h b/src/core/systemd/sd-adapt-core/khash.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt-core/khash.h
+++ b/src/core/systemd/sd-adapt-core/khash.h
diff --git a/src/systemd/sd-adapt-core/sd-daemon.h b/src/core/systemd/sd-adapt-core/network-util.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt-core/sd-daemon.h
+++ b/src/core/systemd/sd-adapt-core/network-util.h
diff --git a/src/systemd/sd-adapt-core/nm-sd-adapt-core.c b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c
index f0f85c43..9339b33d 100644
--- a/src/systemd/sd-adapt-core/nm-sd-adapt-core.c
+++ b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 - 2016 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/systemd/nm-default-systemd.h"
 
 #include "nm-sd-adapt-core.h"
 
diff --git a/src/systemd/sd-adapt-core/nm-sd-adapt-core.h b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h
index 0ec6ade1..f627d7d4 100644
--- a/src/systemd/sd-adapt-core/nm-sd-adapt-core.h
+++ b/src/core/systemd/sd-adapt-core/nm-sd-adapt-core.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014 - 2018 Red Hat, Inc.
  */
@@ -6,13 +6,16 @@
 #ifndef __NM_SD_ADAPT_CORE_H__
 #define __NM_SD_ADAPT_CORE_H__
 
-#include "nm-default.h"
+#include "src/core/systemd/nm-default-systemd.h"
 
 #include <stdbool.h>
 #include <sys/resource.h>
 #include <time.h>
 
+#undef NETWORKMANAGER_COMPILATION
 #include "systemd/sd-adapt-shared/nm-sd-adapt-shared.h"
+#undef NETWORKMANAGER_COMPILATION
+#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_SYSTEMD
 
 #ifndef HAVE_SYS_AUXV_H
     #define HAVE_SYS_AUXV_H 0
diff --git a/src/systemd/sd-adapt-core/sd-device.h b/src/core/systemd/sd-adapt-core/sd-daemon.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt-core/sd-device.h
+++ b/src/core/systemd/sd-adapt-core/sd-daemon.h
diff --git a/src/systemd/sd-adapt-core/udev-util.h b/src/core/systemd/sd-adapt-core/sd-device.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt-core/udev-util.h
+++ b/src/core/systemd/sd-adapt-core/sd-device.h
diff --git a/src/core/systemd/sd-adapt-core/udev-util.h b/src/core/systemd/sd-adapt-core/udev-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/core/systemd/sd-adapt-core/udev-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/src/libsystemd-network/arp-util.c b/src/core/systemd/src/libsystemd-network/arp-util.c
index b2ac2c9c..1c777d8a 100644
--- a/src/systemd/src/libsystemd-network/arp-util.c
+++ b/src/core/systemd/src/libsystemd-network/arp-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2014 Axis Communications AB. All rights reserved.
 ***/
diff --git a/src/systemd/src/libsystemd-network/arp-util.h b/src/core/systemd/src/libsystemd-network/arp-util.h
index 9a4427e8..2dac8cfb 100644
--- a/src/systemd/src/libsystemd-network/arp-util.h
+++ b/src/core/systemd/src/libsystemd-network/arp-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.c b/src/core/systemd/src/libsystemd-network/dhcp-identifier.c
index 026015a7..0720e381 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.c
+++ b/src/core/systemd/src/libsystemd-network/dhcp-identifier.c
@@ -1,8 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-core.h"
 
 #include <linux/if_infiniband.h>
+#include <net/ethernet.h>
 #include <net/if_arp.h>
 
 #include "sd-device.h"
@@ -10,7 +11,7 @@
 
 #include "dhcp-identifier.h"
 #include "dhcp6-protocol.h"
-#include "network-internal.h"
+#include "network-util.h"
 #include "siphash24.h"
 #include "sparse-endian.h"
 #include "stdio-util.h"
@@ -28,11 +29,10 @@ int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict) {
         if (duid_len > MAX_DUID_LEN)
                 return -EINVAL;
 
-        if (!strict) {
+        if (!strict)
                 /* Strict validation is not requested. We only ensure that the
                  * DUID is not too long. */
                 return 0;
-        }
 
         switch (duid_type) {
         case DUID_TYPE_LLT:
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.h b/src/core/systemd/src/libsystemd-network/dhcp-identifier.h
index 76abd658..e9f2ea7e 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.h
+++ b/src/core/systemd/src/libsystemd-network/dhcp-identifier.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include "sd-id128.h"
diff --git a/src/systemd/src/libsystemd-network/dhcp-internal.h b/src/core/systemd/src/libsystemd-network/dhcp-internal.h
index 7e814948..40e6b1f2 100644
--- a/src/systemd/src/libsystemd-network/dhcp-internal.h
+++ b/src/core/systemd/src/libsystemd-network/dhcp-internal.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 /***
@@ -29,10 +29,10 @@ typedef struct DHCPServerData {
 
 extern const struct hash_ops dhcp_option_hash_ops;
 
-int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link,
-                                 uint32_t xid, const uint8_t *mac_addr,
-                                 size_t mac_addr_len, uint16_t arp_type,
-                                 uint16_t port);
+int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid,
+                                 const uint8_t *mac_addr, size_t mac_addr_len,
+                                 const uint8_t *bcast_addr, size_t bcast_addr_len,
+                                 uint16_t arp_type, uint16_t port);
 int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type);
 int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link,
                                  const void *packet, size_t len);
diff --git a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h b/src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h
index 66222ead..49392d1b 100644
--- a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/core/systemd/src/libsystemd-network/dhcp-lease-internal.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/dhcp-network.c b/src/core/systemd/src/libsystemd-network/dhcp-network.c
index 1f01ece1..4477f46f 100644
--- a/src/systemd/src/libsystemd-network/dhcp-network.c
+++ b/src/core/systemd/src/libsystemd-network/dhcp-network.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
@@ -21,9 +21,9 @@
 #include "unaligned.h"
 
 static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
-                            uint32_t xid, const uint8_t *mac_addr,
-                            size_t mac_addr_len,
+                            uint32_t xid,
                             const uint8_t *bcast_addr,
+                            size_t bcast_addr_len,
                             const struct ether_addr *eth_mac,
                             uint16_t arp_type, uint8_t dhcp_hlen,
                             uint16_t port) {
@@ -106,9 +106,9 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
                 .sll_protocol = htobe16(ETH_P_IP),
                 .sll_ifindex = ifindex,
                 .sll_hatype = htobe16(arp_type),
-                .sll_halen = mac_addr_len,
+                .sll_halen = bcast_addr_len,
         };
-        memcpy(link->ll.sll_addr, bcast_addr, mac_addr_len);
+        memcpy(link->ll.sll_addr, bcast_addr, bcast_addr_len); /* We may overflow link->ll. link->ll_buffer ensures we have enough space. */
 
         r = bind(s, &link->sa, SOCKADDR_LL_LEN(link->ll));
         if (r < 0)
@@ -117,34 +117,44 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
         return TAKE_FD(s);
 }
 
-int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link,
-                                 uint32_t xid, const uint8_t *mac_addr,
-                                 size_t mac_addr_len, uint16_t arp_type,
-                                 uint16_t port) {
+int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid,
+                                 const uint8_t *mac_addr, size_t mac_addr_len,
+                                 const uint8_t *bcast_addr, size_t bcast_addr_len,
+                                 uint16_t arp_type, uint16_t port) {
         static const uint8_t eth_bcast[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
         /* Default broadcast address for IPoIB */
         static const uint8_t ib_bcast[] = {
                 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b,
                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                 0xff, 0xff, 0xff, 0xff
-          };
+        };
         struct ether_addr eth_mac = { { 0, 0, 0, 0, 0, 0 } };
-        const uint8_t *bcast_addr = NULL;
+        const uint8_t *default_bcast_addr;
+        size_t expected_bcast_addr_len;
         uint8_t dhcp_hlen = 0;
 
         if (arp_type == ARPHRD_ETHER) {
                 assert_return(mac_addr_len == ETH_ALEN, -EINVAL);
                 memcpy(&eth_mac, mac_addr, ETH_ALEN);
-                bcast_addr = eth_bcast;
                 dhcp_hlen = ETH_ALEN;
+
+                default_bcast_addr = eth_bcast;
+                expected_bcast_addr_len = ETH_ALEN;
         } else if (arp_type == ARPHRD_INFINIBAND) {
-                assert_return(mac_addr_len == INFINIBAND_ALEN, -EINVAL);
-                bcast_addr = ib_bcast;
+                default_bcast_addr = ib_bcast;
+                expected_bcast_addr_len = INFINIBAND_ALEN;
         } else
                 return -EINVAL;
 
-        return _bind_raw_socket(ifindex, link, xid, mac_addr, mac_addr_len,
-                                bcast_addr, &eth_mac, arp_type, dhcp_hlen, port);
+        if (bcast_addr && bcast_addr_len > 0)
+                assert_return(bcast_addr_len == expected_bcast_addr_len, -EINVAL);
+        else {
+                bcast_addr = default_bcast_addr;
+                bcast_addr_len = expected_bcast_addr_len;
+        }
+
+        return _bind_raw_socket(ifindex, link, xid, bcast_addr, bcast_addr_len,
+                                &eth_mac, arp_type, dhcp_hlen, port);
 }
 
 int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) {
diff --git a/src/systemd/src/libsystemd-network/dhcp-option.c b/src/core/systemd/src/libsystemd-network/dhcp-option.c
index 3a0f5155..2c15d85a 100644
--- a/src/systemd/src/libsystemd-network/dhcp-option.c
+++ b/src/core/systemd/src/libsystemd-network/dhcp-option.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
diff --git a/src/systemd/src/libsystemd-network/dhcp-packet.c b/src/core/systemd/src/libsystemd-network/dhcp-packet.c
index 2062be91..eeadd562 100644
--- a/src/systemd/src/libsystemd-network/dhcp-packet.c
+++ b/src/core/systemd/src/libsystemd-network/dhcp-packet.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
diff --git a/src/systemd/src/libsystemd-network/dhcp-protocol.h b/src/core/systemd/src/libsystemd-network/dhcp-protocol.h
index f0366324..11f4201a 100644
--- a/src/systemd/src/libsystemd-network/dhcp-protocol.h
+++ b/src/core/systemd/src/libsystemd-network/dhcp-protocol.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/dhcp6-internal.h b/src/core/systemd/src/libsystemd-network/dhcp6-internal.h
index 9ce6dcd0..24d8a314 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-internal.h
+++ b/src/core/systemd/src/libsystemd-network/dhcp6-internal.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h b/src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h
index df6c95e0..e9e2362d 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
+++ b/src/core/systemd/src/libsystemd-network/dhcp6-lease-internal.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/dhcp6-network.c b/src/core/systemd/src/libsystemd-network/dhcp6-network.c
index 449ff6c4..b6816de4 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-network.c
+++ b/src/core/systemd/src/libsystemd-network/dhcp6-network.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2014 Intel Corporation. All rights reserved.
 ***/
diff --git a/src/systemd/src/libsystemd-network/dhcp6-option.c b/src/core/systemd/src/libsystemd-network/dhcp6-option.c
index 25da477d..21e2d3f0 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-option.c
+++ b/src/core/systemd/src/libsystemd-network/dhcp6-option.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
diff --git a/src/systemd/src/libsystemd-network/dhcp6-protocol.h b/src/core/systemd/src/libsystemd-network/dhcp6-protocol.h
index f7a27028..c7003638 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/core/systemd/src/libsystemd-network/dhcp6-protocol.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 /***
diff --git a/src/systemd/src/libsystemd-network/lldp-internal.h b/src/core/systemd/src/libsystemd-network/lldp-internal.h
index 9598438d..f23695f9 100644
--- a/src/systemd/src/libsystemd-network/lldp-internal.h
+++ b/src/core/systemd/src/libsystemd-network/lldp-internal.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include "sd-event.h"
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.c b/src/core/systemd/src/libsystemd-network/lldp-neighbor.c
index 9baa8e8c..58ff0e0f 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.c
+++ b/src/core/systemd/src/libsystemd-network/lldp-neighbor.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-core.h"
 
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.h b/src/core/systemd/src/libsystemd-network/lldp-neighbor.h
index 74175edf..a5718c8c 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.h
+++ b/src/core/systemd/src/libsystemd-network/lldp-neighbor.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <inttypes.h>
diff --git a/src/systemd/src/libsystemd-network/lldp-network.c b/src/core/systemd/src/libsystemd-network/lldp-network.c
index 85d53e71..75dce58c 100644
--- a/src/systemd/src/libsystemd-network/lldp-network.c
+++ b/src/core/systemd/src/libsystemd-network/lldp-network.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-core.h"
 
diff --git a/src/systemd/src/libsystemd-network/lldp-network.h b/src/core/systemd/src/libsystemd-network/lldp-network.h
index e4ed2898..bc69b324 100644
--- a/src/systemd/src/libsystemd-network/lldp-network.h
+++ b/src/core/systemd/src/libsystemd-network/lldp-network.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include "sd-event.h"
diff --git a/src/core/systemd/src/libsystemd-network/network-internal.c b/src/core/systemd/src/libsystemd-network/network-internal.c
new file mode 100644
index 00000000..efa3dcfa
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/network-internal.c
@@ -0,0 +1,245 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-core.h"
+
+#include <arpa/inet.h>
+#include <linux/if.h>
+#include <netinet/ether.h>
+
+#include "sd-ndisc.h"
+
+#include "alloc-util.h"
+#include "dhcp-lease-internal.h"
+#include "extract-word.h"
+#include "hexdecoct.h"
+#include "log.h"
+#include "network-internal.h"
+#include "parse-util.h"
+
+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)) {
+        assert(f);
+        assert(addresses);
+
+        size_t count = 0;
+        bool _space = false;
+        if (!with_leading_space)
+                with_leading_space = &_space;
+
+        for (size_t i = 0; i < size; i++) {
+                char sbuf[INET_ADDRSTRLEN];
+
+                if (predicate && !predicate(&addresses[i]))
+                        continue;
+
+                if (*with_leading_space)
+                        fputc(' ', f);
+                fputs(inet_ntop(AF_INET, &addresses[i], sbuf, sizeof(sbuf)), f);
+                count++;
+                *with_leading_space = true;
+        }
+
+        return count;
+}
+
+int deserialize_in_addrs(struct in_addr **ret, const char *string) {
+        _cleanup_free_ struct in_addr *addresses = NULL;
+        int size = 0;
+
+        assert(ret);
+        assert(string);
+
+        for (;;) {
+                _cleanup_free_ char *word = NULL;
+                struct in_addr *new_addresses;
+                int r;
+
+                r = extract_first_word(&string, &word, NULL, 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                new_addresses = reallocarray(addresses, size + 1, sizeof(struct in_addr));
+                if (!new_addresses)
+                        return -ENOMEM;
+                else
+                        addresses = new_addresses;
+
+                r = inet_pton(AF_INET, word, &(addresses[size]));
+                if (r <= 0)
+                        continue;
+
+                size++;
+        }
+
+        *ret = size > 0 ? TAKE_PTR(addresses) : NULL;
+
+        return size;
+}
+
+void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size, bool *with_leading_space) {
+        assert(f);
+        assert(addresses);
+        assert(size);
+
+        bool _space = false;
+        if (!with_leading_space)
+                with_leading_space = &_space;
+
+        for (size_t i = 0; i < size; i++) {
+                char buffer[INET6_ADDRSTRLEN];
+
+                if (*with_leading_space)
+                        fputc(' ', f);
+                fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f);
+                *with_leading_space = true;
+        }
+}
+
+int deserialize_in6_addrs(struct in6_addr **ret, const char *string) {
+        _cleanup_free_ struct in6_addr *addresses = NULL;
+        int size = 0;
+
+        assert(ret);
+        assert(string);
+
+        for (;;) {
+                _cleanup_free_ char *word = NULL;
+                struct in6_addr *new_addresses;
+                int r;
+
+                r = extract_first_word(&string, &word, NULL, 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                new_addresses = reallocarray(addresses, size + 1, sizeof(struct in6_addr));
+                if (!new_addresses)
+                        return -ENOMEM;
+                else
+                        addresses = new_addresses;
+
+                r = inet_pton(AF_INET6, word, &(addresses[size]));
+                if (r <= 0)
+                        continue;
+
+                size++;
+        }
+
+        *ret = TAKE_PTR(addresses);
+
+        return size;
+}
+
+void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) {
+        assert(f);
+        assert(key);
+        assert(routes);
+        assert(size);
+
+        fprintf(f, "%s=", key);
+
+        for (size_t i = 0; i < size; i++) {
+                char sbuf[INET_ADDRSTRLEN];
+                struct in_addr dest, gw;
+                uint8_t length;
+
+                assert_se(sd_dhcp_route_get_destination(routes[i], &dest) >= 0);
+                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_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);
+}
+
+int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string) {
+        _cleanup_free_ struct sd_dhcp_route *routes = NULL;
+        size_t size = 0, allocated = 0;
+
+        assert(ret);
+        assert(ret_size);
+        assert(ret_allocated);
+        assert(string);
+
+         /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */
+        for (;;) {
+                _cleanup_free_ char *word = NULL;
+                char *tok, *tok_end;
+                unsigned n;
+                int r;
+
+                r = extract_first_word(&string, &word, NULL, 0);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                if (!GREEDY_REALLOC(routes, allocated, size + 1))
+                        return -ENOMEM;
+
+                tok = word;
+
+                /* get the subnet */
+                tok_end = strchr(tok, '/');
+                if (!tok_end)
+                        continue;
+                *tok_end = '\0';
+
+                r = inet_aton(tok, &routes[size].dst_addr);
+                if (r == 0)
+                        continue;
+
+                tok = tok_end + 1;
+
+                /* get the prefixlen */
+                tok_end = strchr(tok, ',');
+                if (!tok_end)
+                        continue;
+
+                *tok_end = '\0';
+
+                r = safe_atou(tok, &n);
+                if (r < 0 || n > 32)
+                        continue;
+
+                routes[size].dst_prefixlen = (uint8_t) n;
+                tok = tok_end + 1;
+
+                /* get the gateway */
+                r = inet_aton(tok, &routes[size].gw_addr);
+                if (r == 0)
+                        continue;
+
+                size++;
+        }
+
+        *ret_size = size;
+        *ret_allocated = allocated;
+        *ret = TAKE_PTR(routes);
+
+        return 0;
+}
+
+int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size) {
+        _cleanup_free_ char *hex_buf = NULL;
+
+        assert(f);
+        assert(key);
+        assert(data);
+
+        hex_buf = hexmem(data, size);
+        if (!hex_buf)
+                return -ENOMEM;
+
+        fprintf(f, "%s=%s\n", key, hex_buf);
+
+        return 0;
+}
diff --git a/src/core/systemd/src/libsystemd-network/network-internal.h b/src/core/systemd/src/libsystemd-network/network-internal.h
new file mode 100644
index 00000000..e5b853c0
--- /dev/null
+++ b/src/core/systemd/src/libsystemd-network/network-internal.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "sd-dhcp-lease.h"
+
+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,
+                         bool *with_leading_space);
+int deserialize_in6_addrs(struct in6_addr **addresses, const char *string);
+
+/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */
+struct sd_dhcp_route;
+struct sd_dhcp_lease;
+
+void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size);
+int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string);
+
+/* 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);
+
+int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file);
+int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file);
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/core/systemd/src/libsystemd-network/sd-dhcp-client.c
index 6849ed9e..27a3476f 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/core/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
@@ -16,19 +16,22 @@
 #include "sd-dhcp-client.h"
 
 #include "alloc-util.h"
-#include "async.h"
 #include "dhcp-identifier.h"
 #include "dhcp-internal.h"
 #include "dhcp-lease-internal.h"
 #include "dhcp-protocol.h"
 #include "dns-domain.h"
 #include "event-util.h"
+#include "fd-util.h"
 #include "hostname-util.h"
 #include "io-util.h"
 #include "memory-util.h"
 #include "random-util.h"
+#include "set.h"
+#include "sort-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "time-util.h"
 #include "utf8.h"
 #include "web-util.h"
 
@@ -38,6 +41,9 @@
 #define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC)
 #define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE)
 
+#define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report
+                                      * transient failure. */
+
 typedef struct sd_dhcp_client_id {
         uint8_t type;
         union {
@@ -77,13 +83,13 @@ struct sd_dhcp_client {
         union sockaddr_union link;
         sd_event_source *receive_message;
         bool request_broadcast;
-        uint8_t *req_opts;
-        size_t req_opts_allocated;
-        size_t req_opts_size;
+        Set *req_opts;
         bool anonymize;
         be32_t last_addr;
         uint8_t mac_addr[MAX_MAC_ADDR_LEN];
         size_t mac_addr_len;
+        uint8_t bcast_addr[MAX_MAC_ADDR_LEN];
+        size_t bcast_addr_len;
         uint16_t arp_type;
         sd_dhcp_client_id client_id;
         size_t client_id_len;
@@ -95,6 +101,9 @@ struct sd_dhcp_client {
         uint32_t fallback_lease_lifetime;
         uint32_t xid;
         usec_t start_time;
+        usec_t t1_time;
+        usec_t t2_time;
+        usec_t expire_time;
         uint64_t attempt;
         uint64_t max_attempts;
         OrderedHashmap *extra_options;
@@ -232,8 +241,6 @@ int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast)
 }
 
 int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) {
-        size_t i;
-
         assert_return(client, -EINVAL);
         assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY);
 
@@ -250,17 +257,7 @@ int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) {
                 break;
         }
 
-        for (i = 0; i < client->req_opts_size; i++)
-                if (client->req_opts[i] == option)
-                        return -EEXIST;
-
-        if (!GREEDY_REALLOC(client->req_opts, client->req_opts_allocated,
-                            client->req_opts_size + 1))
-                return -ENOMEM;
-
-        client->req_opts[client->req_opts_size++] = option;
-
-        return 0;
+        return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option));
 }
 
 int sd_dhcp_client_set_request_address(
@@ -291,6 +288,7 @@ int sd_dhcp_client_set_ifindex(sd_dhcp_client *client, int ifindex) {
 int sd_dhcp_client_set_mac(
                 sd_dhcp_client *client,
                 const uint8_t *addr,
+                const uint8_t *bcast_addr,
                 size_t addr_len,
                 uint16_t arp_type) {
 
@@ -311,7 +309,9 @@ int sd_dhcp_client_set_mac(
                 return -EINVAL;
 
         if (client->mac_addr_len == addr_len &&
-            memcmp(&client->mac_addr, addr, addr_len) == 0)
+            memcmp(&client->mac_addr, addr, addr_len) == 0 &&
+            (client->bcast_addr_len > 0) == !!bcast_addr &&
+            (!bcast_addr || memcmp(&client->bcast_addr, bcast_addr, addr_len) == 0))
                 return 0;
 
         if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
@@ -323,6 +323,12 @@ int sd_dhcp_client_set_mac(
         memcpy(&client->mac_addr, addr, addr_len);
         client->mac_addr_len = addr_len;
         client->arp_type = arp_type;
+        client->bcast_addr_len = 0;
+
+        if (bcast_addr) {
+                memcpy(&client->bcast_addr, bcast_addr, addr_len);
+                client->bcast_addr_len = addr_len;
+        }
 
         if (need_restart && client->state != DHCP_STATE_STOPPED) {
                 r = sd_dhcp_client_start(client);
@@ -543,7 +549,7 @@ int sd_dhcp_client_set_hostname(
 
         /* Make sure hostnames qualify as DNS and as Linux hostnames */
         if (hostname &&
-            !(hostname_is_valid(hostname, false) && dns_name_is_valid(hostname) > 0))
+            !(hostname_is_valid(hostname, 0) && dns_name_is_valid(hostname) > 0))
                 return -EINVAL;
 
         return free_and_strdup(&client->hostname, hostname);
@@ -698,7 +704,7 @@ static int client_initialize(sd_dhcp_client *client) {
 
         client->receive_message = sd_event_source_unref(client->receive_message);
 
-        client->fd = asynchronous_close(client->fd);
+        client->fd = safe_close(client->fd);
 
         (void) event_source_disable(client->timeout_resend);
         (void) event_source_disable(client->timeout_t1);
@@ -730,6 +736,41 @@ static void client_stop(sd_dhcp_client *client, int error) {
         client_initialize(client);
 }
 
+/* RFC2131 section 4.1:
+ * retransmission delays should include -1 to +1 sec of random 'fuzz'. */
+#define RFC2131_RANDOM_FUZZ \
+        ((int64_t)(random_u64() % (2 * USEC_PER_SEC)) - (int64_t)USEC_PER_SEC)
+
+/* RFC2131 section 4.1:
+ * for retransmission delays, timeout should start at 4s then double
+ * each attempt with max of 64s, with -1 to +1 sec of random 'fuzz' added.
+ * This assumes the first call will be using attempt 1. */
+static usec_t client_compute_request_timeout(usec_t now, uint64_t attempt) {
+        usec_t timeout = (UINT64_C(1) << MIN(attempt + 1, UINT64_C(6))) * USEC_PER_SEC;
+
+        return usec_sub_signed(usec_add(now, timeout), RFC2131_RANDOM_FUZZ);
+}
+
+/* RFC2131 section 4.4.5:
+ * T1 defaults to (0.5 * duration_of_lease).
+ * T2 defaults to (0.875 * duration_of_lease). */
+#define T1_DEFAULT(lifetime) ((lifetime) / 2)
+#define T2_DEFAULT(lifetime) (((lifetime) * 7) / 8)
+
+/* RFC2131 section 4.4.5:
+ * the client SHOULD wait one-half of the remaining time until T2 (in RENEWING state)
+ * and one-half of the remaining lease time (in REBINDING state), down to a minimum
+ * of 60 seconds.
+ * Note that while the default T1/T2 initial times do have random 'fuzz' applied,
+ * the RFC sec 4.4.5 does not mention adding any fuzz to retries. */
+static usec_t client_compute_reacquisition_timeout(usec_t now, usec_t expire) {
+        return now + MAX(usec_sub_unsigned(expire, now) / 2, 60 * USEC_PER_SEC);
+}
+
+static int cmp_uint8(const uint8_t *a, const uint8_t *b) {
+        return CMP(*a, *b);
+}
+
 static int client_message_init(
                 sd_dhcp_client *client,
                 DHCPPacket **ret,
@@ -840,10 +881,26 @@ static int client_message_init(
            MAY contain the Parameter Request List option. */
         /* NOTE: in case that there would be an option to do not send
          * any PRL at all, the size should be checked before sending */
-        if (client->req_opts_size > 0 && type != DHCP_RELEASE) {
+        if (!set_isempty(client->req_opts) && type != DHCP_RELEASE) {
+                _cleanup_free_ uint8_t *opts = NULL;
+                size_t n_opts, i = 0;
+                void *val;
+
+                n_opts = set_size(client->req_opts);
+                opts = new(uint8_t, n_opts);
+                if (!opts)
+                        return -ENOMEM;
+
+                SET_FOREACH(val, client->req_opts)
+                        opts[i++] = PTR_TO_UINT8(val);
+                assert(i == n_opts);
+
+                /* For anonymizing the request, let's sort the options. */
+                typesafe_qsort(opts, n_opts, cmp_uint8);
+
                 r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
                                        SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
-                                       client->req_opts_size, client->req_opts);
+                                       n_opts, opts);
                 if (r < 0)
                         return r;
         }
@@ -1173,9 +1230,8 @@ static int client_timeout_resend(
 
         sd_dhcp_client *client = userdata;
         DHCP_CLIENT_DONT_DESTROY(client);
-        usec_t next_timeout = 0;
+        usec_t next_timeout;
         uint64_t time_now;
-        uint32_t time_left;
         int r;
 
         assert(s);
@@ -1189,22 +1245,11 @@ static int client_timeout_resend(
         switch (client->state) {
 
         case DHCP_STATE_RENEWING:
-
-                time_left = (client->lease->t2 - client->lease->t1) / 2;
-                if (time_left < 60)
-                        time_left = 60;
-
-                next_timeout = time_now + time_left * USEC_PER_SEC;
-
+                next_timeout = client_compute_reacquisition_timeout(time_now, client->t2_time);
                 break;
 
         case DHCP_STATE_REBINDING:
-
-                time_left = (client->lease->lifetime - client->lease->t2) / 2;
-                if (time_left < 60)
-                        time_left = 60;
-
-                next_timeout = time_now + time_left * USEC_PER_SEC;
+                next_timeout = client_compute_reacquisition_timeout(time_now, client->expire_time);
                 break;
 
         case DHCP_STATE_REBOOTING:
@@ -1216,32 +1261,29 @@ static int client_timeout_resend(
                 r = client_start(client);
                 if (r < 0)
                         goto error;
-                else {
-                        log_dhcp_client(client, "REBOOTED");
-                        return 0;
-                }
+
+                log_dhcp_client(client, "REBOOTED");
+                return 0;
 
         case DHCP_STATE_INIT:
         case DHCP_STATE_INIT_REBOOT:
         case DHCP_STATE_SELECTING:
         case DHCP_STATE_REQUESTING:
         case DHCP_STATE_BOUND:
-
-                if (client->attempt < client->max_attempts)
-                        client->attempt++;
-                else
+                if (client->attempt >= client->max_attempts)
                         goto error;
 
-                next_timeout = time_now + ((UINT64_C(1) << MIN(client->attempt, (uint64_t) 6)) - 1) * USEC_PER_SEC;
-
+                client->attempt++;
+                next_timeout = client_compute_request_timeout(time_now, client->attempt);
                 break;
 
         case DHCP_STATE_STOPPED:
                 r = -EINVAL;
                 goto error;
-        }
 
-        next_timeout += (random_u32() & 0x1fffff);
+        default:
+                assert_not_reached("Unhandled choice");
+        }
 
         r = event_reset_time(client->event, &client->timeout_resend,
                              clock_boottime_or_monotonic(),
@@ -1281,12 +1323,10 @@ static int client_timeout_resend(
                         client->state = DHCP_STATE_REBOOTING;
 
                 client->request_sent = time_now;
-
                 break;
 
         case DHCP_STATE_REBOOTING:
         case DHCP_STATE_BOUND:
-
                 break;
 
         case DHCP_STATE_STOPPED:
@@ -1294,6 +1334,9 @@ static int client_timeout_resend(
                 goto error;
         }
 
+        if (client->attempt >= TRANSIENT_FAILURE_ATTEMPTS)
+                client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE);
+
         return 0;
 
 error:
@@ -1378,9 +1421,10 @@ static int client_start_delayed(sd_dhcp_client *client) {
 
         client->xid = random_u32();
 
-        r = dhcp_network_bind_raw_socket(client->ifindex, &client->link,
-                                         client->xid, client->mac_addr,
-                                         client->mac_addr_len, client->arp_type, client->port);
+        r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
+                                         client->mac_addr, client->mac_addr_len,
+                                         client->bcast_addr, client->bcast_addr_len,
+                                         client->arp_type, client->port);
         if (r < 0) {
                 client_stop(client, r);
                 return r;
@@ -1423,15 +1467,15 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata)
         assert(client);
 
         client->receive_message = sd_event_source_unref(client->receive_message);
-        client->fd = asynchronous_close(client->fd);
+        client->fd = safe_close(client->fd);
 
         client->state = DHCP_STATE_REBINDING;
         client->attempt = 0;
 
-        r = dhcp_network_bind_raw_socket(client->ifindex, &client->link,
-                                         client->xid, client->mac_addr,
-                                         client->mac_addr_len, client->arp_type,
-                                         client->port);
+        r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
+                                         client->mac_addr, client->mac_addr_len,
+                                         client->bcast_addr, client->bcast_addr_len,
+                                         client->arp_type, client->port);
         if (r < 0) {
                 client_stop(client, r);
                 return 0;
@@ -1606,25 +1650,8 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t le
         return r;
 }
 
-static uint64_t client_compute_timeout(sd_dhcp_client *client, uint32_t lifetime, double factor) {
-        assert(client);
-        assert(client->request_sent);
-        assert(lifetime > 0);
-
-        if (lifetime > 3)
-                lifetime -= 3;
-        else
-                lifetime = 0;
-
-        return client->request_sent + (lifetime * USEC_PER_SEC * factor) +
-                + (random_u32() & 0x1fffff);
-}
-
 static int client_set_lease_timeouts(sd_dhcp_client *client) {
         usec_t time_now;
-        uint64_t lifetime_timeout;
-        uint64_t t2_timeout;
-        uint64_t t1_timeout;
         char time_string[FORMAT_TIMESPAN_MAX];
         int r;
 
@@ -1647,93 +1674,76 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
                 return r;
         assert(client->request_sent <= time_now);
 
-        /* convert the various timeouts from relative (secs) to absolute (usecs) */
-        lifetime_timeout = client_compute_timeout(client, client->lease->lifetime, 1);
-        if (client->lease->t1 > 0 && client->lease->t2 > 0) {
-                /* both T1 and T2 are given */
-                if (client->lease->t1 < client->lease->t2 &&
-                    client->lease->t2 < client->lease->lifetime) {
-                        /* they are both valid */
-                        t2_timeout = client_compute_timeout(client, client->lease->t2, 1);
-                        t1_timeout = client_compute_timeout(client, client->lease->t1, 1);
-                } else {
-                        /* discard both */
-                        t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0);
-                        client->lease->t2 = (client->lease->lifetime * 7) / 8;
-                        t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5);
-                        client->lease->t1 = client->lease->lifetime / 2;
-                }
-        } else if (client->lease->t2 > 0 && client->lease->t2 < client->lease->lifetime) {
-                /* only T2 is given, and it is valid */
-                t2_timeout = client_compute_timeout(client, client->lease->t2, 1);
-                t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5);
-                client->lease->t1 = client->lease->lifetime / 2;
-                if (t2_timeout <= t1_timeout) {
-                        /* the computed T1 would be invalid, so discard T2 */
-                        t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0);
-                        client->lease->t2 = (client->lease->lifetime * 7) / 8;
-                }
-        } else if (client->lease->t1 > 0 && client->lease->t1 < client->lease->lifetime) {
-                /* only T1 is given, and it is valid */
-                t1_timeout = client_compute_timeout(client, client->lease->t1, 1);
-                t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0);
-                client->lease->t2 = (client->lease->lifetime * 7) / 8;
-                if (t2_timeout <= t1_timeout) {
-                        /* the computed T2 would be invalid, so discard T1 */
-                        t2_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5);
-                        client->lease->t2 = client->lease->lifetime / 2;
-                }
-        } else {
-                /* fall back to the default timeouts */
-                t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5);
-                client->lease->t1 = client->lease->lifetime / 2;
-                t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0);
-                client->lease->t2 = (client->lease->lifetime * 7) / 8;
-        }
+        /* verify that 0 < t2 < lifetime */
+        if (client->lease->t2 == 0 || client->lease->t2 >= client->lease->lifetime)
+                client->lease->t2 = T2_DEFAULT(client->lease->lifetime);
+        /* verify that 0 < t1 < lifetime */
+        if (client->lease->t1 == 0 || client->lease->t1 >= client->lease->t2)
+                client->lease->t1 = T1_DEFAULT(client->lease->lifetime);
+        /* now, if t1 >= t2, t1 *must* be T1_DEFAULT, since the previous check
+         * could not evalate to false if t1 >= t2; so setting t2 to T2_DEFAULT
+         * guarantees t1 < t2. */
+        if (client->lease->t1 >= client->lease->t2)
+                client->lease->t2 = T2_DEFAULT(client->lease->lifetime);
+
+        client->expire_time = client->request_sent + client->lease->lifetime * USEC_PER_SEC;
+        client->t1_time = client->request_sent + client->lease->t1 * USEC_PER_SEC;
+        client->t2_time = client->request_sent + client->lease->t2 * USEC_PER_SEC;
+
+        /* RFC2131 section 4.4.5:
+         * Times T1 and T2 SHOULD be chosen with some random "fuzz".
+         * Since the RFC doesn't specify here the exact 'fuzz' to use,
+         * we use the range from section 4.1: -1 to +1 sec. */
+        client->t1_time = usec_sub_signed(client->t1_time, RFC2131_RANDOM_FUZZ);
+        client->t2_time = usec_sub_signed(client->t2_time, RFC2131_RANDOM_FUZZ);
+
+        /* after fuzzing, ensure t2 is still >= t1 */
+        client->t2_time = MAX(client->t1_time, client->t2_time);
 
         /* arm lifetime timeout */
         r = event_reset_time(client->event, &client->timeout_expire,
                              clock_boottime_or_monotonic(),
-                             lifetime_timeout, 10 * USEC_PER_MSEC,
+                             client->expire_time, 10 * USEC_PER_MSEC,
                              client_timeout_expire, client,
                              client->event_priority, "dhcp4-lifetime", true);
         if (r < 0)
                 return r;
 
-        log_dhcp_client(client, "lease expires in %s",
-                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_timeout - time_now, USEC_PER_SEC));
-
         /* don't arm earlier timeouts if this has already expired */
-        if (lifetime_timeout <= time_now)
+        if (client->expire_time <= time_now)
                 return 0;
 
+        log_dhcp_client(client, "lease expires in %s",
+                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->expire_time - time_now, USEC_PER_SEC));
+
         /* arm T2 timeout */
         r = event_reset_time(client->event, &client->timeout_t2,
                              clock_boottime_or_monotonic(),
-                             t2_timeout, 10 * USEC_PER_MSEC,
+                             client->t2_time, 10 * USEC_PER_MSEC,
                              client_timeout_t2, client,
                              client->event_priority, "dhcp4-t2-timeout", true);
         if (r < 0)
                 return r;
 
-        log_dhcp_client(client, "T2 expires in %s",
-                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, t2_timeout - time_now, USEC_PER_SEC));
-
         /* don't arm earlier timeout if this has already expired */
-        if (t2_timeout <= time_now)
+        if (client->t2_time <= time_now)
                 return 0;
 
+        log_dhcp_client(client, "T2 expires in %s",
+                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t2_time - time_now, USEC_PER_SEC));
+
         /* arm T1 timeout */
         r = event_reset_time(client->event, &client->timeout_t1,
                              clock_boottime_or_monotonic(),
-                             t1_timeout, 10 * USEC_PER_MSEC,
+                             client->t1_time, 10 * USEC_PER_MSEC,
                              client_timeout_t1, client,
                              client->event_priority, "dhcp4-t1-timer", true);
         if (r < 0)
                 return r;
 
-        log_dhcp_client(client, "T1 expires in %s",
-                        format_timespan(time_string, FORMAT_TIMESPAN_MAX, t1_timeout - time_now, USEC_PER_SEC));
+        if (client->t1_time > time_now)
+                log_dhcp_client(client, "T1 expires in %s",
+                                format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t1_time - time_now, USEC_PER_SEC));
 
         return 0;
 }
@@ -1780,7 +1790,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
                         (void) event_source_disable(client->timeout_resend);
                         client->receive_message =
                                 sd_event_source_unref(client->receive_message);
-                        client->fd = asynchronous_close(client->fd);
+                        client->fd = safe_close(client->fd);
 
                         if (IN_SET(client->state, DHCP_STATE_REQUESTING,
                                    DHCP_STATE_REBOOTING))
@@ -1885,12 +1895,11 @@ static int client_receive_message_udp(
         assert(client);
 
         buflen = next_datagram_size_fd(fd);
-        if (buflen == -ENETDOWN) {
+        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;
 
@@ -2133,9 +2142,10 @@ int sd_dhcp_client_send_decline(sd_dhcp_client *client) {
 }
 
 int sd_dhcp_client_stop(sd_dhcp_client *client) {
-        DHCP_CLIENT_DONT_DESTROY(client);
+        if (!client)
+                return 0;
 
-        assert_return(client, -EINVAL);
+        DHCP_CLIENT_DONT_DESTROY(client);
 
         client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
         client->state = DHCP_STATE_STOPPED;
@@ -2193,7 +2203,7 @@ static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) {
 
         sd_dhcp_lease_unref(client->lease);
 
-        free(client->req_opts);
+        set_free(client->req_opts);
         free(client->hostname);
         free(client->vendor_class_identifier);
         free(client->mudurl);
@@ -2206,6 +2216,10 @@ static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *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) {
+        const uint8_t *opts;
+        size_t n_opts;
+        int r;
+
         assert_return(ret, -EINVAL);
 
         _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = new(sd_dhcp_client, 1);
@@ -2225,14 +2239,18 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
         };
         /* NOTE: this could be moved to a function. */
         if (anonymize) {
-                client->req_opts_size = ELEMENTSOF(default_req_opts_anonymize);
-                client->req_opts = memdup(default_req_opts_anonymize, client->req_opts_size);
+                n_opts = ELEMENTSOF(default_req_opts_anonymize);
+                opts = default_req_opts_anonymize;
         } else {
-                client->req_opts_size = ELEMENTSOF(default_req_opts);
-                client->req_opts = memdup(default_req_opts, client->req_opts_size);
+                n_opts = ELEMENTSOF(default_req_opts);
+                opts = default_req_opts;
+        }
+
+        for (size_t i = 0; i < n_opts; i++) {
+                r = sd_dhcp_client_set_request_option(client, opts[i]);
+                if (r < 0)
+                        return r;
         }
-        if (!client->req_opts)
-                return -ENOMEM;
 
         *ret = TAKE_PTR(client);
 
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c
index c810877c..94b0d35c 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/core/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
@@ -1235,10 +1235,8 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                 if (!a)
                         return -ENOMEM;
 
-                if (!strv_isempty(a)) {
-                        lease->search_domains = a;
-                        a = NULL;
-                }
+                if (!strv_isempty(a))
+                        lease->search_domains = TAKE_PTR(a);
         }
 
         if (routes) {
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c
index ec7f1be2..3fafd3c0 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/core/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
@@ -7,7 +7,11 @@
 
 #include <errno.h>
 #include <sys/ioctl.h>
+#if 0 /* NM_IGNORED */
 #include <linux/if_arp.h>
+#else /* NM_IGNORED */
+#include <net/if_arp.h>
+#endif /* NM_IGNORED */
 #include <linux/if_infiniband.h>
 
 #include "sd-dhcp6-client.h"
@@ -23,10 +27,10 @@
 #include "hexdecoct.h"
 #include "hostname-util.h"
 #include "in-addr-util.h"
-#include "network-internal.h"
 #include "random-util.h"
 #include "socket-util.h"
 #include "string-table.h"
+#include "strv.h"
 #include "util.h"
 #include "web-util.h"
 
@@ -190,8 +194,7 @@ int sd_dhcp6_client_set_mac(
 
         assert_return(client, -EINVAL);
         assert_return(addr, -EINVAL);
-        assert_return(addr_len > 0 && addr_len <= MAX_MAC_ADDR_LEN, -EINVAL);
-        assert_return(arp_type > 0, -EINVAL);
+        assert_return(addr_len <= MAX_MAC_ADDR_LEN, -EINVAL);
 
         assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
 
@@ -199,8 +202,11 @@ int sd_dhcp6_client_set_mac(
                 assert_return(addr_len == ETH_ALEN, -EINVAL);
         else if (arp_type == ARPHRD_INFINIBAND)
                 assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
-        else
-                return -EINVAL;
+        else {
+                client->arp_type = ARPHRD_NONE;
+                client->mac_addr_len = 0;
+                return 0;
+        }
 
         if (client->mac_addr_len == addr_len &&
             memcmp(&client->mac_addr, addr, addr_len) == 0)
@@ -411,7 +417,7 @@ int sd_dhcp6_client_set_fqdn(
 
         /* Make sure FQDN qualifies as DNS and as Linux hostname */
         if (fqdn &&
-            !(hostname_is_valid(fqdn, false) && dns_name_is_valid(fqdn) > 0))
+            !(hostname_is_valid(fqdn, 0) && dns_name_is_valid(fqdn) > 0))
                 return -EINVAL;
 
         return free_and_strdup(&client->fqdn, fqdn);
@@ -1426,12 +1432,11 @@ static int client_receive_message(
         assert(client->event);
 
         buflen = next_datagram_size_fd(fd);
-        if (buflen == -ENETDOWN) {
+        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;
 
@@ -1683,7 +1688,8 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
 }
 
 int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
-        assert_return(client, -EINVAL);
+        if (!client)
+                return 0;
 
         client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
 
@@ -1749,8 +1755,7 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) {
         }
 
         log_dhcp6_client(client, "Started in %s mode",
-                         client->information_request? "Information request":
-                         "Managed");
+                         client->information_request ? "Information request" : "Managed");
 
         return client_start(client, state);
 }
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c b/src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c
index 5f5a7fe6..5792f688 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/core/systemd/src/libsystemd-network/sd-dhcp6-lease.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/core/systemd/src/libsystemd-network/sd-ipv4acd.c
index b9e3dce3..096afbd8 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/core/systemd/src/libsystemd-network/sd-ipv4acd.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2014 Axis Communications AB. All rights reserved.
 ***/
@@ -18,10 +18,12 @@
 #include "ether-addr-util.h"
 #include "event-util.h"
 #include "fd-util.h"
+#include "format-util.h"
 #include "in-addr-util.h"
-#include "list.h"
+#include "log-link.h"
 #include "random-util.h"
 #include "siphash24.h"
+#include "string-table.h"
 #include "string-util.h"
 #include "time-util.h"
 
@@ -56,6 +58,7 @@ struct sd_ipv4acd {
         int ifindex;
         int fd;
 
+        char ifname[IF_NAMESIZE + 1];
         unsigned n_iteration;
         unsigned n_conflict;
 
@@ -74,13 +77,30 @@ struct sd_ipv4acd {
         void* userdata;
 };
 
-#define log_ipv4acd_errno(acd, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "IPV4ACD: " fmt, ##__VA_ARGS__)
-#define log_ipv4acd(acd, fmt, ...) log_ipv4acd_errno(acd, 0, fmt, ##__VA_ARGS__)
+#define log_ipv4acd_errno(acd, error, fmt, ...)                         \
+        log_interface_full_errno(sd_ipv4acd_get_ifname(acd), LOG_DEBUG, error, "IPV4ACD: " fmt, ##__VA_ARGS__)
+#define log_ipv4acd(acd, fmt, ...)                      \
+        log_ipv4acd_errno(acd, 0, fmt, ##__VA_ARGS__)
+
+static const char * const ipv4acd_state_table[_IPV4ACD_STATE_MAX] = {
+        [IPV4ACD_STATE_INIT]             = "init",
+        [IPV4ACD_STATE_STARTED]          = "started",
+        [IPV4ACD_STATE_WAITING_PROBE]    = "waiting-probe",
+        [IPV4ACD_STATE_PROBING]          = "probing",
+        [IPV4ACD_STATE_WAITING_ANNOUNCE] = "waiting-announce",
+        [IPV4ACD_STATE_ANNOUNCING]       = "announcing",
+        [IPV4ACD_STATE_RUNNING]          = "running",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(ipv4acd_state, IPv4ACDState);
 
 static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_counter) {
         assert(acd);
         assert(st < _IPV4ACD_STATE_MAX);
 
+        if (st != acd->state)
+                log_ipv4acd(acd, "%s -> %s", ipv4acd_state_to_string(acd->state), ipv4acd_state_to_string(st));
+
         if (st == acd->state && !reset_counter)
                 acd->n_iteration++;
         else {
@@ -146,7 +166,8 @@ static void ipv4acd_client_notify(sd_ipv4acd *acd, int event) {
 int sd_ipv4acd_stop(sd_ipv4acd *acd) {
         IPv4ACDState old_state;
 
-        assert_return(acd, -EINVAL);
+        if (!acd)
+                return 0;
 
         old_state = acd->state;
 
@@ -379,15 +400,35 @@ fail:
 }
 
 int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int ifindex) {
+        char ifname[IF_NAMESIZE + 1];
+
         assert_return(acd, -EINVAL);
         assert_return(ifindex > 0, -EINVAL);
         assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY);
 
+        if (!format_ifname(ifindex, ifname))
+                return -ENODEV;
+
+        strcpy(acd->ifname, ifname);
         acd->ifindex = ifindex;
 
         return 0;
 }
 
+int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd) {
+        if (!acd)
+                return -EINVAL;
+
+        return acd->ifindex;
+}
+
+const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd) {
+        if (!acd)
+                return NULL;
+
+        return empty_to_null(acd->ifname);
+}
+
 int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) {
         assert_return(acd, -EINVAL);
         assert_return(addr, -EINVAL);
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/core/systemd/src/libsystemd-network/sd-ipv4ll.c
index 0bfaf265..c5e7a91f 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/core/systemd/src/libsystemd-network/sd-ipv4ll.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /***
   Copyright © 2014 Axis Communications AB. All rights reserved.
 ***/
@@ -17,7 +17,7 @@
 #include "alloc-util.h"
 #include "ether-addr-util.h"
 #include "in-addr-util.h"
-#include "list.h"
+#include "log-link.h"
 #include "random-util.h"
 #include "siphash24.h"
 #include "sparse-endian.h"
@@ -51,8 +51,10 @@ struct sd_ipv4ll {
         void* userdata;
 };
 
-#define log_ipv4ll_errno(ll, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "IPV4LL: " fmt, ##__VA_ARGS__)
-#define log_ipv4ll(ll, fmt, ...) log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__)
+#define log_ipv4ll_errno(ll, error, fmt, ...)                           \
+        log_interface_full_errno(sd_ipv4ll_get_ifname(ll), LOG_DEBUG, error, "IPV4LL: " fmt, ##__VA_ARGS__)
+#define log_ipv4ll(ll, fmt, ...)                        \
+        log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__)
 
 static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
 
@@ -91,7 +93,8 @@ int sd_ipv4ll_new(sd_ipv4ll **ret) {
 }
 
 int sd_ipv4ll_stop(sd_ipv4ll *ll) {
-        assert_return(ll, -EINVAL);
+        if (!ll)
+                return 0;
 
         return sd_ipv4acd_stop(ll->acd);
 }
@@ -104,6 +107,20 @@ int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) {
         return sd_ipv4acd_set_ifindex(ll->acd, ifindex);
 }
 
+int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll) {
+        if (!ll)
+                return -EINVAL;
+
+        return sd_ipv4acd_get_ifindex(ll->acd);
+}
+
+const char *sd_ipv4ll_get_ifname(sd_ipv4ll *ll) {
+        if (!ll)
+                return NULL;
+
+        return sd_ipv4acd_get_ifname(ll->acd);
+}
+
 int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
         int r;
 
diff --git a/src/systemd/src/libsystemd-network/sd-lldp.c b/src/core/systemd/src/libsystemd-network/sd-lldp.c
index 524b1bab..b12d8e27 100644
--- a/src/systemd/src/libsystemd-network/sd-lldp.c
+++ b/src/core/systemd/src/libsystemd-network/sd-lldp.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-core.h"
 
@@ -278,7 +278,8 @@ fail:
 }
 
 _public_ int sd_lldp_stop(sd_lldp *lldp) {
-        assert_return(lldp, -EINVAL);
+        if (!lldp)
+                return 0;
 
         if (lldp->fd < 0)
                 return 0;
diff --git a/src/systemd/src/libsystemd/sd-event/event-source.h b/src/core/systemd/src/libsystemd/sd-event/event-source.h
index 08eb9b6a..f0d2a1b9 100644
--- a/src/systemd/src/libsystemd/sd-event/event-source.h
+++ b/src/core/systemd/src/libsystemd/sd-event/event-source.h
@@ -1,5 +1,5 @@
 #pragma once
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <sys/epoll.h>
 #include <sys/timerfd.h>
@@ -11,6 +11,7 @@
 #include "hashmap.h"
 #include "list.h"
 #include "prioq.h"
+#include "ratelimit.h"
 
 typedef enum EventSourceType {
         SOURCE_IO,
@@ -60,6 +61,8 @@ struct sd_event_source {
         bool pending:1;
         bool dispatching:1;
         bool floating:1;
+        bool exit_on_failure:1;
+        bool ratelimited:1;
 
         int64_t priority;
         unsigned pending_index;
@@ -71,6 +74,13 @@ struct sd_event_source {
 
         LIST_FIELDS(sd_event_source, sources);
 
+        RateLimit rate_limit;
+
+        /* These are primarily fields relevant for time event sources, but since any event source can
+         * effectively become one when rate-limited, this is part of the common fields. */
+        unsigned earliest_index;
+        unsigned latest_index;
+
         union {
                 struct {
                         sd_event_io_handler_t callback;
@@ -83,8 +93,6 @@ struct sd_event_source {
                 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;
diff --git a/src/systemd/src/libsystemd/sd-event/event-util.c b/src/core/systemd/src/libsystemd/sd-event/event-util.c
index e8384cfd..7cc55be1 100644
--- a/src/systemd/src/libsystemd/sd-event/event-util.c
+++ b/src/core/systemd/src/libsystemd/sd-event/event-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-core.h"
 
diff --git a/src/systemd/src/libsystemd/sd-event/event-util.h b/src/core/systemd/src/libsystemd/sd-event/event-util.h
index 00180955..c8f97bc8 100644
--- a/src/systemd/src/libsystemd/sd-event/event-util.h
+++ b/src/core/systemd/src/libsystemd/sd-event/event-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
diff --git a/src/systemd/src/libsystemd/sd-event/sd-event.c b/src/core/systemd/src/libsystemd/sd-event/sd-event.c
index d5993a22..dda4be00 100644
--- a/src/systemd/src/libsystemd/sd-event/sd-event.c
+++ b/src/core/systemd/src/libsystemd/sd-event/sd-event.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-core.h"
 
@@ -39,6 +39,16 @@ static bool EVENT_SOURCE_WATCH_PIDFD(sd_event_source *s) {
                 s->child.options == WEXITED;
 }
 
+static bool event_source_is_online(sd_event_source *s) {
+        assert(s);
+        return s->enabled != SD_EVENT_OFF && !s->ratelimited;
+}
+
+static bool event_source_is_offline(sd_event_source *s) {
+        assert(s);
+        return s->enabled == SD_EVENT_OFF || s->ratelimited;
+}
+
 static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = {
         [SOURCE_IO] = "io",
         [SOURCE_TIME_REALTIME] = "realtime",
@@ -57,7 +67,25 @@ static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX]
 
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int);
 
-#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)
+#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)
+
+#define EVENT_SOURCE_CAN_RATE_LIMIT(t)          \
+        IN_SET((t),                             \
+               SOURCE_IO,                       \
+               SOURCE_TIME_REALTIME,            \
+               SOURCE_TIME_BOOTTIME,            \
+               SOURCE_TIME_MONOTONIC,           \
+               SOURCE_TIME_REALTIME_ALARM,      \
+               SOURCE_TIME_BOOTTIME_ALARM,      \
+               SOURCE_SIGNAL,                   \
+               SOURCE_DEFER,                    \
+               SOURCE_INOTIFY)
 
 struct sd_event {
         unsigned n_ref;
@@ -83,7 +111,7 @@ struct sd_event {
         Hashmap *signal_data; /* indexed by priority */
 
         Hashmap *child_sources;
-        unsigned n_enabled_child_sources;
+        unsigned n_online_child_sources;
 
         Set *post_sources;
 
@@ -122,7 +150,7 @@ struct sd_event {
 
         LIST_HEAD(sd_event_source, sources);
 
-        usec_t last_run, last_log;
+        usec_t last_run_usec, last_log_usec;
         unsigned delays[sizeof(usec_t) * 8];
 };
 
@@ -148,6 +176,11 @@ static int pending_prioq_compare(const void *a, const void *b) {
         if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
                 return 1;
 
+        /* Non rate-limited ones first. */
+        r = CMP(!!x->ratelimited, !!y->ratelimited);
+        if (r != 0)
+                return r;
+
         /* Lower priority values first */
         r = CMP(x->priority, y->priority);
         if (r != 0)
@@ -170,6 +203,11 @@ static int prepare_prioq_compare(const void *a, const void *b) {
         if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF)
                 return 1;
 
+        /* Non rate-limited ones first. */
+        r = CMP(!!x->ratelimited, !!y->ratelimited);
+        if (r != 0)
+                return r;
+
         /* 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 */
@@ -181,12 +219,30 @@ static int prepare_prioq_compare(const void *a, const void *b) {
         return CMP(x->priority, y->priority);
 }
 
+static usec_t time_event_source_next(const sd_event_source *s) {
+        assert(s);
+
+        /* We have two kinds of event sources that have elapsation times associated with them: the actual
+         * time based ones and the ones for which a ratelimit can be in effect (where we want to be notified
+         * once the ratelimit time window ends). Let's return the next elapsing time depending on what we are
+         * looking at here. */
+
+        if (s->ratelimited) { /* If rate-limited the next elapsation is when the ratelimit time window ends */
+                assert(s->rate_limit.begin != 0);
+                assert(s->rate_limit.interval != 0);
+                return usec_add(s->rate_limit.begin, s->rate_limit.interval);
+        }
+
+        /* Otherwise this must be a time event source, if not ratelimited */
+        if (EVENT_SOURCE_IS_TIME(s->type))
+                return s->time.next;
+
+        return USEC_INFINITY;
+}
+
 static int earliest_time_prioq_compare(const void *a, const void *b) {
         const sd_event_source *x = a, *y = b;
 
-        assert(EVENT_SOURCE_IS_TIME(x->type));
-        assert(x->type == y->type);
-
         /* Enabled ones first */
         if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
                 return -1;
@@ -200,19 +256,30 @@ static int earliest_time_prioq_compare(const void *a, const void *b) {
                 return 1;
 
         /* Order by time */
-        return CMP(x->time.next, y->time.next);
+        return CMP(time_event_source_next(x), time_event_source_next(y));
 }
 
 static usec_t time_event_source_latest(const sd_event_source *s) {
-        return usec_add(s->time.next, s->time.accuracy);
+        assert(s);
+
+        if (s->ratelimited) { /* For ratelimited stuff the earliest and the latest time shall actually be the
+                               * same, as we should avoid adding additional inaccuracy on an inaccuracy time
+                               * window */
+                assert(s->rate_limit.begin != 0);
+                assert(s->rate_limit.interval != 0);
+                return usec_add(s->rate_limit.begin, s->rate_limit.interval);
+        }
+
+        /* Must be a time event source, if not ratelimited */
+        if (EVENT_SOURCE_IS_TIME(s->type))
+                return usec_add(s->time.next, s->time.accuracy);
+
+        return USEC_INFINITY;
 }
 
 static int latest_time_prioq_compare(const void *a, const void *b) {
         const sd_event_source *x = a, *y = b;
 
-        assert(EVENT_SOURCE_IS_TIME(x->type));
-        assert(x->type == y->type);
-
         /* Enabled ones first */
         if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF)
                 return -1;
@@ -382,7 +449,7 @@ static void source_io_unregister(sd_event_source *s) {
                 return;
 
         if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL) < 0)
-                log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll: %m",
+                log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m",
                                 strna(s->description), event_source_type_to_string(s->type));
 
         s->io.registered = false;
@@ -401,13 +468,10 @@ static int source_io_register(
                 .events = events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0),
                 .data.ptr = s,
         };
-        int r;
 
-        r = epoll_ctl(s->event->epoll_fd,
+        if (epoll_ctl(s->event->epoll_fd,
                       s->io.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD,
-                      s->io.fd,
-                      &ev);
-        if (r < 0)
+                      s->io.fd, &ev) < 0)
                 return -errno;
 
         s->io.registered = true;
@@ -427,15 +491,13 @@ static void source_child_pidfd_unregister(sd_event_source *s) {
 
         if (EVENT_SOURCE_WATCH_PIDFD(s))
                 if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->child.pidfd, NULL) < 0)
-                        log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll: %m",
+                        log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m",
                                         strna(s->description), event_source_type_to_string(s->type));
 
         s->child.registered = false;
 }
 
 static int source_child_pidfd_register(sd_event_source *s, int enabled) {
-        int r;
-
         assert(s);
         assert(s->type == SOURCE_CHILD);
         assert(enabled != SD_EVENT_OFF);
@@ -446,11 +508,9 @@ static int source_child_pidfd_register(sd_event_source *s, int enabled) {
                         .data.ptr = s,
                 };
 
-                if (s->child.registered)
-                        r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_MOD, s->child.pidfd, &ev);
-                else
-                        r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_ADD, s->child.pidfd, &ev);
-                if (r < 0)
+                if (epoll_ctl(s->event->epoll_fd,
+                              s->child.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD,
+                              s->child.pidfd, &ev) < 0)
                         return -errno;
         }
 
@@ -618,8 +678,7 @@ static int event_make_signal_data(
                 .data.ptr = d,
         };
 
-        r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev);
-        if (r < 0)  {
+        if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev) < 0) {
                 r = -errno;
                 goto fail;
         }
@@ -671,12 +730,12 @@ static void event_gc_signal_data(sd_event *e, const int64_t *priority, int sig)
          * and possibly drop the signalfd for it. */
 
         if (sig == SIGCHLD &&
-            e->n_enabled_child_sources > 0)
+            e->n_online_child_sources > 0)
                 return;
 
         if (e->signal_sources &&
             e->signal_sources[sig] &&
-            e->signal_sources[sig]->enabled != SD_EVENT_OFF)
+            event_source_is_online(e->signal_sources[sig]))
                 return;
 
         /*
@@ -706,6 +765,52 @@ static void event_gc_signal_data(sd_event *e, const int64_t *priority, int sig)
                 event_unmask_signal_data(e, d, sig);
 }
 
+static void event_source_pp_prioq_reshuffle(sd_event_source *s) {
+        assert(s);
+
+        /* Reshuffles the pending + prepare prioqs. Called whenever the dispatch order changes, i.e. when
+         * they are enabled/disabled or marked pending and such. */
+
+        if (s->pending)
+                prioq_reshuffle(s->event->pending, s, &s->pending_index);
+
+        if (s->prepare)
+                prioq_reshuffle(s->event->prepare, s, &s->prepare_index);
+}
+
+static void event_source_time_prioq_reshuffle(sd_event_source *s) {
+        struct clock_data *d;
+
+        assert(s);
+
+        /* Called whenever the event source's timer ordering properties changed, i.e. time, accuracy,
+         * pending, enable state. Makes sure the two prioq's are ordered properly again. */
+
+        if (s->ratelimited)
+                d = &s->event->monotonic;
+        else {
+                assert(EVENT_SOURCE_IS_TIME(s->type));
+                assert_se(d = event_get_clock_data(s->event, s->type));
+        }
+
+        prioq_reshuffle(d->earliest, s, &s->earliest_index);
+        prioq_reshuffle(d->latest, s, &s->latest_index);
+        d->needs_rearm = true;
+}
+
+static void event_source_time_prioq_remove(
+                sd_event_source *s,
+                struct clock_data *d) {
+
+        assert(s);
+        assert(d);
+
+        prioq_remove(d->earliest, s, &s->earliest_index);
+        prioq_remove(d->latest, s, &s->latest_index);
+        s->earliest_index = s->latest_index = PRIOQ_IDX_NULL;
+        d->needs_rearm = true;
+}
+
 static void source_disconnect(sd_event_source *s) {
         sd_event *event;
 
@@ -728,17 +833,18 @@ static void source_disconnect(sd_event_source *s) {
         case SOURCE_TIME_BOOTTIME:
         case SOURCE_TIME_MONOTONIC:
         case SOURCE_TIME_REALTIME_ALARM:
-        case SOURCE_TIME_BOOTTIME_ALARM: {
-                struct clock_data *d;
+        case SOURCE_TIME_BOOTTIME_ALARM:
+                /* Only remove this event source from the time event source here if it is not ratelimited. If
+                 * it is ratelimited, we'll remove it below, separately. Why? Because the clock used might
+                 * differ: ratelimiting always uses CLOCK_MONOTONIC, but timer events might use any clock */
 
-                d = event_get_clock_data(s->event, s->type);
-                assert(d);
+                if (!s->ratelimited) {
+                        struct clock_data *d;
+                        assert_se(d = event_get_clock_data(s->event, s->type));
+                        event_source_time_prioq_remove(s, d);
+                }
 
-                prioq_remove(d->earliest, s, &s->time.earliest_index);
-                prioq_remove(d->latest, s, &s->time.latest_index);
-                d->needs_rearm = true;
                 break;
-        }
 
         case SOURCE_SIGNAL:
                 if (s->signal.sig > 0) {
@@ -753,9 +859,9 @@ static void source_disconnect(sd_event_source *s) {
 
         case SOURCE_CHILD:
                 if (s->child.pid > 0) {
-                        if (s->enabled != SD_EVENT_OFF) {
-                                assert(s->event->n_enabled_child_sources > 0);
-                                s->event->n_enabled_child_sources--;
+                        if (event_source_is_online(s)) {
+                                assert(s->event->n_online_child_sources > 0);
+                                s->event->n_online_child_sources--;
                         }
 
                         (void) hashmap_remove(s->event->child_sources, PID_TO_PTR(s->child.pid));
@@ -825,6 +931,9 @@ static void source_disconnect(sd_event_source *s) {
         if (s->prepare)
                 prioq_remove(s->event->prepare, s, &s->prepare_index);
 
+        if (s->ratelimited)
+                event_source_time_prioq_remove(s, &s->event->monotonic);
+
         event = TAKE_PTR(s->event);
         LIST_REMOVE(sources, event->sources, s);
         event->n_sources--;
@@ -912,16 +1021,8 @@ static int source_set_pending(sd_event_source *s, bool b) {
         } else
                 assert_se(prioq_remove(s->event->pending, s, &s->pending_index));
 
-        if (EVENT_SOURCE_IS_TIME(s->type)) {
-                struct clock_data *d;
-
-                d = event_get_clock_data(s->event, s->type);
-                assert(d);
-
-                prioq_reshuffle(d->earliest, s, &s->time.earliest_index);
-                prioq_reshuffle(d->latest, s, &s->time.latest_index);
-                d->needs_rearm = true;
-        }
+        if (EVENT_SOURCE_IS_TIME(s->type))
+                event_source_time_prioq_reshuffle(s);
 
         if (s->type == SOURCE_SIGNAL && !b) {
                 struct signal_data *d;
@@ -974,6 +1075,12 @@ static sd_event_source *source_new(sd_event *e, bool floating, EventSourceType t
         return s;
 }
 
+static int io_exit_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        assert(s);
+
+        return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
+}
+
 _public_ int sd_event_add_io(
                 sd_event *e,
                 sd_event_source **ret,
@@ -989,10 +1096,12 @@ _public_ int sd_event_add_io(
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(fd >= 0, -EBADF);
         assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
-        assert_return(callback, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
 
+        if (!callback)
+                callback = io_exit_callback;
+
         s = source_new(e, !ret, SOURCE_IO);
         if (!s)
                 return -ENOMEM;
@@ -1046,7 +1155,6 @@ static int event_setup_timer_fd(
                 return 0;
 
         _cleanup_close_ int fd = -1;
-        int r;
 
         fd = timerfd_create(clock, TFD_NONBLOCK|TFD_CLOEXEC);
         if (fd < 0)
@@ -1059,8 +1167,7 @@ static int event_setup_timer_fd(
                 .data.ptr = d,
         };
 
-        r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, fd, &ev);
-        if (r < 0)
+        if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0)
                 return -errno;
 
         d->fd = TAKE_FD(fd);
@@ -1073,6 +1180,52 @@ static int time_exit_callback(sd_event_source *s, uint64_t usec, void *userdata)
         return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
 }
 
+static int setup_clock_data(sd_event *e, struct clock_data *d, clockid_t clock) {
+        int r;
+
+        assert(d);
+
+        if (d->fd < 0) {
+                r = event_setup_timer_fd(e, d, clock);
+                if (r < 0)
+                        return r;
+        }
+
+        r = prioq_ensure_allocated(&d->earliest, earliest_time_prioq_compare);
+        if (r < 0)
+                return r;
+
+        r = prioq_ensure_allocated(&d->latest, latest_time_prioq_compare);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int event_source_time_prioq_put(
+                sd_event_source *s,
+                struct clock_data *d) {
+
+        int r;
+
+        assert(s);
+        assert(d);
+
+        r = prioq_put(d->earliest, s, &s->earliest_index);
+        if (r < 0)
+                return r;
+
+        r = prioq_put(d->latest, s, &s->latest_index);
+        if (r < 0) {
+                assert_se(prioq_remove(d->earliest, s, &s->earliest_index) > 0);
+                s->earliest_index = PRIOQ_IDX_NULL;
+                return r;
+        }
+
+        d->needs_rearm = true;
+        return 0;
+}
+
 _public_ int sd_event_add_time(
                 sd_event *e,
                 sd_event_source **ret,
@@ -1103,23 +1256,12 @@ _public_ int sd_event_add_time(
         if (!callback)
                 callback = time_exit_callback;
 
-        d = event_get_clock_data(e, type);
-        assert(d);
-
-        r = prioq_ensure_allocated(&d->earliest, earliest_time_prioq_compare);
-        if (r < 0)
-                return r;
+        assert_se(d = event_get_clock_data(e, type));
 
-        r = prioq_ensure_allocated(&d->latest, latest_time_prioq_compare);
+        r = setup_clock_data(e, d, clock);
         if (r < 0)
                 return r;
 
-        if (d->fd < 0) {
-                r = event_setup_timer_fd(e, d, clock);
-                if (r < 0)
-                        return r;
-        }
-
         s = source_new(e, !ret, type);
         if (!s)
                 return -ENOMEM;
@@ -1127,17 +1269,11 @@ _public_ int sd_event_add_time(
         s->time.next = usec;
         s->time.accuracy = accuracy == 0 ? DEFAULT_ACCURACY_USEC : accuracy;
         s->time.callback = callback;
-        s->time.earliest_index = s->time.latest_index = PRIOQ_IDX_NULL;
+        s->earliest_index = s->latest_index = PRIOQ_IDX_NULL;
         s->userdata = userdata;
         s->enabled = SD_EVENT_ONESHOT;
 
-        d->needs_rearm = true;
-
-        r = prioq_put(d->earliest, s, &s->time.earliest_index);
-        if (r < 0)
-                return r;
-
-        r = prioq_put(d->latest, s, &s->time.latest_index);
+        r = event_source_time_prioq_put(s, d);
         if (r < 0)
                 return r;
 
@@ -1238,6 +1374,12 @@ _public_ int sd_event_add_signal(
         return 0;
 }
 
+static int child_exit_callback(sd_event_source *s, const siginfo_t *si, void *userdata) {
+        assert(s);
+
+        return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
+}
+
 static bool shall_use_pidfd(void) {
         /* Mostly relevant for debugging, i.e. this is used in test-event.c to test the event loop once with and once without pidfd */
         return getenv_bool_secure("SYSTEMD_PIDFD") != 0;
@@ -1259,11 +1401,13 @@ _public_ int sd_event_add_child(
         assert_return(pid > 1, -EINVAL);
         assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
         assert_return(options != 0, -EINVAL);
-        assert_return(callback, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
 
-        if (e->n_enabled_child_sources == 0) {
+        if (!callback)
+                callback = child_exit_callback;
+
+        if (e->n_online_child_sources == 0) {
                 /* Caller must block SIGCHLD before using us to watch children, even if pidfd is available,
                  * for compatibility with pre-pidfd and because we don't want the reap the child processes
                  * ourselves, i.e. call waitid(), and don't want Linux' default internal logic for that to
@@ -1313,31 +1457,25 @@ _public_ int sd_event_add_child(
         if (r < 0)
                 return r;
 
-        e->n_enabled_child_sources++;
-
         if (EVENT_SOURCE_WATCH_PIDFD(s)) {
                 /* We have a pidfd and we only want to watch for exit */
-
                 r = source_child_pidfd_register(s, s->enabled);
-                if (r < 0) {
-                        e->n_enabled_child_sources--;
+                if (r < 0)
                         return r;
-                }
+
         } else {
                 /* We have no pidfd or we shall wait for some other event than WEXITED */
-
                 r = event_make_signal_data(e, SIGCHLD, NULL);
-                if (r < 0) {
-                        e->n_enabled_child_sources--;
+                if (r < 0)
                         return r;
-                }
 
                 e->need_process_child = true;
         }
 
+        e->n_online_child_sources++;
+
         if (ret)
                 *ret = s;
-
         TAKE_PTR(s);
         return 0;
 }
@@ -1360,11 +1498,13 @@ _public_ int sd_event_add_child_pidfd(
         assert_return(pidfd >= 0, -EBADF);
         assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
         assert_return(options != 0, -EINVAL);
-        assert_return(callback, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
 
-        if (e->n_enabled_child_sources == 0) {
+        if (!callback)
+                callback = child_exit_callback;
+
+        if (e->n_online_child_sources == 0) {
                 r = signal_is_blocked(SIGCHLD);
                 if (r < 0)
                         return r;
@@ -1400,36 +1540,35 @@ _public_ int sd_event_add_child_pidfd(
         if (r < 0)
                 return r;
 
-        e->n_enabled_child_sources++;
-
         if (EVENT_SOURCE_WATCH_PIDFD(s)) {
                 /* We only want to watch for WEXITED */
-
                 r = source_child_pidfd_register(s, s->enabled);
-                if (r < 0) {
-                        e->n_enabled_child_sources--;
+                if (r < 0)
                         return r;
-                }
         } else {
                 /* We shall wait for some other event than WEXITED */
-
                 r = event_make_signal_data(e, SIGCHLD, NULL);
-                if (r < 0) {
-                        e->n_enabled_child_sources--;
+                if (r < 0)
                         return r;
-                }
 
                 e->need_process_child = true;
         }
 
+        e->n_online_child_sources++;
+
         if (ret)
                 *ret = s;
-
         TAKE_PTR(s);
         return 0;
 }
 #endif /* NM_IGNORED */
 
+static int generic_exit_callback(sd_event_source *s, void *userdata) {
+        assert(s);
+
+        return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
+}
+
 _public_ int sd_event_add_defer(
                 sd_event *e,
                 sd_event_source **ret,
@@ -1441,10 +1580,12 @@ _public_ int sd_event_add_defer(
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(callback, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
 
+        if (!callback)
+                callback = generic_exit_callback;
+
         s = source_new(e, !ret, SOURCE_DEFER);
         if (!s)
                 return -ENOMEM;
@@ -1475,10 +1616,12 @@ _public_ int sd_event_add_post(
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(callback, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
 
+        if (!callback)
+                callback = generic_exit_callback;
+
         s = source_new(e, !ret, SOURCE_POST);
         if (!s)
                 return -ENOMEM;
@@ -1830,6 +1973,12 @@ static int inode_data_realize_watch(sd_event *e, struct inode_data *d) {
         return 1;
 }
 
+static int inotify_exit_callback(sd_event_source *s, const struct inotify_event *event, void *userdata) {
+        assert(s);
+
+        return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata));
+}
+
 _public_ int sd_event_add_inotify(
                 sd_event *e,
                 sd_event_source **ret,
@@ -1848,10 +1997,12 @@ _public_ int sd_event_add_inotify(
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(path, -EINVAL);
-        assert_return(callback, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
 
+        if (!callback)
+                callback = inotify_exit_callback;
+
         /* Refuse IN_MASK_ADD since we coalesce watches on the same inode, and hence really don't want to merge
          * masks. Or in other words, this whole code exists only to manage IN_MASK_ADD type operations for you, hence
          * the user can't use them for us. */
@@ -1990,7 +2141,7 @@ _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) {
         if (s->io.fd == fd)
                 return 0;
 
-        if (s->enabled == SD_EVENT_OFF) {
+        if (event_source_is_offline(s)) {
                 s->io.fd = fd;
                 s->io.registered = false;
         } else {
@@ -2057,7 +2208,7 @@ _public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events)
         if (r < 0)
                 return r;
 
-        if (s->enabled != SD_EVENT_OFF) {
+        if (event_source_is_online(s)) {
                 r = source_io_register(s, s->enabled, events);
                 if (r < 0)
                         return r;
@@ -2160,7 +2311,7 @@ _public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority)
 
                 event_gc_inode_data(s->event, old_inode_data);
 
-        } else if (s->type == SOURCE_SIGNAL && s->enabled != SD_EVENT_OFF) {
+        } else if (s->type == SOURCE_SIGNAL && event_source_is_online(s)) {
                 struct signal_data *old, *d;
 
                 /* Move us from the signalfd belonging to the old
@@ -2180,11 +2331,7 @@ _public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority)
         } else
                 s->priority = priority;
 
-        if (s->pending)
-                prioq_reshuffle(s->event->pending, s, &s->pending_index);
-
-        if (s->prepare)
-                prioq_reshuffle(s->event->prepare, s, &s->prepare_index);
+        event_source_pp_prioq_reshuffle(s);
 
         if (s->type == SOURCE_EXIT)
                 prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
@@ -2201,198 +2348,222 @@ fail:
         return r;
 }
 
-_public_ int sd_event_source_get_enabled(sd_event_source *s, int *m) {
+_public_ int sd_event_source_get_enabled(sd_event_source *s, int *ret) {
         assert_return(s, -EINVAL);
         assert_return(!event_pid_changed(s->event), -ECHILD);
 
-        if (m)
-                *m = s->enabled;
+        if (ret)
+                *ret = s->enabled;
+
         return s->enabled != SD_EVENT_OFF;
 }
 
-_public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
+static int event_source_offline(
+                sd_event_source *s,
+                int enabled,
+                bool ratelimited) {
+
+        bool was_offline;
         int r;
 
-        assert_return(s, -EINVAL);
-        assert_return(IN_SET(m, SD_EVENT_OFF, SD_EVENT_ON, SD_EVENT_ONESHOT), -EINVAL);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert(s);
+        assert(enabled == SD_EVENT_OFF || ratelimited);
 
-        /* If we are dead anyway, we are fine with turning off
-         * sources, but everything else needs to fail. */
-        if (s->event->state == SD_EVENT_FINISHED)
-                return m == SD_EVENT_OFF ? 0 : -ESTALE;
+        /* Unset the pending flag when this event source is disabled */
+        if (s->enabled != SD_EVENT_OFF &&
+            enabled == SD_EVENT_OFF &&
+            !IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) {
+                r = source_set_pending(s, false);
+                if (r < 0)
+                        return r;
+        }
 
-        if (s->enabled == m)
-                return 0;
+        was_offline = event_source_is_offline(s);
+        s->enabled = enabled;
+        s->ratelimited = ratelimited;
 
-        if (m == SD_EVENT_OFF) {
+        switch (s->type) {
 
-                /* Unset the pending flag when this event source is disabled */
-                if (!IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) {
-                        r = source_set_pending(s, false);
-                        if (r < 0)
-                                return r;
-                }
+        case SOURCE_IO:
+                source_io_unregister(s);
+                break;
 
-                switch (s->type) {
+        case SOURCE_TIME_REALTIME:
+        case SOURCE_TIME_BOOTTIME:
+        case SOURCE_TIME_MONOTONIC:
+        case SOURCE_TIME_REALTIME_ALARM:
+        case SOURCE_TIME_BOOTTIME_ALARM:
+                event_source_time_prioq_reshuffle(s);
+                break;
 
-                case SOURCE_IO:
-                        source_io_unregister(s);
-                        s->enabled = m;
-                        break;
+        case SOURCE_SIGNAL:
+                event_gc_signal_data(s->event, &s->priority, s->signal.sig);
+                break;
 
-                case SOURCE_TIME_REALTIME:
-                case SOURCE_TIME_BOOTTIME:
-                case SOURCE_TIME_MONOTONIC:
-                case SOURCE_TIME_REALTIME_ALARM:
-                case SOURCE_TIME_BOOTTIME_ALARM: {
-                        struct clock_data *d;
+        case SOURCE_CHILD:
+                if (!was_offline) {
+                        assert(s->event->n_online_child_sources > 0);
+                        s->event->n_online_child_sources--;
+                }
 
-                        s->enabled = m;
-                        d = event_get_clock_data(s->event, s->type);
-                        assert(d);
+                if (EVENT_SOURCE_WATCH_PIDFD(s))
+                        source_child_pidfd_unregister(s);
+                else
+                        event_gc_signal_data(s->event, &s->priority, SIGCHLD);
+                break;
 
-                        prioq_reshuffle(d->earliest, s, &s->time.earliest_index);
-                        prioq_reshuffle(d->latest, s, &s->time.latest_index);
-                        d->needs_rearm = true;
-                        break;
-                }
+        case SOURCE_EXIT:
+                prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
+                break;
 
-                case SOURCE_SIGNAL:
-                        s->enabled = m;
+        case SOURCE_DEFER:
+        case SOURCE_POST:
+        case SOURCE_INOTIFY:
+                break;
 
-                        event_gc_signal_data(s->event, &s->priority, s->signal.sig);
-                        break;
+        default:
+                assert_not_reached("Wut? I shouldn't exist.");
+        }
 
-                case SOURCE_CHILD:
-                        s->enabled = m;
+        return 1;
+}
 
-                        assert(s->event->n_enabled_child_sources > 0);
-                        s->event->n_enabled_child_sources--;
+static int event_source_online(
+                sd_event_source *s,
+                int enabled,
+                bool ratelimited) {
 
-                        if (EVENT_SOURCE_WATCH_PIDFD(s))
-                                source_child_pidfd_unregister(s);
-                        else
-                                event_gc_signal_data(s->event, &s->priority, SIGCHLD);
+        bool was_online;
+        int r;
 
-                        break;
+        assert(s);
+        assert(enabled != SD_EVENT_OFF || !ratelimited);
 
-                case SOURCE_EXIT:
-                        s->enabled = m;
-                        prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
-                        break;
+        /* Unset the pending flag when this event source is enabled */
+        if (s->enabled == SD_EVENT_OFF &&
+            enabled != SD_EVENT_OFF &&
+            !IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) {
+                r = source_set_pending(s, false);
+                if (r < 0)
+                        return r;
+        }
 
-                case SOURCE_DEFER:
-                case SOURCE_POST:
-                case SOURCE_INOTIFY:
-                        s->enabled = m;
-                        break;
+        /* Are we really ready for onlining? */
+        if (enabled == SD_EVENT_OFF || ratelimited) {
+                /* Nope, we are not ready for onlining, then just update the precise state and exit */
+                s->enabled = enabled;
+                s->ratelimited = ratelimited;
+                return 0;
+        }
 
-                default:
-                        assert_not_reached("Wut? I shouldn't exist.");
-                }
+        was_online = event_source_is_online(s);
 
-        } else {
+        switch (s->type) {
+        case SOURCE_IO:
+                r = source_io_register(s, enabled, s->io.events);
+                if (r < 0)
+                        return r;
+                break;
 
-                /* Unset the pending flag when this event source is enabled */
-                if (s->enabled == SD_EVENT_OFF && !IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) {
-                        r = source_set_pending(s, false);
-                        if (r < 0)
-                                return r;
+        case SOURCE_SIGNAL:
+                r = event_make_signal_data(s->event, s->signal.sig, NULL);
+                if (r < 0) {
+                        event_gc_signal_data(s->event, &s->priority, s->signal.sig);
+                        return r;
                 }
 
-                switch (s->type) {
+                break;
+
+        case SOURCE_CHILD:
+                if (EVENT_SOURCE_WATCH_PIDFD(s)) {
+                        /* yes, we have pidfd */
 
-                case SOURCE_IO:
-                        r = source_io_register(s, m, s->io.events);
+                        r = source_child_pidfd_register(s, enabled);
                         if (r < 0)
                                 return r;
+                } else {
+                        /* no pidfd, or something other to watch for than WEXITED */
 
-                        s->enabled = m;
-                        break;
-
-                case SOURCE_TIME_REALTIME:
-                case SOURCE_TIME_BOOTTIME:
-                case SOURCE_TIME_MONOTONIC:
-                case SOURCE_TIME_REALTIME_ALARM:
-                case SOURCE_TIME_BOOTTIME_ALARM: {
-                        struct clock_data *d;
-
-                        s->enabled = m;
-                        d = event_get_clock_data(s->event, s->type);
-                        assert(d);
-
-                        prioq_reshuffle(d->earliest, s, &s->time.earliest_index);
-                        prioq_reshuffle(d->latest, s, &s->time.latest_index);
-                        d->needs_rearm = true;
-                        break;
+                        r = event_make_signal_data(s->event, SIGCHLD, NULL);
+                        if (r < 0) {
+                                event_gc_signal_data(s->event, &s->priority, SIGCHLD);
+                                return r;
+                        }
                 }
 
-                case SOURCE_SIGNAL:
+                if (!was_online)
+                        s->event->n_online_child_sources++;
+                break;
 
-                        s->enabled = m;
+        case SOURCE_TIME_REALTIME:
+        case SOURCE_TIME_BOOTTIME:
+        case SOURCE_TIME_MONOTONIC:
+        case SOURCE_TIME_REALTIME_ALARM:
+        case SOURCE_TIME_BOOTTIME_ALARM:
+        case SOURCE_EXIT:
+        case SOURCE_DEFER:
+        case SOURCE_POST:
+        case SOURCE_INOTIFY:
+                break;
 
-                        r = event_make_signal_data(s->event, s->signal.sig, NULL);
-                        if (r < 0) {
-                                s->enabled = SD_EVENT_OFF;
-                                event_gc_signal_data(s->event, &s->priority, s->signal.sig);
-                                return r;
-                        }
+        default:
+                assert_not_reached("Wut? I shouldn't exist.");
+        }
 
-                        break;
+        s->enabled = enabled;
+        s->ratelimited = ratelimited;
 
-                case SOURCE_CHILD:
+        /* Non-failing operations below */
+        switch (s->type) {
+        case SOURCE_TIME_REALTIME:
+        case SOURCE_TIME_BOOTTIME:
+        case SOURCE_TIME_MONOTONIC:
+        case SOURCE_TIME_REALTIME_ALARM:
+        case SOURCE_TIME_BOOTTIME_ALARM:
+                event_source_time_prioq_reshuffle(s);
+                break;
 
-                        if (s->enabled == SD_EVENT_OFF)
-                                s->event->n_enabled_child_sources++;
+        case SOURCE_EXIT:
+                prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
+                break;
 
-                        s->enabled = m;
+        default:
+                break;
+        }
 
-                        if (EVENT_SOURCE_WATCH_PIDFD(s)) {
-                                /* yes, we have pidfd */
+        return 1;
+}
 
-                                r = source_child_pidfd_register(s, s->enabled);
-                                if (r < 0) {
-                                        s->enabled = SD_EVENT_OFF;
-                                        s->event->n_enabled_child_sources--;
-                                        return r;
-                                }
-                        } else {
-                                /* no pidfd, or something other to watch for than WEXITED */
+_public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
+        int r;
 
-                                r = event_make_signal_data(s->event, SIGCHLD, NULL);
-                                if (r < 0) {
-                                        s->enabled = SD_EVENT_OFF;
-                                        s->event->n_enabled_child_sources--;
-                                        event_gc_signal_data(s->event, &s->priority, SIGCHLD);
-                                        return r;
-                                }
-                        }
+        assert_return(s, -EINVAL);
+        assert_return(IN_SET(m, SD_EVENT_OFF, SD_EVENT_ON, SD_EVENT_ONESHOT), -EINVAL);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
 
-                        break;
+        /* If we are dead anyway, we are fine with turning off sources, but everything else needs to fail. */
+        if (s->event->state == SD_EVENT_FINISHED)
+                return m == SD_EVENT_OFF ? 0 : -ESTALE;
 
-                case SOURCE_EXIT:
-                        s->enabled = m;
-                        prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
-                        break;
+        if (s->enabled == m) /* No change? */
+                return 0;
 
-                case SOURCE_DEFER:
-                case SOURCE_POST:
-                case SOURCE_INOTIFY:
+        if (m == SD_EVENT_OFF)
+                r = event_source_offline(s, m, s->ratelimited);
+        else {
+                if (s->enabled != SD_EVENT_OFF) {
+                        /* Switching from "on" to "oneshot" or back? If that's the case, we can take a shortcut, the
+                         * event source is already enabled after all. */
                         s->enabled = m;
-                        break;
-
-                default:
-                        assert_not_reached("Wut? I shouldn't exist.");
+                        return 0;
                 }
-        }
-
-        if (s->pending)
-                prioq_reshuffle(s->event->pending, s, &s->pending_index);
 
-        if (s->prepare)
-                prioq_reshuffle(s->event->prepare, s, &s->prepare_index);
+                r = event_source_online(s, m, s->ratelimited);
+        }
+        if (r < 0)
+                return r;
 
+        event_source_pp_prioq_reshuffle(s);
         return 0;
 }
 
@@ -2407,7 +2578,6 @@ _public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) {
 }
 
 _public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) {
-        struct clock_data *d;
         int r;
 
         assert_return(s, -EINVAL);
@@ -2421,13 +2591,7 @@ _public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) {
 
         s->time.next = usec;
 
-        d = event_get_clock_data(s->event, s->type);
-        assert(d);
-
-        prioq_reshuffle(d->earliest, s, &s->time.earliest_index);
-        prioq_reshuffle(d->latest, s, &s->time.latest_index);
-        d->needs_rearm = true;
-
+        event_source_time_prioq_reshuffle(s);
         return 0;
 }
 
@@ -2459,7 +2623,6 @@ _public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *use
 }
 
 _public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec) {
-        struct clock_data *d;
         int r;
 
         assert_return(s, -EINVAL);
@@ -2477,12 +2640,7 @@ _public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec
 
         s->time.accuracy = usec;
 
-        d = event_get_clock_data(s->event, s->type);
-        assert(d);
-
-        prioq_reshuffle(d->latest, s, &s->time.latest_index);
-        d->needs_rearm = true;
-
+        event_source_time_prioq_reshuffle(s);
         return 0;
 }
 
@@ -2657,6 +2815,96 @@ _public_ void *sd_event_source_set_userdata(sd_event_source *s, void *userdata)
         return ret;
 }
 
+static int event_source_enter_ratelimited(sd_event_source *s) {
+        int r;
+
+        assert(s);
+
+        /* When an event source becomes ratelimited, we place it in the CLOCK_MONOTONIC priority queue, with
+         * the end of the rate limit time window, much as if it was a timer event source. */
+
+        if (s->ratelimited)
+                return 0; /* Already ratelimited, this is a NOP hence */
+
+        /* Make sure we can install a CLOCK_MONOTONIC event further down. */
+        r = setup_clock_data(s->event, &s->event->monotonic, CLOCK_MONOTONIC);
+        if (r < 0)
+                return r;
+
+        /* Timer event sources are already using the earliest/latest queues for the timer scheduling. Let's
+         * first remove them from the prioq appropriate for their own clock, so that we can use the prioq
+         * fields of the event source then for adding it to the CLOCK_MONOTONIC prioq instead. */
+        if (EVENT_SOURCE_IS_TIME(s->type))
+                event_source_time_prioq_remove(s, event_get_clock_data(s->event, s->type));
+
+        /* Now, let's add the event source to the monotonic clock instead */
+        r = event_source_time_prioq_put(s, &s->event->monotonic);
+        if (r < 0)
+                goto fail;
+
+        /* And let's take the event source officially offline */
+        r = event_source_offline(s, s->enabled, /* ratelimited= */ true);
+        if (r < 0) {
+                event_source_time_prioq_remove(s, &s->event->monotonic);
+                goto fail;
+        }
+
+        event_source_pp_prioq_reshuffle(s);
+
+        log_debug("Event source %p (%s) entered rate limit state.", s, strna(s->description));
+        return 0;
+
+fail:
+        /* Reinstall time event sources in the priority queue as before. This shouldn't fail, since the queue
+         * space for it should already be allocated. */
+        if (EVENT_SOURCE_IS_TIME(s->type))
+                assert_se(event_source_time_prioq_put(s, event_get_clock_data(s->event, s->type)) >= 0);
+
+        return r;
+}
+
+static int event_source_leave_ratelimit(sd_event_source *s) {
+        int r;
+
+        assert(s);
+
+        if (!s->ratelimited)
+                return 0;
+
+        /* Let's take the event source out of the monotonic prioq first. */
+        event_source_time_prioq_remove(s, &s->event->monotonic);
+
+        /* Let's then add the event source to its native clock prioq again — if this is a timer event source */
+        if (EVENT_SOURCE_IS_TIME(s->type)) {
+                r = event_source_time_prioq_put(s, event_get_clock_data(s->event, s->type));
+                if (r < 0)
+                        goto fail;
+        }
+
+        /* Let's try to take it online again.  */
+        r = event_source_online(s, s->enabled, /* ratelimited= */ false);
+        if (r < 0) {
+                /* Do something roughly sensible when this failed: undo the two prioq ops above */
+                if (EVENT_SOURCE_IS_TIME(s->type))
+                        event_source_time_prioq_remove(s, event_get_clock_data(s->event, s->type));
+
+                goto fail;
+        }
+
+        event_source_pp_prioq_reshuffle(s);
+        ratelimit_reset(&s->rate_limit);
+
+        log_debug("Event source %p (%s) left rate limit state.", s, strna(s->description));
+        return 0;
+
+fail:
+        /* Do something somewhat reasonable when we cannot move an event sources out of ratelimited mode:
+         * simply put it back in it, maybe we can then process it more successfully next iteration. */
+        assert_se(event_source_time_prioq_put(s, &s->event->monotonic) >= 0);
+
+        return r;
+}
+
 static usec_t sleep_between(sd_event *e, usec_t a, usec_t b) {
         usec_t c;
         assert(e);
@@ -2744,7 +2992,6 @@ static int event_arm_timer(
         struct itimerspec its = {};
         sd_event_source *a, *b;
         usec_t t;
-        int r;
 
         assert(e);
         assert(d);
@@ -2755,7 +3002,7 @@ static int event_arm_timer(
                 d->needs_rearm = false;
 
         a = prioq_peek(d->earliest);
-        if (!a || a->enabled == SD_EVENT_OFF || a->time.next == USEC_INFINITY) {
+        if (!a || a->enabled == SD_EVENT_OFF || time_event_source_next(a) == USEC_INFINITY) {
 
                 if (d->fd < 0)
                         return 0;
@@ -2764,9 +3011,8 @@ static int event_arm_timer(
                         return 0;
 
                 /* disarm */
-                r = timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL);
-                if (r < 0)
-                        return r;
+                if (timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL) < 0)
+                        return -errno;
 
                 d->next = USEC_INFINITY;
                 return 0;
@@ -2775,7 +3021,7 @@ static int event_arm_timer(
         b = prioq_peek(d->latest);
         assert_se(b && b->enabled != SD_EVENT_OFF);
 
-        t = sleep_between(e, a->time.next, time_event_source_latest(b));
+        t = sleep_between(e, time_event_source_next(a), time_event_source_latest(b));
         if (d->next == t)
                 return 0;
 
@@ -2788,8 +3034,7 @@ static int event_arm_timer(
         } else
                 timespec_store(&its.it_value, t);
 
-        r = timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL);
-        if (r < 0)
+        if (timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL) < 0)
                 return -errno;
 
         d->next = t;
@@ -2854,19 +3099,29 @@ static int process_timer(
 
         for (;;) {
                 s = prioq_peek(d->earliest);
-                if (!s ||
-                    s->time.next > n ||
-                    s->enabled == SD_EVENT_OFF ||
-                    s->pending)
+                if (!s || time_event_source_next(s) > n)
+                        break;
+
+                if (s->ratelimited) {
+                        /* This is an event sources whose ratelimit window has ended. Let's turn it on
+                         * again. */
+                        assert(s->ratelimited);
+
+                        r = event_source_leave_ratelimit(s);
+                        if (r < 0)
+                                return r;
+
+                        continue;
+                }
+
+                if (s->enabled == SD_EVENT_OFF || s->pending)
                         break;
 
                 r = source_set_pending(s, true);
                 if (r < 0)
                         return r;
 
-                prioq_reshuffle(d->earliest, s, &s->time.earliest_index);
-                prioq_reshuffle(d->latest, s, &s->time.latest_index);
-                d->needs_rearm = true;
+                event_source_time_prioq_reshuffle(s);
         }
 
         return 0;
@@ -2904,7 +3159,7 @@ static int process_child(sd_event *e) {
                 if (s->pending)
                         continue;
 
-                if (s->enabled == SD_EVENT_OFF)
+                if (event_source_is_offline(s))
                         continue;
 
                 if (s->child.exited)
@@ -2914,9 +3169,8 @@ static int process_child(sd_event *e) {
                         continue;
 
                 zero(s->child.siginfo);
-                r = waitid(P_PID, s->child.pid, &s->child.siginfo,
-                           WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options);
-                if (r < 0)
+                if (waitid(P_PID, s->child.pid, &s->child.siginfo,
+                           WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options) < 0)
                         return -errno;
 
                 if (s->child.siginfo.si_pid != 0) {
@@ -2952,7 +3206,7 @@ static int process_pidfd(sd_event *e, sd_event_source *s, uint32_t revents) {
         if (s->pending)
                 return 0;
 
-        if (s->enabled == SD_EVENT_OFF)
+        if (event_source_is_offline(s))
                 return 0;
 
         if (!EVENT_SOURCE_WATCH_PIDFD(s))
@@ -3112,7 +3366,7 @@ static int event_inotify_data_process(sd_event *e, struct inotify_data *d) {
 
                                 LIST_FOREACH(inotify.by_inode_data, s, inode_data->event_sources) {
 
-                                        if (s->enabled == SD_EVENT_OFF)
+                                        if (event_source_is_offline(s))
                                                 continue;
 
                                         r = source_set_pending(s, true);
@@ -3148,7 +3402,7 @@ static int event_inotify_data_process(sd_event *e, struct inotify_data *d) {
                          * sources if IN_IGNORED or IN_UNMOUNT is set. */
                         LIST_FOREACH(inotify.by_inode_data, s, inode_data->event_sources) {
 
-                                if (s->enabled == SD_EVENT_OFF)
+                                if (event_source_is_offline(s))
                                         continue;
 
                                 if ((d->buffer.ev.mask & (IN_IGNORED|IN_UNMOUNT)) == 0 &&
@@ -3187,16 +3441,31 @@ static int process_inotify(sd_event *e) {
 }
 
 static int source_dispatch(sd_event_source *s) {
+        _cleanup_(sd_event_unrefp) sd_event *saved_event = NULL;
         EventSourceType saved_type;
         int r = 0;
 
         assert(s);
         assert(s->pending || s->type == SOURCE_EXIT);
 
-        /* Save the event source type, here, so that we still know it after the event callback which might invalidate
-         * the event. */
+        /* Save the event source type, here, so that we still know it after the event callback which might
+         * invalidate the event. */
         saved_type = s->type;
 
+        /* Similar, store a reference to the event loop object, so that we can still access it after the
+         * callback might have invalidated/disconnected the event source. */
+        saved_event = sd_event_ref(s->event);
+
+        /* Check if we hit the ratelimit for this event source, if so, let's disable it. */
+        assert(!s->ratelimited);
+        if (!ratelimit_below(&s->rate_limit)) {
+                r = event_source_enter_ratelimited(s);
+                if (r < 0)
+                        return r;
+
+                return 1;
+        }
+
         if (!IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) {
                 r = source_set_pending(s, false);
                 if (r < 0)
@@ -3210,7 +3479,7 @@ static int source_dispatch(sd_event_source *s) {
                  * post sources as pending */
 
                 SET_FOREACH(z, s->event->post_sources) {
-                        if (z->enabled == SD_EVENT_OFF)
+                        if (event_source_is_offline(z))
                                 continue;
 
                         r = source_set_pending(z, true);
@@ -3303,9 +3572,15 @@ static int source_dispatch(sd_event_source *s) {
 
         s->dispatching = false;
 
-        if (r < 0)
-                log_debug_errno(r, "Event source %s (type %s) returned error, disabling: %m",
-                                strna(s->description), event_source_type_to_string(saved_type));
+        if (r < 0) {
+                log_debug_errno(r, "Event source %s (type %s) returned error, %s: %m",
+                                strna(s->description),
+                                event_source_type_to_string(saved_type),
+                                s->exit_on_failure ? "exiting" : "disabling");
+
+                if (s->exit_on_failure)
+                        (void) sd_event_exit(saved_event, r);
+        }
 
         if (s->n_ref == 0)
                 source_free(s);
@@ -3324,7 +3599,7 @@ static int event_prepare(sd_event *e) {
                 sd_event_source *s;
 
                 s = prioq_peek(e->prepare);
-                if (!s || s->prepare_iteration == e->iteration || s->enabled == SD_EVENT_OFF)
+                if (!s || s->prepare_iteration == e->iteration || event_source_is_offline(s))
                         break;
 
                 s->prepare_iteration = e->iteration;
@@ -3338,9 +3613,15 @@ static int event_prepare(sd_event *e) {
                 r = s->prepare(s, s->userdata);
                 s->dispatching = false;
 
-                if (r < 0)
-                        log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, disabling: %m",
-                                        strna(s->description), event_source_type_to_string(s->type));
+                if (r < 0) {
+                        log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, %s: %m",
+                                        strna(s->description),
+                                        event_source_type_to_string(s->type),
+                                        s->exit_on_failure ? "exiting" : "disabling");
+
+                        if (s->exit_on_failure)
+                                (void) sd_event_exit(e, r);
+                }
 
                 if (s->n_ref == 0)
                         source_free(s);
@@ -3353,18 +3634,17 @@ static int event_prepare(sd_event *e) {
 
 static int dispatch_exit(sd_event *e) {
         sd_event_source *p;
-        _cleanup_(sd_event_unrefp) sd_event *ref = NULL;
         int r;
 
         assert(e);
 
         p = prioq_peek(e->exit);
-        if (!p || p->enabled == SD_EVENT_OFF) {
+        if (!p || event_source_is_offline(p)) {
                 e->state = SD_EVENT_FINISHED;
                 return 0;
         }
 
-        ref = sd_event_ref(e);
+        _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e);
         e->iteration++;
         e->state = SD_EVENT_EXITING;
         r = source_dispatch(p);
@@ -3381,7 +3661,7 @@ static sd_event_source* event_next_pending(sd_event *e) {
         if (!p)
                 return NULL;
 
-        if (p->enabled == SD_EVENT_OFF)
+        if (event_source_is_offline(p))
                 return NULL;
 
         return p;
@@ -3390,7 +3670,6 @@ static sd_event_source* event_next_pending(sd_event *e) {
 static int arm_watchdog(sd_event *e) {
         struct itimerspec its = {};
         usec_t t;
-        int r;
 
         assert(e);
         assert(e->watchdog_fd >= 0);
@@ -3406,8 +3685,7 @@ static int arm_watchdog(sd_event *e) {
         if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0)
                 its.it_value.tv_nsec = 1;
 
-        r = timerfd_settime(e->watchdog_fd, TFD_TIMER_ABSTIME, &its, NULL);
-        if (r < 0)
+        if (timerfd_settime(e->watchdog_fd, TFD_TIMER_ABSTIME, &its, NULL) < 0)
                 return -errno;
 
         return 0;
@@ -3462,6 +3740,9 @@ _public_ int sd_event_prepare(sd_event *e) {
          * syscalls */
         assert_return(!e->default_event_ptr || e->tid == gettid(), -EREMOTEIO);
 
+        /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */
+        _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e);
+
         if (e->exit_requested)
                 goto pending;
 
@@ -3667,9 +3948,8 @@ _public_ int sd_event_dispatch(sd_event *e) {
 
         p = event_next_pending(e);
         if (p) {
-                _cleanup_(sd_event_unrefp) sd_event *ref = NULL;
+                _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e);
 
-                ref = sd_event_ref(e);
                 e->state = SD_EVENT_RUNNING;
                 r = source_dispatch(p);
                 e->state = SD_EVENT_INITIAL;
@@ -3703,29 +3983,32 @@ _public_ int sd_event_run(sd_event *e, uint64_t timeout) {
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
 
-        if (e->profile_delays && e->last_run) {
+        if (e->profile_delays && e->last_run_usec != 0) {
                 usec_t this_run;
                 unsigned l;
 
                 this_run = now(CLOCK_MONOTONIC);
 
-                l = u64log2(this_run - e->last_run);
-                assert(l < sizeof(e->delays));
+                l = u64log2(this_run - e->last_run_usec);
+                assert(l < ELEMENTSOF(e->delays));
                 e->delays[l]++;
 
-                if (this_run - e->last_log >= 5*USEC_PER_SEC) {
+                if (this_run - e->last_log_usec >= 5*USEC_PER_SEC) {
                         event_log_delays(e);
-                        e->last_log = this_run;
+                        e->last_log_usec = this_run;
                 }
         }
 
+        /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */
+        _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = sd_event_ref(e);
+
         r = sd_event_prepare(e);
         if (r == 0)
                 /* There was nothing? Then wait... */
                 r = sd_event_wait(e, timeout);
 
         if (e->profile_delays)
-                e->last_run = now(CLOCK_MONOTONIC);
+                e->last_run_usec = now(CLOCK_MONOTONIC);
 
         if (r > 0) {
                 /* There's something now, then let's dispatch it */
@@ -3740,7 +4023,6 @@ _public_ int sd_event_run(sd_event *e, uint64_t timeout) {
 }
 
 _public_ int sd_event_loop(sd_event *e) {
-        _cleanup_(sd_event_unrefp) sd_event *ref = NULL;
         int r;
 
         assert_return(e, -EINVAL);
@@ -3748,7 +4030,7 @@ _public_ int sd_event_loop(sd_event *e) {
         assert_return(!event_pid_changed(e), -ECHILD);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
 
-        ref = sd_event_ref(e);
+        _unused_ _cleanup_(sd_event_unrefp) sd_event *ref = NULL;
 
         while (e->state != SD_EVENT_FINISHED) {
                 r = sd_event_run(e, (uint64_t) -1);
@@ -3760,7 +4042,6 @@ _public_ int sd_event_loop(sd_event *e) {
 }
 
 _public_ int sd_event_get_fd(sd_event *e) {
-
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(!event_pid_changed(e), -ECHILD);
@@ -3817,8 +4098,7 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) {
                 return -EOPNOTSUPP;
 
         if (!triple_timestamp_is_set(&e->timestamp)) {
-                /* Implicitly fall back to now() if we never ran
-                 * before and thus have no cached time. */
+                /* Implicitly fall back to now() if we never ran before and thus have no cached time. */
                 *usec = now(clock);
                 return 1;
         }
@@ -3898,8 +4178,7 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) {
                         .data.ptr = INT_TO_PTR(SOURCE_WATCHDOG),
                 };
 
-                r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, e->watchdog_fd, &ev);
-                if (r < 0) {
+                if (epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, e->watchdog_fd, &ev) < 0) {
                         r = -errno;
                         goto fail;
                 }
@@ -3979,4 +4258,72 @@ _public_ int sd_event_source_set_floating(sd_event_source *s, int b) {
 
         return 1;
 }
+
+_public_ int sd_event_source_get_exit_on_failure(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type != SOURCE_EXIT, -EDOM);
+
+        return s->exit_on_failure;
+}
+
+_public_ int sd_event_source_set_exit_on_failure(sd_event_source *s, int b) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type != SOURCE_EXIT, -EDOM);
+
+        if (s->exit_on_failure == !!b)
+                return 0;
+
+        s->exit_on_failure = b;
+        return 1;
+}
+
+_public_ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval, unsigned burst) {
+        int r;
+
+        assert_return(s, -EINVAL);
+
+        /* Turning on ratelimiting on event source types that don't support it, is a loggable offense. Doing
+         * so is a programming error. */
+        assert_return(EVENT_SOURCE_CAN_RATE_LIMIT(s->type), -EDOM);
+
+        /* When ratelimiting is configured we'll always reset the rate limit state first and start fresh,
+         * non-ratelimited. */
+        r = event_source_leave_ratelimit(s);
+        if (r < 0)
+                return r;
+
+        s->rate_limit = (RateLimit) { interval, burst };
+        return 0;
+}
+
+_public_ int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval, unsigned *ret_burst) {
+        assert_return(s, -EINVAL);
+
+        /* Querying whether an event source has ratelimiting configured is not a loggable offsense, hence
+         * don't use assert_return(). Unlike turning on ratelimiting it's not really a programming error */
+        if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type))
+                return -EDOM;
+
+        if (!ratelimit_configured(&s->rate_limit))
+                return -ENOEXEC;
+
+        if (ret_interval)
+                *ret_interval = s->rate_limit.interval;
+        if (ret_burst)
+                *ret_burst = s->rate_limit.burst;
+
+        return 0;
+}
+
+_public_ int sd_event_source_is_ratelimited(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+
+        if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type))
+                return false;
+
+        if (!ratelimit_configured(&s->rate_limit))
+                return false;
+
+        return s->ratelimited;
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.c b/src/core/systemd/src/libsystemd/sd-id128/id128-util.c
index 3cb96378..b61cfdb0 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.c
+++ b/src/core/systemd/src/libsystemd/sd-id128/id128-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-core.h"
 
@@ -12,6 +12,7 @@
 #include "id128-util.h"
 #include "io-util.h"
 #include "stdio-util.h"
+#include "string-util.h"
 
 #if 0 /* NM_IGNORED */
 char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]) {
@@ -101,6 +102,11 @@ int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) {
 
         switch (l) {
 
+        case 13:
+        case 14:
+                /* Treat an "uninitialized" id file like an empty one */
+                return f == ID128_PLAIN_OR_UNINIT && strneq(buffer, "uninitialized\n", l) ? -ENOMEDIUM : -EINVAL;
+
         case 33: /* plain UUID with trailing newline */
                 if (buffer[32] != '\n')
                         return -EINVAL;
@@ -119,7 +125,7 @@ int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) {
 
                 _fallthrough_;
         case 36: /* RFC UUID without trailing newline */
-                if (f == ID128_PLAIN)
+                if (IN_SET(f, ID128_PLAIN, ID128_PLAIN_OR_UNINIT))
                         return -EINVAL;
 
                 buffer[36] = 0;
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.h b/src/core/systemd/src/libsystemd/sd-id128/id128-util.h
index 1901bf11..6b09bcd9 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.h
+++ b/src/core/systemd/src/libsystemd/sd-id128/id128-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
@@ -17,6 +17,10 @@ bool id128_is_valid(const char *s) _pure_;
 typedef enum Id128Format {
         ID128_ANY,
         ID128_PLAIN,  /* formatted as 32 hex chars as-is */
+        ID128_PLAIN_OR_UNINIT,  /* formatted as 32 hex chars as-is; allow special "uninitialized"
+                                 * value when reading from file (id128_read() and id128_read_fd()).
+                                 *
+                                 * This format should be used when reading a machine-id file. */
         ID128_UUID,   /* formatted as 36 character uuid string */
         _ID128_FORMAT_MAX,
 } Id128Format;
diff --git a/src/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/core/systemd/src/libsystemd/sd-id128/sd-id128.c
index d585ff25..64b8c787 100644
--- a/src/systemd/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/core/systemd/src/libsystemd/sd-id128/sd-id128.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-core.h"
 
diff --git a/src/systemd/src/systemd/_sd-common.h b/src/core/systemd/src/systemd/_sd-common.h
index 1055b00d..e3de2ae5 100644
--- a/src/systemd/src/systemd/_sd-common.h
+++ b/src/core/systemd/src/systemd/_sd-common.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosdcommonhfoo
 #define foosdcommonhfoo
 
diff --git a/src/systemd/src/systemd/sd-dhcp-client.h b/src/core/systemd/src/systemd/sd-dhcp-client.h
index ac3b5b36..c35328a9 100644
--- a/src/systemd/src/systemd/sd-dhcp-client.h
+++ b/src/core/systemd/src/systemd/sd-dhcp-client.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosddhcpclienthfoo
 #define foosddhcpclienthfoo
 
@@ -40,6 +40,8 @@ enum {
         SD_DHCP_CLIENT_EVENT_EXPIRED            = 3,
         SD_DHCP_CLIENT_EVENT_RENEW              = 4,
         SD_DHCP_CLIENT_EVENT_SELECTING          = 5,
+        SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE  = 6, /* Sent when we have not received a reply after the first few attempts.
+                                                      * The client may want to start acquiring link-local addresses. */
 };
 
 enum {
@@ -126,6 +128,7 @@ int sd_dhcp_client_set_ifindex(
 int sd_dhcp_client_set_mac(
                 sd_dhcp_client *client,
                 const uint8_t *addr,
+                const uint8_t *bcast_addr,
                 size_t addr_len,
                 uint16_t arp_type);
 int sd_dhcp_client_set_client_id(
diff --git a/src/systemd/src/systemd/sd-dhcp-lease.h b/src/core/systemd/src/systemd/sd-dhcp-lease.h
index 17bd4918..c255a1f9 100644
--- a/src/systemd/src/systemd/sd-dhcp-lease.h
+++ b/src/core/systemd/src/systemd/sd-dhcp-lease.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosddhcpleasehfoo
 #define foosddhcpleasehfoo
 
diff --git a/src/systemd/src/systemd/sd-dhcp-option.h b/src/core/systemd/src/systemd/sd-dhcp-option.h
index 45dbd279..71aa479b 100644
--- a/src/systemd/src/systemd/sd-dhcp-option.h
+++ b/src/core/systemd/src/systemd/sd-dhcp-option.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosddhcpoptionhfoo
 #define foosddhcpoptionhfoo
 
diff --git a/src/systemd/src/systemd/sd-dhcp6-client.h b/src/core/systemd/src/systemd/sd-dhcp6-client.h
index 2b0d63a5..75ee27d6 100644
--- a/src/systemd/src/systemd/sd-dhcp6-client.h
+++ b/src/core/systemd/src/systemd/sd-dhcp6-client.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosddhcp6clienthfoo
 #define foosddhcp6clienthfoo
 
diff --git a/src/systemd/src/systemd/sd-dhcp6-lease.h b/src/core/systemd/src/systemd/sd-dhcp6-lease.h
index 240df74a..f77b31ac 100644
--- a/src/systemd/src/systemd/sd-dhcp6-lease.h
+++ b/src/core/systemd/src/systemd/sd-dhcp6-lease.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosddhcp6leasehfoo
 #define foosddhcp6leasehfoo
 
diff --git a/src/systemd/src/systemd/sd-dhcp6-option.h b/src/core/systemd/src/systemd/sd-dhcp6-option.h
index 88a49863..ddb2c7ce 100644
--- a/src/systemd/src/systemd/sd-dhcp6-option.h
+++ b/src/core/systemd/src/systemd/sd-dhcp6-option.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosddhcp6optionhfoo
 #define foosddhcp6optionhfoo
 
diff --git a/src/systemd/src/systemd/sd-event.h b/src/core/systemd/src/systemd/sd-event.h
index dc96bfa6..2ae2a0da 100644
--- a/src/systemd/src/systemd/sd-event.h
+++ b/src/core/systemd/src/systemd/sd-event.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosdeventhfoo
 #define foosdeventhfoo
 
@@ -160,6 +160,11 @@ int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t
 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);
+int sd_event_source_get_exit_on_failure(sd_event_source *s);
+int sd_event_source_set_exit_on_failure(sd_event_source *s, int b);
+int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval_usec, unsigned burst);
+int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval_usec, unsigned *ret_burst);
+int sd_event_source_is_ratelimited(sd_event_source *s);
 
 /* 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/core/systemd/src/systemd/sd-id128.h
index 9b00b76e..02aa318a 100644
--- a/src/systemd/src/systemd/sd-id128.h
+++ b/src/core/systemd/src/systemd/sd-id128.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosdid128hfoo
 #define foosdid128hfoo
 
diff --git a/src/systemd/src/systemd/sd-ipv4acd.h b/src/core/systemd/src/systemd/sd-ipv4acd.h
index ebf723fc..2809d874 100644
--- a/src/systemd/src/systemd/sd-ipv4acd.h
+++ b/src/core/systemd/src/systemd/sd-ipv4acd.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosdipv4acdfoo
 #define foosdipv4acdfoo
 
@@ -43,6 +43,8 @@ int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address);
 int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata);
 int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr);
 int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int interface_index);
+int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd);
+const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd);
 int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address);
 int sd_ipv4acd_is_running(sd_ipv4acd *acd);
 int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts);
diff --git a/src/systemd/src/systemd/sd-ipv4ll.h b/src/core/systemd/src/systemd/sd-ipv4ll.h
index 71bd4cfe..aa4d174e 100644
--- a/src/systemd/src/systemd/sd-ipv4ll.h
+++ b/src/core/systemd/src/systemd/sd-ipv4ll.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosdipv4llfoo
 #define foosdipv4llfoo
 
@@ -43,6 +43,8 @@ int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address);
 int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata);
 int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr);
 int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int interface_index);
+int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll);
+const char *sd_ipv4ll_get_ifname(sd_ipv4ll *ll);
 int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address);
 int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed);
 int sd_ipv4ll_is_running(sd_ipv4ll *ll);
diff --git a/src/systemd/src/systemd/sd-lldp.h b/src/core/systemd/src/systemd/sd-lldp.h
index c2abc201..f551f6b4 100644
--- a/src/systemd/src/systemd/sd-lldp.h
+++ b/src/core/systemd/src/systemd/sd-lldp.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosdlldphfoo
 #define foosdlldphfoo
 
diff --git a/src/systemd/src/systemd/sd-ndisc.h b/src/core/systemd/src/systemd/sd-ndisc.h
index 3ddfc8cb..c0e37899 100644
--- a/src/systemd/src/systemd/sd-ndisc.h
+++ b/src/core/systemd/src/systemd/sd-ndisc.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #ifndef foosdndiscfoo
 #define foosdndiscfoo
 
diff --git a/src/tests/config/NetworkManager-warn.conf b/src/core/tests/config/NetworkManager-warn.conf
index 80df7c52..80df7c52 100644
--- a/src/tests/config/NetworkManager-warn.conf
+++ b/src/core/tests/config/NetworkManager-warn.conf
diff --git a/src/tests/config/NetworkManager.conf b/src/core/tests/config/NetworkManager.conf
index a447b6d6..a447b6d6 100644
--- a/src/tests/config/NetworkManager.conf
+++ b/src/core/tests/config/NetworkManager.conf
diff --git a/src/tests/config/NetworkManager.state b/src/core/tests/config/NetworkManager.state
index 1b9d604a..1b9d604a 100644
--- a/src/tests/config/NetworkManager.state
+++ b/src/core/tests/config/NetworkManager.state
diff --git a/src/tests/config/bad.conf b/src/core/tests/config/bad.conf
index 20d6a67a..20d6a67a 100644
--- a/src/tests/config/bad.conf
+++ b/src/core/tests/config/bad.conf
diff --git a/src/tests/config/conf.d/00-overrides.conf b/src/core/tests/config/conf.d/00-overrides.conf
index f26ed93b..f26ed93b 100644
--- a/src/tests/config/conf.d/00-overrides.conf
+++ b/src/core/tests/config/conf.d/00-overrides.conf
diff --git a/src/tests/config/conf.d/10-more.conf b/src/core/tests/config/conf.d/10-more.conf
index eadb7f96..eadb7f96 100644
--- a/src/tests/config/conf.d/10-more.conf
+++ b/src/core/tests/config/conf.d/10-more.conf
diff --git a/src/tests/config/conf.d/20-config-enable-1.conf b/src/core/tests/config/conf.d/20-config-enable-1.conf
index e6800f52..e6800f52 100644
--- a/src/tests/config/conf.d/20-config-enable-1.conf
+++ b/src/core/tests/config/conf.d/20-config-enable-1.conf
diff --git a/src/tests/config/conf.d/90-last.conf b/src/core/tests/config/conf.d/90-last.conf
index 7d078788..7d078788 100644
--- a/src/tests/config/conf.d/90-last.conf
+++ b/src/core/tests/config/conf.d/90-last.conf
diff --git a/src/tests/config/global-dns-invalid.conf b/src/core/tests/config/global-dns-invalid.conf
index e490f609..e490f609 100644
--- a/src/tests/config/global-dns-invalid.conf
+++ b/src/core/tests/config/global-dns-invalid.conf
diff --git a/src/tests/config/meson.build b/src/core/tests/config/meson.build
index cfe9c615..d0d8b1cf 100644
--- a/src/tests/config/meson.build
+++ b/src/core/tests/config/meson.build
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 test_config_dir = meson.current_source_dir()
 
@@ -12,7 +12,7 @@ sources = files(
 exe = executable(
   test_unit,
   sources,
-  dependencies: libnetwork_manager_test_dep,
+  dependencies: libNetworkManagerTest_dep,
   c_args: test_c_flags,
 )
 
diff --git a/src/tests/config/nm-test-device.c b/src/core/tests/config/nm-test-device.c
index e897f68e..f724b03a 100644
--- a/src/tests/config/nm-test-device.c
+++ b/src/core/tests/config/nm-test-device.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-test-device.h"
 
diff --git a/src/tests/config/nm-test-device.h b/src/core/tests/config/nm-test-device.h
index cba17151..638177a0 100644
--- a/src/tests/config/nm-test-device.h
+++ b/src/core/tests/config/nm-test-device.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
diff --git a/src/tests/config/test-config.c b/src/core/tests/config/test-config.c
index b2f07b26..3bb14815 100644
--- a/src/tests/config/test-config.c
+++ b/src/core/tests/config/test-config.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <unistd.h>
 
@@ -16,8 +16,8 @@
 
 #include "nm-test-utils-core.h"
 
-#define TEST_DIR  NM_BUILD_SRCDIR "/src/tests/config"
-#define BUILD_DIR NM_BUILD_BUILDDIR "/src/tests/config"
+#define TEST_DIR  NM_BUILD_SRCDIR "/src/core/tests/config"
+#define BUILD_DIR NM_BUILD_BUILDDIR "/src/core/tests/config"
 
 /*****************************************************************************/
 
diff --git a/src/tests/meson.build b/src/core/tests/meson.build
index 8ee8bd93..994563c4 100644
--- a/src/tests/meson.build
+++ b/src/core/tests/meson.build
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 subdir('config')
 
@@ -17,7 +17,7 @@ foreach test_unit: test_units
   exe = executable(
     test_unit,
     test_unit + '.c',
-    dependencies: libnetwork_manager_test_dep,
+    dependencies: libNetworkManagerTest_dep,
     c_args: test_c_flags,
   )
 
@@ -29,28 +29,24 @@ foreach test_unit: test_units
   )
 endforeach
 
-test_unit = 'test-systemd'
-
-c_flags = [
-  '-DNETWORKMANAGER_COMPILATION_TEST',
-  '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD',
-]
-
-links = [
-  libnm_systemd_core,
-  libnm_systemd_shared,
-]
-
 exe = executable(
-  test_unit,
-  test_unit + '.c',
-  dependencies: daemon_nm_default_dep,
-  c_args: c_flags,
-  link_with: links,
+  'test-systemd',
+  'test-systemd.c',
+  include_directories: [
+    top_inc,
+    src_inc,
+  ],
+  dependencies: [
+    libnm_systemd_core_dep,
+    libnm_systemd_shared_dep,
+  ],
+  c_args: [
+    '-DG_LOG_DOMAIN="test"',
+  ],
 )
 
 test(
-  test_unit,
+  'test-systemd',
   test_script,
   args: test_args + [exe.full_path()],
 )
diff --git a/src/tests/test-core-with-expect.c b/src/core/tests/test-core-with-expect.c
index 62b93a04..022cf8f3 100644
--- a/src/tests/test-core-with-expect.c
+++ b/src/core/tests/test-core-with-expect.c
@@ -1,12 +1,11 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <time.h>
-#include <netinet/ether.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <fcntl.h>
@@ -236,7 +235,7 @@ do_test_nm_utils_kill_child(void)
     char *argv1[] = {
         "bash",
         "-c",
-        "trap \"sleep 0.3; exit 10\" EXIT; "
+        "trap \"sleep 0.5; exit 10\" EXIT; "
         "sleep 100000; exit $? #" TEST_TOKEN,
         NULL,
     };
@@ -335,7 +334,7 @@ do_test_nm_utils_kill_child(void)
      * is a race here. */
     NMTST_EXPECT_NM_ERROR(
         "kill child process 'test-s-3-2' (*): failed due to unexpected return value -1 by waitpid "
-        "(No child processes, 10) after sending no signal (0)");
+        "(No child process*, 10) after sending no signal (0)");
     test_nm_utils_kill_child_sync_do("test-s-3-2", pid3s, 0, 0, FALSE, NULL);
 
     NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-4' (*): waiting up to 1 milliseconds for "
@@ -398,7 +397,7 @@ do_test_nm_utils_kill_child(void)
      * is a race here. */
     NMTST_EXPECT_NM_ERROR(
         "kill child process 'test-a-3-2' (*): failed due to unexpected return value -1 by waitpid "
-        "(No child processes, 10) after sending no signal (0)");
+        "(No child process*, 10) after sending no signal (0)");
     NMTST_EXPECT_NM_DEBUG(
         "kill child process 'test-a-3-2' (*): invoke callback: killing child failed");
     test_nm_utils_kill_child_async_do("test-a-3-2", pid3a, 0, 0, FALSE, NULL);
@@ -562,30 +561,26 @@ test_nm_utils_array_remove_at_indexes(void)
 static void
 test_nm_ethernet_address_is_valid(void)
 {
-    g_assert(!nm_ethernet_address_is_valid(NULL, -1));
-    g_assert(!nm_ethernet_address_is_valid(NULL, ETH_ALEN));
+    g_assert(!nm_ether_addr_is_valid_str(NULL));
+    g_assert(!nm_ether_addr_is_valid(NULL));
 
-    g_assert(!nm_ethernet_address_is_valid("FF:FF:FF:FF:FF:FF", -1));
-    g_assert(!nm_ethernet_address_is_valid("00:00:00:00:00:00", -1));
-    g_assert(!nm_ethernet_address_is_valid("44:44:44:44:44:44", -1));
-    g_assert(!nm_ethernet_address_is_valid("00:30:b4:00:00:00", -1));
+    g_assert(!nm_ether_addr_is_valid_str("FF:FF:FF:FF:FF:FF"));
+    g_assert(!nm_ether_addr_is_valid_str("00:00:00:00:00:00"));
+    g_assert(!nm_ether_addr_is_valid_str("44:44:44:44:44:44"));
+    g_assert(!nm_ether_addr_is_valid_str("00:30:b4:00:00:00"));
 
-    g_assert(!nm_ethernet_address_is_valid("", -1));
-    g_assert(!nm_ethernet_address_is_valid("1", -1));
-    g_assert(!nm_ethernet_address_is_valid("2", -1));
+    g_assert(!nm_ether_addr_is_valid_str(""));
+    g_assert(!nm_ether_addr_is_valid_str("1"));
+    g_assert(!nm_ether_addr_is_valid_str("2"));
 
-    g_assert(
-        !nm_ethernet_address_is_valid(((guint8[8]){0x00, 0x30, 0xb4, 0x00, 0x00, 0x00}), ETH_ALEN));
-    g_assert(
-        nm_ethernet_address_is_valid(((guint8[8]){0x00, 0x30, 0xb4, 0x00, 0x00, 0x01}), ETH_ALEN));
+    g_assert(!nm_ether_addr_is_valid(&NM_ETHER_ADDR_INIT(0x00, 0x30, 0xb4, 0x00, 0x00, 0x00)));
+    g_assert(nm_ether_addr_is_valid(&NM_ETHER_ADDR_INIT(0x00, 0x30, 0xb4, 0x00, 0x00, 0x01)));
 
     /* some Broad cast addresses (with MSB of first octet set). */
-    g_assert(!nm_ethernet_address_is_valid("57:44:44:44:44:44", -1));
-    g_assert(nm_ethernet_address_is_valid("56:44:44:44:44:44", -1));
-    g_assert(
-        !nm_ethernet_address_is_valid(((guint8[8]){0x03, 0x30, 0xb4, 0x00, 0x00, 0x00}), ETH_ALEN));
-    g_assert(
-        nm_ethernet_address_is_valid(((guint8[8]){0x02, 0x30, 0xb4, 0x00, 0x00, 0x01}), ETH_ALEN));
+    g_assert(!nm_ether_addr_is_valid_str("57:44:44:44:44:44"));
+    g_assert(nm_ether_addr_is_valid_str("56:44:44:44:44:44"));
+    g_assert(!nm_ether_addr_is_valid(&NM_ETHER_ADDR_INIT(0x03, 0x30, 0xb4, 0x00, 0x00, 0x00)));
+    g_assert(nm_ether_addr_is_valid(&NM_ETHER_ADDR_INIT(0x02, 0x30, 0xb4, 0x00, 0x00, 0x01)));
 }
 
 /*****************************************************************************/
diff --git a/src/tests/test-core.c b/src/core/tests/test-core.c
index 37b4a625..8698c406 100644
--- a/src/tests/test-core.c
+++ b/src/core/tests/test-core.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <net/if.h>
 #include <byteswap.h>
@@ -105,6 +105,32 @@ test_logging_domains(void)
 /*****************************************************************************/
 
 static void
+test_logging_error(void)
+{
+    gs_free_error GError *error = NULL;
+    gboolean              success;
+
+    g_assert_cmpint(NM_MANAGER_ERROR, ==, _NM_MANAGER_ERROR);
+    G_STATIC_ASSERT_EXPR(NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL == _NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL);
+    G_STATIC_ASSERT_EXPR(NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN
+                         == _NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN);
+
+    success = nm_logging_setup("bogus", "ALL", NULL, &error);
+    nmtst_assert_no_success(success, error);
+    g_assert_cmpint(error->domain, ==, NM_MANAGER_ERROR);
+    g_assert_cmpint(error->code, ==, NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL);
+    nm_clear_pointer(&error, g_error_free);
+
+    success = nm_logging_setup("debug", "bogus", NULL, &error);
+    nmtst_assert_no_success(success, error);
+    g_assert_cmpint(error->domain, ==, NM_MANAGER_ERROR);
+    g_assert_cmpint(error->code, ==, NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN);
+    nm_clear_pointer(&error, g_error_free);
+}
+
+/*****************************************************************************/
+
+static void
 _test_same_prefix(const char *a1, const char *a2, guint8 plen)
 {
     struct in6_addr a = *nmtst_inet6_from_string(a1);
@@ -980,10 +1006,11 @@ test_connection_match_ip6_routes(void)
     g_assert(matched == copy);
 }
 
-#define do_test_wildcard_match(str, result, ...)                                               \
-    g_assert(                                                                                  \
-        nm_wildcard_match_check(str, (const char *const[]){__VA_ARGS__}, NM_NARG(__VA_ARGS__)) \
-        == result);
+#define do_test_wildcard_match_eval(str, ...) \
+    nm_wildcard_match_check(str, (const char *const[]){__VA_ARGS__}, NM_NARG(__VA_ARGS__))
+
+#define do_test_wildcard_match(str, result, ...) \
+    g_assert(do_test_wildcard_match_eval(str, __VA_ARGS__) == result)
 
 static void
 test_wildcard_match(void)
@@ -1056,7 +1083,14 @@ test_wildcard_match(void)
     do_test_wildcard_match("name3", TRUE, "name[123]");
     do_test_wildcard_match("name4", FALSE, "name[123]");
 
-    do_test_wildcard_match("[a]", TRUE, "\\[a\\]");
+    if (do_test_wildcard_match_eval("[a]", "\\[a\\]") != TRUE) {
+#if defined(__GLIBC__)
+        do_test_wildcard_match("[a]", TRUE, "\\[a\\]");
+        g_assert_not_reached();
+#endif
+        /* It seems musl's fnmatch() does not like such ranges. */
+        g_test_skip("libc does not support ranges with fnmatch()!!");
+    }
 
     do_test_wildcard_match("aa", FALSE, "!a*");
     do_test_wildcard_match("aa", FALSE, "&!a*");
@@ -1929,7 +1963,7 @@ test_reverse_dns_ip4(void)
     GPtrArray *domains = g_ptr_array_new_full(8, g_free);
 
     inet_pton(AF_INET, "7.2.3.0", &addr);
-    nm_utils_get_reverse_dns_domains_ip4(addr, 27, domains);
+    nm_utils_get_reverse_dns_domains_ip_4(addr, 27, domains);
     g_assert_cmpuint(domains->len, ==, 32);
     g_assert_cmpstr(domains->pdata[0], ==, "0.3.2.7.in-addr.arpa");
     g_assert_cmpstr(domains->pdata[31], ==, "31.3.2.7.in-addr.arpa");
@@ -1937,7 +1971,7 @@ test_reverse_dns_ip4(void)
     g_ptr_array_set_size(domains, 0);
 
     inet_pton(AF_INET, "10.155.16.0", &addr);
-    nm_utils_get_reverse_dns_domains_ip4(addr, 22, domains);
+    nm_utils_get_reverse_dns_domains_ip_4(addr, 22, domains);
     g_assert_cmpuint(domains->len, ==, 4);
     g_assert_cmpstr(domains->pdata[0], ==, "16.155.10.in-addr.arpa");
     g_assert_cmpstr(domains->pdata[1], ==, "17.155.10.in-addr.arpa");
@@ -1947,21 +1981,21 @@ test_reverse_dns_ip4(void)
     g_ptr_array_set_size(domains, 0);
 
     inet_pton(AF_INET, "4.5.6.7", &addr);
-    nm_utils_get_reverse_dns_domains_ip4(addr, 32, domains);
+    nm_utils_get_reverse_dns_domains_ip_4(addr, 32, domains);
     g_assert_cmpuint(domains->len, ==, 1);
     g_assert_cmpstr(domains->pdata[0], ==, "7.6.5.4.in-addr.arpa");
 
     g_ptr_array_set_size(domains, 0);
 
     inet_pton(AF_INET, "4.5.6.7", &addr);
-    nm_utils_get_reverse_dns_domains_ip4(addr, 8, domains);
+    nm_utils_get_reverse_dns_domains_ip_4(addr, 8, domains);
     g_assert_cmpuint(domains->len, ==, 1);
     g_assert_cmpstr(domains->pdata[0], ==, "4.in-addr.arpa");
 
     g_ptr_array_set_size(domains, 0);
 
     inet_pton(AF_INET, "4.180.6.7", &addr);
-    nm_utils_get_reverse_dns_domains_ip4(addr, 9, domains);
+    nm_utils_get_reverse_dns_domains_ip_4(addr, 9, domains);
     g_assert_cmpuint(domains->len, ==, 128);
     g_assert_cmpstr(domains->pdata[0], ==, "128.4.in-addr.arpa");
     g_assert_cmpstr(domains->pdata[1], ==, "129.4.in-addr.arpa");
@@ -1970,7 +2004,7 @@ test_reverse_dns_ip4(void)
     g_ptr_array_set_size(domains, 0);
 
     inet_pton(AF_INET, "172.16.0.0", &addr);
-    nm_utils_get_reverse_dns_domains_ip4(addr, 12, domains);
+    nm_utils_get_reverse_dns_domains_ip_4(addr, 12, domains);
     g_assert_cmpuint(domains->len, ==, 16);
     g_assert_cmpstr(domains->pdata[0], ==, "16.172.in-addr.arpa");
     g_assert_cmpstr(domains->pdata[1], ==, "17.172.in-addr.arpa");
@@ -1980,7 +2014,7 @@ test_reverse_dns_ip4(void)
     g_ptr_array_set_size(domains, 0);
 
     inet_pton(AF_INET, "1.2.3.4", &addr);
-    nm_utils_get_reverse_dns_domains_ip4(addr, 0, domains);
+    nm_utils_get_reverse_dns_domains_ip_4(addr, 0, domains);
     g_assert_cmpuint(domains->len, ==, 0);
 
     g_ptr_array_unref(domains);
@@ -1993,14 +2027,14 @@ test_reverse_dns_ip6(void)
     GPtrArray *     domains = g_ptr_array_new_full(8, g_free);
 
     inet_pton(AF_INET6, "1234::56", &addr);
-    nm_utils_get_reverse_dns_domains_ip6(&addr, 16, domains);
+    nm_utils_get_reverse_dns_domains_ip_6(&addr, 16, domains);
     g_assert_cmpuint(domains->len, ==, 1);
     g_assert_cmpstr(domains->pdata[0], ==, "4.3.2.1.ip6.arpa");
 
     g_ptr_array_set_size(domains, 0);
 
     inet_pton(AF_INET6, "1234::56", &addr);
-    nm_utils_get_reverse_dns_domains_ip6(&addr, 17, domains);
+    nm_utils_get_reverse_dns_domains_ip_6(&addr, 17, domains);
     g_assert_cmpuint(domains->len, ==, 8);
     g_assert_cmpstr(domains->pdata[0], ==, "0.4.3.2.1.ip6.arpa");
     g_assert_cmpstr(domains->pdata[1], ==, "1.4.3.2.1.ip6.arpa");
@@ -2009,7 +2043,7 @@ test_reverse_dns_ip6(void)
     g_ptr_array_set_size(domains, 0);
 
     inet_pton(AF_INET6, "2001:db8::", &addr);
-    nm_utils_get_reverse_dns_domains_ip6(&addr, 29, domains);
+    nm_utils_get_reverse_dns_domains_ip_6(&addr, 29, domains);
     g_assert_cmpuint(domains->len, ==, 8);
     g_assert_cmpstr(domains->pdata[0], ==, "8.b.d.0.1.0.0.2.ip6.arpa");
     g_assert_cmpstr(domains->pdata[1], ==, "9.b.d.0.1.0.0.2.ip6.arpa");
@@ -2018,7 +2052,7 @@ test_reverse_dns_ip6(void)
     g_ptr_array_set_size(domains, 0);
 
     inet_pton(AF_INET6, "0123:4567:89ab:cdef::", &addr);
-    nm_utils_get_reverse_dns_domains_ip6(&addr, 63, domains);
+    nm_utils_get_reverse_dns_domains_ip_6(&addr, 63, domains);
     g_assert_cmpuint(domains->len, ==, 2);
     g_assert_cmpstr(domains->pdata[0], ==, "e.e.d.c.b.a.9.8.7.6.5.4.3.2.1.0.ip6.arpa");
     g_assert_cmpstr(domains->pdata[1], ==, "f.e.d.c.b.a.9.8.7.6.5.4.3.2.1.0.ip6.arpa");
@@ -2026,7 +2060,7 @@ test_reverse_dns_ip6(void)
     g_ptr_array_set_size(domains, 0);
 
     inet_pton(AF_INET6, "fec0:1234:5678:9ab0::", &addr);
-    nm_utils_get_reverse_dns_domains_ip6(&addr, 61, domains);
+    nm_utils_get_reverse_dns_domains_ip_6(&addr, 61, domains);
     g_assert_cmpuint(domains->len, ==, 8);
     g_assert_cmpstr(domains->pdata[0], ==, "0.b.a.9.8.7.6.5.4.3.2.1.0.c.e.f.ip6.arpa");
     g_assert_cmpstr(domains->pdata[7], ==, "7.b.a.9.8.7.6.5.4.3.2.1.0.c.e.f.ip6.arpa");
@@ -2034,7 +2068,7 @@ test_reverse_dns_ip6(void)
     g_ptr_array_set_size(domains, 0);
 
     inet_pton(AF_INET6, "0123:4567:89ab:cdee::", &addr);
-    nm_utils_get_reverse_dns_domains_ip6(&addr, 0, domains);
+    nm_utils_get_reverse_dns_domains_ip_6(&addr, 0, domains);
     g_assert_cmpuint(domains->len, ==, 0);
 
     g_ptr_array_unref(domains);
@@ -2536,6 +2570,7 @@ main(int argc, char **argv)
     nmtst_init_with_logging(&argc, &argv, NULL, "ALL");
 
     g_test_add_func("/general/test_logging_domains", test_logging_domains);
+    g_test_add_func("/general/test_logging_error", test_logging_error);
 
     g_test_add_func("/general/nm_utils_strbuf_append", test_nm_utils_strbuf_append);
 
diff --git a/src/tests/test-dcb.c b/src/core/tests/test-dcb.c
index 0df21066..fc591fcb 100644
--- a/src/tests/test-dcb.c
+++ b/src/core/tests/test-dcb.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-dcb.h"
 
diff --git a/src/tests/test-ip4-config.c b/src/core/tests/test-ip4-config.c
index 5a9eae90..27b1d611 100644
--- a/src/tests/test-ip4-config.c
+++ b/src/core/tests/test-ip4-config.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 - 2014 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <arpa/inet.h>
 
diff --git a/src/tests/test-ip6-config.c b/src/core/tests/test-ip6-config.c
index 6b995fe5..3b48fb7f 100644
--- a/src/tests/test-ip6-config.c
+++ b/src/core/tests/test-ip6-config.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2013 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <arpa/inet.h>
 #include <linux/if_addr.h>
diff --git a/src/core/tests/test-l3cfg.c b/src/core/tests/test-l3cfg.c
new file mode 100644
index 00000000..43132c6d
--- /dev/null
+++ b/src/core/tests/test-l3cfg.c
@@ -0,0 +1,795 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-l3cfg.h"
+#include "nm-l3-ipv4ll.h"
+#include "nm-netns.h"
+#include "platform/nm-platform.h"
+
+#include "platform/tests/test-common.h"
+
+/*****************************************************************************/
+
+static NML3Cfg *
+_netns_access_l3cfg(NMNetns *netns, int ifindex)
+{
+    NML3Cfg *l3cfg;
+
+    g_assert(NM_IS_NETNS(netns));
+    g_assert(ifindex > 0);
+
+    g_assert(!nm_netns_get_l3cfg(netns, ifindex));
+
+    l3cfg = nm_netns_access_l3cfg(netns, ifindex);
+    g_assert(NM_IS_L3CFG(l3cfg));
+    return l3cfg;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+    int                test_idx;
+    NMPlatform *       platform;
+    NMNetns *          netns;
+    NMDedupMultiIndex *multiidx;
+    const char *       ifname0;
+    const char *       ifname1;
+    NMPLinkAddress     hwaddr0;
+    NMPLinkAddress     hwaddr1;
+    int                ifindex0;
+    int                ifindex1;
+} TestFixture1;
+
+static const TestFixture1 *
+_test_fixture_1_setup(TestFixture1 *f, int test_idx)
+{
+    const NMPlatformLink *l0;
+    const NMPlatformLink *l1;
+    const NMEtherAddr     addr0 = NM_ETHER_ADDR_INIT(0xAA, 0xAA, test_idx, 0x00, 0x00, 0x00);
+    const NMEtherAddr     addr1 = NM_ETHER_ADDR_INIT(0xAA, 0xAA, test_idx, 0x00, 0x00, 0x11);
+
+    g_assert_cmpint(test_idx, >, 0);
+    g_assert_cmpint(f->test_idx, ==, 0);
+
+    f->test_idx = test_idx;
+
+    f->ifname0 = "nm-test-veth0";
+    f->ifname1 = "nm-test-veth1";
+
+    f->platform = g_object_ref(NM_PLATFORM_GET);
+    f->multiidx = nm_dedup_multi_index_ref(nm_platform_get_multi_idx(f->platform));
+    f->netns    = nm_netns_new(f->platform);
+
+    nmtstp_link_veth_add(f->platform, -1, f->ifname0, f->ifname1);
+
+    l0 = nmtstp_link_get_typed(f->platform, -1, f->ifname0, NM_LINK_TYPE_VETH);
+    l1 = nmtstp_link_get_typed(f->platform, -1, f->ifname1, NM_LINK_TYPE_VETH);
+
+    f->ifindex0 = l0->ifindex;
+    f->ifindex1 = l1->ifindex;
+
+    g_assert_cmpint(nm_platform_link_set_address(f->platform, f->ifindex0, &addr0, sizeof(addr0)),
+                    ==,
+                    0);
+    g_assert_cmpint(nm_platform_link_set_address(f->platform, f->ifindex1, &addr1, sizeof(addr1)),
+                    ==,
+                    0);
+
+    l0 = nmtstp_link_get_typed(f->platform, f->ifindex0, f->ifname0, NM_LINK_TYPE_VETH);
+    l1 = nmtstp_link_get_typed(f->platform, f->ifindex1, f->ifname1, NM_LINK_TYPE_VETH);
+
+    f->hwaddr0 = l0->l_address;
+    f->hwaddr1 = l1->l_address;
+
+    g_assert(nm_platform_link_set_up(f->platform, f->ifindex0, NULL));
+    g_assert(nm_platform_link_set_up(f->platform, f->ifindex1, NULL));
+
+    return f;
+}
+
+static void
+_test_fixture_1_teardown(TestFixture1 *f)
+{
+    g_assert(f);
+
+    if (f->test_idx == 0)
+        return;
+
+    _LOGD("test teatdown");
+
+    nmtstp_link_delete(f->platform, -1, f->ifindex0, f->ifname0, TRUE);
+    g_assert(!nm_platform_link_get(f->platform, f->ifindex0));
+    g_assert(!nm_platform_link_get(f->platform, f->ifindex1));
+    g_assert(!nm_platform_link_get_by_ifname(f->platform, f->ifname0));
+    g_assert(!nm_platform_link_get_by_ifname(f->platform, f->ifname1));
+
+    g_object_unref(f->netns);
+    g_object_unref(f->platform);
+    nm_dedup_multi_index_unref(f->multiidx);
+
+    *f = (TestFixture1){
+        .test_idx = 0,
+    };
+}
+
+/*****************************************************************************/
+
+typedef enum {
+    TEST_L3CFG_NOTIFY_TYPE_NONE,
+    TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL,
+    TEST_L3CFG_NOTIFY_TYPE_COMMIT_1,
+    TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1,
+} TestL3cfgNotifyType;
+
+typedef struct {
+    const TestFixture1 *f;
+
+    bool has_addr4_101 : 1;
+    bool add_addr4_101 : 1;
+
+    guint32           acd_timeout_msec_a;
+    NML3AcdDefendType acd_defend_type_a;
+
+    TestL3cfgNotifyType notify_type;
+    guint               post_commit_event_count;
+    guint               general_event_count;
+    guint               general_event_flags;
+    union {
+        struct {
+            int  cb_count;
+            bool expected_probe_result : 1;
+            bool acd_event_ready_45 : 1;
+            bool acd_event_ready_101 : 1;
+        } wait_for_acd_ready_1;
+    } notify_result;
+} TestL3cfgData;
+
+static void
+_test_l3cfg_data_set_notify_type(TestL3cfgData *tdata, TestL3cfgNotifyType notify_type)
+{
+    g_assert(tdata);
+
+    tdata->notify_type             = notify_type;
+    tdata->post_commit_event_count = 0;
+    tdata->general_event_count     = 0;
+    tdata->general_event_flags     = 0;
+    memset(&tdata->notify_result, 0, sizeof(tdata->notify_result));
+}
+
+static void
+_test_l3cfg_signal_notify(NML3Cfg *                   l3cfg,
+                          const NML3ConfigNotifyData *notify_data,
+                          TestL3cfgData *             tdata)
+{
+    guint i;
+
+    g_assert(NM_IS_L3CFG(l3cfg));
+    g_assert(tdata);
+    g_assert(notify_data);
+    g_assert(_NM_INT_NOT_NEGATIVE(notify_data->notify_type));
+    g_assert(notify_data->notify_type < _NM_L3_CONFIG_NOTIFY_TYPE_NUM);
+
+    if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE)
+        g_assert(notify_data->platform_change_on_idle.obj_type_flags != 0u);
+    else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE) {
+        g_assert(NMP_OBJECT_IS_VALID(notify_data->platform_change.obj));
+        g_assert(notify_data->platform_change.change_type != 0);
+    } else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) {
+        g_assert_cmpint(notify_data->acd_event.info.n_track_infos, >=, 1);
+        g_assert(notify_data->acd_event.info.track_infos);
+        for (i = 0; i < notify_data->acd_event.info.n_track_infos; i++) {
+            const NML3AcdAddrTrackInfo *ti = &notify_data->acd_event.info.track_infos[i];
+
+            nm_assert(NMP_OBJECT_GET_TYPE(ti->obj) == NMP_OBJECT_TYPE_IP4_ADDRESS);
+            nm_assert(NMP_OBJECT_CAST_IP4_ADDRESS(ti->obj)->address
+                      == notify_data->acd_event.info.addr);
+            nm_assert(NM_IS_L3_CONFIG_DATA(ti->l3cd));
+            nm_assert(ti->tag);
+        }
+    }
+
+    switch (tdata->notify_type) {
+    case TEST_L3CFG_NOTIFY_TYPE_NONE:
+        g_assert_not_reached();
+        break;
+    case TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL:
+        if (NM_IN_SET(notify_data->notify_type,
+                      NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE,
+                      NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE))
+            return;
+        g_assert_not_reached();
+        return;
+    case TEST_L3CFG_NOTIFY_TYPE_COMMIT_1:
+        g_assert_cmpint(tdata->post_commit_event_count, ==, 0);
+        switch (notify_data->notify_type) {
+        case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT:
+            tdata->post_commit_event_count++;
+            return;
+        case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT:
+            switch (tdata->f->test_idx) {
+            case 2:
+            case 3:
+                nmtst_assert_ip4_address(notify_data->acd_event.info.addr, "192.168.133.45");
+                if (tdata->f->test_idx == 2)
+                    g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_DEFENDING);
+                else
+                    g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING);
+                g_assert(tdata->general_event_count == 0);
+                tdata->general_event_count++;
+                return;
+            case 4:
+                if (notify_data->acd_event.info.addr == nmtst_inet4_from_string("192.168.133.45")) {
+                    g_assert(!NM_FLAGS_HAS(tdata->general_event_flags, 0x1u));
+                    tdata->general_event_flags |= 0x1u;
+                    g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING);
+                    tdata->general_event_count++;
+                } else if (notify_data->acd_event.info.addr
+                           == nmtst_inet4_from_string("192.168.133.101")) {
+                    g_assert(!NM_FLAGS_HAS(tdata->general_event_flags, 0x4u));
+                    tdata->general_event_flags |= 0x4u;
+                    g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING);
+                    tdata->general_event_count++;
+                } else
+                    g_assert_not_reached();
+                return;
+            default:
+                g_assert_not_reached();
+                return;
+            }
+        case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE:
+            return;
+        default:
+            g_assert_not_reached();
+            return;
+        }
+    case TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1:
+    {
+        int num_acd_completed_events =
+            1 + 2 + (tdata->add_addr4_101 ? (tdata->has_addr4_101 ? 1 : 3) : 0);
+
+        if (NM_IN_SET(notify_data->notify_type,
+                      NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE,
+                      NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE))
+            return;
+        if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) {
+            if (notify_data->acd_event.info.addr == nmtst_inet4_from_string("192.168.133.45")) {
+                g_assert(NM_IN_SET(notify_data->acd_event.info.state,
+                                   NM_L3_ACD_ADDR_STATE_READY,
+                                   NM_L3_ACD_ADDR_STATE_DEFENDING));
+                tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_45 = TRUE;
+            } else if (notify_data->acd_event.info.addr
+                       == nmtst_inet4_from_string("192.168.133.101")) {
+                if (tdata->has_addr4_101) {
+                    g_assert(
+                        NM_IN_SET(notify_data->acd_event.info.state, NM_L3_ACD_ADDR_STATE_USED));
+                } else {
+                    g_assert(NM_IN_SET(notify_data->acd_event.info.state,
+                                       NM_L3_ACD_ADDR_STATE_READY,
+                                       NM_L3_ACD_ADDR_STATE_DEFENDING));
+                    tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_101 = TRUE;
+                }
+            } else
+                g_assert_not_reached();
+
+            g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count,
+                            <,
+                            num_acd_completed_events);
+            tdata->notify_result.wait_for_acd_ready_1.cb_count++;
+            return;
+        }
+        if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT) {
+            g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, >, 0);
+            g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count,
+                            <,
+                            num_acd_completed_events);
+            tdata->notify_result.wait_for_acd_ready_1.cb_count++;
+            nmtstp_platform_ip_addresses_assert(
+                tdata->f->platform,
+                tdata->f->ifindex0,
+                TRUE,
+                TRUE,
+                TRUE,
+                tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_45 ? "192.168.133.45"
+                                                                             : NULL,
+                tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_101 ? "192.168.133.101"
+                                                                              : NULL,
+                "1:2:3:4::45");
+            return;
+        }
+        g_assert_not_reached();
+        return;
+    }
+    }
+
+    g_assert_not_reached();
+}
+
+static void
+test_l3cfg(gconstpointer test_data)
+{
+    const int                                      TEST_IDX = GPOINTER_TO_INT(test_data);
+    const guint32                                  ACD_TIMEOUT_BASE_MSEC = 1000;
+    nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture          = {};
+    const TestFixture1 *                           f;
+    NML3CfgCommitTypeHandle *                      commit_type_1;
+    NML3CfgCommitTypeHandle *                      commit_type_2;
+    gs_unref_object NML3Cfg *l3cfg0                      = NULL;
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd_a      = NULL;
+    TestL3cfgData                            tdata_stack = {
+        .f = NULL,
+    };
+    TestL3cfgData *const tdata = &tdata_stack;
+
+    _LOGD("test start (/l3cfg/%d)", TEST_IDX);
+
+    if (nmtst_test_quick()) {
+        gs_free char *msg =
+            g_strdup_printf("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n",
+                            g_get_prgname() ?: "test-l3cfg");
+
+        g_test_skip(msg);
+        return;
+    }
+
+    f = _test_fixture_1_setup(&test_fixture, TEST_IDX);
+
+    tdata->f             = f;
+    tdata->has_addr4_101 = (f->test_idx == 4 && nmtst_get_rand_bool());
+    tdata->add_addr4_101 = (f->test_idx == 4 && nmtst_get_rand_bool());
+
+    tdata->acd_timeout_msec_a = NM_IN_SET(f->test_idx, 3, 4) ? ACD_TIMEOUT_BASE_MSEC : 0u;
+    tdata->acd_defend_type_a  = NM_IN_SET(f->test_idx, 4)
+                                    ? nmtst_rand_select(NM_L3_ACD_DEFEND_TYPE_NEVER,
+                                                       NM_L3_ACD_DEFEND_TYPE_ONCE,
+                                                       NM_L3_ACD_DEFEND_TYPE_ALWAYS)
+                                    : NM_L3_ACD_DEFEND_TYPE_NEVER;
+
+    if (tdata->has_addr4_101) {
+        nmtstp_ip4_address_add(f->platform,
+                               -1,
+                               f->ifindex1,
+                               nmtst_inet4_from_string("192.168.133.101"),
+                               24,
+                               nmtst_inet4_from_string("192.168.133.101"),
+                               100000,
+                               0,
+                               0,
+                               NULL);
+    }
+
+    l3cfg0 = _netns_access_l3cfg(f->netns, f->ifindex0);
+
+    g_signal_connect(l3cfg0, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_test_l3cfg_signal_notify), tdata);
+
+    commit_type_1 = nm_l3cfg_commit_type_register(l3cfg0, NM_L3_CFG_COMMIT_TYPE_UPDATE, NULL);
+
+    if (!nmtst_get_rand_one_case_in(4)) {
+        commit_type_2 =
+            nm_l3cfg_commit_type_register(l3cfg0,
+                                          nmtst_rand_select(NM_L3_CFG_COMMIT_TYPE_NONE,
+                                                            NM_L3_CFG_COMMIT_TYPE_ASSUME,
+                                                            NM_L3_CFG_COMMIT_TYPE_UPDATE),
+                                          NULL);
+    } else
+        commit_type_2 = NULL;
+
+    switch (f->test_idx) {
+    case 1:
+        break;
+    case 2:
+    case 3:
+    case 4:
+    {
+        nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
+
+        l3cd = nm_l3_config_data_new(f->multiidx, f->ifindex0);
+
+        nm_l3_config_data_add_address_4(
+            l3cd,
+            NM_PLATFORM_IP4_ADDRESS_INIT(.address      = nmtst_inet4_from_string("192.168.133.45"),
+                                         .peer_address = nmtst_inet4_from_string("192.168.133.45"),
+                                         .plen         = 24, ));
+
+        if (tdata->add_addr4_101) {
+            nm_l3_config_data_add_address_4(
+                l3cd,
+                NM_PLATFORM_IP4_ADDRESS_INIT(.address = nmtst_inet4_from_string("192.168.133.101"),
+                                             .peer_address =
+                                                 nmtst_inet4_from_string("192.168.133.101"),
+                                             .plen = 24, ));
+        }
+
+        nm_l3_config_data_add_address_6(
+            l3cd,
+            NM_PLATFORM_IP6_ADDRESS_INIT(.address = *nmtst_inet6_from_string("1:2:3:4::45"),
+                                         .plen    = 64, ));
+
+        if (nmtst_get_rand_one_case_in(2))
+            nm_l3_config_data_seal(l3cd);
+        l3cd_a = g_steal_pointer(&l3cd);
+        break;
+    }
+    }
+
+    nm_l3_config_data_log(l3cd_a, "l3cd_a", "platform-test: l3cd_a: ", LOGL_DEBUG, LOGD_PLATFORM);
+
+    if (l3cd_a) {
+        nm_l3cfg_add_config(l3cfg0,
+                            GINT_TO_POINTER('a'),
+                            nmtst_get_rand_bool(),
+                            l3cd_a,
+                            'a',
+                            0,
+                            0,
+                            NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4,
+                            NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
+                            0,
+                            0,
+                            tdata->acd_defend_type_a,
+                            tdata->acd_timeout_msec_a,
+                            NM_L3_CONFIG_MERGE_FLAGS_NONE);
+    }
+
+    nm_l3_config_data_log(nm_l3cfg_get_combined_l3cd(l3cfg0, FALSE),
+                          "test",
+                          "platform-test: l3cfg0: ",
+                          LOGL_DEBUG,
+                          LOGD_PLATFORM);
+
+    _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_COMMIT_1);
+    nm_l3cfg_commit(l3cfg0, NM_L3_CFG_COMMIT_TYPE_REAPPLY);
+    g_assert_cmpint(tdata->post_commit_event_count, ==, 1);
+    _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE);
+
+    nmtstp_platform_ip_addresses_assert(tdata->f->platform,
+                                        tdata->f->ifindex0,
+                                        TRUE,
+                                        TRUE,
+                                        TRUE,
+                                        NM_IN_SET(f->test_idx, 2) ? "192.168.133.45" : NULL,
+                                        NM_IN_SET(f->test_idx, 2, 3, 4) ? "1:2:3:4::45" : NULL);
+
+    if (NM_IN_SET(f->test_idx, 1, 2)) {
+        _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL);
+        _LOGT("poll 1 start");
+        nmtst_main_context_iterate_until(NULL,
+                                         nmtst_get_rand_uint32() % (ACD_TIMEOUT_BASE_MSEC * 5u),
+                                         FALSE);
+        _LOGT("poll 1 end");
+        _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE);
+    }
+
+    if (NM_IN_SET(f->test_idx, 3, 4)) {
+        _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1);
+        tdata->notify_result.wait_for_acd_ready_1.expected_probe_result = TRUE;
+        _LOGT("poll 2 start");
+        nmtst_main_context_iterate_until(
+            NULL,
+            ACD_TIMEOUT_BASE_MSEC * 3u / 2u
+                + (nmtst_get_rand_uint32() % (2u * ACD_TIMEOUT_BASE_MSEC)),
+            FALSE);
+        _LOGT("poll 2 end");
+        g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count,
+                        ==,
+                        1 + 2 + (tdata->add_addr4_101 ? (tdata->has_addr4_101 ? 1 : 3) : 0));
+        _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE);
+    }
+
+    g_signal_handlers_disconnect_by_func(l3cfg0, G_CALLBACK(_test_l3cfg_signal_notify), tdata);
+
+    nm_l3cfg_commit_type_unregister(l3cfg0, commit_type_1);
+    nm_l3cfg_commit_type_unregister(l3cfg0, commit_type_2);
+
+    if (nmtst_get_rand_one_case_in(3))
+        _test_fixture_1_teardown(&test_fixture);
+
+    nm_l3cfg_remove_config_all(l3cfg0, GINT_TO_POINTER('a'), FALSE);
+
+    if (nmtst_get_rand_one_case_in(3))
+        _test_fixture_1_teardown(&test_fixture);
+
+    _LOGD("test end (/l3cfg/%d)", TEST_IDX);
+}
+
+/*****************************************************************************/
+
+#define L3IPV4LL_ACD_TIMEOUT_MSEC 1500u
+
+typedef struct {
+    const TestFixture1 *     f;
+    NML3CfgCommitTypeHandle *l3cfg_commit_type_1;
+    guint                    acd_timeout_msec;
+    NML3IPv4LL *             l3ipv4ll;
+    bool                     has_addr4_101;
+    gint8                    ready_seen;
+    gint8                    addr_commit;
+    in_addr_t                addr_commit_addr;
+    bool                     add_conflict_checked : 1;
+    bool                     add_conflict_done;
+} TestL3IPv4LLData;
+
+static gconstpointer
+TEST_L3_IPV4LL_TAG(const TestL3IPv4LLData *tdata, guint offset)
+{
+    return (&(((const char *) tdata)[offset]));
+}
+
+static void
+_test_l3_ipv4ll_maybe_add_addr_4(const TestL3IPv4LLData *tdata,
+                                 int                     ifindex,
+                                 guint                   one_case_in_num,
+                                 bool *                  has_addr,
+                                 const char *            addr)
+{
+    if (has_addr) {
+        if (*has_addr || !nmtst_get_rand_one_case_in(one_case_in_num))
+            return;
+        *has_addr = TRUE;
+    }
+
+    if (ifindex == 0)
+        ifindex = tdata->f->ifindex0;
+
+    g_assert_cmpint(ifindex, >, 0);
+
+    _LOGT("add test address: %s on ifindex=%d", addr, ifindex);
+
+    nmtstp_ip4_address_add(tdata->f->platform,
+                           -1,
+                           ifindex,
+                           nmtst_inet4_from_string(addr),
+                           24,
+                           nmtst_inet4_from_string(addr),
+                           100000,
+                           0,
+                           0,
+                           NULL);
+}
+
+static void
+_test_l3_ipv4ll_signal_notify(NML3Cfg *                   l3cfg,
+                              const NML3ConfigNotifyData *notify_data,
+                              TestL3IPv4LLData *          tdata)
+{
+    char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+
+    g_assert(NM_IS_L3CFG(l3cfg));
+    g_assert(tdata);
+    g_assert(notify_data);
+    g_assert(_NM_INT_NOT_NEGATIVE(notify_data->notify_type));
+    g_assert(notify_data->notify_type < _NM_L3_CONFIG_NOTIFY_TYPE_NUM);
+
+    if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT) {
+        g_assert(tdata->l3ipv4ll == notify_data->ipv4ll_event.ipv4ll);
+        g_assert(NM_IN_SET(tdata->ready_seen, 0, 1));
+        g_assert(NM_IN_SET(tdata->addr_commit, 0, 1));
+
+        if (nm_l3_ipv4ll_get_state(tdata->l3ipv4ll) == NM_L3_IPV4LL_STATE_READY) {
+            g_assert_cmpint(tdata->ready_seen, ==, 0);
+            g_assert_cmpint(tdata->addr_commit, ==, 0);
+            tdata->ready_seen++;
+
+            if (tdata->f->test_idx == 2 && nmtst_get_rand_bool()) {
+                tdata->addr_commit++;
+                tdata->addr_commit_addr = nm_l3_ipv4ll_get_addr(tdata->l3ipv4ll);
+                g_assert(nm_utils_ip4_address_is_link_local(tdata->addr_commit_addr));
+                _LOGT("add address %s that passed ACD",
+                      _nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr));
+                if (!nm_l3cfg_add_config(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
+                                         TEST_L3_IPV4LL_TAG(tdata, 1),
+                                         nmtst_get_rand_bool(),
+                                         nm_l3_ipv4ll_get_l3cd(tdata->l3ipv4ll),
+                                         NM_L3CFG_CONFIG_PRIORITY_IPV4LL,
+                                         0,
+                                         0,
+                                         104,
+                                         105,
+                                         0,
+                                         0,
+                                         NM_L3_ACD_DEFEND_TYPE_ONCE,
+                                         nmtst_get_rand_bool() ? tdata->acd_timeout_msec : 0u,
+                                         NM_L3_CONFIG_MERGE_FLAGS_NONE))
+                    g_assert_not_reached();
+                nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll));
+
+                tdata->l3cfg_commit_type_1 =
+                    nm_l3cfg_commit_type_register(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
+                                                  NM_L3_CFG_COMMIT_TYPE_UPDATE,
+                                                  tdata->l3cfg_commit_type_1);
+            }
+        } else if (nm_l3_ipv4ll_get_state(tdata->l3ipv4ll) != NM_L3_IPV4LL_STATE_DEFENDING
+                   && tdata->ready_seen > 0) {
+            g_assert_cmpint(tdata->ready_seen, ==, 1);
+            tdata->ready_seen--;
+            if (tdata->addr_commit > 0) {
+                g_assert_cmpint(tdata->addr_commit, ==, 1);
+                tdata->addr_commit--;
+                g_assert(nm_utils_ip4_address_is_link_local(tdata->addr_commit_addr));
+                _LOGT("remove address %s that previously passed ACD",
+                      _nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr));
+                if (!nm_l3cfg_remove_config_all(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
+                                                TEST_L3_IPV4LL_TAG(tdata, 1),
+                                                FALSE))
+                    g_assert_not_reached();
+                nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll));
+                nm_l3cfg_commit_type_unregister(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
+                                                g_steal_pointer(&tdata->l3cfg_commit_type_1));
+            }
+        }
+        return;
+    }
+}
+
+static void
+test_l3_ipv4ll(gconstpointer test_data)
+{
+    const int                                      TEST_IDX     = GPOINTER_TO_INT(test_data);
+    nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {};
+    const TestFixture1 *                           f;
+    gs_unref_object NML3Cfg *l3cfg0      = NULL;
+    TestL3IPv4LLData         tdata_stack = {
+        .f = NULL,
+    };
+    TestL3IPv4LLData *const tdata                 = &tdata_stack;
+    NMTstpAcdDefender *     acd_defender_1        = NULL;
+    NMTstpAcdDefender *     acd_defender_2        = NULL;
+    nm_auto_unref_l3ipv4ll NML3IPv4LL *  l3ipv4ll = NULL;
+    gint64                               start_time_msec;
+    gint64                               total_poll_time_msec;
+    nm_auto_remove_l3ipv4ll_registration NML3IPv4LLRegistration *l3ipv4ll_reg = NULL;
+    char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+
+    _LOGD("test start (/l3-ipv4ll/%d)", TEST_IDX);
+
+    if (nmtst_test_quick()) {
+        gs_free char *msg =
+            g_strdup_printf("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n",
+                            g_get_prgname() ?: "test-l3-ipv4ll");
+
+        g_test_skip(msg);
+        return;
+    }
+
+    f = _test_fixture_1_setup(&test_fixture, TEST_IDX);
+
+    tdata->f = f;
+
+    if (tdata->f->test_idx == 1)
+        tdata->acd_timeout_msec = 0;
+    else
+        tdata->acd_timeout_msec = L3IPV4LL_ACD_TIMEOUT_MSEC;
+
+    _test_l3_ipv4ll_maybe_add_addr_4(tdata, 0, 4, &tdata->has_addr4_101, "192.168.133.101");
+
+    l3cfg0 = _netns_access_l3cfg(f->netns, f->ifindex0);
+
+    g_signal_connect(l3cfg0,
+                     NM_L3CFG_SIGNAL_NOTIFY,
+                     G_CALLBACK(_test_l3_ipv4ll_signal_notify),
+                     tdata);
+
+    l3ipv4ll = nm_l3_ipv4ll_new(l3cfg0);
+
+    tdata->l3ipv4ll = l3ipv4ll;
+
+    g_assert_cmpint(nm_l3_ipv4ll_get_ifindex(l3ipv4ll), ==, f->ifindex0);
+    g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_DISABLED);
+    g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll), ==, 0u);
+
+    if (tdata->f->test_idx == 1) {
+        if (nmtst_get_rand_one_case_in(2))
+            l3ipv4ll_reg = nm_l3_ipv4ll_register_new(l3ipv4ll, tdata->acd_timeout_msec);
+    } else
+        l3ipv4ll_reg = nm_l3_ipv4ll_register_new(l3ipv4ll, tdata->acd_timeout_msec);
+
+    g_assert(tdata->acd_timeout_msec == 0 || l3ipv4ll_reg);
+    g_assert(!l3ipv4ll_reg || l3ipv4ll == nm_l3_ipv4ll_register_get_instance(l3ipv4ll_reg));
+
+    if (tdata->acd_timeout_msec == 0) {
+        g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_DISABLED);
+        g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll), ==, 0u);
+    } else {
+        g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_PROBING);
+        if (f->test_idx == 1) {
+            g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll),
+                            ==,
+                            nmtst_inet4_from_string("169.254.30.158"));
+        } else {
+            g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll),
+                            ==,
+                            nmtst_inet4_from_string("169.254.17.45"));
+        }
+        g_assert(nm_l3_ipv4ll_get_l3cd(l3ipv4ll));
+    }
+
+    _test_l3_ipv4ll_maybe_add_addr_4(tdata, 0, 4, &tdata->has_addr4_101, "192.168.133.101");
+
+    if (tdata->f->test_idx == 2 && nmtst_get_rand_one_case_in(3)) {
+        in_addr_t a = nm_l3_ipv4ll_get_addr(l3ipv4ll);
+
+        g_assert(nm_utils_ip4_address_is_link_local(a));
+        _test_l3_ipv4ll_maybe_add_addr_4(tdata,
+                                         tdata->f->ifindex1,
+                                         2,
+                                         &tdata->add_conflict_done,
+                                         _nm_utils_inet4_ntop(a, sbuf_addr));
+        g_assert_cmpint(tdata->f->hwaddr1.len, ==, sizeof(NMEtherAddr));
+        acd_defender_2 =
+            nmtstp_acd_defender_new(tdata->f->ifindex1, a, &tdata->f->hwaddr1.ether_addr);
+    }
+
+    start_time_msec = nm_utils_get_monotonic_timestamp_msec();
+    total_poll_time_msec =
+        (L3IPV4LL_ACD_TIMEOUT_MSEC * 3 / 2) + (nmtst_get_rand_uint32() % L3IPV4LL_ACD_TIMEOUT_MSEC);
+    _LOGT("poll 1 start (wait %" G_GINT64_FORMAT " msec)", total_poll_time_msec);
+    while (TRUE) {
+        gint64 next_timeout_msec;
+
+        next_timeout_msec =
+            start_time_msec + total_poll_time_msec - nm_utils_get_monotonic_timestamp_msec();
+        if (next_timeout_msec <= 0)
+            break;
+
+        next_timeout_msec = NM_MIN(next_timeout_msec, nmtst_get_rand_uint32() % 1000u);
+        nmtst_main_context_iterate_until(NULL, next_timeout_msec, FALSE);
+        _LOGT("poll 1 intermezzo");
+
+        _test_l3_ipv4ll_maybe_add_addr_4(tdata,
+                                         0,
+                                         1 + total_poll_time_msec / 1000,
+                                         &tdata->has_addr4_101,
+                                         "192.168.133.101");
+
+        if (tdata->addr_commit == 1 && !tdata->add_conflict_checked) {
+            tdata->add_conflict_checked = TRUE;
+            _test_l3_ipv4ll_maybe_add_addr_4(
+                tdata,
+                tdata->f->ifindex1,
+                2,
+                &tdata->add_conflict_done,
+                _nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr));
+            if (tdata->add_conflict_done)
+                total_poll_time_msec += L3IPV4LL_ACD_TIMEOUT_MSEC / 2;
+            g_assert_cmpint(tdata->f->hwaddr1.len, ==, sizeof(NMEtherAddr));
+            acd_defender_2 = nmtstp_acd_defender_new(tdata->f->ifindex1,
+                                                     tdata->addr_commit_addr,
+                                                     &tdata->f->hwaddr1.ether_addr);
+        }
+    }
+    _LOGT("poll 1 end");
+
+    if (tdata->addr_commit || nmtst_get_rand_bool()) {
+        nm_l3cfg_remove_config_all(nm_l3_ipv4ll_get_l3cfg(l3ipv4ll),
+                                   TEST_L3_IPV4LL_TAG(tdata, 1),
+                                   FALSE);
+    }
+
+    nmtstp_acd_defender_destroy(g_steal_pointer(&acd_defender_1));
+    nmtstp_acd_defender_destroy(g_steal_pointer(&acd_defender_2));
+
+    nm_l3cfg_commit_type_unregister(l3cfg0, g_steal_pointer(&tdata->l3cfg_commit_type_1));
+
+    g_signal_handlers_disconnect_by_func(l3cfg0, G_CALLBACK(_test_l3_ipv4ll_signal_notify), tdata);
+}
+
+/*****************************************************************************/
+
+NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup;
+
+void
+_nmtstp_init_tests(int *argc, char ***argv)
+{
+    nmtst_init_with_logging(argc, argv, "ERR", "ALL");
+}
+
+void
+_nmtstp_setup_tests(void)
+{
+    g_test_add_data_func("/l3cfg/1", GINT_TO_POINTER(1), test_l3cfg);
+    g_test_add_data_func("/l3cfg/2", GINT_TO_POINTER(2), test_l3cfg);
+    g_test_add_data_func("/l3cfg/3", GINT_TO_POINTER(3), test_l3cfg);
+    g_test_add_data_func("/l3cfg/4", GINT_TO_POINTER(4), test_l3cfg);
+    g_test_add_data_func("/l3-ipv4ll/1", GINT_TO_POINTER(1), test_l3_ipv4ll);
+    g_test_add_data_func("/l3-ipv4ll/2", GINT_TO_POINTER(2), test_l3_ipv4ll);
+}
diff --git a/src/tests/test-secret-agent.py b/src/core/tests/test-secret-agent.py
index 46f82b34..46f82b34 100755
--- a/src/tests/test-secret-agent.py
+++ b/src/core/tests/test-secret-agent.py
diff --git a/src/tests/test-systemd.c b/src/core/tests/test-systemd.c
index 54cca509..03576c13 100644
--- a/src/tests/test-systemd.c
+++ b/src/core/tests/test-systemd.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/systemd/nm-default-systemd.h"
 
 #include "systemd/nm-sd.h"
 #include "systemd/nm-sd-utils-shared.h"
diff --git a/src/tests/test-utils.c b/src/core/tests/test-utils.c
index 4635d982..1d562bde 100644
--- a/src/tests/test-utils.c
+++ b/src/core/tests/test-utils.c
@@ -1,11 +1,12 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include <arpa/inet.h>
+#include <linux/if_ether.h>
 
 #include "nm-test-utils-core.h"
 
diff --git a/src/tests/test-wired-defname.c b/src/core/tests/test-wired-defname.c
index 6e214549..f810c56f 100644
--- a/src/tests/test-wired-defname.c
+++ b/src/core/tests/test-wired-defname.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2010 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-simple-connection.h"
 #include "nm-setting-connection.h"
diff --git a/src/vpn/nm-vpn-connection.c b/src/core/vpn/nm-vpn-connection.c
index 68c66af7..02831b08 100644
--- a/src/vpn/nm-vpn-connection.c
+++ b/src/core/vpn/nm-vpn-connection.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2013 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-vpn-connection.h"
 
@@ -862,22 +862,22 @@ nm_vpn_connection_new(NMSettingsConnection * settings_connection,
     g_return_val_if_fail(NM_IS_DEVICE(parent_device), NULL);
     g_return_val_if_fail(specific_object, NULL);
 
-    return (NMVpnConnection *) g_object_new(NM_TYPE_VPN_CONNECTION,
-                                            NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION,
-                                            settings_connection,
-                                            NM_ACTIVE_CONNECTION_INT_DEVICE,
-                                            parent_device,
-                                            NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT,
-                                            specific_object,
-                                            NM_ACTIVE_CONNECTION_INT_SUBJECT,
-                                            subject,
-                                            NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON,
-                                            activation_reason,
-                                            NM_ACTIVE_CONNECTION_VPN,
-                                            TRUE,
-                                            NM_ACTIVE_CONNECTION_STATE_FLAGS,
-                                            (guint) initial_state_flags,
-                                            NULL);
+    return g_object_new(NM_TYPE_VPN_CONNECTION,
+                        NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION,
+                        settings_connection,
+                        NM_ACTIVE_CONNECTION_INT_DEVICE,
+                        parent_device,
+                        NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT,
+                        specific_object,
+                        NM_ACTIVE_CONNECTION_INT_SUBJECT,
+                        subject,
+                        NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON,
+                        activation_reason,
+                        NM_ACTIVE_CONNECTION_VPN,
+                        TRUE,
+                        NM_ACTIVE_CONNECTION_STATE_FLAGS,
+                        (guint) initial_state_flags,
+                        NULL);
 }
 
 const char *
diff --git a/src/vpn/nm-vpn-connection.h b/src/core/vpn/nm-vpn-connection.h
index 9cf80a1e..66ec7eec 100644
--- a/src/vpn/nm-vpn-connection.h
+++ b/src/core/vpn/nm-vpn-connection.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2011 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
diff --git a/src/vpn/nm-vpn-manager.c b/src/core/vpn/nm-vpn-manager.c
index 607ad7f3..53fe58dd 100644
--- a/src/vpn/nm-vpn-manager.c
+++ b/src/core/vpn/nm-vpn-manager.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2012 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
-#include "nm-default.h"
+#include "src/core/nm-default-daemon.h"
 
 #include "nm-vpn-manager.h"
 
diff --git a/src/vpn/nm-vpn-manager.h b/src/core/vpn/nm-vpn-manager.h
index 5f24df25..41ecb8d2 100644
--- a/src/vpn/nm-vpn-manager.h
+++ b/src/core/vpn/nm-vpn-manager.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * Copyright (C) 2005 - 2011 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
diff --git a/src/devices/bluetooth/meson.build b/src/devices/bluetooth/meson.build
deleted file mode 100644
index 638a01a2..00000000
--- a/src/devices/bluetooth/meson.build
+++ /dev/null
@@ -1,60 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-sources = files(
-  'nm-bluez-manager.c',
-  'nm-bt-error.c',
-  'nm-device-bt.c',
-)
-
-deps = [
-  daemon_nm_default_dep,
-  libnm_wwan_dep,
-]
-
-if enable_bluez5_dun
-  sources += files('nm-bluez5-dun.c')
-
-  deps += bluez5_dep
-endif
-
-libnm_device_plugin_bluetooth_static = static_library(
-  'nm-device-plugin-bluetooth-static',
-  sources: sources,
-  dependencies: deps,
-  c_args: daemon_c_flags,
-)
-
-libnm_device_plugin_bluetooth_static_dep = declare_dependency(
-  link_whole: libnm_device_plugin_bluetooth_static,
-)
-
-deps += libnm_device_plugin_bluetooth_static_dep
-
-libnm_device_plugin_bluetooth = shared_module(
-  'nm-device-plugin-bluetooth',
-  dependencies: deps,
-  link_args: ldflags_linker_script_devices,
-  link_depends: linker_script_devices,
-  install: true,
-  install_dir: nm_plugindir,
-  install_rpath: nm_plugindir,
-)
-
-core_plugins += libnm_device_plugin_bluetooth
-
-test(
-  'check-local-devices-bluetooth',
-  check_exports,
-  args: [libnm_device_plugin_bluetooth.full_path(), linker_script_devices],
-)
-
-if enable_tests
-  test_unit = 'nm-bt-test'
-
-  executable(
-    test_unit,
-    'tests/' + test_unit + '.c',
-    dependencies: [ libnetwork_manager_test_dep, deps ],
-    c_args: test_c_flags,
-  )
-endif
diff --git a/src/devices/ovs/meson.build b/src/devices/ovs/meson.build
deleted file mode 100644
index 8457e68a..00000000
--- a/src/devices/ovs/meson.build
+++ /dev/null
@@ -1,33 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-sources = files(
-  'nm-device-ovs-bridge.c',
-  'nm-device-ovs-interface.c',
-  'nm-device-ovs-port.c',
-  'nm-ovsdb.c',
-  'nm-ovs-factory.c',
-)
-
-deps = [
-  daemon_nm_default_dep,
-  jansson_dep,
-]
-
-libnm_device_plugin_ovs = shared_module(
-  'nm-device-plugin-ovs',
-  sources: sources,
-  dependencies: deps,
-  c_args: daemon_c_flags,
-  link_args: ldflags_linker_script_devices,
-  link_depends: linker_script_devices,
-  install: true,
-  install_dir: nm_plugindir,
-)
-
-core_plugins += libnm_device_plugin_ovs
-
-test(
-  'check-local-devices-ovs',
-  check_exports,
-  args: [libnm_device_plugin_ovs.full_path(), linker_script_devices],
-)
diff --git a/src/dhcp/meson.build b/src/dhcp/meson.build
deleted file mode 100644
index 67f055d9..00000000
--- a/src/dhcp/meson.build
+++ /dev/null
@@ -1,23 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-name = 'nm-dhcp-helper'
-
-c_flags = [
-  '-DG_LOG_DOMAIN="@0@"'.format(name),
-  '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB',
-]
-
-executable(
-  name,
-  name + '.c',
-  dependencies: glib_nm_default_dep,
-  c_args: c_flags,
-  link_args: ldflags_linker_script_binary,
-  link_depends: linker_script_binary,
-  install: true,
-  install_dir: nm_libexecdir,
-)
-
-if enable_tests
-  subdir('tests')
-endif
diff --git a/src/initrd/meson.build b/src/initrd/meson.build
deleted file mode 100644
index c056f3c2..00000000
--- a/src/initrd/meson.build
+++ /dev/null
@@ -1,37 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-sources = files(
-  'nmi-cmdline-reader.c',
-  'nmi-dt-reader.c',
-  'nmi-ibft-reader.c',
-)
-
-libnmi_core = static_library(
-  'nmi-core',
-  sources: sources,
-  dependencies: daemon_nm_default_dep,
-  c_args: daemon_c_flags,
-)
-
-name = 'nm-initrd-generator'
-
-links = [
-  libnetwork_manager_base,
-  libnmi_core,
-]
-
-executable(
-  name,
-  name + '.c',
-  dependencies: daemon_nm_default_dep,
-  c_args: daemon_c_flags,
-  link_with: links,
-  link_args: ldflags_linker_script_binary,
-  link_depends: linker_script_binary,
-  install: true,
-  install_dir: nm_libexecdir,
-)
-
-if enable_tests
-  subdir('tests')
-endif
diff --git a/src/meson.build b/src/meson.build
deleted file mode 100644
index 667a6a2c..00000000
--- a/src/meson.build
+++ /dev/null
@@ -1,318 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-src_inc = include_directories('.')
-
-daemon_nm_default_dep = declare_dependency(
-  sources: libnm_core_enum_sources[1],
-  include_directories: src_inc,
-  dependencies: libnm_core_nm_default_dep,
-)
-
-install_data(
-  'org.freedesktop.NetworkManager.conf',
-  install_dir: dbus_conf_dir,
-)
-
-subdir('systemd')
-
-core_plugins = []
-
-daemon_c_flags = ['-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_DAEMON']
-
-sources = files(
-  'dhcp/nm-dhcp-client.c',
-  'dhcp/nm-dhcp-manager.c',
-  'dhcp/nm-dhcp-nettools.c',
-  'dhcp/nm-dhcp-systemd.c',
-  'dhcp/nm-dhcp-utils.c',
-  'dhcp/nm-dhcp-options.c',
-  'ndisc/nm-lndp-ndisc.c',
-  'ndisc/nm-ndisc.c',
-  'platform/nm-netlink.c',
-  'platform/wifi/nm-wifi-utils-nl80211.c',
-  'platform/wifi/nm-wifi-utils.c',
-  'platform/wpan/nm-wpan-utils.c',
-  'platform/nm-linux-platform.c',
-  'platform/nm-platform.c',
-  'platform/nm-platform-utils.c',
-  'platform/nmp-netns.c',
-  'platform/nmp-object.c',
-  'platform/nmp-rules-manager.c',
-  'main-utils.c',
-  'NetworkManagerUtils.c',
-  'nm-core-utils.c',
-  'nm-dbus-object.c',
-  'nm-dbus-utils.c',
-  'nm-netns.c',
-  'nm-l3-config-data.c',
-  'nm-l3cfg.c',
-  'nm-ip-config.c',
-  'nm-ip4-config.c',
-  'nm-ip6-config.c',
-  'nm-logging.c',
-)
-
-deps = [
-  daemon_nm_default_dep,
-  libn_dhcp4_dep,
-  libnm_keyfile_dep,
-  libnm_core_dep,
-  libnm_systemd_shared_dep,
-  libnm_udev_aux_dep,
-  libsystemd_dep,
-  libudev_dep,
-]
-
-if enable_wext
-  sources += files('platform/wifi/nm-wifi-utils-wext.c')
-endif
-
-libnetwork_manager_base = static_library(
-  nm_name + 'Base',
-  sources: sources,
-  dependencies: deps,
-  c_args: daemon_c_flags,
-)
-
-sources = files(
-  'devices/nm-acd-manager.c',
-  'devices/nm-device-6lowpan.c',
-  'devices/nm-device-bond.c',
-  'devices/nm-device-bridge.c',
-  'devices/nm-device.c',
-  'devices/nm-device-dummy.c',
-  'devices/nm-device-ethernet.c',
-  'devices/nm-device-ethernet-utils.c',
-  'devices/nm-device-factory.c',
-  'devices/nm-device-generic.c',
-  'devices/nm-device-infiniband.c',
-  'devices/nm-device-ip-tunnel.c',
-  'devices/nm-device-macsec.c',
-  'devices/nm-device-macvlan.c',
-  'devices/nm-device-ppp.c',
-  'devices/nm-device-tun.c',
-  'devices/nm-device-veth.c',
-  'devices/nm-device-vlan.c',
-  'devices/nm-device-vrf.c',
-  'devices/nm-device-vxlan.c',
-  'devices/nm-device-wireguard.c',
-  'devices/nm-device-wpan.c',
-  'devices/nm-lldp-listener.c',
-  'dhcp/nm-dhcp-dhclient.c',
-  'dhcp/nm-dhcp-dhclient-utils.c',
-  'dhcp/nm-dhcp-dhcpcanon.c',
-  'dhcp/nm-dhcp-dhcpcd.c',
-  'dhcp/nm-dhcp-listener.c',
-  'dns/nm-dns-dnsmasq.c',
-  'dns/nm-dns-manager.c',
-  'dns/nm-dns-plugin.c',
-  'dns/nm-dns-systemd-resolved.c',
-  'dns/nm-dns-unbound.c',
-  'dnsmasq/nm-dnsmasq-manager.c',
-  'dnsmasq/nm-dnsmasq-utils.c',
-  'ppp/nm-ppp-manager-call.c',
-  'settings/plugins/keyfile/nms-keyfile-storage.c',
-  'settings/plugins/keyfile/nms-keyfile-plugin.c',
-  'settings/plugins/keyfile/nms-keyfile-reader.c',
-  'settings/plugins/keyfile/nms-keyfile-utils.c',
-  'settings/plugins/keyfile/nms-keyfile-writer.c',
-  'settings/nm-agent-manager.c',
-  'settings/nm-secret-agent.c',
-  'settings/nm-settings.c',
-  'settings/nm-settings-connection.c',
-  'settings/nm-settings-plugin.c',
-  'settings/nm-settings-storage.c',
-  'settings/nm-settings-utils.c',
-  'supplicant/nm-supplicant-config.c',
-  'supplicant/nm-supplicant-interface.c',
-  'supplicant/nm-supplicant-manager.c',
-  'supplicant/nm-supplicant-settings-verify.c',
-  'vpn/nm-vpn-connection.c',
-  'vpn/nm-vpn-manager.c',
-  'nm-active-connection.c',
-  'nm-act-request.c',
-  'nm-audit-manager.c',
-  'nm-auth-manager.c',
-  'nm-auth-utils.c',
-  'nm-dbus-manager.c',
-  'nm-checkpoint.c',
-  'nm-checkpoint-manager.c',
-  'nm-config.c',
-  'nm-config-data.c',
-  'nm-connectivity.c',
-  'nm-dcb.c',
-  'nm-dhcp-config.c',
-  'nm-dispatcher.c',
-  'nm-firewall-manager.c',
-  'nm-hostname-manager.c',
-  'nm-keep-alive.c',
-  'nm-manager.c',
-  'nm-pacrunner-manager.c',
-  'nm-policy.c',
-  'nm-proxy-config.c',
-  'nm-rfkill-manager.c',
-  'nm-session-monitor.c',
-  'nm-sleep-monitor.c',
-)
-
-nm_deps = [
-  daemon_nm_default_dep,
-  dl_dep,
-  libn_acd_dep,
-  libndp_dep,
-  libudev_dep,
-  logind_dep,
-]
-
-nm_links = [
-  libnetwork_manager_base,
-  libnm_systemd_core,
-  libnm_systemd_shared,
-]
-
-if enable_concheck
-  nm_deps += libcurl_dep
-endif
-
-if enable_libaudit
-  nm_deps += libaudit_dep
-endif
-
-if enable_libpsl
-  nm_deps += libpsl_dep
-endif
-
-if enable_selinux
-  nm_deps += selinux_dep
-endif
-
-libnetwork_manager = static_library(
-  nm_name,
-  sources: sources,
-  dependencies: nm_deps,
-  c_args: daemon_c_flags,
-  link_with: nm_links,
-)
-
-executable(
-  'nm-iface-helper',
-  'nm-iface-helper.c',
-  dependencies: [
-    daemon_nm_default_dep,
-    dl_dep,
-    libndp_dep,
-    libudev_dep,
-    libn_acd_dep,
-  ],
-  c_args: daemon_c_flags,
-  link_with: nm_links,
-  link_args: ldflags_linker_script_binary,
-  link_depends: linker_script_binary,
-  install: true,
-  install_dir: nm_libexecdir,
-)
-
-if enable_tests
-  test_c_flags = daemon_c_flags + ['-DNETWORKMANAGER_COMPILATION_TEST']
-  if require_root_tests
-    test_c_flags += ['-DREQUIRE_ROOT_TESTS=1']
-  endif
-
-  sources = files(
-    'ndisc/nm-fake-ndisc.c',
-    'platform/tests/test-common.c',
-    'platform/nm-fake-platform.c',
-  )
-
-  libnetwork_manager_test = static_library(
-    nm_name + 'Test',
-    sources: sources,
-    dependencies: daemon_nm_default_dep,
-    c_args: test_c_flags,
-    link_with: libnetwork_manager,
-  )
-
-  libnetwork_manager_test_dep = declare_dependency(
-    dependencies: daemon_nm_default_dep,
-    link_with: libnetwork_manager_test,
-  )
-
-  subdir('dnsmasq/tests')
-  subdir('ndisc/tests')
-  subdir('platform/tests')
-  subdir('supplicant/tests')
-  subdir('tests')
-endif
-
-subdir('dhcp')
-
-if enable_ppp
-  subdir('ppp')
-endif
-
-subdir('devices')
-subdir('initrd')
-subdir('settings/plugins')
-
-# NetworkManager binary
-
-create_exports_networkmanager = join_paths(source_root, 'tools', 'create-exports-NetworkManager.sh')
-symbol_map_name = 'NetworkManager.ver'
-
-# libNetworkManager.a, as built by meson doesn't contain all symbols
-# from libNetworkManagerBase.a and other static libraries, unless we
-# add dependencies with link_whole, only supported in meson >= 0.46.
-# Create an executable with full symbols that we use in place of the
-# library to enumerate the symbols.
-network_manager_all_sym = executable(
-  'NetworkManager-all-sym',
-  'main.c',
-  dependencies: nm_deps,
-  c_args: daemon_c_flags,
-  link_args: '-Wl,--no-gc-sections',
-  link_whole: [libnetwork_manager, libnetwork_manager_base, libnm_core],
-)
-
-# this uses symbols from nm-full-symbols instead of libNetworkManager.a
-ver_script = custom_target(
-  symbol_map_name,
-  output: symbol_map_name,
-  depends: [network_manager_all_sym, core_plugins],
-  command: [create_exports_networkmanager, '--called-from-build', source_root],
-)
-
-ldflags = [
-  '-rdynamic',
-  '-Wl,--version-script,@0@'.format(ver_script.full_path()),
-]
-
-network_manager = executable(
-  nm_name,
-  'main.c',
-  dependencies: nm_deps,
-  c_args: daemon_c_flags,
-  link_with: libnetwork_manager,
-  link_args: ldflags,
-  link_depends: ver_script,
-  install: true,
-  install_dir: nm_sbindir,
-)
-
-if enable_tests
-  foreach plugin: core_plugins
-    plugin_path = plugin.full_path()
-
-    test(
-      'sym/' + plugin_path.split('/')[-1],
-      network_manager,
-      args: '--version',
-      env: ['LD_BIND_NOW=1', 'LD_PRELOAD=' + plugin_path],
-    )
-  endforeach
-endif
-
-test(
-  'check-config-options',
-  find_program(join_paths(source_root, 'tools', 'check-config-options.sh')),
-  args: source_root,
-)
diff --git a/src/ndisc/tests/test-ndisc-fake.c b/src/ndisc/tests/test-ndisc-fake.c
deleted file mode 100644
index dfb0b37b..00000000
--- a/src/ndisc/tests/test-ndisc-fake.c
+++ /dev/null
@@ -1,538 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * Copyright (C) 2015 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include <syslog.h>
-
-#include "ndisc/nm-ndisc.h"
-#include "ndisc/nm-fake-ndisc.h"
-
-#include "platform/nm-fake-platform.h"
-
-#include "nm-test-utils-core.h"
-
-static NMFakeNDisc *
-ndisc_new(void)
-{
-    NMNDisc *          ndisc;
-    const int          ifindex = 1;
-    const char *       ifname  = nm_platform_link_get_name(NM_PLATFORM_GET, ifindex);
-    NMUtilsIPv6IfaceId iid     = {};
-
-    ndisc        = nm_fake_ndisc_new(ifindex, ifname);
-    iid.id_u8[7] = 1;
-    nm_ndisc_set_iid(ndisc, iid);
-    g_assert(ndisc);
-    return NM_FAKE_NDISC(ndisc);
-}
-
-static void
-match_gateway(const NMNDiscData *rdata,
-              guint              idx,
-              const char *       addr,
-              guint32            ts,
-              guint32            lt,
-              NMIcmpv6RouterPref pref)
-{
-    const NMNDiscGateway *gw;
-    char                  buf[INET6_ADDRSTRLEN];
-
-    g_assert(rdata);
-    g_assert_cmpint(idx, <, rdata->gateways_n);
-    g_assert(rdata->gateways);
-
-    gw = &rdata->gateways[idx];
-
-    g_assert_cmpstr(inet_ntop(AF_INET6, &gw->address, buf, sizeof(buf)), ==, addr);
-    g_assert_cmpint(gw->timestamp, ==, ts);
-    g_assert_cmpint(gw->lifetime, ==, lt);
-    g_assert_cmpint(gw->preference, ==, pref);
-}
-
-#define match_address(rdata, idx, addr, ts, lt, pref)                     \
-    G_STMT_START                                                          \
-    {                                                                     \
-        const NMNDiscData *   _rdata = (rdata);                           \
-        guint                 _idx   = (idx);                             \
-        const NMNDiscAddress *_a;                                         \
-        guint                 _ts = (ts);                                 \
-                                                                          \
-        g_assert(_rdata);                                                 \
-        g_assert_cmpint(_idx, <, _rdata->addresses_n);                    \
-        g_assert(_rdata->addresses);                                      \
-                                                                          \
-        _a = &_rdata->addresses[_idx];                                    \
-                                                                          \
-        nmtst_assert_ip6_address(&_a->address, (addr));                   \
-        g_assert_cmpint(_a->timestamp, <=, _ts + 1);                      \
-        g_assert_cmpint((int) _a->timestamp, >=, (int) _ts - 1);          \
-        g_assert_cmpint(_a->timestamp + _a->lifetime, ==, _ts + (lt));    \
-        g_assert_cmpint(_a->timestamp + _a->preferred, ==, _ts + (pref)); \
-    }                                                                     \
-    G_STMT_END
-
-#define match_route(rdata, idx, nw, pl, gw, ts, lt, pref) \
-    G_STMT_START                                          \
-    {                                                     \
-        const NMNDiscData * _rdata = (rdata);             \
-        guint               _idx   = (idx);               \
-        const NMNDiscRoute *_r;                           \
-        int                 _plen = (pl);                 \
-                                                          \
-        g_assert(_rdata);                                 \
-        g_assert_cmpint(_idx, <, _rdata->routes_n);       \
-        g_assert(_rdata->routes);                         \
-        g_assert(_plen > 0 && _plen <= 128);              \
-                                                          \
-        _r = &_rdata->routes[idx];                        \
-                                                          \
-        nmtst_assert_ip6_address(&_r->network, (nw));     \
-        g_assert_cmpint((int) _r->plen, ==, _plen);       \
-        nmtst_assert_ip6_address(&_r->gateway, (gw));     \
-        g_assert_cmpint(_r->timestamp, ==, (ts));         \
-        g_assert_cmpint(_r->lifetime, ==, (lt));          \
-        g_assert_cmpint(_r->preference, ==, (pref));      \
-    }                                                     \
-    G_STMT_END
-
-static void
-match_dns_server(const NMNDiscData *rdata, guint idx, const char *addr, guint32 ts, guint32 lt)
-{
-    const NMNDiscDNSServer *dns;
-    char                    buf[INET6_ADDRSTRLEN];
-
-    g_assert(rdata);
-    g_assert_cmpint(idx, <, rdata->dns_servers_n);
-    g_assert(rdata->dns_servers);
-
-    dns = &rdata->dns_servers[idx];
-
-    g_assert_cmpstr(inet_ntop(AF_INET6, &dns->address, buf, sizeof(buf)), ==, addr);
-    g_assert_cmpint(dns->timestamp, ==, ts);
-    g_assert_cmpint(dns->lifetime, ==, lt);
-}
-
-static void
-match_dns_domain(const NMNDiscData *rdata, guint idx, const char *domain, guint32 ts, guint32 lt)
-{
-    const NMNDiscDNSDomain *dns;
-
-    g_assert(rdata);
-    g_assert_cmpint(idx, <, rdata->dns_domains_n);
-    g_assert(rdata->dns_domains);
-
-    dns = &rdata->dns_domains[idx];
-
-    g_assert_cmpstr(dns->domain, ==, domain);
-    g_assert_cmpint(dns->timestamp, ==, ts);
-    g_assert_cmpint(dns->lifetime, ==, lt);
-}
-
-typedef struct {
-    GMainLoop *loop;
-    guint      counter;
-    guint      rs_counter;
-    guint32    timestamp1;
-    guint32    first_solicit;
-    guint32    timeout_id;
-} TestData;
-
-static void
-test_simple_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data)
-{
-    NMNDiscConfigMap changed = changed_int;
-
-    g_assert_cmpint(changed,
-                    ==,
-                    NM_NDISC_CONFIG_DHCP_LEVEL | NM_NDISC_CONFIG_GATEWAYS
-                        | NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES
-                        | NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS
-                        | NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU);
-    g_assert_cmpint(rdata->dhcp_level, ==, NM_NDISC_DHCP_LEVEL_OTHERCONF);
-    match_gateway(rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-    match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
-    match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 10);
-    match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp1, 10);
-    match_dns_domain(rdata, 0, "foobar.com", data->timestamp1, 10);
-
-    g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
-    data->counter++;
-    g_main_loop_quit(data->loop);
-}
-
-static void
-test_simple(void)
-{
-    NMFakeNDisc *ndisc = ndisc_new();
-    guint32      now   = nm_utils_get_monotonic_timestamp_sec();
-    TestData     data  = {g_main_loop_new(NULL, FALSE), 0, 0, now};
-    guint        id;
-
-    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_OTHERCONF, 4, 1500);
-    g_assert(id);
-    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-    nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 10);
-    nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now, 10);
-    nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now, 10);
-
-    g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_simple_changed), &data);
-
-    nm_ndisc_start(NM_NDISC(ndisc));
-    g_main_loop_run(data.loop);
-    g_assert_cmpint(data.counter, ==, 1);
-
-    g_object_unref(ndisc);
-    g_main_loop_unref(data.loop);
-}
-
-static void
-test_everything_rs_sent(NMNDisc *ndisc, TestData *data)
-{
-    g_assert_cmpint(data->rs_counter, ==, 0);
-    data->rs_counter++;
-}
-
-static void
-test_everything_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data)
-{
-    NMNDiscConfigMap changed = changed_int;
-
-    if (data->counter == 0) {
-        g_assert_cmpint(data->rs_counter, ==, 1);
-        g_assert_cmpint(changed,
-                        ==,
-                        NM_NDISC_CONFIG_DHCP_LEVEL | NM_NDISC_CONFIG_GATEWAYS
-                            | NM_NDISC_CONFIG_ADDRESSES | NM_NDISC_CONFIG_ROUTES
-                            | NM_NDISC_CONFIG_DNS_SERVERS | NM_NDISC_CONFIG_DNS_DOMAINS
-                            | NM_NDISC_CONFIG_HOP_LIMIT | NM_NDISC_CONFIG_MTU);
-        match_gateway(rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-        match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
-        match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 10);
-        match_dns_server(rdata, 0, "2001:db8:c:c::1", data->timestamp1, 10);
-        match_dns_domain(rdata, 0, "foobar.com", data->timestamp1, 10);
-    } else if (data->counter == 1) {
-        g_assert_cmpint(changed,
-                        ==,
-                        NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES
-                            | NM_NDISC_CONFIG_ROUTES | NM_NDISC_CONFIG_DNS_SERVERS
-                            | NM_NDISC_CONFIG_DNS_DOMAINS);
-
-        g_assert_cmpint(rdata->gateways_n, ==, 1);
-        match_gateway(rdata, 0, "fe80::2", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-        g_assert_cmpint(rdata->addresses_n, ==, 2);
-        match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 0);
-        match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1, 10, 10);
-        g_assert_cmpint(rdata->routes_n, ==, 1);
-        match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1, 10, 10);
-        g_assert_cmpint(rdata->dns_servers_n, ==, 1);
-        match_dns_server(rdata, 0, "2001:db8:c:c::2", data->timestamp1, 10);
-        g_assert_cmpint(rdata->dns_domains_n, ==, 1);
-        match_dns_domain(rdata, 0, "foobar2.com", data->timestamp1, 10);
-
-        g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
-        g_main_loop_quit(data->loop);
-    } else
-        g_assert_not_reached();
-
-    data->counter++;
-}
-
-static void
-test_everything(void)
-{
-    NMFakeNDisc *ndisc = ndisc_new();
-    guint32      now   = nm_utils_get_monotonic_timestamp_sec();
-    TestData     data  = {g_main_loop_new(NULL, FALSE), 0, 0, now};
-    guint        id;
-
-    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
-    g_assert(id);
-    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-    nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 10);
-    nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now, 10);
-    nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now, 10);
-
-    /* expire everything from the first RA in the second */
-    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
-    g_assert(id);
-    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 0, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-    nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 0, 0, 0);
-    nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now, 0);
-    nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar.com", now, 0);
-
-    /* and add some new stuff */
-    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-    nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10, 10);
-    nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::2", now, 10);
-    nm_fake_ndisc_add_dns_domain(ndisc, id, "foobar2.com", now, 10);
-
-    g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_everything_changed), &data);
-    g_signal_connect(ndisc, NM_FAKE_NDISC_RS_SENT, G_CALLBACK(test_everything_rs_sent), &data);
-
-    nm_ndisc_start(NM_NDISC(ndisc));
-    g_main_loop_run(data.loop);
-    g_assert_cmpint(data.counter, ==, 2);
-    g_assert_cmpint(data.rs_counter, ==, 1);
-
-    g_object_unref(ndisc);
-    g_main_loop_unref(data.loop);
-}
-
-static void
-test_preference_order_cb(NMNDisc *          ndisc,
-                         const NMNDiscData *rdata,
-                         guint              changed_int,
-                         TestData *         data)
-{
-    NMNDiscConfigMap changed = changed_int;
-
-    if (data->counter == 1) {
-        g_assert_cmpint(changed,
-                        ==,
-                        NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES
-                            | NM_NDISC_CONFIG_ROUTES);
-
-        g_assert_cmpint(rdata->gateways_n, ==, 2);
-        match_gateway(rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_HIGH);
-        match_gateway(rdata, 1, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_LOW);
-        g_assert_cmpint(rdata->addresses_n, ==, 2);
-        match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
-        match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10);
-        g_assert_cmpint(rdata->routes_n, ==, 2);
-        match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10);
-        match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 5);
-
-        g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
-        g_main_loop_quit(data->loop);
-    }
-
-    data->counter++;
-}
-
-static void
-test_preference_order(void)
-{
-    NMFakeNDisc *ndisc = ndisc_new();
-    guint32      now   = nm_utils_get_monotonic_timestamp_sec();
-    TestData     data  = {g_main_loop_new(NULL, FALSE), 0, 0, now};
-    guint        id;
-
-    /* Test insertion order of gateways */
-
-    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
-    g_assert(id);
-    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_HIGH);
-    nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 5);
-
-    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
-    g_assert(id);
-    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", ++now, 10, NM_ICMPV6_ROUTER_PREF_LOW);
-    nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10, 10);
-
-    g_signal_connect(ndisc, NM_NDISC_CONFIG_RECEIVED, G_CALLBACK(test_preference_order_cb), &data);
-
-    nm_ndisc_start(NM_NDISC(ndisc));
-    g_main_loop_run(data.loop);
-    g_assert_cmpint(data.counter, ==, 2);
-
-    g_object_unref(ndisc);
-    g_main_loop_unref(data.loop);
-}
-
-static void
-test_preference_changed_cb(NMNDisc *          ndisc,
-                           const NMNDiscData *rdata,
-                           guint              changed_int,
-                           TestData *         data)
-{
-    NMNDiscConfigMap changed = changed_int;
-
-    if (data->counter == 1) {
-        g_assert_cmpint(changed,
-                        ==,
-                        NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES
-                            | NM_NDISC_CONFIG_ROUTES);
-        g_assert_cmpint(rdata->gateways_n, ==, 2);
-        match_gateway(rdata, 0, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-        match_gateway(rdata, 1, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_LOW);
-        g_assert_cmpint(rdata->addresses_n, ==, 2);
-        match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
-        match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10);
-        g_assert_cmpint(rdata->routes_n, ==, 2);
-        match_route(rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10);
-        match_route(rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 5);
-    } else if (data->counter == 2) {
-        g_assert_cmpint(changed,
-                        ==,
-                        NM_NDISC_CONFIG_GATEWAYS | NM_NDISC_CONFIG_ADDRESSES
-                            | NM_NDISC_CONFIG_ROUTES);
-
-        g_assert_cmpint(rdata->gateways_n, ==, 2);
-        match_gateway(rdata, 0, "fe80::1", data->timestamp1 + 2, 10, NM_ICMPV6_ROUTER_PREF_HIGH);
-        match_gateway(rdata, 1, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-        g_assert_cmpint(rdata->addresses_n, ==, 2);
-        match_address(rdata, 0, "2001:db8:a:a::1", data->timestamp1 + 3, 9, 9);
-        match_address(rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10);
-        g_assert_cmpint(rdata->routes_n, ==, 2);
-        match_route(rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1 + 2, 10, 15);
-        match_route(rdata, 1, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10);
-
-        g_assert(nm_fake_ndisc_done(NM_FAKE_NDISC(ndisc)));
-        g_main_loop_quit(data->loop);
-    }
-
-    data->counter++;
-}
-
-static void
-test_preference_changed(void)
-{
-    NMFakeNDisc *ndisc = ndisc_new();
-    guint32      now   = nm_utils_get_monotonic_timestamp_sec();
-    TestData     data  = {g_main_loop_new(NULL, FALSE), 0, 0, now};
-    guint        id;
-
-    /* Test that when a low-preference and medium gateway send advertisements,
-     * that if the low-preference gateway switches to high-preference, we do
-     * not get duplicates in the gateway list.
-     */
-
-    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
-    g_assert(id);
-    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_LOW);
-    nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 5);
-
-    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
-    g_assert(id);
-    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::2", ++now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-    nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10, 10);
-
-    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
-    g_assert(id);
-    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", ++now, 10, NM_ICMPV6_ROUTER_PREF_HIGH);
-    nm_fake_ndisc_add_prefix(ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 15);
-
-    g_signal_connect(ndisc,
-                     NM_NDISC_CONFIG_RECEIVED,
-                     G_CALLBACK(test_preference_changed_cb),
-                     &data);
-
-    nm_ndisc_start(NM_NDISC(ndisc));
-    g_main_loop_run(data.loop);
-    g_assert_cmpint(data.counter, ==, 3);
-
-    g_object_unref(ndisc);
-    g_main_loop_unref(data.loop);
-}
-
-static void
-test_dns_solicit_loop_changed(NMNDisc *          ndisc,
-                              const NMNDiscData *rdata,
-                              guint              changed_int,
-                              TestData *         data)
-{
-    data->counter++;
-}
-
-static gboolean
-success_timeout(TestData *data)
-{
-    data->timeout_id = 0;
-    g_main_loop_quit(data->loop);
-    return G_SOURCE_REMOVE;
-}
-
-static void
-test_dns_solicit_loop_rs_sent(NMFakeNDisc *ndisc, TestData *data)
-{
-    guint32 now = nm_utils_get_monotonic_timestamp_sec();
-    guint   id;
-
-    if (data->rs_counter > 0 && data->rs_counter < 6) {
-        if (data->rs_counter == 1) {
-            data->first_solicit = now;
-            /* Kill the test after 10 seconds if it hasn't failed yet */
-            data->timeout_id = g_timeout_add_seconds(10, (GSourceFunc) success_timeout, data);
-        }
-
-        /* On all but the first solicitation, which should be triggered by the
-         * DNS servers reaching 1/2 lifetime, emit a new RA without the DNS
-         * servers again.
-         */
-        id = nm_fake_ndisc_add_ra(ndisc, 0, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
-        g_assert(id);
-        nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-
-        nm_fake_ndisc_emit_new_ras(ndisc);
-    } else if (data->rs_counter >= 6) {
-        /* Fail if we've sent too many solicitations in the past 4 seconds */
-        g_assert_cmpint(now - data->first_solicit, >, 4);
-        g_source_remove(data->timeout_id);
-        g_main_loop_quit(data->loop);
-    }
-    data->rs_counter++;
-}
-
-static void
-test_dns_solicit_loop(void)
-{
-    NMFakeNDisc *ndisc = ndisc_new();
-    guint32      now   = nm_utils_get_monotonic_timestamp_sec();
-    TestData     data  = {g_main_loop_new(NULL, FALSE), 0, 0, now, 0};
-    guint        id;
-
-    /* Ensure that no solicitation loop happens when DNS servers or domains
-     * stop being sent in advertisements.  This can happen if two routers
-     * send RAs, but the one sending DNS info stops responding, or if one
-     * router removes the DNS info from the RA without zero-lifetiming them
-     * first.
-     */
-
-    id = nm_fake_ndisc_add_ra(ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
-    g_assert(id);
-    nm_fake_ndisc_add_gateway(ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_LOW);
-    nm_fake_ndisc_add_dns_server(ndisc, id, "2001:db8:c:c::1", now, 6);
-
-    g_signal_connect(ndisc,
-                     NM_NDISC_CONFIG_RECEIVED,
-                     G_CALLBACK(test_dns_solicit_loop_changed),
-                     &data);
-    g_signal_connect(ndisc,
-                     NM_FAKE_NDISC_RS_SENT,
-                     G_CALLBACK(test_dns_solicit_loop_rs_sent),
-                     &data);
-
-    nm_ndisc_start(NM_NDISC(ndisc));
-    g_main_loop_run(data.loop);
-    g_assert_cmpint(data.counter, ==, 3);
-
-    g_object_unref(ndisc);
-    g_main_loop_unref(data.loop);
-}
-
-NMTST_DEFINE();
-
-int
-main(int argc, char **argv)
-{
-    nmtst_init_with_logging(&argc, &argv, NULL, "DEFAULT");
-
-    if (nmtst_test_quick()) {
-        g_print("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n",
-                g_get_prgname() ?: "test-ndisc-fake");
-        return g_test_run();
-    }
-
-    nm_fake_platform_setup();
-
-    g_test_add_func("/ndisc/simple", test_simple);
-    g_test_add_func("/ndisc/everything-changed", test_everything);
-    g_test_add_func("/ndisc/preference-order", test_preference_order);
-    g_test_add_func("/ndisc/preference-changed", test_preference_changed);
-    g_test_add_func("/ndisc/dns-solicit-loop", test_dns_solicit_loop);
-
-    return g_test_run();
-}
diff --git a/src/nm-logging.c b/src/nm-logging.c
deleted file mode 100644
index 11affbfb..00000000
--- a/src/nm-logging.c
+++ /dev/null
@@ -1,1036 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * Copyright (C) 2006 - 2012 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-logging.h"
-
-#include <dlfcn.h>
-#include <syslog.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/wait.h>
-#include <sys/stat.h>
-#include <strings.h>
-
-#if SYSTEMD_JOURNAL
-    #define SD_JOURNAL_SUPPRESS_LOCATION
-    #include <systemd/sd-journal.h>
-#endif
-
-#include "nm-glib-aux/nm-logging-base.h"
-#include "nm-glib-aux/nm-time-utils.h"
-#include "nm-errors.h"
-
-/*****************************************************************************/
-
-/* 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.
- */
-
-/*****************************************************************************/
-
-G_STATIC_ASSERT(LOG_EMERG == 0);
-G_STATIC_ASSERT(LOG_ALERT == 1);
-G_STATIC_ASSERT(LOG_CRIT == 2);
-G_STATIC_ASSERT(LOG_ERR == 3);
-G_STATIC_ASSERT(LOG_WARNING == 4);
-G_STATIC_ASSERT(LOG_NOTICE == 5);
-G_STATIC_ASSERT(LOG_INFO == 6);
-G_STATIC_ASSERT(LOG_DEBUG == 7);
-
-/* We have more then 32 logging domains. Assert that it compiles to a 64 bit sized enum */
-G_STATIC_ASSERT(sizeof(NMLogDomain) >= sizeof(guint64));
-
-/* 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;
-    const char *name;
-} LogDesc;
-
-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);
-     *
-     * Note: LOGD_VPN_PLUGIN is special and must be disabled for
-     * DEBUG and TRACE levels. */
-    [LOGL_INFO] = LOGD_DEFAULT,
-    [LOGL_WARN] = LOGD_DEFAULT,
-    [LOGL_ERR]  = LOGD_DEFAULT,
-};
-
-/*****************************************************************************/
-
-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,
-                                NMLogLevel        log_level,
-                                const NMLogDomain log_state[static _LOGL_N_REAL]);
-
-/*****************************************************************************/
-
-static gboolean
-_syslog_identifier_valid_domain(const char *domain)
-{
-    char c;
-
-    if (!domain || !domain[0])
-        return FALSE;
-
-    /* we pass the syslog identifier as format string. No funny stuff. */
-
-    for (; (c = domain[0]); domain++) {
-        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
-            || NM_IN_SET(c, '-', '_'))
-            continue;
-        return FALSE;
-    }
-    return TRUE;
-}
-
-static gboolean
-_syslog_identifier_assert(const char *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 char *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 char *syslog_identifier)
-{
-    nm_assert(_syslog_identifier_assert(syslog_identifier));
-    return &syslog_identifier[0];
-}
-#endif
-
-/*****************************************************************************/
-
-static gboolean
-match_log_level(const char *level, NMLogLevel *out_level, GError **error)
-{
-    if (_nm_log_parse_level(level, out_level))
-        return TRUE;
-
-    g_set_error(error,
-                NM_MANAGER_ERROR,
-                NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL,
-                _("Unknown log level '%s'"),
-                level);
-    return FALSE;
-}
-
-gboolean
-nm_logging_setup(const char *level, const char *domains, char **bad_domains, GError **error)
-{
-    GString *            unrecognized = NULL;
-    NMLogDomain          cur_log_state[_LOGL_N_REAL];
-    NMLogDomain          new_log_state[_LOGL_N_REAL];
-    NMLogLevel           cur_log_level;
-    NMLogLevel           new_log_level;
-    gs_free const char **domains_v = NULL;
-    gsize                i_d;
-    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);
-
-    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_log_state); i++)
-        new_log_state[i] = 0;
-
-    if (level && *level) {
-        if (!match_log_level(level, &new_log_level, error))
-            return FALSE;
-        if (new_log_level == _LOGL_KEEP) {
-            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];
-        }
-    }
-
-    domains_v = nm_utils_strsplit_set(domains, ", ");
-    for (i_d = 0; domains_v && domains_v[i_d]; i_d++) {
-        const char *   s = domains_v[i_d];
-        const char *   p;
-        const LogDesc *diter;
-        NMLogLevel     domain_log_level;
-        NMLogDomain    bits;
-
-        /* LOGD_VPN_PLUGIN is protected, that is, when setting ALL or DEFAULT,
-         * it does not enable the verbose levels DEBUG and TRACE, because that
-         * may expose sensitive data. */
-        NMLogDomain protect = LOGD_NONE;
-
-        p = strchr(s, ':');
-        if (p) {
-            *((char *) p) = '\0';
-            if (!match_log_level(p + 1, &domain_log_level, error))
-                return FALSE;
-        } else
-            domain_log_level = new_log_level;
-
-        bits = 0;
-
-        if (domains_free) {
-            /* The caller didn't provide any domains to set (`nmcli general logging level DEBUG`).
-             * We reset all domains that were previously set, but we still want to protect
-             * VPN_PLUGIN domain. */
-            protect = LOGD_VPN_PLUGIN;
-        }
-
-        /* Check for combined domains */
-        if (!g_ascii_strcasecmp(s, LOGD_ALL_STRING)) {
-            bits    = LOGD_ALL;
-            protect = LOGD_VPN_PLUGIN;
-        } else if (!g_ascii_strcasecmp(s, LOGD_DEFAULT_STRING)) {
-            bits    = LOGD_DEFAULT;
-            protect = LOGD_VPN_PLUGIN;
-        } else if (!g_ascii_strcasecmp(s, LOGD_DHCP_STRING))
-            bits = LOGD_DHCP;
-        else if (!g_ascii_strcasecmp(s, LOGD_IP_STRING))
-            bits = LOGD_IP;
-
-        /* Check for compatibility domains */
-        else if (!g_ascii_strcasecmp(s, "HW"))
-            bits = LOGD_PLATFORM;
-        else if (!g_ascii_strcasecmp(s, "WIMAX"))
-            continue;
-
-        else {
-            for (diter = &domain_desc[0]; diter->name; diter++) {
-                if (!g_ascii_strcasecmp(diter->name, s)) {
-                    bits = diter->num;
-                    break;
-                }
-            }
-
-            if (!bits) {
-                if (!bad_domains) {
-                    g_set_error(error,
-                                NM_MANAGER_ERROR,
-                                NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN,
-                                _("Unknown log domain '%s'"),
-                                s);
-                    return FALSE;
-                }
-
-                if (unrecognized)
-                    g_string_append(unrecognized, ", ");
-                else
-                    unrecognized = g_string_new(NULL);
-                g_string_append(unrecognized, s);
-                continue;
-            }
-        }
-
-        if (domain_log_level == _LOGL_KEEP) {
-            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_log_state); i++) {
-                if (i < domain_log_level)
-                    new_log_state[i] &= ~bits;
-                else {
-                    new_log_state[i] |= bits;
-                    if ((protect & bits) && i < LOGL_INFO)
-                        new_log_state[i] &= ~protect;
-                }
-            }
-        }
-    }
-
-    nm_clear_g_free(&gl_main.logging_domains_to_string);
-
-    had_platform_debug = _nm_logging_enabled_lockfree(LOGL_DEBUG, LOGD_PLATFORM);
-
-    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_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. */
-        _nm_logging_clear_platform_logging_cache();
-    }
-
-    if (unrecognized)
-        *bad_domains = g_string_free(unrecognized, FALSE);
-
-    return TRUE;
-}
-
-const char *
-nm_logging_level_to_string(void)
-{
-    NM_ASSERT_ON_MAIN_THREAD();
-
-    return level_desc[gl.imm.log_level].name;
-}
-
-const char *
-nm_logging_all_levels_to_string(void)
-{
-    static GString *str;
-
-    if (G_UNLIKELY(!str)) {
-        int i;
-
-        str = g_string_new(NULL);
-        for (i = 0; i < G_N_ELEMENTS(level_desc); i++) {
-            if (str->len)
-                g_string_append_c(str, ',');
-            g_string_append(str, level_desc[i].name);
-        }
-    }
-
-    return str->str;
-}
-
-const char *
-nm_logging_domains_to_string(void)
-{
-    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 gl_main.logging_domains_to_string;
-}
-
-static char *
-_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() 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 = &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 & log_state[LOGL_ERR]))
-            continue;
-
-        if (str->len)
-            g_string_append_c(str, ',');
-        g_string_append(str, diter->name);
-
-        if (!include_level_override)
-            continue;
-
-        /* Check if it's logging at a lower level than the default. */
-        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 & 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;
-                }
-            }
-        }
-    }
-    return g_string_free(str, FALSE);
-}
-
-static char _all_logging_domains_to_str[273];
-
-const char *
-nm_logging_all_domains_to_string(void)
-{
-    static const char *volatile str = NULL;
-    const char *s;
-
-again:
-    s = g_atomic_pointer_get(&str);
-    if (G_UNLIKELY(!s)) {
-        static gsize   once = 0;
-        const LogDesc *diter;
-        gsize          buf_l;
-        char *         buf_p;
-
-        if (!g_once_init_enter(&once))
-            goto again;
-
-        buf_p = _all_logging_domains_to_str;
-        buf_l = sizeof(_all_logging_domains_to_str);
-
-        nm_utils_strbuf_append_str(&buf_p, &buf_l, LOGD_DEFAULT_STRING);
-        for (diter = &domain_desc[0]; diter->name; diter++) {
-            nm_utils_strbuf_append_c(&buf_p, &buf_l, ',');
-            nm_utils_strbuf_append_str(&buf_p, &buf_l, diter->name);
-            if (diter->num == LOGD_DHCP6)
-                nm_utils_strbuf_append_str(&buf_p, &buf_l, "," LOGD_DHCP_STRING);
-            else if (diter->num == LOGD_IP6)
-                nm_utils_strbuf_append_str(&buf_p, &buf_l, "," LOGD_IP_STRING);
-        }
-        nm_utils_strbuf_append_str(&buf_p, &buf_l, LOGD_ALL_STRING);
-
-        /* Did you modify the logging domains (or their names)? Adjust the size of
-         * _all_logging_domains_to_str buffer above to have the exact size. */
-        nm_assert(strlen(_all_logging_domains_to_str) == sizeof(_all_logging_domains_to_str) - 1);
-        nm_assert(buf_l == 1);
-
-        s = _all_logging_domains_to_str;
-        g_atomic_pointer_set(&str, s);
-        g_once_init_leave(&once, 1);
-    }
-
-    return s;
-}
-
-/**
- * nm_logging_get_level:
- * @domain: find the lowest enabled logging level for the
- *   given domain. If this is a set of multiple
- *   domains, the most verbose level will be returned.
- *
- * Returns: the lowest (most verbose) logging level for the
- *   give @domain, or %_LOGL_OFF if it is disabled.
- **/
-NMLogLevel
-nm_logging_get_level(NMLogDomain domain)
-{
-    NMLogLevel sl = _LOGL_OFF;
-
-    G_STATIC_ASSERT(LOGL_TRACE == 0);
-    while (sl > LOGL_TRACE && _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)
-{
-    iov->iov_base = (void *) str;
-    iov->iov_len  = len;
-}
-
-static void
-_iovec_set_string(struct iovec *iov, const char *str)
-{
-    _iovec_set(iov, str, strlen(str));
-}
-
-    #define _iovec_set_string_literal(iov, str) _iovec_set((iov), "" str "", NM_STRLEN(str))
-
-_nm_printf(3, 4) static void _iovec_set_format(struct iovec *iov,
-                                               char **       iov_free,
-                                               const char *  format,
-                                               ...)
-{
-    va_list ap;
-    char *  str;
-
-    va_start(ap, format);
-    str = g_strdup_vprintf(format, ap);
-    va_end(ap);
-
-    _iovec_set_string(iov, str);
-    *iov_free = str;
-}
-
-    #define _iovec_set_format_a(iov, reserve_extra, format, ...)                   \
-        G_STMT_START                                                               \
-        {                                                                          \
-            const gsize _size = (reserve_extra) + (NM_STRLEN(format) + 3);         \
-            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 == 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,
-             const char *ifname,
-             const char *conn_uuid,
-             const char *fmt,
-             ...)
-{
-    va_list            args;
-    char *             msg;
-    GTimeVal           tv;
-    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;
-    }
-
-    (void) cur_log_state;
-
-    errsv = errno;
-
-    /* Make sure that %m maps to the specified error */
-    if (error != 0) {
-        if (error < 0)
-            error = -error;
-        errno = error;
-    }
-
-    va_start(args, fmt);
-    msg = g_strdup_vprintf(fmt, args);
-    va_end(args);
-
-#define MESSAGE_FMT "%s%-7s [%ld.%04ld] %s"
-#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 (g->debug_stderr)
-        g_printerr(MESSAGE_FMT "\n", MESSAGE_ARG(g->prefix, tv, msg));
-
-    switch (g->log_backend) {
-#if SYSTEMD_JOURNAL
-    case LOG_BACKEND_JOURNAL:
-    {
-        gint64         now, boottime;
-        struct iovec   iov_data[15];
-        struct iovec * iov = iov_data;
-        char *         iov_free_data[5];
-        char **        iov_free = iov_free_data;
-        const LogDesc *diter;
-        NMLogDomain    dom_all;
-        char  s_log_domains_buf[NM_STRLEN("NM_LOG_DOMAINS=") + sizeof(_all_logging_domains_to_str)];
-        char *s_log_domains;
-        gsize l_log_domains;
-
-        now      = nm_utils_get_monotonic_timestamp_nsec();
-        boottime = nm_utils_monotonic_timestamp_as_boottime(now, 1);
-
-        _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());
-
-        dom_all       = domain;
-        s_log_domains = s_log_domains_buf;
-        l_log_domains = sizeof(s_log_domains_buf);
-
-        nm_utils_strbuf_append_str(&s_log_domains, &l_log_domains, "NM_LOG_DOMAINS=");
-        for (diter = &domain_desc[0]; dom_all != 0 && diter->name; diter++) {
-            if (!NM_FLAGS_ANY(dom_all, diter->num))
-                continue;
-            if (dom_all != domain)
-                nm_utils_strbuf_append_c(&s_log_domains, &l_log_domains, ',');
-            nm_utils_strbuf_append_str(&s_log_domains, &l_log_domains, diter->name);
-            dom_all &= ~diter->num;
-        }
-        nm_assert(l_log_domains > 0);
-        _iovec_set(iov++, s_log_domains_buf, s_log_domains - s_log_domains_buf);
-
-        G_STATIC_ASSERT_EXPR(LOG_FAC(LOG_DAEMON) == 3);
-        _iovec_set_string_literal(iov++, "SYSLOG_FACILITY=3");
-        _iovec_set_format_str_a(iov++, 15, "NM_LOG_LEVEL=%s", level_desc[level].name);
-        if (func)
-            _iovec_set_format(iov++, iov_free++, "CODE_FUNC=%s", func);
-        _iovec_set_format(iov++, iov_free++, "CODE_FILE=%s", file ?: "");
-        _iovec_set_format_a(iov++, 20, "CODE_LINE=%u", line);
-        _iovec_set_format_a(iov++,
-                            60,
-                            "TIMESTAMP_MONOTONIC=%lld.%06lld",
-                            (long long) (now / NM_UTILS_NSEC_PER_SEC),
-                            (long long) ((now % NM_UTILS_NSEC_PER_SEC) / 1000));
-        _iovec_set_format_a(iov++,
-                            60,
-                            "TIMESTAMP_BOOTTIME=%lld.%06lld",
-                            (long long) (boottime / NM_UTILS_NSEC_PER_SEC),
-                            (long long) ((boottime % NM_UTILS_NSEC_PER_SEC) / 1000));
-        if (error != 0)
-            _iovec_set_format_a(iov++, 30, "ERRNO=%d", error);
-        if (ifname)
-            _iovec_set_format(iov++, iov_free++, "NM_DEVICE=%s", ifname);
-        if (conn_uuid)
-            _iovec_set_format(iov++, iov_free++, "NM_CONNECTION=%s", conn_uuid);
-
-        nm_assert(iov <= &iov_data[G_N_ELEMENTS(iov_data)]);
-        nm_assert(iov_free <= &iov_free_data[G_N_ELEMENTS(iov_free_data)]);
-
-        sd_journal_sendv(iov_data, iov - iov_data);
-
-        for (; --iov_free >= iov_free_data;)
-            g_free(*iov_free);
-    } break;
-#endif
-    case LOG_BACKEND_SYSLOG:
-        syslog(level_desc[level].syslog_level, MESSAGE_FMT, MESSAGE_ARG(g->prefix, tv, msg));
-        break;
-    default:
-        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 = 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);
-    }
-}
-
-/*****************************************************************************/
-
-static void
-nm_log_handler(const char *log_domain, GLogLevelFlags level, const char *message, gpointer ignored)
-{
-    int syslog_priority;
-
-    switch (level & G_LOG_LEVEL_MASK) {
-    case G_LOG_LEVEL_ERROR:
-        syslog_priority = LOG_CRIT;
-        break;
-    case G_LOG_LEVEL_CRITICAL:
-        syslog_priority = LOG_ERR;
-        break;
-    case G_LOG_LEVEL_WARNING:
-        syslog_priority = LOG_WARNING;
-        break;
-    case G_LOG_LEVEL_MESSAGE:
-        syslog_priority = LOG_NOTICE;
-        break;
-    case G_LOG_LEVEL_DEBUG:
-        syslog_priority = LOG_DEBUG;
-        break;
-    case G_LOG_LEVEL_INFO:
-    default:
-        syslog_priority = LOG_INFO;
-        break;
-    }
-
-    /* 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);
-
-    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:
-    {
-        gint64 now, boottime;
-
-        now      = nm_utils_get_monotonic_timestamp_nsec();
-        boottime = nm_utils_monotonic_timestamp_as_boottime(now, 1);
-
-        sd_journal_send("PRIORITY=%d",
-                        syslog_priority,
-                        "MESSAGE=%s%s",
-                        gl.imm.prefix,
-                        message ?: "",
-                        syslog_identifier_full(gl.imm.syslog_identifier),
-                        "SYSLOG_PID=%ld",
-                        (long) getpid(),
-                        "SYSLOG_FACILITY=3",
-                        "GLIB_DOMAIN=%s",
-                        log_domain ?: "",
-                        "GLIB_LEVEL=%d",
-                        (int) (level & G_LOG_LEVEL_MASK),
-                        "TIMESTAMP_MONOTONIC=%lld.%06lld",
-                        (long long) (now / NM_UTILS_NSEC_PER_SEC),
-                        (long long) ((now % NM_UTILS_NSEC_PER_SEC) / 1000),
-                        "TIMESTAMP_BOOTTIME=%lld.%06lld",
-                        (long long) (boottime / NM_UTILS_NSEC_PER_SEC),
-                        (long long) ((boottime % NM_UTILS_NSEC_PER_SEC) / 1000),
-                        NULL);
-    } break;
-#endif
-    default:
-        syslog(syslog_priority, "%s%s", gl.imm.prefix, message ?: "");
-        break;
-    }
-}
-
-gboolean
-nm_logging_syslog_enabled(void)
-{
-    NM_ASSERT_ON_MAIN_THREAD();
-
-    return gl.imm.uses_syslog;
-}
-
-void
-nm_logging_init_pre(const char *syslog_identifier, char *prefix_take)
-{
-    /* 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();
-
-    if (gl.imm.init_pre_done)
-        g_return_if_reached();
-
-    if (gl.imm.init_done)
-        g_return_if_reached();
-
-    if (!_syslog_identifier_valid_domain(syslog_identifier))
-        g_return_if_reached();
-
-    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. */
-    gl.mut.prefix = prefix_take;
-
-    G_UNLOCK(log);
-}
-
-void
-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 (gl.imm.init_done)
-        g_return_if_reached();
-
-    if (!logging_backend)
-        logging_backend = "" NM_CONFIG_DEFAULT_LOGGING_BACKEND;
-
-    if (nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_DEBUG)) {
-        /* "debug" was wrongly documented as a valid logging backend. It makes no sense however,
-         * because printing to stderr only makes sense when not demonizing. Whether to daemonize
-         * is only controlled via command line arguments (--no-daemon, --debug) and not via the
-         * logging backend from configuration.
-         *
-         * Fall back to the default. */
-        logging_backend        = "" NM_CONFIG_DEFAULT_LOGGING_BACKEND;
-        obsolete_debug_backend = TRUE;
-    }
-
-    G_LOCK(log);
-
-#if SYSTEMD_JOURNAL
-    if (!nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_SYSLOG)) {
-        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
-    {
-        x_log_backend = LOG_BACKEND_SYSLOG;
-        openlog(syslog_identifier_domain(gl.imm.syslog_identifier), LOG_PID, LOG_DAEMON);
-    }
-
-    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. */
-        nm_utils_get_monotonic_timestamp_nsec();
-    }
-
-    if (obsolete_debug_backend)
-        nm_log_dbg(LOGD_CORE,
-                   "config: ignore deprecated logging backend 'debug', fallback to '%s'",
-                   logging_backend);
-
-    if (nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_SYSLOG)) {
-        /* good */
-    } else if (nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_JOURNAL)) {
-#if !SYSTEMD_JOURNAL
-        nm_log_warn(LOGD_CORE,
-                    "config: logging backend 'journal' is not available, fallback to 'syslog'");
-#endif
-    } else {
-        nm_log_warn(LOGD_CORE,
-                    "config: invalid logging backend '%s', fallback to '%s'",
-                    logging_backend,
-#if SYSTEMD_JOURNAL
-                    NM_LOG_CONFIG_BACKEND_JOURNAL
-#else
-                    NM_LOG_CONFIG_BACKEND_SYSLOG
-#endif
-        );
-    }
-}
diff --git a/src/nm-logging.h b/src/nm-logging.h
deleted file mode 100644
index b2251c92..00000000
--- a/src/nm-logging.h
+++ /dev/null
@@ -1,201 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * Copyright (C) 2006 - 2012 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- */
-
-#ifndef __NETWORKMANAGER_LOGGING_H__
-#define __NETWORKMANAGER_LOGGING_H__
-
-#ifdef __NM_TEST_UTILS_H__
-    #error nm-test-utils.h must be included as last header
-#endif
-
-#include "nm-glib-aux/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"
-
-static inline NMLogDomain
-LOGD_IP_from_af(int addr_family)
-{
-    switch (addr_family) {
-    case AF_INET:
-        return LOGD_IP4;
-    case AF_INET6:
-        return LOGD_IP6;
-    }
-    g_return_val_if_reached(LOGD_NONE);
-}
-
-#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__)
-#define nm_log_dbg(domain, ...)   nm_log(LOGL_DEBUG, (domain), NULL, NULL, __VA_ARGS__)
-#define nm_log_trace(domain, ...) nm_log(LOGL_TRACE, (domain), NULL, NULL, __VA_ARGS__)
-
-//#define _NM_LOG_FUNC G_STRFUNC
-#define _NM_LOG_FUNC NULL
-
-/* 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_mt(mt_require_locking, level, domain, error, ifname, con_uuid, ...) \
-    G_STMT_START                                                                    \
-    {                                                                               \
-        _nm_log_impl(__FILE__,                                                      \
-                     __LINE__,                                                      \
-                     _NM_LOG_FUNC,                                                  \
-                     (mt_require_locking),                                          \
-                     (level),                                                       \
-                     (domain),                                                      \
-                     (error),                                                       \
-                     (ifname),                                                      \
-                     (con_uuid),                                                    \
-                     ""__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, ...)                  \
-    G_STMT_START                                                      \
-    {                                                                 \
-        if (nm_logging_enabled((level), (domain))) {                  \
-            _nm_log(level, domain, 0, ifname, con_uuid, __VA_ARGS__); \
-        }                                                             \
-    }                                                                 \
-    G_STMT_END
-
-#define _nm_log_ptr(level, domain, ifname, con_uuid, self, prefix, ...)             \
-    nm_log((level),                                                                 \
-           (domain),                                                                \
-           (ifname),                                                                \
-           (con_uuid),                                                              \
-           "%s[" NM_HASH_OBFUSCATE_PTR_FMT "] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-           (prefix) ?: "",                                                          \
-           NM_HASH_OBFUSCATE_PTR(self) _NM_UTILS_MACRO_REST(__VA_ARGS__))
-
-static inline gboolean
-_nm_log_ptr_is_debug(NMLogLevel level)
-{
-    return level <= LOGL_DEBUG;
-}
-
-/* log a message for an object (with providing a generic @self pointer) */
-#define nm_log_ptr(level, domain, ifname, con_uuid, self, prefix, ...)                           \
-    G_STMT_START                                                                                 \
-    {                                                                                            \
-        if (_nm_log_ptr_is_debug(level)) {                                                       \
-            _nm_log_ptr((level), (domain), (ifname), (con_uuid), (self), (prefix), __VA_ARGS__); \
-        } else {                                                                                 \
-            const char *__prefix = (prefix);                                                     \
-                                                                                                 \
-            nm_log((level),                                                                      \
-                   (domain),                                                                     \
-                   (ifname),                                                                     \
-                   (con_uuid),                                                                   \
-                   "%s%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                                    \
-                   __prefix ?: "",                                                               \
-                   __prefix ? " " : "" _NM_UTILS_MACRO_REST(__VA_ARGS__));                       \
-        }                                                                                        \
-    }                                                                                            \
-    G_STMT_END
-
-#define _nm_log_obj(level, domain, ifname, con_uuid, self, prefix, ...) \
-    _nm_log_ptr((level), (domain), (ifname), (con_uuid), (self), prefix, __VA_ARGS__)
-
-/* log a message for an object (with providing a @self pointer to a GObject).
- * Contrary to nm_log_ptr(), @self must be a GObject type (or %NULL).
- * As of now, nm_log_obj() is identical to nm_log_ptr(), but we might change that */
-#define nm_log_obj(level, domain, ifname, con_uuid, self, prefix, ...) \
-    nm_log_ptr((level), (domain), (ifname), (con_uuid), (self), prefix, __VA_ARGS__)
-
-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_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);
-const char *nm_logging_all_domains_to_string(void);
-
-gboolean
-nm_logging_setup(const char *level, const char *domains, char **bad_domains, GError **error);
-
-void nm_logging_init_pre(const char *syslog_identifier, char *prefix_take);
-
-void nm_logging_init(const char *logging_backend, gboolean debug);
-
-gboolean nm_logging_syslog_enabled(void);
-
-/*****************************************************************************/
-
-#define __NMLOG_DEFAULT(level, domain, prefix, ...)         \
-    G_STMT_START                                            \
-    {                                                       \
-        nm_log((level),                                     \
-               (domain),                                    \
-               NULL,                                        \
-               NULL,                                        \
-               "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),   \
-               (prefix) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
-    }                                                       \
-    G_STMT_END
-
-#define __NMLOG_DEFAULT_WITH_ADDR(level, domain, prefix, ...)                            \
-    G_STMT_START                                                                         \
-    {                                                                                    \
-        nm_log((level),                                                                  \
-               (domain),                                                                 \
-               NULL,                                                                     \
-               NULL,                                                                     \
-               "%s[" NM_HASH_OBFUSCATE_PTR_FMT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-               (prefix),                                                                 \
-               NM_HASH_OBFUSCATE_PTR(self) _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/platform/nm-netlink.c b/src/platform/nm-netlink.c
deleted file mode 100644
index 965a73bc..00000000
--- a/src/platform/nm-netlink.c
+++ /dev/null
@@ -1,1518 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * Copyright (C) 2018 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-netlink.h"
-
-#include <unistd.h>
-#include <fcntl.h>
-
-/*****************************************************************************/
-
-#ifndef SOL_NETLINK
-    #define SOL_NETLINK 270
-#endif
-
-/*****************************************************************************/
-
-#define NL_SOCK_PASSCRED     (1 << 1)
-#define NL_MSG_PEEK          (1 << 3)
-#define NL_MSG_PEEK_EXPLICIT (1 << 4)
-#define NL_NO_AUTO_ACK       (1 << 5)
-
-#ifndef NETLINK_EXT_ACK
-    #define NETLINK_EXT_ACK 11
-#endif
-
-struct nl_msg {
-    int                nm_protocol;
-    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 {
-    struct sockaddr_nl s_local;
-    struct sockaddr_nl s_peer;
-    int                s_fd;
-    int                s_proto;
-    unsigned int       s_seq_next;
-    unsigned int       s_seq_expect;
-    int                s_flags;
-    size_t             s_bufsize;
-};
-
-/*****************************************************************************/
-
-NM_UTILS_ENUM2STR_DEFINE(nl_nlmsgtype2str,
-                         int,
-                         NM_UTILS_ENUM2STR(NLMSG_NOOP, "NOOP"),
-                         NM_UTILS_ENUM2STR(NLMSG_ERROR, "ERROR"),
-                         NM_UTILS_ENUM2STR(NLMSG_DONE, "DONE"),
-                         NM_UTILS_ENUM2STR(NLMSG_OVERRUN, "OVERRUN"), );
-
-NM_UTILS_FLAGS2STR_DEFINE(nl_nlmsg_flags2str,
-                          int,
-                          NM_UTILS_FLAGS2STR(NLM_F_REQUEST, "REQUEST"),
-                          NM_UTILS_FLAGS2STR(NLM_F_MULTI, "MULTI"),
-                          NM_UTILS_FLAGS2STR(NLM_F_ACK, "ACK"),
-                          NM_UTILS_FLAGS2STR(NLM_F_ECHO, "ECHO"),
-                          NM_UTILS_FLAGS2STR(NLM_F_ROOT, "ROOT"),
-                          NM_UTILS_FLAGS2STR(NLM_F_MATCH, "MATCH"),
-                          NM_UTILS_FLAGS2STR(NLM_F_ATOMIC, "ATOMIC"),
-                          NM_UTILS_FLAGS2STR(NLM_F_REPLACE, "REPLACE"),
-                          NM_UTILS_FLAGS2STR(NLM_F_EXCL, "EXCL"),
-                          NM_UTILS_FLAGS2STR(NLM_F_CREATE, "CREATE"),
-                          NM_UTILS_FLAGS2STR(NLM_F_APPEND, "APPEND"), );
-
-/*****************************************************************************/
-
-const char *
-nl_nlmsghdr_to_str(const struct nlmsghdr *hdr, char *buf, gsize len)
-{
-    const char *b;
-    const char *s;
-    guint       flags, flags_before;
-    const char *prefix;
-
-    if (!nm_utils_to_string_buffer_init_null(hdr, &buf, &len))
-        return buf;
-
-    b = buf;
-
-    switch (hdr->nlmsg_type) {
-    case RTM_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)
-        nm_utils_strbuf_append_str(&buf, &len, s);
-    else
-        nm_utils_strbuf_append(&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type);
-
-    flags = hdr->nlmsg_flags;
-
-    if (!flags) {
-        nm_utils_strbuf_append_str(&buf, &len, ", flags 0");
-        goto flags_done;
-    }
-
-#define _F(f, n)                                                   \
-    G_STMT_START                                                   \
-    {                                                              \
-        if (NM_FLAGS_ALL(flags, f)) {                              \
-            flags &= ~(f);                                         \
-            nm_utils_strbuf_append(&buf, &len, "%s%s", prefix, n); \
-            if (!flags)                                            \
-                goto flags_done;                                   \
-            prefix = ",";                                          \
-        }                                                          \
-    }                                                              \
-    G_STMT_END
-
-    prefix       = ", flags ";
-    flags_before = flags;
-    _F(NLM_F_REQUEST, "request");
-    _F(NLM_F_MULTI, "multi");
-    _F(NLM_F_ACK, "ack");
-    _F(NLM_F_ECHO, "echo");
-    _F(NLM_F_DUMP_INTR, "dump_intr");
-    _F(0x20 /*NLM_F_DUMP_FILTERED*/, "dump_filtered");
-
-    if (flags_before != flags)
-        prefix = ";";
-
-    switch (hdr->nlmsg_type) {
-    case RTM_NEWLINK:
-    case RTM_NEWADDR:
-    case RTM_NEWROUTE:
-    case RTM_NEWQDISC:
-    case RTM_NEWTFILTER:
-        _F(NLM_F_REPLACE, "replace");
-        _F(NLM_F_EXCL, "excl");
-        _F(NLM_F_CREATE, "create");
-        _F(NLM_F_APPEND, "append");
-        break;
-    case RTM_GETLINK:
-    case RTM_GETADDR:
-    case RTM_GETROUTE:
-    case RTM_DELQDISC:
-    case RTM_DELTFILTER:
-        _F(NLM_F_DUMP, "dump");
-        _F(NLM_F_ROOT, "root");
-        _F(NLM_F_MATCH, "match");
-        _F(NLM_F_ATOMIC, "atomic");
-        break;
-    }
-
-#undef _F
-
-    if (flags_before != flags)
-        prefix = ";";
-    nm_utils_strbuf_append(&buf, &len, "%s0x%04x", prefix, flags);
-
-flags_done:
-
-    nm_utils_strbuf_append(&buf, &len, ", seq %u", (unsigned) hdr->nlmsg_seq);
-
-    return b;
-}
-
-/*****************************************************************************/
-
-struct nlmsghdr *
-nlmsg_hdr(struct nl_msg *n)
-{
-    return n->nm_nlh;
-}
-
-void *
-nlmsg_reserve(struct nl_msg *n, size_t len, int pad)
-{
-    char * buf       = (char *) n->nm_nlh;
-    size_t nlmsg_len = n->nm_nlh->nlmsg_len;
-    size_t tlen;
-
-    nm_assert(pad >= 0);
-
-    if (len > n->nm_size)
-        return NULL;
-
-    tlen = pad ? ((len + (pad - 1)) & ~(pad - 1)) : len;
-
-    if ((tlen + nlmsg_len) > n->nm_size)
-        return NULL;
-
-    buf += nlmsg_len;
-    n->nm_nlh->nlmsg_len += tlen;
-
-    if (tlen > len)
-        memset(buf + len, 0, tlen - len);
-
-    return buf;
-}
-
-/*****************************************************************************/
-
-struct nlattr *
-nla_reserve(struct nl_msg *msg, int attrtype, int attrlen)
-{
-    struct nlattr *nla;
-    int            tlen;
-
-    if (attrlen < 0)
-        return NULL;
-
-    tlen = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) + nla_total_size(attrlen);
-
-    if (tlen > msg->nm_size)
-        return NULL;
-
-    nla           = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
-    nla->nla_type = attrtype;
-    nla->nla_len  = nla_attr_size(attrlen);
-
-    if (attrlen)
-        memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen));
-    msg->nm_nlh->nlmsg_len = tlen;
-
-    return nla;
-}
-
-/*****************************************************************************/
-
-struct nl_msg *
-nlmsg_alloc_size(size_t len)
-{
-    struct nl_msg *nm;
-
-    if (len < sizeof(struct nlmsghdr))
-        len = sizeof(struct nlmsghdr);
-
-    nm  = g_slice_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;
-}
-
-/**
- * Allocate a new netlink message with the default maximum payload size.
- *
- * Allocates a new netlink message without any further payload. The
- * maximum payload size defaults to PAGESIZE or as otherwise specified
- * with nlmsg_set_default_size().
- *
- * @return Newly allocated netlink message or NULL.
- */
-struct nl_msg *
-nlmsg_alloc(void)
-{
-    return nlmsg_alloc_size(nm_utils_getpagesize());
-}
-
-struct nl_msg *
-nlmsg_alloc_convert(struct nlmsghdr *hdr)
-{
-    struct nl_msg *nm;
-
-    nm = nlmsg_alloc_size(NLMSG_ALIGN(hdr->nlmsg_len));
-    memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len);
-    return nm;
-}
-
-struct nl_msg *
-nlmsg_alloc_simple(int nlmsgtype, int flags)
-{
-    struct nl_msg *nm;
-    struct nlmsghdr *new;
-
-    nm               = nlmsg_alloc();
-    new              = nm->nm_nlh;
-    new->nlmsg_type  = nlmsgtype;
-    new->nlmsg_flags = flags;
-    return nm;
-}
-
-void
-nlmsg_free(struct nl_msg *msg)
-{
-    if (!msg)
-        return;
-
-    g_free(msg->nm_nlh);
-    g_slice_free(struct nl_msg, msg);
-}
-
-/*****************************************************************************/
-
-int
-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;
-
-    memcpy(tmp, data, len);
-    return 0;
-}
-
-/*****************************************************************************/
-
-int
-nlmsg_parse(struct nlmsghdr *        nlh,
-            int                      hdrlen,
-            struct nlattr *          tb[],
-            int                      maxtype,
-            const struct nla_policy *policy)
-{
-    if (!nlmsg_valid_hdr(nlh, hdrlen))
-        return -NME_NL_MSG_TOOSHORT;
-
-    return nla_parse(tb, maxtype, nlmsg_attrdata(nlh, hdrlen), nlmsg_attrlen(nlh, hdrlen), policy);
-}
-
-struct nlmsghdr *
-nlmsg_put(struct nl_msg *n, uint32_t pid, uint32_t seq, int type, int payload, int flags)
-{
-    struct nlmsghdr *nlh;
-
-    if (n->nm_nlh->nlmsg_len < NLMSG_HDRLEN)
-        g_return_val_if_reached(NULL);
-
-    nlh              = (struct nlmsghdr *) n->nm_nlh;
-    nlh->nlmsg_type  = type;
-    nlh->nlmsg_flags = flags;
-    nlh->nlmsg_pid   = pid;
-    nlh->nlmsg_seq   = seq;
-
-    if (payload > 0 && nlmsg_reserve(n, payload, NLMSG_ALIGNTO) == NULL)
-        return NULL;
-
-    return nlh;
-}
-
-size_t
-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;
-    }
-
-    nm_assert(dst);
-
-    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;
-            }
-        }
-    }
-
-    memset(dst, 0, dstsize);
-    return 0;
-}
-
-size_t
-nla_memcpy(void *dst, const struct nlattr *nla, size_t dstsize)
-{
-    size_t len;
-    int    srclen;
-
-    if (!nla)
-        return 0;
-
-    srclen = nla_len(nla);
-
-    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
-nla_put(struct nl_msg *msg, int attrtype, int datalen, const void *data)
-{
-    struct nlattr *nla;
-
-    nla = nla_reserve(msg, attrtype, datalen);
-    if (!nla) {
-        if (datalen < 0)
-            g_return_val_if_reached(-NME_BUG);
-
-        return -ENOMEM;
-    }
-
-    if (datalen > 0)
-        memcpy(nla_data(nla), data, datalen);
-
-    return 0;
-}
-
-struct nlattr *
-nla_find(const struct nlattr *head, int len, int attrtype)
-{
-    const struct nlattr *nla;
-    int                  rem;
-
-    nla_for_each_attr (nla, head, len, rem) {
-        if (nla_type(nla) == attrtype)
-            return (struct nlattr *) nla;
-    }
-
-    return NULL;
-}
-
-void
-nla_nest_cancel(struct nl_msg *msg, const struct nlattr *attr)
-{
-    ssize_t len;
-
-    len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) attr;
-    if (len < 0)
-        g_return_if_reached();
-    else if (len > 0) {
-        msg->nm_nlh->nlmsg_len -= len;
-        memset(nlmsg_tail(msg->nm_nlh), 0, len);
-    }
-}
-
-struct nlattr *
-nla_nest_start(struct nl_msg *msg, int attrtype)
-{
-    struct nlattr *start = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
-
-    if (nla_put(msg, NLA_F_NESTED | attrtype, 0, NULL) < 0)
-        return NULL;
-
-    return start;
-}
-
-static int
-_nest_end(struct nl_msg *msg, struct nlattr *start, int keep_empty)
-{
-    size_t pad, len;
-
-    len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) start;
-
-    if (len > USHRT_MAX || (!keep_empty && len == NLA_HDRLEN)) {
-        /*
-         * Max nlattr size exceeded or empty nested attribute, trim the
-         * attribute header again
-         */
-        nla_nest_cancel(msg, start);
-
-        /* Return error only if nlattr size was exceeded */
-        return (len == NLA_HDRLEN) ? 0 : -NME_NL_ATTRSIZE;
-    }
-
-    start->nla_len = len;
-
-    pad = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len;
-    if (pad > 0) {
-        /*
-         * Data inside attribute does not end at a alignment boundary.
-         * Pad accordingly and account 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(-NME_BUG);
-    }
-
-    return 0;
-}
-
-int
-nla_nest_end(struct nl_msg *msg, struct nlattr *start)
-{
-    return _nest_end(msg, start, 0);
-}
-
-static const uint16_t nla_attr_minlen[NLA_TYPE_MAX + 1] = {
-    [NLA_U8]     = sizeof(uint8_t),
-    [NLA_U16]    = sizeof(uint16_t),
-    [NLA_U32]    = sizeof(uint32_t),
-    [NLA_U64]    = sizeof(uint64_t),
-    [NLA_STRING] = 1,
-    [NLA_FLAG]   = 0,
-};
-
-static int
-validate_nla(const struct nlattr *nla, int maxtype, const struct nla_policy *policy)
-{
-    const struct nla_policy *pt;
-    unsigned int             minlen = 0;
-    int                      type   = nla_type(nla);
-
-    if (type < 0 || type > maxtype)
-        return 0;
-
-    pt = &policy[type];
-
-    if (pt->type > NLA_TYPE_MAX)
-        g_return_val_if_reached(-NME_BUG);
-
-    if (pt->minlen)
-        minlen = pt->minlen;
-    else if (pt->type != NLA_UNSPEC)
-        minlen = nla_attr_minlen[pt->type];
-
-    if (nla_len(nla) < minlen)
-        return -NME_UNSPEC;
-
-    if (pt->maxlen && nla_len(nla) > pt->maxlen)
-        return -NME_UNSPEC;
-
-    if (pt->type == NLA_STRING) {
-        const char *data;
-
-        nm_assert(minlen > 0);
-
-        data = nla_data(nla);
-        if (data[nla_len(nla) - 1] != '\0')
-            return -NME_UNSPEC;
-    }
-
-    return 0;
-}
-
-int
-nla_parse(struct nlattr *          tb[],
-          int                      maxtype,
-          struct nlattr *          head,
-          int                      len,
-          const struct nla_policy *policy)
-{
-    struct nlattr *nla;
-    int            rem, nmerr;
-
-    memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1));
-
-    nla_for_each_attr (nla, head, len, rem) {
-        int type = nla_type(nla);
-
-        if (type > maxtype)
-            continue;
-
-        if (policy) {
-            nmerr = validate_nla(nla, maxtype, policy);
-            if (nmerr < 0)
-                return nmerr;
-        }
-
-        tb[type] = nla;
-    }
-
-    return 0;
-}
-
-/*****************************************************************************/
-
-int
-nlmsg_get_proto(struct nl_msg *msg)
-{
-    return msg->nm_protocol;
-}
-
-void
-nlmsg_set_proto(struct nl_msg *msg, int protocol)
-{
-    msg->nm_protocol = protocol;
-}
-
-void
-nlmsg_set_src(struct nl_msg *msg, struct sockaddr_nl *addr)
-{
-    memcpy(&msg->nm_src, addr, sizeof(*addr));
-}
-
-struct ucred *
-nlmsg_get_creds(struct nl_msg *msg)
-{
-    if (msg->nm_creds_has)
-        return &msg->nm_creds;
-    return NULL;
-}
-
-void
-nlmsg_set_creds(struct nl_msg *msg, struct ucred *creds)
-{
-    if (creds) {
-        memcpy(&msg->nm_creds, creds, sizeof(*creds));
-        msg->nm_creds_has = TRUE;
-    } else
-        msg->nm_creds_has = FALSE;
-}
-
-/*****************************************************************************/
-
-void *
-genlmsg_put(struct nl_msg *msg,
-            uint32_t       port,
-            uint32_t       seq,
-            int            family,
-            int            hdrlen,
-            int            flags,
-            uint8_t        cmd,
-            uint8_t        version)
-{
-    struct nlmsghdr * nlh;
-    struct genlmsghdr hdr = {
-        .cmd     = cmd,
-        .version = version,
-    };
-
-    nlh = nlmsg_put(msg, port, seq, family, GENL_HDRLEN + hdrlen, flags);
-    if (nlh == NULL)
-        return NULL;
-
-    memcpy(nlmsg_data(nlh), &hdr, sizeof(hdr));
-
-    return (char *) nlmsg_data(nlh) + GENL_HDRLEN;
-}
-
-void *
-genlmsg_data(const struct genlmsghdr *gnlh)
-{
-    return ((unsigned char *) gnlh + GENL_HDRLEN);
-}
-
-void *
-genlmsg_user_hdr(const struct genlmsghdr *gnlh)
-{
-    return genlmsg_data(gnlh);
-}
-
-struct genlmsghdr *
-genlmsg_hdr(struct nlmsghdr *nlh)
-{
-    return nlmsg_data(nlh);
-}
-
-void *
-genlmsg_user_data(const struct genlmsghdr *gnlh, const int hdrlen)
-{
-    return (char *) genlmsg_user_hdr(gnlh) + NLMSG_ALIGN(hdrlen);
-}
-
-struct nlattr *
-genlmsg_attrdata(const struct genlmsghdr *gnlh, int hdrlen)
-{
-    return genlmsg_user_data(gnlh, hdrlen);
-}
-
-int
-genlmsg_len(const struct genlmsghdr *gnlh)
-{
-    const struct nlmsghdr *nlh;
-
-    nlh = (const struct nlmsghdr *) ((const unsigned char *) gnlh - NLMSG_HDRLEN);
-    return (nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN);
-}
-
-int
-genlmsg_attrlen(const struct genlmsghdr *gnlh, int hdrlen)
-{
-    return genlmsg_len(gnlh) - NLMSG_ALIGN(hdrlen);
-}
-
-int
-genlmsg_valid_hdr(struct nlmsghdr *nlh, int hdrlen)
-{
-    struct genlmsghdr *ghdr;
-
-    if (!nlmsg_valid_hdr(nlh, GENL_HDRLEN))
-        return 0;
-
-    ghdr = nlmsg_data(nlh);
-    if (genlmsg_len(ghdr) < NLMSG_ALIGN(hdrlen))
-        return 0;
-
-    return 1;
-}
-
-int
-genlmsg_parse(struct nlmsghdr *        nlh,
-              int                      hdrlen,
-              struct nlattr *          tb[],
-              int                      maxtype,
-              const struct nla_policy *policy)
-{
-    struct genlmsghdr *ghdr;
-
-    if (!genlmsg_valid_hdr(nlh, hdrlen))
-        return -NME_NL_MSG_TOOSHORT;
-
-    ghdr = nlmsg_data(nlh);
-    return nla_parse(tb,
-                     maxtype,
-                     genlmsg_attrdata(ghdr, hdrlen),
-                     genlmsg_attrlen(ghdr, hdrlen),
-                     policy);
-}
-
-static int
-_genl_parse_getfamily(struct nl_msg *msg, void *arg)
-{
-    static const struct nla_policy ctrl_policy[] = {
-        [CTRL_ATTR_FAMILY_ID]    = {.type = NLA_U16},
-        [CTRL_ATTR_FAMILY_NAME]  = {.type = NLA_STRING, .maxlen = GENL_NAMSIZ},
-        [CTRL_ATTR_VERSION]      = {.type = NLA_U32},
-        [CTRL_ATTR_HDRSIZE]      = {.type = NLA_U32},
-        [CTRL_ATTR_MAXATTR]      = {.type = NLA_U32},
-        [CTRL_ATTR_OPS]          = {.type = NLA_NESTED},
-        [CTRL_ATTR_MCAST_GROUPS] = {.type = NLA_NESTED},
-    };
-    struct nlattr *  tb[G_N_ELEMENTS(ctrl_policy)];
-    struct nlmsghdr *nlh           = nlmsg_hdr(msg);
-    gint32 *         response_data = arg;
-
-    if (genlmsg_parse_arr(nlh, 0, tb, ctrl_policy) < 0)
-        return NL_SKIP;
-
-    if (tb[CTRL_ATTR_FAMILY_ID])
-        *response_data = nla_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
-
-    return NL_STOP;
-}
-
-int
-genl_ctrl_resolve(struct nl_sock *sk, const char *name)
-{
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
-    int                          nmerr;
-    gint32                       response_data = -1;
-    const struct nl_cb           cb            = {
-        .valid_cb  = _genl_parse_getfamily,
-        .valid_arg = &response_data,
-    };
-
-    msg = nlmsg_alloc();
-
-    if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL, 0, 0, CTRL_CMD_GETFAMILY, 1))
-        return -ENOMEM;
-
-    nmerr = nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, name);
-    if (nmerr < 0)
-        return nmerr;
-
-    nmerr = nl_send_auto(sk, msg);
-    if (nmerr < 0)
-        return nmerr;
-
-    nmerr = nl_recvmsgs(sk, &cb);
-    if (nmerr < 0)
-        return nmerr;
-
-    /* If search was successful, request may be ACKed after data */
-    nmerr = nl_wait_for_ack(sk, NULL);
-    if (nmerr < 0)
-        return nmerr;
-
-    if (response_data < 0)
-        return -NME_UNSPEC;
-
-    return response_data;
-}
-
-/*****************************************************************************/
-
-struct nl_sock *
-nl_socket_alloc(void)
-{
-    struct nl_sock *sk;
-
-    sk = g_slice_new0(struct nl_sock);
-
-    sk->s_fd              = -1;
-    sk->s_local.nl_family = AF_NETLINK;
-    sk->s_peer.nl_family  = AF_NETLINK;
-    sk->s_seq_expect = sk->s_seq_next = time(NULL);
-
-    return sk;
-}
-
-void
-nl_socket_free(struct nl_sock *sk)
-{
-    if (!sk)
-        return;
-
-    if (sk->s_fd >= 0)
-        nm_close(sk->s_fd);
-    g_slice_free(struct nl_sock, sk);
-}
-
-int
-nl_socket_get_fd(const struct nl_sock *sk)
-{
-    return sk->s_fd;
-}
-
-uint32_t
-nl_socket_get_local_port(const struct nl_sock *sk)
-{
-    return sk->s_local.nl_pid;
-}
-
-size_t
-nl_socket_get_msg_buf_size(struct nl_sock *sk)
-{
-    return sk->s_bufsize;
-}
-
-int
-nl_socket_set_passcred(struct nl_sock *sk, int state)
-{
-    int err;
-
-    if (sk->s_fd == -1)
-        return -NME_NL_BAD_SOCK;
-
-    err = setsockopt(sk->s_fd, SOL_SOCKET, SO_PASSCRED, &state, sizeof(state));
-    if (err < 0)
-        return -nm_errno_from_native(errno);
-
-    if (state)
-        sk->s_flags |= NL_SOCK_PASSCRED;
-    else
-        sk->s_flags &= ~NL_SOCK_PASSCRED;
-
-    return 0;
-}
-
-int
-nl_socket_set_msg_buf_size(struct nl_sock *sk, size_t bufsize)
-{
-    sk->s_bufsize = bufsize;
-
-    return 0;
-}
-
-struct sockaddr_nl *
-nlmsg_get_dst(struct nl_msg *msg)
-{
-    return &msg->nm_dst;
-}
-
-int
-nl_socket_set_nonblocking(const struct nl_sock *sk)
-{
-    if (sk->s_fd == -1)
-        return -NME_NL_BAD_SOCK;
-
-    if (fcntl(sk->s_fd, F_SETFL, O_NONBLOCK) < 0)
-        return -nm_errno_from_native(errno);
-
-    return 0;
-}
-
-int
-nl_socket_set_buffer_size(struct nl_sock *sk, int rxbuf, int txbuf)
-{
-    int err;
-
-    if (rxbuf <= 0)
-        rxbuf = 32768;
-
-    if (txbuf <= 0)
-        txbuf = 32768;
-
-    if (sk->s_fd == -1)
-        return -NME_NL_BAD_SOCK;
-
-    err = setsockopt(sk->s_fd, SOL_SOCKET, SO_SNDBUF, &txbuf, sizeof(txbuf));
-    if (err < 0) {
-        return -nm_errno_from_native(errno);
-    }
-
-    err = setsockopt(sk->s_fd, SOL_SOCKET, SO_RCVBUF, &rxbuf, sizeof(rxbuf));
-    if (err < 0) {
-        return -nm_errno_from_native(errno);
-    }
-
-    return 0;
-}
-
-int
-nl_socket_add_memberships(struct nl_sock *sk, int group, ...)
-{
-    int     err;
-    va_list ap;
-
-    if (sk->s_fd == -1)
-        return -NME_NL_BAD_SOCK;
-
-    va_start(ap, group);
-
-    while (group != 0) {
-        if (group < 0) {
-            va_end(ap);
-            g_return_val_if_reached(-NME_BUG);
-        }
-
-        err = setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group));
-        if (err < 0) {
-            int errsv = errno;
-
-            va_end(ap);
-            return -nm_errno_from_native(errsv);
-        }
-
-        group = va_arg(ap, int);
-    }
-
-    va_end(ap);
-
-    return 0;
-}
-
-int
-nl_socket_set_ext_ack(struct nl_sock *sk, gboolean enable)
-{
-    int err, val;
-
-    if (sk->s_fd == -1)
-        return -NME_NL_BAD_SOCK;
-
-    val = !!enable;
-    err = setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_EXT_ACK, &val, sizeof(val));
-    if (err < 0)
-        return -nm_errno_from_native(errno);
-
-    return 0;
-}
-
-void
-nl_socket_disable_msg_peek(struct nl_sock *sk)
-{
-    sk->s_flags |= NL_MSG_PEEK_EXPLICIT;
-    sk->s_flags &= ~NL_MSG_PEEK;
-}
-
-int
-nl_connect(struct nl_sock *sk, int protocol)
-{
-    int                err, nmerr;
-    socklen_t          addrlen;
-    struct sockaddr_nl local = {0};
-
-    if (sk->s_fd != -1)
-        return -NME_NL_BAD_SOCK;
-
-    sk->s_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
-    if (sk->s_fd < 0) {
-        nmerr = -nm_errno_from_native(errno);
-        goto errout;
-    }
-
-    nmerr = nl_socket_set_buffer_size(sk, 0, 0);
-    if (nmerr < 0)
-        goto errout;
-
-    nm_assert(sk->s_local.nl_pid == 0);
-
-    err = bind(sk->s_fd, (struct sockaddr *) &sk->s_local, sizeof(sk->s_local));
-    if (err != 0) {
-        nmerr = -nm_errno_from_native(errno);
-        goto errout;
-    }
-
-    addrlen = sizeof(local);
-    err     = getsockname(sk->s_fd, (struct sockaddr *) &local, &addrlen);
-    if (err < 0) {
-        nmerr = -nm_errno_from_native(errno);
-        goto errout;
-    }
-
-    if (addrlen != sizeof(local)) {
-        nmerr = -NME_UNSPEC;
-        goto errout;
-    }
-
-    if (local.nl_family != AF_NETLINK) {
-        nmerr = -NME_UNSPEC;
-        goto errout;
-    }
-
-    sk->s_local = local;
-    sk->s_proto = protocol;
-
-    return 0;
-
-errout:
-    if (sk->s_fd != -1) {
-        close(sk->s_fd);
-        sk->s_fd = -1;
-    }
-    return nmerr;
-}
-
-/*****************************************************************************/
-
-static void
-_cb_init(struct nl_cb *dst, const struct nl_cb *src)
-{
-    nm_assert(dst);
-
-    if (src)
-        *dst = *src;
-    else
-        memset(dst, 0, sizeof(*dst));
-}
-
-static int
-ack_wait_handler(struct nl_msg *msg, void *arg)
-{
-    return NL_STOP;
-}
-
-int
-nl_wait_for_ack(struct nl_sock *sk, const struct nl_cb *cb)
-{
-    struct nl_cb cb2;
-
-    _cb_init(&cb2, cb);
-    cb2.ack_cb = ack_wait_handler;
-    return nl_recvmsgs(sk, &cb2);
-}
-
-#define NL_CB_CALL(cb, type, msg)                                \
-    do {                                                         \
-        const struct nl_cb *_cb = (cb);                          \
-                                                                 \
-        if (_cb && _cb->type##_cb) {                             \
-            /* the returned value here must be either a negative
-         * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */ \
-            nmerr = _cb->type##_cb((msg), _cb->type##_arg);      \
-            switch (nmerr) {                                     \
-            case NL_OK:                                          \
-                nm_assert(nmerr == 0);                           \
-                break;                                           \
-            case NL_SKIP:                                        \
-                goto skip;                                       \
-            case NL_STOP:                                        \
-                goto stop;                                       \
-            default:                                             \
-                if (nmerr >= 0) {                                \
-                    nm_assert_not_reached();                     \
-                    nmerr = -NME_BUG;                            \
-                }                                                \
-                goto out;                                        \
-            }                                                    \
-        }                                                        \
-    } while (0)
-
-int
-nl_recvmsgs(struct nl_sock *sk, const struct nl_cb *cb)
-{
-    int                    n, nmerr = 0, multipart = 0, interrupted = 0, nrecv = 0;
-    gs_free unsigned char *buf = NULL;
-    struct nlmsghdr *      hdr;
-    struct sockaddr_nl     nla = {0};
-    struct ucred           creds;
-    gboolean               creds_has;
-
-continue_reading:
-    n = nl_recv(sk, &nla, &buf, &creds, &creds_has);
-    if (n <= 0)
-        return n;
-
-    hdr = (struct nlmsghdr *) buf;
-    while (nlmsg_ok(hdr, n)) {
-        nm_auto_nlmsg struct nl_msg *msg = NULL;
-
-        msg = nlmsg_alloc_convert(hdr);
-
-        nlmsg_set_proto(msg, sk->s_proto);
-        nlmsg_set_src(msg, &nla);
-        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) {
-                nmerr = -NME_NL_SEQ_MISMATCH;
-                goto out;
-            }
-        }
-
-        if (hdr->nlmsg_type == NLMSG_DONE || hdr->nlmsg_type == NLMSG_ERROR
-            || hdr->nlmsg_type == NLMSG_NOOP || hdr->nlmsg_type == NLMSG_OVERRUN) {
-            /* We can't check for !NLM_F_MULTI since some netlink
-             * users in the kernel are broken. */
-            sk->s_seq_expect++;
-        }
-
-        if (hdr->nlmsg_flags & NLM_F_MULTI)
-            multipart = 1;
-
-        if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
-            /*
-             * We have to continue reading to clear
-             * all messages until a NLMSG_DONE is
-             * received and report the inconsistency.
-             */
-            interrupted = 1;
-        }
-
-        /* messages terminates a multipart message, this is
-         * usually the end of a message and therefore we slip
-         * out of the loop by default. the user may overrule
-         * this action by skipping this packet. */
-        if (hdr->nlmsg_type == NLMSG_DONE) {
-            multipart = 0;
-            NL_CB_CALL(cb, finish, msg);
-        }
-
-        /* Message to be ignored, the default action is to
-         * skip this message if no callback is specified. The
-         * user may overrule this action by returning
-         * NL_PROCEED. */
-        else if (hdr->nlmsg_type == NLMSG_NOOP)
-            goto skip;
-
-        /* Data got lost, report back to user. The default action is to
-         * quit parsing. The user may overrule this action by returning
-         * NL_SKIP or NL_PROCEED (dangerous) */
-        else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
-            nmerr = -NME_NL_MSG_OVERFLOW;
-            goto out;
-        }
-
-        /* Message carries a nlmsgerr */
-        else if (hdr->nlmsg_type == NLMSG_ERROR) {
-            struct nlmsgerr *e = nlmsg_data(hdr);
-
-            if (hdr->nlmsg_len < nlmsg_size(sizeof(*e))) {
-                /* Truncated error message, the default action
-                 * is to stop parsing. The user may overrule
-                 * this action by returning NL_SKIP or
-                 * NL_PROCEED (dangerous) */
-                nmerr = -NME_NL_MSG_TRUNC;
-                goto out;
-            }
-            if (e->error) {
-                /* Error message reported back from kernel. */
-                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. */
-                    nmerr = cb->err_cb(&nla, e, cb->err_arg);
-                    if (nmerr < 0)
-                        goto out;
-                    else if (nmerr == NL_SKIP)
-                        goto skip;
-                    else if (nmerr == NL_STOP) {
-                        nmerr = -nm_errno_from_native(e->error);
-                        goto out;
-                    }
-                    nm_assert(nmerr == NL_OK);
-                } else {
-                    nmerr = -nm_errno_from_native(e->error);
-                    goto out;
-                }
-            } else
-                NL_CB_CALL(cb, ack, msg);
-        } else {
-            /* Valid message (not checking for MULTIPART bit to
-             * get along with broken kernels. NL_SKIP has no
-             * effect on this.  */
-            NL_CB_CALL(cb, valid, msg);
-        }
-skip:
-        nmerr = 0;
-        hdr   = nlmsg_next(hdr, &n);
-    }
-
-    if (multipart) {
-        /* Multipart message not yet complete, continue reading */
-        nm_clear_g_free(&buf);
-
-        nmerr = 0;
-        goto continue_reading;
-    }
-
-stop:
-    nmerr = 0;
-
-out:
-    if (interrupted)
-        nmerr = -NME_NL_DUMP_INTR;
-
-    nm_assert(nmerr <= 0);
-    return nmerr ?: nrecv;
-}
-
-int
-nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr)
-{
-    int ret;
-
-    if (sk->s_fd < 0)
-        return -NME_NL_BAD_SOCK;
-
-    nlmsg_set_src(msg, &sk->s_local);
-
-    ret = sendmsg(sk->s_fd, hdr, 0);
-    if (ret < 0)
-        return -nm_errno_from_native(errno);
-
-    return ret;
-}
-
-int
-nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen)
-{
-    struct sockaddr_nl *dst;
-    struct ucred *      creds;
-    struct msghdr       hdr = {
-        .msg_name    = (void *) &sk->s_peer,
-        .msg_namelen = sizeof(struct sockaddr_nl),
-        .msg_iov     = iov,
-        .msg_iovlen  = iovlen,
-    };
-    char buf[CMSG_SPACE(sizeof(struct ucred))];
-
-    /* Overwrite destination if specified in the message itself, defaults
-     * to the peer address of the socket.
-     */
-    dst = nlmsg_get_dst(msg);
-    if (dst->nl_family == AF_NETLINK)
-        hdr.msg_name = dst;
-
-    /* Add credentials if present. */
-    creds = nlmsg_get_creds(msg);
-    if (creds != NULL) {
-        struct cmsghdr *cmsg;
-
-        hdr.msg_control    = buf;
-        hdr.msg_controllen = sizeof(buf);
-
-        cmsg             = CMSG_FIRSTHDR(&hdr);
-        cmsg->cmsg_level = SOL_SOCKET;
-        cmsg->cmsg_type  = SCM_CREDENTIALS;
-        cmsg->cmsg_len   = CMSG_LEN(sizeof(struct ucred));
-        memcpy(CMSG_DATA(cmsg), creds, sizeof(struct ucred));
-    }
-
-    return nl_sendmsg(sk, msg, &hdr);
-}
-
-void
-nl_complete_msg(struct nl_sock *sk, struct nl_msg *msg)
-{
-    struct nlmsghdr *nlh;
-
-    nlh = nlmsg_hdr(msg);
-    if (nlh->nlmsg_pid == NL_AUTO_PORT)
-        nlh->nlmsg_pid = nl_socket_get_local_port(sk);
-
-    if (nlh->nlmsg_seq == NL_AUTO_SEQ)
-        nlh->nlmsg_seq = sk->s_seq_next++;
-
-    if (msg->nm_protocol == -1)
-        msg->nm_protocol = sk->s_proto;
-
-    nlh->nlmsg_flags |= NLM_F_REQUEST;
-
-    if (!(sk->s_flags & NL_NO_AUTO_ACK))
-        nlh->nlmsg_flags |= NLM_F_ACK;
-}
-
-int
-nl_send(struct nl_sock *sk, struct nl_msg *msg)
-{
-    struct iovec iov = {
-        .iov_base = (void *) nlmsg_hdr(msg),
-        .iov_len  = nlmsg_hdr(msg)->nlmsg_len,
-    };
-
-    return nl_send_iovec(sk, msg, &iov, 1);
-}
-
-int
-nl_send_auto(struct nl_sock *sk, struct nl_msg *msg)
-{
-    nl_complete_msg(sk, msg);
-
-    return nl_send(sk, msg);
-}
-
-int
-nl_recv(struct nl_sock *    sk,
-        struct sockaddr_nl *nla,
-        unsigned char **    buf,
-        struct ucred *      out_creds,
-        gboolean *          out_creds_has)
-{
-    ssize_t       n;
-    int           flags = 0;
-    struct iovec  iov;
-    struct msghdr msg = {
-        .msg_name    = (void *) nla,
-        .msg_namelen = sizeof(struct sockaddr_nl),
-        .msg_iov     = &iov,
-        .msg_iovlen  = 1,
-    };
-    struct ucred tmpcreds;
-    gboolean     tmpcreds_has = FALSE;
-    int          retval;
-    int          errsv;
-
-    nm_assert(nla);
-    nm_assert(buf && !*buf);
-    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;
-
-    iov.iov_len  = sk->s_bufsize ?: (((size_t) nm_utils_getpagesize()) * 4u);
-    iov.iov_base = g_malloc(iov.iov_len);
-
-    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);
-    }
-
-retry:
-    n = recvmsg(sk->s_fd, &msg, flags);
-    if (!n) {
-        retval = 0;
-        goto abort;
-    }
-
-    if (n < 0) {
-        errsv = errno;
-        if (errsv == EINTR)
-            goto retry;
-        retval = -nm_errno_from_native(errsv);
-        goto abort;
-    }
-
-    if (msg.msg_flags & MSG_CTRUNC) {
-        if (msg.msg_controllen == 0) {
-            retval = -NME_NL_MSG_TRUNC;
-            goto abort;
-        }
-
-        msg.msg_controllen *= 2;
-        msg.msg_control = g_realloc(msg.msg_control, msg.msg_controllen);
-        goto retry;
-    }
-
-    if (iov.iov_len < n || (msg.msg_flags & MSG_TRUNC)) {
-        /* respond with error to an incomplete message */
-        if (flags == 0) {
-            retval = -NME_NL_MSG_TRUNC;
-            goto abort;
-        }
-
-        /* Provided buffer is not long enough, enlarge it
-         * to size of n (which should be total length of the message)
-         * and try again. */
-        iov.iov_base = g_realloc(iov.iov_base, n);
-        iov.iov_len  = n;
-        flags        = 0;
-        goto retry;
-    }
-
-    if (flags != 0) {
-        /* Buffer is big enough, do the actual reading */
-        flags = 0;
-        goto retry;
-    }
-
-    if (msg.msg_namelen != sizeof(struct sockaddr_nl)) {
-        retval = -NME_UNSPEC;
-        goto abort;
-    }
-
-    if (out_creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
-        struct cmsghdr *cmsg;
-
-        for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
-            if (cmsg->cmsg_level != SOL_SOCKET)
-                continue;
-            if (cmsg->cmsg_type != SCM_CREDENTIALS)
-                continue;
-            memcpy(&tmpcreds, CMSG_DATA(cmsg), sizeof(tmpcreds));
-            tmpcreds_has = TRUE;
-            break;
-        }
-    }
-
-    retval = n;
-
-abort:
-    g_free(msg.msg_control);
-
-    if (retval <= 0) {
-        g_free(iov.iov_base);
-        return retval;
-    }
-
-    *buf = iov.iov_base;
-    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
deleted file mode 100644
index 52283e9f..00000000
--- a/src/platform/nm-netlink.h
+++ /dev/null
@@ -1,616 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * Copyright (C) 2018 Red Hat, Inc.
- */
-
-#ifndef __NM_NETLINK_H__
-#define __NM_NETLINK_H__
-
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <linux/genetlink.h>
-
-#include "nm-std-aux/unaligned.h"
-
-/*****************************************************************************/
-
-#define NLMSGERR_ATTR_UNUSED 0
-#define NLMSGERR_ATTR_MSG    1
-#define NLMSGERR_ATTR_OFFS   2
-#define NLMSGERR_ATTR_COOKIE 3
-#define NLMSGERR_ATTR_MAX    3
-
-#ifndef NLM_F_ACK_TLVS
-    #define NLM_F_ACK_TLVS 0x200
-#endif
-
-/*****************************************************************************/
-
-/* Basic attribute data types */
-enum {
-    NLA_UNSPEC, /* Unspecified type, binary data chunk */
-    NLA_U8,     /* 8 bit integer */
-    NLA_U16,    /* 16 bit integer */
-    NLA_U32,    /* 32 bit integer */
-    NLA_U64,    /* 64 bit integer */
-    NLA_STRING, /* NUL terminated character string */
-    NLA_FLAG,   /* Flag */
-    NLA_MSECS,  /* Micro seconds (64bit) */
-    NLA_NESTED, /* Nested attributes */
-    NLA_NESTED_COMPAT,
-    NLA_NUL_STRING,
-    NLA_BINARY,
-    NLA_S8,
-    NLA_S16,
-    NLA_S32,
-    NLA_S64,
-    __NLA_TYPE_MAX,
-};
-
-#define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1)
-
-struct nl_msg;
-
-/*****************************************************************************/
-
-const char *nl_nlmsgtype2str(int type, char *buf, size_t size);
-
-const char *nl_nlmsg_flags2str(int flags, char *buf, size_t len);
-
-const char *nl_nlmsghdr_to_str(const struct nlmsghdr *hdr, char *buf, gsize len);
-
-/*****************************************************************************/
-
-struct nla_policy {
-    /* Type of attribute or NLA_UNSPEC */
-    uint16_t type;
-
-    /* Minimal length of payload required */
-    uint16_t minlen;
-
-    /* Maximal length of payload allowed */
-    uint16_t maxlen;
-};
-
-/*****************************************************************************/
-
-/* static 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 a C array or NULL. The sizeof()
-         * must either match the expected array size or the sizeof(NULL),
-         * but not both. */                      \
-        G_STATIC_ASSERT_EXPR((sizeof(policy) == G_N_ELEMENTS(tb) * sizeof(struct nla_policy)) \
-                             ^ (sizeof(policy) == sizeof(NULL)));                             \
-    }                                                                                         \
-    G_STMT_END
-
-/*****************************************************************************/
-
-static inline int
-nla_attr_size(int payload)
-{
-    nm_assert(payload >= 0);
-
-    return NLA_HDRLEN + payload;
-}
-
-static inline int
-nla_total_size(int payload)
-{
-    return NLA_ALIGN(nla_attr_size(payload));
-}
-
-static inline int
-nla_padlen(int payload)
-{
-    return nla_total_size(payload) - nla_attr_size(payload);
-}
-
-struct nlattr *nla_reserve(struct nl_msg *msg, int attrtype, int attrlen);
-
-static inline int
-nla_len(const struct nlattr *nla)
-{
-    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_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)
-{
-    nm_assert(nla_len(nla) >= sizeof(uint8_t));
-
-    return *((const uint8_t *) nla_data(nla));
-}
-
-static inline int8_t
-nla_get_s8(const struct nlattr *nla)
-{
-    nm_assert(nla_len(nla) >= sizeof(int8_t));
-
-    return *((const int8_t *) nla_data(nla));
-}
-
-static inline uint8_t
-nla_get_u8_cond(/*const*/ struct nlattr *const *tb, int attr, uint8_t default_val)
-{
-    nm_assert(tb);
-    nm_assert(attr >= 0);
-
-    return tb[attr] ? nla_get_u8(tb[attr]) : default_val;
-}
-
-static inline uint16_t
-nla_get_u16(const struct nlattr *nla)
-{
-    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)
-{
-    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)
-{
-    nm_assert(nla_len(nla) >= sizeof(int32_t));
-
-    return *((const int32_t *) nla_data(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);
-
-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)
-{
-    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)          \
-    G_STMT_START                                       \
-    {                                                  \
-        if (nla_put(msg, attrtype, attrlen, data) < 0) \
-            goto nla_put_failure;                      \
-    }                                                  \
-    G_STMT_END
-
-#define NLA_PUT_TYPE(msg, type, attrtype, value)          \
-    G_STMT_START                                          \
-    {                                                     \
-        type __nla_tmp = value;                           \
-        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)
-
-#define NLA_PUT_S8(msg, attrtype, value) NLA_PUT_TYPE(msg, int8_t, attrtype, value)
-
-#define NLA_PUT_U16(msg, attrtype, value) NLA_PUT_TYPE(msg, uint16_t, attrtype, value)
-
-#define NLA_PUT_U32(msg, attrtype, value) NLA_PUT_TYPE(msg, uint32_t, attrtype, value)
-
-#define NLA_PUT_S32(msg, attrtype, value) NLA_PUT_TYPE(msg, int32_t, attrtype, value)
-
-#define NLA_PUT_U64(msg, attrtype, value) NLA_PUT_TYPE(msg, uint64_t, attrtype, value)
-
-#define NLA_PUT_STRING(msg, attrtype, value) NLA_PUT(msg, attrtype, (int) strlen(value) + 1, value)
-
-#define NLA_PUT_FLAG(msg, attrtype) 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)
-           && nla->nla_len <= remaining;
-}
-
-static inline struct nlattr *
-nla_next(const struct nlattr *nla, int *remaining)
-{
-    int totlen = NLA_ALIGN(nla->nla_len);
-
-    *remaining -= totlen;
-    return (struct nlattr *) ((char *) nla + totlen);
-}
-
-#define nla_for_each_attr(pos, head, len, rem) \
-    for (pos = head, rem = len; nla_ok(pos, rem); pos = nla_next(pos, &(rem)))
-
-#define nla_for_each_nested(pos, nla, rem)                                            \
-    for (pos = (struct nlattr *) nla_data(nla), rem = nla_len(nla); nla_ok(pos, rem); \
-         pos = nla_next(pos, &(rem)))
-
-void           nla_nest_cancel(struct nl_msg *msg, const struct nlattr *attr);
-struct nlattr *nla_nest_start(struct nl_msg *msg, int attrtype);
-int            nla_nest_end(struct nl_msg *msg, struct nlattr *start);
-
-int nla_parse(struct nlattr *          tb[],
-              int                      maxtype,
-              struct nlattr *          head,
-              int                      len,
-              const struct nla_policy *policy);
-
-#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,
-                 const struct nla_policy *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);
-
-struct nl_msg *nlmsg_alloc_size(size_t max);
-
-struct nl_msg *nlmsg_alloc_convert(struct nlmsghdr *hdr);
-
-struct nl_msg *nlmsg_alloc_simple(int nlmsgtype, int flags);
-
-void *nlmsg_reserve(struct nl_msg *n, size_t len, int pad);
-
-int nlmsg_append(struct nl_msg *n, 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);
-
-static inline int
-nlmsg_size(int payload)
-{
-    nm_assert(payload >= 0 && payload < G_MAXINT - NLMSG_HDRLEN - 4);
-    return NLMSG_HDRLEN + payload;
-}
-
-static inline int
-nlmsg_total_size(int payload)
-{
-    return NLMSG_ALIGN(nlmsg_size(payload));
-}
-
-static inline int
-nlmsg_ok(const struct nlmsghdr *nlh, int remaining)
-{
-    return (remaining >= (int) sizeof(struct nlmsghdr) && nlh->nlmsg_len >= sizeof(struct nlmsghdr)
-            && nlh->nlmsg_len <= remaining);
-}
-
-static inline struct nlmsghdr *
-nlmsg_next(struct nlmsghdr *nlh, int *remaining)
-{
-    int totlen = NLMSG_ALIGN(nlh->nlmsg_len);
-
-    *remaining -= totlen;
-
-    return (struct nlmsghdr *) ((unsigned char *) nlh + totlen);
-}
-
-int  nlmsg_get_proto(struct nl_msg *msg);
-void nlmsg_set_proto(struct nl_msg *msg, int protocol);
-
-void nlmsg_set_src(struct nl_msg *msg, struct sockaddr_nl *addr);
-
-struct ucred *nlmsg_get_creds(struct nl_msg *msg);
-void          nlmsg_set_creds(struct nl_msg *msg, struct ucred *creds);
-
-static inline void
-_nm_auto_nl_msg_cleanup(struct nl_msg **ptr)
-{
-    nlmsg_free(*ptr);
-}
-#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup)
-
-static inline void *
-nlmsg_data(const struct nlmsghdr *nlh)
-{
-    return (unsigned char *) nlh + NLMSG_HDRLEN;
-}
-
-static inline void *
-nlmsg_tail(const struct nlmsghdr *nlh)
-{
-    return (unsigned char *) nlh + NLMSG_ALIGN(nlh->nlmsg_len);
-}
-
-struct nlmsghdr *nlmsg_hdr(struct nl_msg *n);
-
-static inline int
-nlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen)
-{
-    if (nlh->nlmsg_len < nlmsg_size(hdrlen))
-        return 0;
-
-    return 1;
-}
-
-static inline int
-nlmsg_datalen(const struct nlmsghdr *nlh)
-{
-    return nlh->nlmsg_len - NLMSG_HDRLEN;
-}
-
-static inline int
-nlmsg_attrlen(const struct nlmsghdr *nlh, int hdrlen)
-{
-    return NM_MAX((int) (nlmsg_datalen(nlh) - NLMSG_ALIGN(hdrlen)), 0);
-}
-
-static inline struct nlattr *
-nlmsg_attrdata(const struct nlmsghdr *nlh, int hdrlen)
-{
-    unsigned char *data = nlmsg_data(nlh);
-    return (struct nlattr *) (data + NLMSG_ALIGN(hdrlen));
-}
-
-static inline struct nlattr *
-nlmsg_find_attr(struct nlmsghdr *nlh, int hdrlen, int attrtype)
-{
-    return nla_find(nlmsg_attrdata(nlh, hdrlen), nlmsg_attrlen(nlh, hdrlen), attrtype);
-}
-
-int nlmsg_parse(struct nlmsghdr *        nlh,
-                int                      hdrlen,
-                struct nlattr *          tb[],
-                int                      maxtype,
-                const struct nla_policy *policy);
-
-#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);
-
-/*****************************************************************************/
-
-#define NL_AUTO_PORT 0
-#define NL_AUTO_SEQ  0
-
-struct nl_sock;
-
-struct nl_sock *nl_socket_alloc(void);
-
-void nl_socket_free(struct nl_sock *sk);
-
-int nl_socket_get_fd(const struct nl_sock *sk);
-
-struct sockaddr_nl *nlmsg_get_dst(struct nl_msg *msg);
-
-size_t nl_socket_get_msg_buf_size(struct nl_sock *sk);
-int    nl_socket_set_msg_buf_size(struct nl_sock *sk, size_t bufsize);
-
-int nl_socket_set_buffer_size(struct nl_sock *sk, int rxbuf, int txbuf);
-
-int nl_socket_set_passcred(struct nl_sock *sk, int state);
-
-int nl_socket_set_nonblocking(const struct nl_sock *sk);
-
-void nl_socket_disable_msg_peek(struct nl_sock *sk);
-
-uint32_t nl_socket_get_local_port(const struct nl_sock *sk);
-
-int nl_socket_add_memberships(struct nl_sock *sk, int group, ...);
-
-int nl_connect(struct nl_sock *sk, int protocol);
-
-int nl_recv(struct nl_sock *    sk,
-            struct sockaddr_nl *nla,
-            unsigned char **    buf,
-            struct ucred *      out_creds,
-            gboolean *          out_creds_has);
-
-int nl_send(struct nl_sock *sk, struct nl_msg *msg);
-
-int nl_send_auto(struct nl_sock *sk, struct nl_msg *msg);
-
-/*****************************************************************************/
-
-enum nl_cb_action {
-    /* Proceed with wathever would come next */
-    NL_OK,
-    /* Skip this message */
-    NL_SKIP,
-    /* Stop parsing altogether and discard remaining messages */
-    NL_STOP,
-};
-
-typedef int (*nl_recvmsg_msg_cb_t)(struct nl_msg *msg, void *arg);
-
-typedef int (*nl_recvmsg_err_cb_t)(struct sockaddr_nl *nla, struct nlmsgerr *nlerr, void *arg);
-
-struct nl_cb {
-    nl_recvmsg_msg_cb_t valid_cb;
-    void *              valid_arg;
-
-    nl_recvmsg_msg_cb_t finish_cb;
-    void *              finish_arg;
-
-    nl_recvmsg_msg_cb_t ack_cb;
-    void *              ack_arg;
-
-    nl_recvmsg_err_cb_t err_cb;
-    void *              err_arg;
-};
-
-int nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr);
-
-int nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen);
-
-void nl_complete_msg(struct nl_sock *sk, struct nl_msg *msg);
-
-int nl_recvmsgs(struct nl_sock *sk, const struct nl_cb *cb);
-
-int nl_wait_for_ack(struct nl_sock *sk, const struct nl_cb *cb);
-
-int nl_socket_set_ext_ack(struct nl_sock *sk, gboolean enable);
-
-/*****************************************************************************/
-
-void *             genlmsg_put(struct nl_msg *msg,
-                               uint32_t       port,
-                               uint32_t       seq,
-                               int            family,
-                               int            hdrlen,
-                               int            flags,
-                               uint8_t        cmd,
-                               uint8_t        version);
-void *             genlmsg_data(const struct genlmsghdr *gnlh);
-void *             genlmsg_user_hdr(const struct genlmsghdr *gnlh);
-struct genlmsghdr *genlmsg_hdr(struct nlmsghdr *nlh);
-void *             genlmsg_user_data(const struct genlmsghdr *gnlh, const int hdrlen);
-struct nlattr *    genlmsg_attrdata(const struct genlmsghdr *gnlh, int hdrlen);
-int                genlmsg_len(const struct genlmsghdr *gnlh);
-int                genlmsg_attrlen(const struct genlmsghdr *gnlh, int hdrlen);
-int                genlmsg_valid_hdr(struct nlmsghdr *nlh, int hdrlen);
-
-int genlmsg_parse(struct nlmsghdr *        nlh,
-                  int                      hdrlen,
-                  struct nlattr *          tb[],
-                  int                      maxtype,
-                  const struct nla_policy *policy);
-
-#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);
-
-/*****************************************************************************/
-
-#endif /* __NM_NETLINK_H__ */
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
deleted file mode 100644
index ea45050d..00000000
--- a/src/platform/nm-platform-utils.c
+++ /dev/null
@@ -1,1785 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * Copyright (C) 2015 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-platform-utils.h"
-
-#include <unistd.h>
-#include <sys/ioctl.h>
-#include <linux/ethtool.h>
-#include <linux/sockios.h>
-#include <linux/mii.h>
-#include <linux/if.h>
-#include <linux/version.h>
-#include <linux/rtnetlink.h>
-#include <fcntl.h>
-#include <libudev.h>
-
-#include "nm-utils.h"
-#include "nm-setting-wired.h"
-#include "nm-libnm-core-intern/nm-ethtool-utils.h"
-
-#include "nm-core-utils.h"
-
-#define ONOFF(bool_val) ((bool_val) ? "on" : "off")
-
-/******************************************************************************
- * utils
- *****************************************************************************/
-
-extern char *if_indextoname(unsigned __ifindex, char *__ifname);
-unsigned     if_nametoindex(const char *__ifname);
-
-const char *
-nmp_utils_if_indextoname(int ifindex, char *out_ifname /*IFNAMSIZ*/)
-{
-    g_return_val_if_fail(ifindex > 0, NULL);
-    g_return_val_if_fail(out_ifname, NULL);
-
-    return if_indextoname(ifindex, out_ifname);
-}
-
-int
-nmp_utils_if_nametoindex(const char *ifname)
-{
-    g_return_val_if_fail(ifname, 0);
-
-    return if_nametoindex(ifname);
-}
-
-/*****************************************************************************/
-
-typedef struct {
-    int       fd;
-    const int ifindex;
-    char      ifname[IFNAMSIZ];
-} SocketHandle;
-
-#define SOCKET_HANDLE_INIT(_ifindex)     \
-    {                                    \
-        .fd = -1, .ifindex = (_ifindex), \
-    }
-
-static void
-_nm_auto_socket_handle(SocketHandle *shandle)
-{
-    if (shandle->fd >= 0)
-        nm_close(shandle->fd);
-}
-
-#define nm_auto_socket_handle nm_auto(_nm_auto_socket_handle)
-
-/*****************************************************************************/
-
-typedef enum {
-    IOCTL_CALL_DATA_TYPE_NONE,
-    IOCTL_CALL_DATA_TYPE_IFRDATA,
-    IOCTL_CALL_DATA_TYPE_IFRU,
-} IoctlCallDataType;
-
-static int
-_ioctl_call(const char *      log_ioctl_type,
-            const char *      log_subtype,
-            unsigned long int ioctl_request,
-            int               ifindex,
-            int *             inout_fd,
-            char *            inout_ifname,
-            IoctlCallDataType edata_type,
-            gpointer          edata,
-            gsize             edata_size,
-            struct ifreq *    out_ifreq)
-{
-    nm_auto_close int fd_close = -1;
-    int               fd;
-    int               r;
-    gpointer          edata_backup      = NULL;
-    gs_free gpointer  edata_backup_free = NULL;
-    guint             try_count;
-    char              known_ifnames[2][IFNAMSIZ];
-    const char *      failure_reason = NULL;
-    struct ifreq      ifr;
-
-    nm_assert(ifindex > 0);
-    nm_assert(NM_IN_SET(edata_type,
-                        IOCTL_CALL_DATA_TYPE_NONE,
-                        IOCTL_CALL_DATA_TYPE_IFRDATA,
-                        IOCTL_CALL_DATA_TYPE_IFRU));
-    nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_NONE || edata_size == 0);
-    nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_IFRDATA || edata_size > 0);
-    nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_IFRU
-              || (edata_size > 0 && edata_size <= sizeof(ifr.ifr_ifru)));
-    nm_assert(edata_size == 0 || edata);
-
-    /* open a file descriptor (or use the one provided). */
-    if (inout_fd && *inout_fd >= 0)
-        fd = *inout_fd;
-    else {
-        fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-        if (fd < 0) {
-            r              = -NM_ERRNO_NATIVE(errno);
-            failure_reason = "failed creating socket or ioctl";
-            goto out;
-        }
-        if (inout_fd)
-            *inout_fd = fd;
-        else
-            fd_close = fd;
-    }
-
-    /* resolve the ifindex to name (or use the one provided). */
-    if (inout_ifname && inout_ifname[0])
-        nm_utils_ifname_cpy(known_ifnames[0], inout_ifname);
-    else {
-        if (!nmp_utils_if_indextoname(ifindex, known_ifnames[0])) {
-            failure_reason = "cannot resolve ifindex";
-            r              = -ENODEV;
-            goto out;
-        }
-        if (inout_ifname)
-            nm_utils_ifname_cpy(inout_ifname, known_ifnames[0]);
-    }
-
-    /* we might need to retry the request. Backup edata so that we can
-     * restore it on retry. */
-    if (edata_size > 0)
-        edata_backup = nm_memdup_maybe_a(500, edata, edata_size, &edata_backup_free);
-
-    try_count = 0;
-
-again:
-{
-    const char *ifname = known_ifnames[try_count % 2];
-
-    nm_assert(ifindex > 0);
-    nm_assert(ifname && nm_utils_ifname_valid_kernel(ifname, NULL));
-    nm_assert(fd >= 0);
-
-    memset(&ifr, 0, sizeof(ifr));
-    nm_utils_ifname_cpy(ifr.ifr_name, ifname);
-    if (edata_type == IOCTL_CALL_DATA_TYPE_IFRDATA)
-        ifr.ifr_data = edata;
-    else if (edata_type == IOCTL_CALL_DATA_TYPE_IFRU)
-        memcpy(&ifr.ifr_ifru, edata, NM_MIN(edata_size, sizeof(ifr.ifr_ifru)));
-
-    if (ioctl(fd, ioctl_request, &ifr) < 0) {
-        r = -NM_ERRNO_NATIVE(errno);
-        nm_log_trace(LOGD_PLATFORM,
-                     "%s[%d]: %s, %s: failed: %s",
-                     log_ioctl_type,
-                     ifindex,
-                     log_subtype,
-                     ifname,
-                     nm_strerror_native(-r));
-    } else {
-        r = 0;
-        nm_log_trace(LOGD_PLATFORM,
-                     "%s[%d]: %s, %s: success",
-                     log_ioctl_type,
-                     ifindex,
-                     log_subtype,
-                     ifname);
-    }
-}
-
-    try_count++;
-
-    /* resolve the name again to see whether the ifindex still has the same name. */
-    if (!nmp_utils_if_indextoname(ifindex, known_ifnames[try_count % 2])) {
-        /* we could not find the ifindex again. Probably the device just got
-         * removed.
-         *
-         * In both cases we return the error code we got from ioctl above.
-         * Either it failed because the device was gone already or it still
-         * managed to complete the call. In both cases, the error code is good. */
-        failure_reason =
-            "cannot resolve ifindex after ioctl call. Probably the device was just removed";
-        goto out;
-    }
-
-    /* check whether the ifname changed in the meantime. If yes, would render the result
-     * invalid. Note that this cannot detect every race regarding renames, for example:
-     *
-     *  - if_indextoname(#10) gives eth0
-     *  - rename(#10) => eth0_tmp
-     *  - rename(#11) => eth0
-     *  - ioctl(eth0) (wrongly fetching #11, formerly eth1)
-     *  - rename(#11) => eth_something
-     *  - rename(#10) => eth0
-     *  - if_indextoname(#10) gives eth0
-     */
-    if (!nm_streq(known_ifnames[0], known_ifnames[1])) {
-        gboolean retry;
-
-        /* we detected a possible(!) rename.
-         *
-         * For getters it's straight forward to just retry the call.
-         *
-         * For setters we also always retry. If our previous call operated on the right device,
-         * calling it again should have no bad effect (just setting the same thing more than once).
-         *
-         * The only potential bad thing is if there was a race involving swapping names, and we just
-         * set the ioctl option on the wrong device. But then the bad thing already happenned and
-         * we cannot detect it (nor do anything about it). At least, we can retry and set the
-         * option on the right interface. */
-        retry = (try_count < 5);
-
-        nm_log_trace(LOGD_PLATFORM,
-                     "%s[%d]: %s: rename detected from \"%s\" to \"%s\". %s",
-                     log_ioctl_type,
-                     ifindex,
-                     log_subtype,
-                     known_ifnames[(try_count - 1) % 2],
-                     known_ifnames[try_count % 2],
-                     retry ? "Retry" : "No retry");
-        if (inout_ifname)
-            nm_utils_ifname_cpy(inout_ifname, known_ifnames[try_count % 2]);
-        if (retry) {
-            if (edata_size > 0)
-                memcpy(edata, edata_backup, edata_size);
-            goto again;
-        }
-    }
-
-out:
-    if (failure_reason) {
-        nm_log_trace(LOGD_PLATFORM,
-                     "%s[%d]: %s: %s: %s",
-                     log_ioctl_type,
-                     ifindex,
-                     log_subtype,
-                     failure_reason,
-                     r < 0 ? nm_strerror_native(-r) : "assume success");
-    }
-    if (r >= 0)
-        NM_SET_OUT(out_ifreq, ifr);
-    return r;
-}
-
-/******************************************************************************
- * ethtool
- *****************************************************************************/
-
-static NM_UTILS_ENUM2STR_DEFINE(_ethtool_cmd_to_string,
-                                guint32,
-                                NM_UTILS_ENUM2STR(ETHTOOL_GCOALESCE, "ETHTOOL_GCOALESCE"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_GDRVINFO, "ETHTOOL_GDRVINFO"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_GFEATURES, "ETHTOOL_GFEATURES"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_GLINK, "ETHTOOL_GLINK"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_GPERMADDR, "ETHTOOL_GPERMADDR"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_GRINGPARAM, "ETHTOOL_GRINGPARAM"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_GSET, "ETHTOOL_GSET"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_GSSET_INFO, "ETHTOOL_GSSET_INFO"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_GSTATS, "ETHTOOL_GSTATS"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_GSTRINGS, "ETHTOOL_GSTRINGS"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_GWOL, "ETHTOOL_GWOL"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_SCOALESCE, "ETHTOOL_SCOALESCE"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_SFEATURES, "ETHTOOL_SFEATURES"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_SRINGPARAM, "ETHTOOL_SRINGPARAM"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_SSET, "ETHTOOL_SSET"),
-                                NM_UTILS_ENUM2STR(ETHTOOL_SWOL, "ETHTOOL_SWOL"), );
-
-static const char *
-_ethtool_edata_to_string(gpointer edata, gsize edata_size, char *sbuf, gsize sbuf_len)
-{
-    nm_assert(edata);
-    nm_assert(edata_size >= sizeof(guint32));
-    nm_assert((((intptr_t) edata) % _nm_alignof(guint32)) == 0);
-
-    return _ethtool_cmd_to_string(*((guint32 *) edata), sbuf, sbuf_len);
-}
-
-/*****************************************************************************/
-
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
-    #define ethtool_cmd_speed(pedata) ((pedata)->speed)
-
-    #define ethtool_cmd_speed_set(pedata, speed) \
-        G_STMT_START                             \
-        {                                        \
-            (pedata)->speed = (guint16)(speed);  \
-        }                                        \
-        G_STMT_END
-#endif
-
-static int
-_ethtool_call_handle(SocketHandle *shandle, gpointer edata, gsize edata_size)
-{
-    char sbuf[50];
-
-    return _ioctl_call("ethtool",
-                       _ethtool_edata_to_string(edata, edata_size, sbuf, sizeof(sbuf)),
-                       SIOCETHTOOL,
-                       shandle->ifindex,
-                       &shandle->fd,
-                       shandle->ifname,
-                       IOCTL_CALL_DATA_TYPE_IFRDATA,
-                       edata,
-                       edata_size,
-                       NULL);
-}
-
-static int
-_ethtool_call_once(int ifindex, gpointer edata, gsize edata_size)
-{
-    char sbuf[50];
-
-    return _ioctl_call("ethtool",
-                       _ethtool_edata_to_string(edata, edata_size, sbuf, sizeof(sbuf)),
-                       SIOCETHTOOL,
-                       ifindex,
-                       NULL,
-                       NULL,
-                       IOCTL_CALL_DATA_TYPE_IFRDATA,
-                       edata,
-                       edata_size,
-                       NULL);
-}
-
-/*****************************************************************************/
-
-static struct ethtool_gstrings *
-ethtool_get_stringset(SocketHandle *shandle, int stringset_id)
-{
-    struct {
-        struct ethtool_sset_info info;
-        guint32                  sentinel;
-    } sset_info = {
-        .info.cmd       = ETHTOOL_GSSET_INFO,
-        .info.reserved  = 0,
-        .info.sset_mask = (1ULL << stringset_id),
-    };
-    const guint32 *                  pdata;
-    gs_free struct ethtool_gstrings *gstrings = NULL;
-    gsize                            gstrings_len;
-    guint32                          i, len;
-
-    if (_ethtool_call_handle(shandle, &sset_info, sizeof(sset_info)) < 0)
-        return NULL;
-    if (!sset_info.info.sset_mask)
-        return NULL;
-
-    pdata = (guint32 *) sset_info.info.data;
-
-    len = *pdata;
-
-    gstrings_len         = sizeof(*gstrings) + (len * ETH_GSTRING_LEN);
-    gstrings             = g_malloc0(gstrings_len);
-    gstrings->cmd        = ETHTOOL_GSTRINGS;
-    gstrings->string_set = stringset_id;
-    gstrings->len        = len;
-    if (gstrings->len > 0) {
-        if (_ethtool_call_handle(shandle, gstrings, gstrings_len) < 0)
-            return NULL;
-        for (i = 0; i < gstrings->len; i++) {
-            /* ensure NUL terminated */
-            gstrings->data[i * ETH_GSTRING_LEN + (ETH_GSTRING_LEN - 1)] = '\0';
-        }
-    }
-
-    return g_steal_pointer(&gstrings);
-}
-
-static int
-ethtool_gstrings_find(const struct ethtool_gstrings *gstrings, const char *needle)
-{
-    guint32 i;
-
-    /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN.
-     * that means, we cannot possibly request longer names. */
-    nm_assert(needle && strlen(needle) < ETH_GSTRING_LEN);
-
-    for (i = 0; i < gstrings->len; i++) {
-        if (nm_streq((char *) &gstrings->data[i * ETH_GSTRING_LEN], needle))
-            return i;
-    }
-    return -1;
-}
-
-static int
-ethtool_get_stringset_index(SocketHandle *shandle, int stringset_id, const char *needle)
-{
-    gs_free struct ethtool_gstrings *gstrings = NULL;
-
-    /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN.
-     * that means, we cannot possibly request longer names. */
-    nm_assert(needle && strlen(needle) < ETH_GSTRING_LEN);
-
-    gstrings = ethtool_get_stringset(shandle, stringset_id);
-    if (gstrings)
-        return ethtool_gstrings_find(gstrings, needle);
-    return -1;
-}
-
-/*****************************************************************************/
-
-static const NMEthtoolFeatureInfo _ethtool_feature_infos[_NM_ETHTOOL_ID_FEATURE_NUM] = {
-#define ETHT_FEAT(eid, ...)                                        \
-    {                                                              \
-        .ethtool_id = eid, .n_kernel_names = NM_NARG(__VA_ARGS__), \
-        .kernel_names = ((const char *const[]){__VA_ARGS__}),      \
-    }
-
-    /* the order does only matter for one thing: if it happens that more than one NMEthtoolID
-     * reference the same kernel-name, then the one that is mentioned *later* will win in
-     * case these NMEthtoolIDs are set. That mostly only makes sense for ethtool-ids which
-     * refer to multiple features ("feature-tso"), while also having more specific ids
-     * ("feature-tx-tcp-segmentation"). */
-
-    /* names from ethtool utility, which are aliases for multiple features. */
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_SG, "tx-scatter-gather", "tx-scatter-gather-fraglist"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TSO,
-              "tx-tcp-segmentation",
-              "tx-tcp-ecn-segmentation",
-              "tx-tcp-mangleid-segmentation",
-              "tx-tcp6-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX,
-              "tx-checksum-ipv4",
-              "tx-checksum-ip-generic",
-              "tx-checksum-ipv6",
-              "tx-checksum-fcoe-crc",
-              "tx-checksum-sctp"),
-
-    /* names from ethtool utility, which are aliases for one feature. */
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_GRO, "rx-gro"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_GSO, "tx-generic-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_LRO, "rx-lro"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_NTUPLE, "rx-ntuple-filter"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX, "rx-checksum"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RXHASH, "rx-hashing"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert"),
-
-    /* names of features, as known by kernel. */
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD, "esp-hw-offload"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD, "esp-tx-csum-hw-offload"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_FCOE_MTU, "fcoe-mtu"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_HIGHDMA, "highdma"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD, "hw-tc-offload"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD, "l2-fwd-offload"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_LOOPBACK, "loopback"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_ALL, "rx-all"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_FCS, "rx-fcs"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_GRO_HW, "rx-gro-hw"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD, "rx-udp_tunnel-port-offload"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER, "rx-vlan-filter"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER, "rx-vlan-stag-filter"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE, "rx-vlan-stag-hw-parse"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD, "tls-hw-record"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD, "tls-hw-tx-offload"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC, "tx-checksum-fcoe-crc"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4, "tx-checksum-ipv4"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6, "tx-checksum-ipv6"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC, "tx-checksum-ip-generic"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP, "tx-checksum-sctp"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION, "tx-esp-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION, "tx-fcoe-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION, "tx-gre-csum-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION, "tx-gre-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL, "tx-gso-partial"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST, "tx-gso-robust"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION, "tx-ipxip4-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION, "tx-ipxip6-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY, "tx-nocache-copy"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER, "tx-scatter-gather"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST, "tx-scatter-gather-fraglist"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION, "tx-sctp-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, "tx-tcp6-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION, "tx-tcp-ecn-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, "tx-tcp-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION, "tx-udp-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION, "tx-udp_tnl-segmentation"),
-    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT, "tx-vlan-stag-hw-insert"),
-};
-
-/* the number of kernel features that we handle. It essentially is the sum of all
- * kernel_names. So, all ethtool-ids that reference exactly one kernel-name
- * (_NM_ETHTOOL_ID_FEATURE_NUM) + some extra, for ethtool-ids that are aliases
- * for multiple kernel-names. */
-#define N_ETHTOOL_KERNEL_FEATURES (((guint) _NM_ETHTOOL_ID_FEATURE_NUM) + 8u)
-
-static void
-_ASSERT_ethtool_feature_infos(void)
-{
-#if NM_MORE_ASSERTS > 10
-    guint i, k, n;
-    bool  found[_NM_ETHTOOL_ID_FEATURE_NUM] = {};
-
-    G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(_ethtool_feature_infos) == _NM_ETHTOOL_ID_FEATURE_NUM);
-
-    n = 0;
-    for (i = 0; i < G_N_ELEMENTS(_ethtool_feature_infos); i++) {
-        NMEthtoolFeatureState       kstate;
-        const NMEthtoolFeatureInfo *inf = &_ethtool_feature_infos[i];
-
-        g_assert(inf->ethtool_id >= _NM_ETHTOOL_ID_FEATURE_FIRST);
-        g_assert(inf->ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST);
-        g_assert(inf->n_kernel_names > 0);
-
-        for (k = 0; k < i; k++)
-            g_assert(inf->ethtool_id != _ethtool_feature_infos[k].ethtool_id);
-
-        g_assert(!found[_NM_ETHTOOL_ID_FEATURE_AS_IDX(inf->ethtool_id)]);
-        found[_NM_ETHTOOL_ID_FEATURE_AS_IDX(inf->ethtool_id)] = TRUE;
-
-        kstate.idx_kernel_name = inf->n_kernel_names - 1;
-        g_assert((guint) kstate.idx_kernel_name == (guint)(inf->n_kernel_names - 1));
-
-        n += inf->n_kernel_names;
-        for (k = 0; k < inf->n_kernel_names; k++) {
-            g_assert(nm_utils_strv_find_first((char **) inf->kernel_names, k, inf->kernel_names[k])
-                     < 0);
-        }
-    }
-
-    for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++)
-        g_assert(found[i]);
-
-    g_assert(n == N_ETHTOOL_KERNEL_FEATURES);
-#endif
-}
-
-static NMEthtoolFeatureStates *
-ethtool_get_features(SocketHandle *shandle)
-{
-    gs_free NMEthtoolFeatureStates * states      = NULL;
-    gs_free struct ethtool_gstrings *ss_features = NULL;
-
-    _ASSERT_ethtool_feature_infos();
-
-    ss_features = ethtool_get_stringset(shandle, ETH_SS_FEATURES);
-    if (!ss_features)
-        return NULL;
-
-    if (ss_features->len > 0) {
-        gs_free struct ethtool_gfeatures *  gfeatures_free = NULL;
-        struct ethtool_gfeatures *          gfeatures;
-        gsize                               gfeatures_len;
-        guint                               idx;
-        const NMEthtoolFeatureState *       states_list0   = NULL;
-        const NMEthtoolFeatureState *const *states_plist0  = NULL;
-        guint                               states_plist_n = 0;
-
-        gfeatures_len = sizeof(struct ethtool_gfeatures)
-                        + (NM_DIV_ROUND_UP(ss_features->len, 32u) * sizeof(gfeatures->features[0]));
-        gfeatures       = nm_malloc0_maybe_a(300, gfeatures_len, &gfeatures_free);
-        gfeatures->cmd  = ETHTOOL_GFEATURES;
-        gfeatures->size = NM_DIV_ROUND_UP(ss_features->len, 32u);
-        if (_ethtool_call_handle(shandle, gfeatures, gfeatures_len) < 0)
-            return NULL;
-
-        for (idx = 0; idx < G_N_ELEMENTS(_ethtool_feature_infos); idx++) {
-            const NMEthtoolFeatureInfo *info = &_ethtool_feature_infos[idx];
-            guint                       idx_kernel_name;
-
-            for (idx_kernel_name = 0; idx_kernel_name < info->n_kernel_names; idx_kernel_name++) {
-                NMEthtoolFeatureState *kstate;
-                const char *           kernel_name = info->kernel_names[idx_kernel_name];
-                int                    i_feature;
-                guint                  i_block;
-                guint32                i_flag;
-
-                i_feature = ethtool_gstrings_find(ss_features, kernel_name);
-                if (i_feature < 0)
-                    continue;
-
-                i_block = ((guint) i_feature) / 32u;
-                i_flag  = (guint32)(1u << (((guint) i_feature) % 32u));
-
-                if (!states) {
-                    states = g_malloc0(
-                        sizeof(NMEthtoolFeatureStates)
-                        + (N_ETHTOOL_KERNEL_FEATURES * sizeof(NMEthtoolFeatureState))
-                        + ((N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos))
-                           * sizeof(NMEthtoolFeatureState *)));
-                    states_list0          = &states->states_list[0];
-                    states_plist0         = (gpointer) &states_list0[N_ETHTOOL_KERNEL_FEATURES];
-                    states->n_ss_features = ss_features->len;
-                }
-
-                nm_assert(states->n_states < N_ETHTOOL_KERNEL_FEATURES);
-                kstate = (NMEthtoolFeatureState *) &states_list0[states->n_states];
-                states->n_states++;
-
-                kstate->info            = info;
-                kstate->idx_ss_features = i_feature;
-                kstate->idx_kernel_name = idx_kernel_name;
-                kstate->available       = !!(gfeatures->features[i_block].available & i_flag);
-                kstate->requested       = !!(gfeatures->features[i_block].requested & i_flag);
-                kstate->active          = !!(gfeatures->features[i_block].active & i_flag);
-                kstate->never_changed   = !!(gfeatures->features[i_block].never_changed & i_flag);
-
-                nm_assert(states_plist_n
-                          < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos));
-
-                if (!states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)])
-                    states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)] =
-                        &states_plist0[states_plist_n];
-                ((const NMEthtoolFeatureState **) states_plist0)[states_plist_n] = kstate;
-                states_plist_n++;
-            }
-
-            if (states && states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)]) {
-                nm_assert(states_plist_n
-                          < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos));
-                nm_assert(!states_plist0[states_plist_n]);
-                states_plist_n++;
-            }
-        }
-    }
-
-    return g_steal_pointer(&states);
-}
-
-NMEthtoolFeatureStates *
-nmp_utils_ethtool_get_features(int ifindex)
-{
-    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
-    NMEthtoolFeatureStates *           features;
-
-    g_return_val_if_fail(ifindex > 0, 0);
-
-    features = ethtool_get_features(&shandle);
-
-    if (!features) {
-        nm_log_trace(LOGD_PLATFORM,
-                     "ethtool[%d]: %s: failure getting features",
-                     ifindex,
-                     "get-features");
-        return NULL;
-    }
-
-    nm_log_trace(LOGD_PLATFORM,
-                 "ethtool[%d]: %s: retrieved kernel features",
-                 ifindex,
-                 "get-features");
-    return features;
-}
-
-static const char *
-_ethtool_feature_state_to_string(char *                       buf,
-                                 gsize                        buf_size,
-                                 const NMEthtoolFeatureState *s,
-                                 const char *                 prefix)
-{
-    int l;
-
-    l = g_snprintf(buf,
-                   buf_size,
-                   "%s %s%s",
-                   prefix ?: "",
-                   ONOFF(s->active),
-                   (!s->available || s->never_changed)
-                       ? ", [fixed]"
-                       : ((s->requested != s->active)
-                              ? (s->requested ? ", [requested on]" : ", [requested off]")
-                              : ""));
-    nm_assert(l < buf_size);
-    return buf;
-}
-
-gboolean
-nmp_utils_ethtool_set_features(
-    int                           ifindex,
-    const NMEthtoolFeatureStates *features,
-    const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
-    gboolean         do_set /* or reset */)
-{
-    nm_auto_socket_handle SocketHandle shandle        = SOCKET_HANDLE_INIT(ifindex);
-    gs_free struct ethtool_sfeatures * sfeatures_free = NULL;
-    struct ethtool_sfeatures *         sfeatures;
-    gsize                              sfeatures_len;
-    int                                r;
-    guint                              i, j;
-    struct {
-        const NMEthtoolFeatureState *f_state;
-        NMTernary                    requested;
-    } set_states[N_ETHTOOL_KERNEL_FEATURES];
-    guint    set_states_n = 0;
-    gboolean success      = TRUE;
-
-    g_return_val_if_fail(ifindex > 0, 0);
-    g_return_val_if_fail(features, 0);
-    g_return_val_if_fail(requested, 0);
-
-    nm_assert(features->n_states <= N_ETHTOOL_KERNEL_FEATURES);
-
-    for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) {
-        const NMEthtoolFeatureState *const *states_indexed;
-
-        if (requested[i] == NM_TERNARY_DEFAULT)
-            continue;
-
-        if (!(states_indexed = features->states_indexed[i])) {
-            if (do_set) {
-                nm_log_trace(LOGD_PLATFORM,
-                             "ethtool[%d]: %s: set feature %s: skip (not found)",
-                             ifindex,
-                             "set-features",
-                             nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname);
-                success = FALSE;
-            }
-            continue;
-        }
-
-        for (j = 0; states_indexed[j]; j++) {
-            const NMEthtoolFeatureState *s = states_indexed[j];
-            char                         sbuf[255];
-
-            if (set_states_n >= G_N_ELEMENTS(set_states))
-                g_return_val_if_reached(FALSE);
-
-            if (s->never_changed) {
-                nm_log_trace(LOGD_PLATFORM,
-                             "ethtool[%d]: %s: %s feature %s (%s): %s, %s (skip feature marked as "
-                             "never changed)",
-                             ifindex,
-                             "set-features",
-                             do_set ? "set" : "reset",
-                             nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
-                             s->info->kernel_names[s->idx_kernel_name],
-                             ONOFF(do_set ? requested[i] == NM_TERNARY_TRUE : s->active),
-                             _ethtool_feature_state_to_string(sbuf,
-                                                              sizeof(sbuf),
-                                                              s,
-                                                              do_set ? " currently:" : " before:"));
-                continue;
-            }
-
-            nm_log_trace(LOGD_PLATFORM,
-                         "ethtool[%d]: %s: %s feature %s (%s): %s, %s",
-                         ifindex,
-                         "set-features",
-                         do_set ? "set" : "reset",
-                         nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
-                         s->info->kernel_names[s->idx_kernel_name],
-                         ONOFF(do_set ? requested[i] == NM_TERNARY_TRUE : s->active),
-                         _ethtool_feature_state_to_string(sbuf,
-                                                          sizeof(sbuf),
-                                                          s,
-                                                          do_set ? " currently:" : " before:"));
-
-            if (do_set && (!s->available || s->never_changed)
-                && (s->active != (requested[i] == NM_TERNARY_TRUE))) {
-                /* we request to change a flag which kernel reported as fixed.
-                 * While the ethtool operation will silently succeed, mark the request
-                 * as failure. */
-                success = FALSE;
-            }
-
-            set_states[set_states_n].f_state   = s;
-            set_states[set_states_n].requested = requested[i];
-            set_states_n++;
-        }
-    }
-
-    if (set_states_n == 0) {
-        nm_log_trace(LOGD_PLATFORM,
-                     "ethtool[%d]: %s: no feature requested",
-                     ifindex,
-                     "set-features");
-        return TRUE;
-    }
-
-    sfeatures_len =
-        sizeof(struct ethtool_sfeatures)
-        + (NM_DIV_ROUND_UP(features->n_ss_features, 32U) * sizeof(sfeatures->features[0]));
-    sfeatures       = nm_malloc0_maybe_a(300, sfeatures_len, &sfeatures_free);
-    sfeatures->cmd  = ETHTOOL_SFEATURES;
-    sfeatures->size = NM_DIV_ROUND_UP(features->n_ss_features, 32U);
-
-    for (i = 0; i < set_states_n; i++) {
-        const NMEthtoolFeatureState *s = set_states[i].f_state;
-        guint                        i_block;
-        guint32                      i_flag;
-        gboolean                     is_requested;
-
-        i_block = s->idx_ss_features / 32u;
-        i_flag  = (guint32)(1u << (s->idx_ss_features % 32u));
-
-        sfeatures->features[i_block].valid |= i_flag;
-
-        if (do_set)
-            is_requested = (set_states[i].requested == NM_TERNARY_TRUE);
-        else
-            is_requested = s->active;
-
-        if (is_requested)
-            sfeatures->features[i_block].requested |= i_flag;
-        else
-            sfeatures->features[i_block].requested &= ~i_flag;
-    }
-
-    r = _ethtool_call_handle(&shandle, sfeatures, sfeatures_len);
-    if (r < 0) {
-        success = FALSE;
-        nm_log_trace(LOGD_PLATFORM,
-                     "ethtool[%d]: %s: failure setting features (%s)",
-                     ifindex,
-                     "set-features",
-                     nm_strerror_native(-r));
-        return FALSE;
-    }
-
-    nm_log_trace(LOGD_PLATFORM,
-                 "ethtool[%d]: %s: %s",
-                 ifindex,
-                 "set-features",
-                 success ? "successfully setting features"
-                         : "at least some of the features were not successfully set");
-    return success;
-}
-
-static gboolean
-ethtool_get_coalesce(SocketHandle *shandle, NMEthtoolCoalesceState *coalesce)
-{
-    struct ethtool_coalesce eth_data;
-
-    eth_data.cmd = ETHTOOL_GCOALESCE;
-
-    if (_ethtool_call_handle(shandle, &eth_data, sizeof(struct ethtool_coalesce)) != 0)
-        return FALSE;
-
-    *coalesce = (NMEthtoolCoalesceState){
-        .s = {
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)] =
-                eth_data.rx_coalesce_usecs,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES)] =
-                eth_data.rx_max_coalesced_frames,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)] =
-                eth_data.rx_coalesce_usecs_irq,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)] =
-                eth_data.rx_max_coalesced_frames_irq,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS)] =
-                eth_data.tx_coalesce_usecs,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES)] =
-                eth_data.tx_max_coalesced_frames,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)] =
-                eth_data.tx_coalesce_usecs_irq,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)] =
-                eth_data.tx_max_coalesced_frames_irq,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)] =
-                eth_data.stats_block_coalesce_usecs,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)] =
-                eth_data.use_adaptive_rx_coalesce,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)] =
-                eth_data.use_adaptive_tx_coalesce,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)] =
-                eth_data.pkt_rate_low,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)] =
-                eth_data.rx_coalesce_usecs_low,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)] =
-                eth_data.rx_max_coalesced_frames_low,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)] =
-                eth_data.tx_coalesce_usecs_low,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)] =
-                eth_data.tx_max_coalesced_frames_low,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)] =
-                eth_data.pkt_rate_high,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)] =
-                eth_data.rx_coalesce_usecs_high,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)] =
-                eth_data.rx_max_coalesced_frames_high,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)] =
-                eth_data.tx_coalesce_usecs_high,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)] =
-                eth_data.tx_max_coalesced_frames_high,
-            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)] =
-                eth_data.rate_sample_interval,
-        }};
-    return TRUE;
-}
-
-gboolean
-nmp_utils_ethtool_get_coalesce(int ifindex, NMEthtoolCoalesceState *coalesce)
-{
-    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(coalesce, FALSE);
-
-    if (!ethtool_get_coalesce(&shandle, coalesce)) {
-        nm_log_trace(LOGD_PLATFORM,
-                     "ethtool[%d]: %s: failure getting coalesce settings",
-                     ifindex,
-                     "get-coalesce");
-        return FALSE;
-    }
-
-    nm_log_trace(LOGD_PLATFORM,
-                 "ethtool[%d]: %s: retrieved kernel coalesce settings",
-                 ifindex,
-                 "get-coalesce");
-    return TRUE;
-}
-
-static gboolean
-ethtool_set_coalesce(SocketHandle *shandle, const NMEthtoolCoalesceState *coalesce)
-{
-    struct ethtool_coalesce eth_data;
-    gboolean                success;
-
-    nm_assert(shandle);
-    nm_assert(coalesce);
-
-    eth_data = (struct ethtool_coalesce){
-        .cmd = ETHTOOL_SCOALESCE,
-        .rx_coalesce_usecs =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)],
-        .rx_max_coalesced_frames =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES)],
-        .rx_coalesce_usecs_irq =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)],
-        .rx_max_coalesced_frames_irq =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)],
-        .tx_coalesce_usecs =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS)],
-        .tx_max_coalesced_frames =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES)],
-        .tx_coalesce_usecs_irq =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)],
-        .tx_max_coalesced_frames_irq =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)],
-        .stats_block_coalesce_usecs =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)],
-        .use_adaptive_rx_coalesce =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)],
-        .use_adaptive_tx_coalesce =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)],
-        .pkt_rate_low =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)],
-        .rx_coalesce_usecs_low =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)],
-        .rx_max_coalesced_frames_low =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)],
-        .tx_coalesce_usecs_low =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)],
-        .tx_max_coalesced_frames_low =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)],
-        .pkt_rate_high =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)],
-        .rx_coalesce_usecs_high =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)],
-        .rx_max_coalesced_frames_high =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)],
-        .tx_coalesce_usecs_high =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)],
-        .tx_max_coalesced_frames_high =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)],
-        .rate_sample_interval =
-            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)],
-    };
-
-    success = (_ethtool_call_handle(shandle, &eth_data, sizeof(struct ethtool_coalesce)) == 0);
-    return success;
-}
-
-gboolean
-nmp_utils_ethtool_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coalesce)
-{
-    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(coalesce, FALSE);
-
-    if (!ethtool_set_coalesce(&shandle, coalesce)) {
-        nm_log_trace(LOGD_PLATFORM,
-                     "ethtool[%d]: %s: failure setting coalesce settings",
-                     ifindex,
-                     "set-coalesce");
-        return FALSE;
-    }
-
-    nm_log_trace(LOGD_PLATFORM,
-                 "ethtool[%d]: %s: set kernel coalesce settings",
-                 ifindex,
-                 "set-coalesce");
-    return TRUE;
-}
-
-static gboolean
-ethtool_get_ring(SocketHandle *shandle, NMEthtoolRingState *ring)
-{
-    struct ethtool_ringparam eth_data;
-
-    eth_data.cmd = ETHTOOL_GRINGPARAM;
-
-    if (_ethtool_call_handle(shandle, &eth_data, sizeof(struct ethtool_ringparam)) != 0)
-        return FALSE;
-
-    ring->rx_pending       = eth_data.rx_pending;
-    ring->rx_jumbo_pending = eth_data.rx_jumbo_pending;
-    ring->rx_mini_pending  = eth_data.rx_mini_pending;
-    ring->tx_pending       = eth_data.tx_pending;
-
-    return TRUE;
-}
-
-gboolean
-nmp_utils_ethtool_get_ring(int ifindex, NMEthtoolRingState *ring)
-{
-    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(ring, FALSE);
-
-    if (!ethtool_get_ring(&shandle, ring)) {
-        nm_log_trace(LOGD_PLATFORM,
-                     "ethtool[%d]: %s: failure getting ring settings",
-                     ifindex,
-                     "get-ring");
-        return FALSE;
-    }
-
-    nm_log_trace(LOGD_PLATFORM,
-                 "ethtool[%d]: %s: retrieved kernel ring settings",
-                 ifindex,
-                 "get-ring");
-    return TRUE;
-}
-
-static gboolean
-ethtool_set_ring(SocketHandle *shandle, const NMEthtoolRingState *ring)
-{
-    gboolean                 success;
-    struct ethtool_ringparam eth_data;
-
-    g_return_val_if_fail(shandle, FALSE);
-    g_return_val_if_fail(ring, FALSE);
-
-    eth_data = (struct ethtool_ringparam){
-        .cmd              = ETHTOOL_SRINGPARAM,
-        .rx_pending       = ring->rx_pending,
-        .rx_jumbo_pending = ring->rx_jumbo_pending,
-        .rx_mini_pending  = ring->rx_mini_pending,
-        .tx_pending       = ring->tx_pending,
-    };
-
-    success = (_ethtool_call_handle(shandle, &eth_data, sizeof(struct ethtool_ringparam)) == 0);
-    return success;
-}
-
-gboolean
-nmp_utils_ethtool_set_ring(int ifindex, const NMEthtoolRingState *ring)
-{
-    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(ring, FALSE);
-
-    if (!ethtool_set_ring(&shandle, ring)) {
-        nm_log_trace(LOGD_PLATFORM,
-                     "ethtool[%d]: %s: failure setting ring settings",
-                     ifindex,
-                     "set-ring");
-        return FALSE;
-    }
-
-    nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel ring settings", ifindex, "set-ring");
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-gboolean
-nmp_utils_ethtool_get_driver_info(int ifindex, NMPUtilsEthtoolDriverInfo *data)
-{
-    struct ethtool_drvinfo *drvinfo;
-
-    G_STATIC_ASSERT_EXPR(sizeof(*data) == sizeof(*drvinfo));
-    G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, driver)
-                         == offsetof(struct ethtool_drvinfo, driver));
-    G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, version)
-                         == offsetof(struct ethtool_drvinfo, version));
-    G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, fw_version)
-                         == offsetof(struct ethtool_drvinfo, fw_version));
-    G_STATIC_ASSERT_EXPR(sizeof(data->driver) == sizeof(drvinfo->driver));
-    G_STATIC_ASSERT_EXPR(sizeof(data->version) == sizeof(drvinfo->version));
-    G_STATIC_ASSERT_EXPR(sizeof(data->fw_version) == sizeof(drvinfo->fw_version));
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(data, FALSE);
-
-    drvinfo  = (struct ethtool_drvinfo *) data;
-    *drvinfo = (struct ethtool_drvinfo){
-        .cmd = ETHTOOL_GDRVINFO,
-    };
-    return _ethtool_call_once(ifindex, drvinfo, sizeof(*drvinfo)) >= 0;
-}
-
-gboolean
-nmp_utils_ethtool_get_permanent_address(int ifindex, guint8 *buf, size_t *length)
-{
-    struct {
-        struct ethtool_perm_addr e;
-        guint8                   _extra_data[NM_UTILS_HWADDR_LEN_MAX + 1];
-    } edata = {
-        .e.cmd  = ETHTOOL_GPERMADDR,
-        .e.size = NM_UTILS_HWADDR_LEN_MAX,
-    };
-    const guint8 *pdata;
-
-    guint i;
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    if (_ethtool_call_once(ifindex, &edata, sizeof(edata)) < 0)
-        return FALSE;
-
-    if (edata.e.size > NM_UTILS_HWADDR_LEN_MAX)
-        return FALSE;
-    if (edata.e.size < 1)
-        return FALSE;
-
-    pdata = (const guint8 *) edata.e.data;
-
-    if (NM_IN_SET(pdata[0], 0, 0xFF)) {
-        /* Some drivers might return a permanent address of all zeros.
-         * Reject that (rh#1264024)
-         *
-         * Some drivers return a permanent address of all ones. Reject that too */
-        for (i = 1; i < edata.e.size; i++) {
-            if (pdata[0] != pdata[i])
-                goto not_all_0or1;
-        }
-        return FALSE;
-    }
-
-not_all_0or1:
-    memcpy(buf, pdata, edata.e.size);
-    *length = edata.e.size;
-    return TRUE;
-}
-
-gboolean
-nmp_utils_ethtool_supports_carrier_detect(int ifindex)
-{
-    struct ethtool_cmd edata = {.cmd = ETHTOOL_GLINK};
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    /* We ignore the result. If the ETHTOOL_GLINK call succeeded, then we
-     * assume the device supports carrier-detect, otherwise we assume it
-     * doesn't.
-     */
-    return _ethtool_call_once(ifindex, &edata, sizeof(edata)) >= 0;
-}
-
-gboolean
-nmp_utils_ethtool_supports_vlans(int ifindex)
-{
-    nm_auto_socket_handle SocketHandle shandle       = SOCKET_HANDLE_INIT(ifindex);
-    gs_free struct ethtool_gfeatures * features_free = NULL;
-    struct ethtool_gfeatures *         features;
-    gsize                              features_len;
-    int                                idx, block, bit, size;
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    idx = ethtool_get_stringset_index(&shandle, ETH_SS_FEATURES, "vlan-challenged");
-    if (idx < 0) {
-        nm_log_dbg(LOGD_PLATFORM,
-                   "ethtool[%d]: vlan-challenged ethtool feature does not exist?",
-                   ifindex);
-        return FALSE;
-    }
-
-    block = idx / 32;
-    bit   = idx % 32;
-    size  = block + 1;
-
-    features_len   = sizeof(*features) + (size * sizeof(struct ethtool_get_features_block));
-    features       = nm_malloc0_maybe_a(300, features_len, &features_free);
-    features->cmd  = ETHTOOL_GFEATURES;
-    features->size = size;
-
-    if (_ethtool_call_handle(&shandle, features, features_len) < 0)
-        return FALSE;
-
-    return !(features->features[block].active & (1 << bit));
-}
-
-int
-nmp_utils_ethtool_get_peer_ifindex(int ifindex)
-{
-    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
-    gsize                              stats_len;
-    gs_free struct ethtool_stats *     stats_free = NULL;
-    struct ethtool_stats *             stats;
-    int                                peer_ifindex_stat;
-
-    g_return_val_if_fail(ifindex > 0, 0);
-
-    peer_ifindex_stat = ethtool_get_stringset_index(&shandle, ETH_SS_STATS, "peer_ifindex");
-    if (peer_ifindex_stat < 0) {
-        nm_log_dbg(LOGD_PLATFORM, "ethtool[%d]: peer_ifindex stat does not exist?", ifindex);
-        return FALSE;
-    }
-
-    stats_len      = sizeof(*stats) + (peer_ifindex_stat + 1) * sizeof(guint64);
-    stats          = nm_malloc0_maybe_a(300, stats_len, &stats_free);
-    stats->cmd     = ETHTOOL_GSTATS;
-    stats->n_stats = peer_ifindex_stat + 1;
-    if (_ethtool_call_handle(&shandle, stats, stats_len) < 0)
-        return 0;
-
-    return stats->data[peer_ifindex_stat];
-}
-
-gboolean
-nmp_utils_ethtool_get_wake_on_lan(int ifindex)
-{
-    struct ethtool_wolinfo wol = {
-        .cmd = ETHTOOL_GWOL,
-    };
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    if (_ethtool_call_once(ifindex, &wol, sizeof(wol)) < 0)
-        return FALSE;
-
-    return wol.wolopts != 0;
-}
-
-gboolean
-nmp_utils_ethtool_get_link_settings(int                       ifindex,
-                                    gboolean *                out_autoneg,
-                                    guint32 *                 out_speed,
-                                    NMPlatformLinkDuplexType *out_duplex)
-{
-    struct ethtool_cmd edata = {
-        .cmd = ETHTOOL_GSET,
-    };
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    if (_ethtool_call_once(ifindex, &edata, sizeof(edata)) < 0)
-        return FALSE;
-
-    NM_SET_OUT(out_autoneg, (edata.autoneg == AUTONEG_ENABLE));
-
-    if (out_speed) {
-        guint32 speed;
-
-        speed = ethtool_cmd_speed(&edata);
-        if (speed == G_MAXUINT16 || speed == G_MAXUINT32)
-            speed = 0;
-
-        *out_speed = speed;
-    }
-
-    if (out_duplex) {
-        switch (edata.duplex) {
-        case DUPLEX_HALF:
-            *out_duplex = NM_PLATFORM_LINK_DUPLEX_HALF;
-            break;
-        case DUPLEX_FULL:
-            *out_duplex = NM_PLATFORM_LINK_DUPLEX_FULL;
-            break;
-        default: /* DUPLEX_UNKNOWN */
-            *out_duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN;
-            break;
-        }
-    }
-
-    return TRUE;
-}
-
-#define ADVERTISED_INVALID 0
-#define BASET_ALL_MODES                                                                 \
-    (ADVERTISED_10baseT_Half | ADVERTISED_10baseT_Full | ADVERTISED_100baseT_Half       \
-     | ADVERTISED_100baseT_Full | ADVERTISED_1000baseT_Half | ADVERTISED_1000baseT_Full \
-     | ADVERTISED_10000baseT_Full)
-
-static guint32
-get_baset_mode(guint32 speed, NMPlatformLinkDuplexType duplex)
-{
-    if (duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN)
-        return ADVERTISED_INVALID;
-
-    if (duplex == NM_PLATFORM_LINK_DUPLEX_HALF) {
-        switch (speed) {
-        case 10:
-            return ADVERTISED_10baseT_Half;
-        case 100:
-            return ADVERTISED_100baseT_Half;
-        case 1000:
-            return ADVERTISED_1000baseT_Half;
-        default:
-            return ADVERTISED_INVALID;
-        }
-    } else {
-        switch (speed) {
-        case 10:
-            return ADVERTISED_10baseT_Full;
-        case 100:
-            return ADVERTISED_100baseT_Full;
-        case 1000:
-            return ADVERTISED_1000baseT_Full;
-        case 10000:
-            return ADVERTISED_10000baseT_Full;
-        default:
-            return ADVERTISED_INVALID;
-        }
-    }
-}
-
-gboolean
-nmp_utils_ethtool_set_link_settings(int                      ifindex,
-                                    gboolean                 autoneg,
-                                    guint32                  speed,
-                                    NMPlatformLinkDuplexType duplex)
-{
-    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
-    struct ethtool_cmd                 edata   = {
-        .cmd = ETHTOOL_GSET,
-    };
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail((speed && duplex != NM_PLATFORM_LINK_DUPLEX_UNKNOWN)
-                             || (!speed && duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN),
-                         FALSE);
-
-    /* retrieve first current settings */
-    if (_ethtool_call_handle(&shandle, &edata, sizeof(edata)) < 0)
-        return FALSE;
-
-    /* FIXME: try first new ETHTOOL_GLINKSETTINGS/SLINKSETTINGS API
-     * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3f1ac7a700d039c61d8d8b99f28d605d489a60cf
-     */
-
-    /* then change the needed ones */
-    edata.cmd = ETHTOOL_SSET;
-    if (autoneg) {
-        edata.autoneg = AUTONEG_ENABLE;
-        if (!speed)
-            edata.advertising = edata.supported;
-        else {
-            guint32 mode;
-
-            mode = get_baset_mode(speed, duplex);
-
-            if (!mode) {
-                nm_log_trace(LOGD_PLATFORM,
-                             "ethtool[%d]: %uBASE-T %s duplex mode cannot be advertised",
-                             ifindex,
-                             speed,
-                             nm_platform_link_duplex_type_to_string(duplex));
-                return FALSE;
-            }
-            if (!(edata.supported & mode)) {
-                nm_log_trace(LOGD_PLATFORM,
-                             "ethtool[%d]: device does not support %uBASE-T %s duplex mode",
-                             ifindex,
-                             speed,
-                             nm_platform_link_duplex_type_to_string(duplex));
-                return FALSE;
-            }
-            edata.advertising = (edata.supported & ~BASET_ALL_MODES) | mode;
-        }
-    } else {
-        edata.autoneg = AUTONEG_DISABLE;
-
-        if (speed)
-            ethtool_cmd_speed_set(&edata, speed);
-
-        switch (duplex) {
-        case NM_PLATFORM_LINK_DUPLEX_HALF:
-            edata.duplex = DUPLEX_HALF;
-            break;
-        case NM_PLATFORM_LINK_DUPLEX_FULL:
-            edata.duplex = DUPLEX_FULL;
-            break;
-        case NM_PLATFORM_LINK_DUPLEX_UNKNOWN:
-            break;
-        default:
-            g_return_val_if_reached(FALSE);
-        }
-    }
-
-    return _ethtool_call_handle(&shandle, &edata, sizeof(edata)) >= 0;
-}
-
-gboolean
-nmp_utils_ethtool_set_wake_on_lan(int                     ifindex,
-                                  NMSettingWiredWakeOnLan wol,
-                                  const char *            wol_password)
-{
-    struct ethtool_wolinfo wol_info = {
-        .cmd     = ETHTOOL_SWOL,
-        .wolopts = 0,
-    };
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    if (wol == NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE)
-        return TRUE;
-
-    nm_log_dbg(LOGD_PLATFORM,
-               "ethtool[%d]: setting Wake-on-LAN options 0x%x, password '%s'",
-               ifindex,
-               (unsigned) wol,
-               wol_password);
-
-    if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_PHY))
-        wol_info.wolopts |= WAKE_PHY;
-    if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST))
-        wol_info.wolopts |= WAKE_UCAST;
-    if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST))
-        wol_info.wolopts |= WAKE_MCAST;
-    if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST))
-        wol_info.wolopts |= WAKE_BCAST;
-    if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_ARP))
-        wol_info.wolopts |= WAKE_ARP;
-    if (NM_FLAGS_HAS(wol, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC))
-        wol_info.wolopts |= WAKE_MAGIC;
-
-    if (wol_password) {
-        if (!nm_utils_hwaddr_aton(wol_password, wol_info.sopass, ETH_ALEN)) {
-            nm_log_dbg(LOGD_PLATFORM,
-                       "ethtool[%d]: couldn't parse Wake-on-LAN password '%s'",
-                       ifindex,
-                       wol_password);
-            return FALSE;
-        }
-        wol_info.wolopts |= WAKE_MAGICSECURE;
-    }
-
-    return _ethtool_call_once(ifindex, &wol_info, sizeof(wol_info)) >= 0;
-}
-
-/******************************************************************************
- * mii
- *****************************************************************************/
-
-gboolean
-nmp_utils_mii_supports_carrier_detect(int ifindex)
-{
-    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
-    int                                r;
-    struct ifreq                       ifr;
-    struct mii_ioctl_data *            mii;
-
-    g_return_val_if_fail(ifindex > 0, FALSE);
-
-    r = _ioctl_call("mii",
-                    "SIOCGMIIPHY",
-                    SIOCGMIIPHY,
-                    shandle.ifindex,
-                    &shandle.fd,
-                    shandle.ifname,
-                    IOCTL_CALL_DATA_TYPE_NONE,
-                    NULL,
-                    0,
-                    &ifr);
-    if (r < 0)
-        return FALSE;
-
-    /* If we can read the BMSR register, we assume that the card supports MII link detection */
-    mii          = (struct mii_ioctl_data *) &ifr.ifr_ifru;
-    mii->reg_num = MII_BMSR;
-
-    r = _ioctl_call("mii",
-                    "SIOCGMIIREG",
-                    SIOCGMIIREG,
-                    shandle.ifindex,
-                    &shandle.fd,
-                    shandle.ifname,
-                    IOCTL_CALL_DATA_TYPE_IFRU,
-                    mii,
-                    sizeof(*mii),
-                    &ifr);
-    if (r < 0)
-        return FALSE;
-
-    mii = (struct mii_ioctl_data *) &ifr.ifr_ifru;
-    nm_log_trace(LOGD_PLATFORM,
-                 "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X",
-                 ifindex,
-                 shandle.ifname,
-                 mii->val_out);
-    return TRUE;
-}
-
-/******************************************************************************
- * udev
- *****************************************************************************/
-
-const char *
-nmp_utils_udev_get_driver(struct udev_device *udevice)
-{
-    struct udev_device *parent = NULL, *grandparent = NULL;
-    const char *        driver, *subsys;
-
-    driver = udev_device_get_driver(udevice);
-    if (driver)
-        goto out;
-
-    /* Try the parent */
-    parent = udev_device_get_parent(udevice);
-    if (parent) {
-        driver = udev_device_get_driver(parent);
-        if (!driver) {
-            /* Try the grandparent if it's an ibmebus device or if the
-             * subsys is NULL which usually indicates some sort of
-             * platform device like a 'gadget' net interface.
-             */
-            subsys = udev_device_get_subsystem(parent);
-            if ((g_strcmp0(subsys, "ibmebus") == 0) || (subsys == NULL)) {
-                grandparent = udev_device_get_parent(parent);
-                if (grandparent)
-                    driver = udev_device_get_driver(grandparent);
-            }
-        }
-    }
-
-out:
-    /* Intern the string so we don't have to worry about memory
-     * management in NMPlatformLink. */
-    return g_intern_string(driver);
-}
-
-/******************************************************************************
- * utils
- *****************************************************************************/
-
-NMIPConfigSource
-nmp_utils_ip_config_source_from_rtprot(guint8 rtprot)
-{
-    return ((int) rtprot) + 1;
-}
-
-NMIPConfigSource
-nmp_utils_ip_config_source_round_trip_rtprot(NMIPConfigSource source)
-{
-    /* when adding a route to kernel for a give @source, the resulting route
-     * will be put into the cache with a source of NM_IP_CONFIG_SOURCE_RTPROT_*.
-     * This function returns that. */
-    return nmp_utils_ip_config_source_from_rtprot(
-        nmp_utils_ip_config_source_coerce_to_rtprot(source));
-}
-
-guint8
-nmp_utils_ip_config_source_coerce_to_rtprot(NMIPConfigSource source)
-{
-    /* when adding a route to kernel, we coerce the @source field
-     * to rtm_protocol. This is not lossless as we map different
-     * source values to the same RTPROT uint8 value. */
-    if (source <= NM_IP_CONFIG_SOURCE_UNKNOWN)
-        return RTPROT_UNSPEC;
-
-    if (source <= _NM_IP_CONFIG_SOURCE_RTPROT_LAST)
-        return source - 1;
-
-    switch (source) {
-    case NM_IP_CONFIG_SOURCE_KERNEL:
-        return RTPROT_KERNEL;
-    case NM_IP_CONFIG_SOURCE_IP6LL:
-        return RTPROT_KERNEL;
-    case NM_IP_CONFIG_SOURCE_DHCP:
-        return RTPROT_DHCP;
-    case NM_IP_CONFIG_SOURCE_NDISC:
-        return RTPROT_RA;
-
-    default:
-        return RTPROT_STATIC;
-    }
-}
-
-NMIPConfigSource
-nmp_utils_ip_config_source_coerce_from_rtprot(NMIPConfigSource source)
-{
-    /* When we receive a route from kernel and put it into the platform cache,
-     * we preserve the protocol field by converting it to a NMIPConfigSource
-     * via nmp_utils_ip_config_source_from_rtprot().
-     *
-     * However, that is not the inverse of nmp_utils_ip_config_source_coerce_to_rtprot().
-     * Instead, to go back to the original value, you need another step:
-     *   nmp_utils_ip_config_source_coerce_from_rtprot (nmp_utils_ip_config_source_from_rtprot (rtprot)).
-     *
-     * This might partly restore the original source value, but of course that
-     * is not really possible because nmp_utils_ip_config_source_coerce_to_rtprot()
-     * is not injective.
-     * */
-    switch (source) {
-    case NM_IP_CONFIG_SOURCE_RTPROT_UNSPEC:
-        return NM_IP_CONFIG_SOURCE_UNKNOWN;
-
-    case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL:
-    case NM_IP_CONFIG_SOURCE_RTPROT_REDIRECT:
-        return NM_IP_CONFIG_SOURCE_KERNEL;
-
-    case NM_IP_CONFIG_SOURCE_RTPROT_RA:
-        return NM_IP_CONFIG_SOURCE_NDISC;
-
-    case NM_IP_CONFIG_SOURCE_RTPROT_DHCP:
-        return NM_IP_CONFIG_SOURCE_DHCP;
-
-    default:
-        return NM_IP_CONFIG_SOURCE_USER;
-    }
-}
-
-const char *
-nmp_utils_ip_config_source_to_string(NMIPConfigSource source, char *buf, gsize len)
-{
-    const char *s = NULL;
-    nm_utils_to_string_buffer_init(&buf, &len);
-
-    if (!len)
-        return buf;
-
-    switch (source) {
-    case NM_IP_CONFIG_SOURCE_UNKNOWN:
-        s = "unknown";
-        break;
-
-    case NM_IP_CONFIG_SOURCE_RTPROT_UNSPEC:
-        s = "rt-unspec";
-        break;
-    case NM_IP_CONFIG_SOURCE_RTPROT_REDIRECT:
-        s = "rt-redirect";
-        break;
-    case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL:
-        s = "rt-kernel";
-        break;
-    case NM_IP_CONFIG_SOURCE_RTPROT_BOOT:
-        s = "rt-boot";
-        break;
-    case NM_IP_CONFIG_SOURCE_RTPROT_STATIC:
-        s = "rt-static";
-        break;
-    case NM_IP_CONFIG_SOURCE_RTPROT_DHCP:
-        s = "rt-dhcp";
-        break;
-    case NM_IP_CONFIG_SOURCE_RTPROT_RA:
-        s = "rt-ra";
-        break;
-
-    case NM_IP_CONFIG_SOURCE_KERNEL:
-        s = "kernel";
-        break;
-    case NM_IP_CONFIG_SOURCE_SHARED:
-        s = "shared";
-        break;
-    case NM_IP_CONFIG_SOURCE_IP4LL:
-        s = "ipv4ll";
-        break;
-    case NM_IP_CONFIG_SOURCE_IP6LL:
-        s = "ipv6ll";
-        break;
-    case NM_IP_CONFIG_SOURCE_PPP:
-        s = "ppp";
-        break;
-    case NM_IP_CONFIG_SOURCE_WWAN:
-        s = "wwan";
-        break;
-    case NM_IP_CONFIG_SOURCE_VPN:
-        s = "vpn";
-        break;
-    case NM_IP_CONFIG_SOURCE_DHCP:
-        s = "dhcp";
-        break;
-    case NM_IP_CONFIG_SOURCE_NDISC:
-        s = "ndisc";
-        break;
-    case NM_IP_CONFIG_SOURCE_USER:
-        s = "user";
-        break;
-    default:
-        break;
-    }
-
-    if (source >= 1 && source <= 0x100) {
-        if (s)
-            g_snprintf(buf, len, "%s", s);
-        else
-            g_snprintf(buf, len, "rt-%d", ((int) source) - 1);
-    } else {
-        if (s)
-            g_strlcpy(buf, s, len);
-        else
-            g_snprintf(buf, len, "(%d)", source);
-    }
-    return buf;
-}
-
-/**
- * nmp_utils_sysctl_open_netdir:
- * @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 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.
- *
- * Returns: a negative value on failure, on success returns the open fd
- *   to the "/sys/class/net/%s" directory for @ifindex.
- */
-int
-nmp_utils_sysctl_open_netdir(int ifindex, const char *ifname_guess, char *out_ifname)
-{
-#define SYS_CLASS_NET "/sys/class/net/"
-    const char *ifname = ifname_guess;
-    char        ifname_buf_last_try[IFNAMSIZ];
-    char        ifname_buf[IFNAMSIZ];
-    guint       try_count                                   = 0;
-    char        sysdir[NM_STRLEN(SYS_CLASS_NET) + IFNAMSIZ] = SYS_CLASS_NET;
-    char        fd_buf[256];
-    ssize_t     nn;
-
-    g_return_val_if_fail(ifindex >= 0, -1);
-
-    ifname_buf_last_try[0] = '\0';
-
-    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;
-
-        if (!ifname) {
-            ifname = nmp_utils_if_indextoname(ifindex, ifname_buf);
-            if (!ifname)
-                return -1;
-        }
-
-        nm_assert(nm_utils_ifname_valid_kernel(ifname, NULL));
-
-        if (g_strlcpy(&sysdir[NM_STRLEN(SYS_CLASS_NET)], ifname, IFNAMSIZ) >= IFNAMSIZ)
-            g_return_val_if_reached(-1);
-
-        /* we only retry, if the name changed since previous attempt.
-         * Hence, it is extremely unlikely that this loop runes until the
-         * end of the @try_count. */
-        if (nm_streq(ifname, ifname_buf_last_try))
-            return -1;
-        strcpy(ifname_buf_last_try, ifname);
-
-        fd_dir = open(sysdir, O_DIRECTORY | O_CLOEXEC);
-        if (fd_dir < 0)
-            continue;
-
-        fd_ifindex = openat(fd_dir, "ifindex", O_CLOEXEC);
-        if (fd_ifindex < 0)
-            continue;
-
-        nn = nm_utils_fd_read_loop(fd_ifindex, fd_buf, sizeof(fd_buf) - 2, FALSE);
-        if (nn <= 0)
-            continue;
-        fd_buf[nn] = '\0';
-
-        if (ifindex != (int) _nm_utils_ascii_str_to_int64(fd_buf, 10, 1, G_MAXINT, -1))
-            continue;
-
-        if (out_ifname)
-            strcpy(out_ifname, ifname);
-
-        return nm_steal_fd(&fd_dir);
-    }
-
-    return -1;
-}
diff --git a/src/platform/nm-platform-utils.h b/src/platform/nm-platform-utils.h
deleted file mode 100644
index 076a2c7d..00000000
--- a/src/platform/nm-platform-utils.h
+++ /dev/null
@@ -1,140 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * Copyright (C) 2015 Red Hat, Inc.
- */
-
-#ifndef __NM_PLATFORM_UTILS_H__
-#define __NM_PLATFORM_UTILS_H__
-
-#include "nm-platform.h"
-#include "nm-setting-wired.h"
-#include "nm-libnm-core-intern/nm-ethtool-utils.h"
-
-/*****************************************************************************/
-
-const char *nmp_utils_ethtool_get_driver(int ifindex);
-gboolean    nmp_utils_ethtool_supports_carrier_detect(int ifindex);
-gboolean    nmp_utils_ethtool_supports_vlans(int ifindex);
-int         nmp_utils_ethtool_get_peer_ifindex(int ifindex);
-gboolean    nmp_utils_ethtool_get_wake_on_lan(int ifindex);
-gboolean    nmp_utils_ethtool_set_wake_on_lan(int                     ifindex,
-                                              NMSettingWiredWakeOnLan wol,
-                                              const char *            wol_password);
-
-gboolean nmp_utils_ethtool_get_link_settings(int                       ifindex,
-                                             gboolean *                out_autoneg,
-                                             guint32 *                 out_speed,
-                                             NMPlatformLinkDuplexType *out_duplex);
-gboolean nmp_utils_ethtool_set_link_settings(int                      ifindex,
-                                             gboolean                 autoneg,
-                                             guint32                  speed,
-                                             NMPlatformLinkDuplexType duplex);
-
-gboolean nmp_utils_ethtool_get_permanent_address(int ifindex, guint8 *buf, size_t *length);
-
-typedef struct {
-    /* We don't want to include <linux/ethtool.h> in header files,
-     * thus create a ABI compatible version of struct ethtool_drvinfo.*/
-    guint32 _private_cmd;
-    char    driver[32];
-    char    version[32];
-    char    fw_version[32];
-    char    _private_bus_info[32];
-    char    _private_erom_version[32];
-    char    _private_reserved2[12];
-    guint32 _private_n_priv_flags;
-    guint32 _private_n_stats;
-    guint32 _private_testinfo_len;
-    guint32 _private_eedump_len;
-    guint32 _private_regdump_len;
-} NMPUtilsEthtoolDriverInfo;
-
-gboolean nmp_utils_ethtool_get_driver_info(int ifindex, NMPUtilsEthtoolDriverInfo *data);
-
-typedef struct {
-    NMEthtoolID ethtool_id;
-
-    guint8 n_kernel_names;
-
-    /* one NMEthtoolID refers to one or more kernel_names. The reason for supporting this complexity
-     * (where one NMSettingEthtool option refers to multiple kernel features)  is to follow what
-     * ethtool does, where "tx" is an alias for multiple features. */
-    const char *const *kernel_names;
-} NMEthtoolFeatureInfo;
-
-typedef struct {
-    const NMEthtoolFeatureInfo *info;
-
-    guint idx_ss_features;
-
-    /* one NMEthtoolFeatureInfo references one or more kernel_names. This is the index
-     * of the matching info->kernel_names */
-    guint8 idx_kernel_name;
-
-    bool available : 1;
-    bool requested : 1;
-    bool active : 1;
-    bool never_changed : 1;
-} NMEthtoolFeatureState;
-
-struct _NMEthtoolFeatureStates {
-    guint n_states;
-
-    guint n_ss_features;
-
-    /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */
-    const NMEthtoolFeatureState *const *states_indexed[_NM_ETHTOOL_ID_FEATURE_NUM];
-
-    /* the same content, here as a list of n_states entries. */
-    const NMEthtoolFeatureState states_list[];
-};
-
-NMEthtoolFeatureStates *nmp_utils_ethtool_get_features(int ifindex);
-
-gboolean nmp_utils_ethtool_set_features(
-    int                           ifindex,
-    const NMEthtoolFeatureStates *features,
-    const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
-    gboolean         do_set /* or reset */);
-
-struct _NMEthtoolCoalesceState {
-    guint32
-        s[_NM_ETHTOOL_ID_COALESCE_NUM /* indexed by (NMEthtoolID - _NM_ETHTOOL_ID_COALESCE_FIRST) */
-    ];
-};
-
-gboolean nmp_utils_ethtool_get_coalesce(int ifindex, NMEthtoolCoalesceState *coalesce);
-
-gboolean nmp_utils_ethtool_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coalesce);
-
-struct _NMEthtoolRingState {
-    guint32 rx_pending;
-    guint32 rx_mini_pending;
-    guint32 rx_jumbo_pending;
-    guint32 tx_pending;
-};
-
-gboolean nmp_utils_ethtool_get_ring(int ifindex, NMEthtoolRingState *ring);
-
-gboolean nmp_utils_ethtool_set_ring(int ifindex, const NMEthtoolRingState *ring);
-
-/*****************************************************************************/
-
-gboolean nmp_utils_mii_supports_carrier_detect(int ifindex);
-
-struct udev_device;
-
-const char *nmp_utils_udev_get_driver(struct udev_device *udevice);
-
-NMIPConfigSource nmp_utils_ip_config_source_from_rtprot(guint8 rtprot) _nm_const;
-guint8           nmp_utils_ip_config_source_coerce_to_rtprot(NMIPConfigSource source) _nm_const;
-NMIPConfigSource nmp_utils_ip_config_source_coerce_from_rtprot(NMIPConfigSource source) _nm_const;
-NMIPConfigSource nmp_utils_ip_config_source_round_trip_rtprot(NMIPConfigSource source) _nm_const;
-const char *nmp_utils_ip_config_source_to_string(NMIPConfigSource source, char *buf, gsize len);
-
-const char *nmp_utils_if_indextoname(int ifindex, char *out_ifname /*IFNAMSIZ*/);
-int         nmp_utils_if_nametoindex(const char *ifname);
-
-int nmp_utils_sysctl_open_netdir(int ifindex, const char *ifname_guess, char *out_ifname);
-
-#endif /* __NM_PLATFORM_UTILS_H__ */
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
deleted file mode 100644
index 07f7a7c9..00000000
--- a/src/platform/nmp-netns.c
+++ /dev/null
@@ -1,764 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * Copyright (C) 2016 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nmp-netns.h"
-
-#include <fcntl.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <pthread.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"
-
-#define _CLONE_NS_ALL   ((int) (CLONE_NEWNS | CLONE_NEWNET))
-#define _CLONE_NS_ALL_V CLONE_NEWNS, CLONE_NEWNET
-
-static NM_UTILS_FLAGS2STR_DEFINE(_clone_ns_to_str,
-                                 int,
-                                 NM_UTILS_FLAGS2STR(CLONE_NEWNS, "mnt"),
-                                 NM_UTILS_FLAGS2STR(CLONE_NEWNET, "net"), );
-
-static const char *
-__ns_types_to_str(int ns_types, int ns_types_already_set, char *buf, gsize len)
-{
-    const char *b = buf;
-    char        bb[200];
-
-    nm_utils_strbuf_append_c(&buf, &len, '[');
-    if (ns_types & ~ns_types_already_set) {
-        nm_utils_strbuf_append_str(
-            &buf,
-            &len,
-            _clone_ns_to_str(ns_types & ~ns_types_already_set, bb, sizeof(bb)));
-    }
-    if (ns_types & ns_types_already_set) {
-        if (ns_types & ~ns_types_already_set)
-            nm_utils_strbuf_append_c(&buf, &len, '/');
-        nm_utils_strbuf_append_str(
-            &buf,
-            &len,
-            _clone_ns_to_str(ns_types & ns_types_already_set, bb, sizeof(bb)));
-    }
-    nm_utils_strbuf_append_c(&buf, &len, ']');
-    return b;
-}
-#define _ns_types_to_str(ns_types, ns_types_already_set, buf) \
-    __ns_types_to_str(ns_types, ns_types_already_set, buf, sizeof(buf))
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN      LOGD_PLATFORM
-#define _NMLOG_PREFIX_NAME "netns"
-#define _NMLOG(level, netns, ...)                                     \
-    G_STMT_START                                                      \
-    {                                                                 \
-        NMLogLevel _level = (level);                                  \
-                                                                      \
-        if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) {              \
-            NMPNetns *_netns = (netns);                               \
-            char      _sbuf[20];                                      \
-                                                                      \
-            _nm_log(_level,                                           \
-                    _NMLOG_DOMAIN,                                    \
-                    0,                                                \
-                    NULL,                                             \
-                    NULL,                                             \
-                    "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),      \
-                    _NMLOG_PREFIX_NAME,                               \
-                    (_netns ? nm_sprintf_buf(_sbuf, "[%p]", _netns)   \
-                            : "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
-        }                                                             \
-    }                                                                 \
-    G_STMT_END
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_FD_NET, PROP_FD_MNT, );
-
-typedef struct {
-    int fd_net;
-    int fd_mnt;
-} NMPNetnsPrivate;
-
-struct _NMPNetns {
-    GObject         parent;
-    NMPNetnsPrivate _priv;
-};
-
-struct _NMPNetnsClass {
-    GObjectClass parent;
-};
-
-G_DEFINE_TYPE(NMPNetns, nmp_netns, G_TYPE_OBJECT);
-
-#define NMP_NETNS_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMPNetns, NMP_IS_NETNS)
-
-/*****************************************************************************/
-
-typedef struct {
-    NMPNetns *netns;
-    int       count;
-    int       ns_types;
-} NetnsInfo;
-
-static void      _stack_push(GArray *netns_stack, NMPNetns *netns, int ns_types);
-static NMPNetns *_netns_new(GError **error);
-
-/*****************************************************************************/
-
-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
-_netns_stack_clear_cb(gpointer data)
-{
-    NetnsInfo *info = data;
-
-    nm_assert(NMP_IS_NETNS(info->netns));
-    g_object_unref(info->netns);
-}
-
-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) {
-        _LOGE(NULL, "failed to create initial netns: %s", error->message);
-        return s;
-    }
-
-    /* we leak this instance inside the stack. */
-    _stack_push(s, netns, _CLONE_NS_ALL);
-
-    /* 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 _netns_stack_get()                \
-    ({                                    \
-        GArray *_s = _netns_stack;        \
-                                          \
-        if (G_UNLIKELY(!_s))              \
-            _s = _netns_stack_get_impl(); \
-        _s;                               \
-    })
-
-/*****************************************************************************/
-
-static NMPNetns *
-_stack_current_netns(GArray *netns_stack, int ns_types)
-{
-    guint j;
-
-    nm_assert(netns_stack && netns_stack->len > 0);
-
-    /* we search the stack top-down to find the netns that has
-     * all @ns_types set. */
-    for (j = netns_stack->len; ns_types && j >= 1;) {
-        NetnsInfo *info;
-
-        info = &g_array_index(netns_stack, NetnsInfo, --j);
-
-        if (NM_FLAGS_ALL(info->ns_types, ns_types))
-            return info->netns;
-    }
-
-    g_return_val_if_reached(NULL);
-}
-
-static int
-_stack_current_ns_types(GArray *netns_stack, NMPNetns *netns, int ns_types)
-{
-    const int ns_types_check[] = {_CLONE_NS_ALL_V};
-    guint     i, j;
-    int       res = 0;
-
-    nm_assert(netns);
-    nm_assert(netns_stack && netns_stack->len > 0);
-
-    /* we search the stack top-down to check which of @ns_types
-     * are already set to @netns. */
-    for (j = netns_stack->len; ns_types && j >= 1;) {
-        NetnsInfo *info;
-
-        info = &g_array_index(netns_stack, NetnsInfo, --j);
-        if (info->netns != netns) {
-            ns_types = NM_FLAGS_UNSET(ns_types, info->ns_types);
-            continue;
-        }
-
-        for (i = 0; i < G_N_ELEMENTS(ns_types_check); i++) {
-            if (NM_FLAGS_ANY(ns_types, ns_types_check[i])
-                && NM_FLAGS_ANY(info->ns_types, ns_types_check[i])) {
-                res      = NM_FLAGS_SET(res, ns_types_check[i]);
-                ns_types = NM_FLAGS_UNSET(ns_types, ns_types_check[i]);
-            }
-        }
-    }
-
-    return res;
-}
-
-static NetnsInfo *
-_stack_peek(GArray *netns_stack)
-{
-    if (netns_stack->len > 0)
-        return &g_array_index(netns_stack, NetnsInfo, (netns_stack->len - 1));
-    return NULL;
-}
-
-static NetnsInfo *
-_stack_bottom(GArray *netns_stack)
-{
-    if (netns_stack->len > 0)
-        return &g_array_index(netns_stack, NetnsInfo, 0);
-    return NULL;
-}
-
-static void
-_stack_push(GArray *netns_stack, NMPNetns *netns, int ns_types)
-{
-    NetnsInfo *info;
-
-    nm_assert(netns_stack);
-    nm_assert(NMP_IS_NETNS(netns));
-    nm_assert(NM_FLAGS_ANY(ns_types, _CLONE_NS_ALL));
-    nm_assert(!NM_FLAGS_ANY(ns_types, ~_CLONE_NS_ALL));
-
-    g_array_set_size(netns_stack, netns_stack->len + 1);
-
-    info  = &g_array_index(netns_stack, NetnsInfo, (netns_stack->len - 1));
-    *info = (NetnsInfo){
-        .netns    = g_object_ref(netns),
-        .ns_types = ns_types,
-        .count    = 1,
-    };
-}
-
-static void
-_stack_pop(GArray *netns_stack)
-{
-    NetnsInfo *info;
-
-    nm_assert(netns_stack);
-    nm_assert(netns_stack->len > 1);
-
-    info = &g_array_index(netns_stack, NetnsInfo, (netns_stack->len - 1));
-
-    nm_assert(NMP_IS_NETNS(info->netns));
-    nm_assert(info->count == 1);
-
-    g_array_set_size(netns_stack, netns_stack->len - 1);
-}
-
-static guint
-_stack_size(GArray *netns_stack)
-{
-    nm_assert(netns_stack);
-
-    return netns_stack->len;
-}
-
-/*****************************************************************************/
-
-static NMPNetns *
-_netns_new(GError **error)
-{
-    NMPNetns *self;
-    int       fd_net, fd_mnt;
-    int       errsv;
-
-    fd_net = open(PROC_SELF_NS_NET, O_RDONLY | O_CLOEXEC);
-    if (fd_net == -1) {
-        errsv = errno;
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    "Failed opening netns: %s",
-                    nm_strerror_native(errsv));
-        errno = errsv;
-        return NULL;
-    }
-
-    fd_mnt = open(PROC_SELF_NS_MNT, O_RDONLY | O_CLOEXEC);
-    if (fd_mnt == -1) {
-        errsv = errno;
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    "Failed opening mntns: %s",
-                    nm_strerror_native(errsv));
-        nm_close(fd_net);
-        errno = errsv;
-        return NULL;
-    }
-
-    self = g_object_new(NMP_TYPE_NETNS, NMP_NETNS_FD_NET, fd_net, NMP_NETNS_FD_MNT, fd_mnt, NULL);
-
-    _LOGD(self, "new netns (net:%d, mnt:%d)", fd_net, fd_mnt);
-
-    return self;
-}
-
-static int
-_setns(NMPNetns *self, int type)
-{
-    char             buf[100];
-    int              fd;
-    NMPNetnsPrivate *priv = NMP_NETNS_GET_PRIVATE(self);
-
-    nm_assert(NM_IN_SET(type, _CLONE_NS_ALL_V));
-
-    fd = (type == CLONE_NEWNET) ? priv->fd_net : priv->fd_mnt;
-
-    _LOGt(self, "set netns(%s, %d)", _ns_types_to_str(type, 0, buf), fd);
-
-    return setns(fd, type);
-}
-
-static gboolean
-_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(netns_stack, self, CLONE_NEWNET)
-        && _setns(self, CLONE_NEWNET) != 0) {
-        errsv = errno;
-        _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(netns_stack, self, CLONE_NEWNS)
-        && _setns(self, CLONE_NEWNS) != 0) {
-        errsv = errno;
-        _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(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", nm_strerror_native(errsv));
-            }
-        }
-        return FALSE;
-    }
-
-    return TRUE;
-}
-
-static gboolean
-_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(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", nm_strerror_native(errsv));
-            success = FALSE;
-        }
-    }
-    if (NM_FLAGS_HAS(ns_types, 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", nm_strerror_native(errsv));
-            success = FALSE;
-        }
-    }
-
-    return success;
-}
-
-/*****************************************************************************/
-
-int
-nmp_netns_get_fd_net(NMPNetns *self)
-{
-    g_return_val_if_fail(NMP_IS_NETNS(self), 0);
-
-    return NMP_NETNS_GET_PRIVATE(self)->fd_net;
-}
-
-int
-nmp_netns_get_fd_mnt(NMPNetns *self)
-{
-    g_return_val_if_fail(NMP_IS_NETNS(self), 0);
-
-    return NMP_NETNS_GET_PRIVATE(self)->fd_mnt;
-}
-
-/*****************************************************************************/
-
-static gboolean
-_nmp_netns_push_type(NMPNetns *self, int ns_types)
-{
-    GArray *   netns_stack = _netns_stack_get();
-    NetnsInfo *info;
-    char       sbuf[100];
-
-    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(netns_stack) - 1,
-              _ns_types_to_str(ns_types, ns_types, sbuf),
-              info->count);
-        return TRUE;
-    }
-
-    _LOGD(self,
-          "push#%u %s",
-          _stack_size(netns_stack),
-          _ns_types_to_str(ns_types, _stack_current_ns_types(netns_stack, self, ns_types), sbuf));
-
-    if (!_netns_switch_push(netns_stack, self, ns_types))
-        return FALSE;
-
-    _stack_push(netns_stack, self, ns_types);
-    return TRUE;
-}
-
-gboolean
-nmp_netns_push(NMPNetns *self)
-{
-    g_return_val_if_fail(NMP_IS_NETNS(self), FALSE);
-
-    return _nmp_netns_push_type(self, _CLONE_NS_ALL);
-}
-
-gboolean
-nmp_netns_push_type(NMPNetns *self, int ns_types)
-{
-    g_return_val_if_fail(NMP_IS_NETNS(self), FALSE);
-    g_return_val_if_fail(!NM_FLAGS_ANY(ns_types, ~_CLONE_NS_ALL), FALSE);
-
-    return _nmp_netns_push_type(self, ns_types == 0 ? _CLONE_NS_ALL : ns_types);
-}
-
-NMPNetns *
-nmp_netns_new(void)
-{
-    GArray *      netns_stack = _netns_stack_get();
-    NMPNetns *    self;
-    int           errsv;
-    GError *      error      = NULL;
-    unsigned long mountflags = 0;
-
-    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;
-        return NULL;
-    }
-
-    if (unshare(_CLONE_NS_ALL) != 0) {
-        errsv = errno;
-        _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", nm_strerror_native(errsv));
-        goto err_out;
-    }
-
-    if (umount2("/sys", MNT_DETACH) != 0) {
-        errsv = errno;
-        _LOGE(NULL, "failed umount /sys: %s", nm_strerror_native(errsv));
-        goto err_out;
-    }
-
-    if (access("/sys", W_OK) == -1)
-        mountflags = MS_RDONLY;
-
-    if (mount("sysfs", "/sys", "sysfs", mountflags, NULL) != 0) {
-        errsv = errno;
-        _LOGE(NULL, "failed mount /sys: %s", nm_strerror_native(errsv));
-        goto err_out;
-    }
-
-    self = _netns_new(&error);
-    if (!self) {
-        errsv = errno;
-        _LOGE(NULL, "failed to create netns after unshare: %s", error->message);
-        g_clear_error(&error);
-        goto err_out;
-    }
-
-    _stack_push(netns_stack, self, _CLONE_NS_ALL);
-
-    return self;
-err_out:
-    _netns_switch_pop(netns_stack, NULL, _CLONE_NS_ALL);
-    errno = errsv;
-    return NULL;
-}
-
-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);
-
-    info = _stack_peek(netns_stack);
-
-    g_return_val_if_fail(info, FALSE);
-    g_return_val_if_fail(info->netns == self, FALSE);
-
-    if (info->count > 1) {
-        info->count--;
-        _LOGt(self, "pop#%u* (decrease count to %d)", _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(netns_stack) > 1, FALSE);
-
-    _LOGD(self, "pop#%u", _stack_size(netns_stack) - 1);
-
-    ns_types = info->ns_types;
-
-    _stack_pop(netns_stack);
-
-    return _netns_switch_pop(netns_stack, self, ns_types);
-}
-
-NMPNetns *
-nmp_netns_get_current(void)
-{
-    return _netns_get(_stack_peek(_netns_stack_get()));
-}
-
-NMPNetns *
-nmp_netns_get_initial(void)
-{
-    return _netns_get(_stack_bottom(_netns_stack_get()));
-}
-
-gboolean
-nmp_netns_is_initial(void)
-{
-    GArray *netns_stack = _netns_stack_get();
-
-    return (_netns_get(_stack_peek(netns_stack)) == _netns_get(_stack_bottom(netns_stack)));
-}
-
-/*****************************************************************************/
-
-gboolean
-nmp_netns_bind_to_path(NMPNetns *self, const char *filename, int *out_fd)
-{
-    gs_free char *    dirname = NULL;
-    int               errsv;
-    int               fd;
-    nm_auto_pop_netns NMPNetns *netns_pop = NULL;
-
-    g_return_val_if_fail(NMP_IS_NETNS(self), FALSE);
-    g_return_val_if_fail(filename && filename[0] == '/', FALSE);
-
-    if (!nmp_netns_push_type(self, CLONE_NEWNET))
-        return FALSE;
-    netns_pop = self;
-
-    dirname = g_path_get_dirname(filename);
-    if (mkdir(dirname, 0) != 0) {
-        errsv = errno;
-        if (errsv != EEXIST) {
-            _LOGE(self,
-                  "bind: failed to create directory %s: %s",
-                  dirname,
-                  nm_strerror_native(errsv));
-            return FALSE;
-        }
-    }
-
-    if ((fd = creat(filename, S_IRUSR | S_IRGRP | S_IROTH)) == -1) {
-        errsv = errno;
-        _LOGE(self, "bind: failed to create %s: %s", filename, nm_strerror_native(errsv));
-        return FALSE;
-    }
-    nm_close(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,
-              nm_strerror_native(errsv));
-        unlink(filename);
-        return FALSE;
-    }
-
-    if (out_fd) {
-        if ((fd = open(filename, O_RDONLY | O_CLOEXEC)) == -1) {
-            errsv = errno;
-            _LOGE(self, "bind: failed to open %s: %s", filename, nm_strerror_native(errsv));
-            umount2(filename, MNT_DETACH);
-            unlink(filename);
-            return FALSE;
-        }
-        *out_fd = fd;
-    }
-
-    return TRUE;
-}
-
-gboolean
-nmp_netns_bind_to_path_destroy(NMPNetns *self, const char *filename)
-{
-    int errsv;
-
-    g_return_val_if_fail(NMP_IS_NETNS(self), FALSE);
-    g_return_val_if_fail(filename && filename[0] == '/', FALSE);
-
-    if (umount2(filename, MNT_DETACH) != 0) {
-        errsv = errno;
-        _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, nm_strerror_native(errsv));
-        return FALSE;
-    }
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-static void
-set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
-{
-    NMPNetns *       self = NMP_NETNS(object);
-    NMPNetnsPrivate *priv = NMP_NETNS_GET_PRIVATE(self);
-
-    switch (prop_id) {
-    case PROP_FD_NET:
-        /* construct-only */
-        priv->fd_net = g_value_get_int(value);
-        g_return_if_fail(priv->fd_net > 0);
-        break;
-    case PROP_FD_MNT:
-        /* construct-only */
-        priv->fd_mnt = g_value_get_int(value);
-        g_return_if_fail(priv->fd_mnt > 0);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-static void
-nmp_netns_init(NMPNetns *self)
-{}
-
-static void
-dispose(GObject *object)
-{
-    NMPNetns *       self = NMP_NETNS(object);
-    NMPNetnsPrivate *priv = NMP_NETNS_GET_PRIVATE(self);
-
-    nm_close(priv->fd_net);
-    priv->fd_net = -1;
-
-    nm_close(priv->fd_mnt);
-    priv->fd_mnt = -1;
-
-    G_OBJECT_CLASS(nmp_netns_parent_class)->dispose(object);
-}
-
-static void
-nmp_netns_class_init(NMPNetnsClass *klass)
-{
-    GObjectClass *object_class = G_OBJECT_CLASS(klass);
-
-    object_class->set_property = set_property;
-    object_class->dispose      = dispose;
-
-    obj_properties[PROP_FD_NET] =
-        g_param_spec_int(NMP_NETNS_FD_NET,
-                         "",
-                         "",
-                         0,
-                         G_MAXINT,
-                         0,
-                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    obj_properties[PROP_FD_MNT] =
-        g_param_spec_int(NMP_NETNS_FD_MNT,
-                         "",
-                         "",
-                         0,
-                         G_MAXINT,
-                         0,
-                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-}
diff --git a/src/platform/nmp-netns.h b/src/platform/nmp-netns.h
deleted file mode 100644
index 2516a79e..00000000
--- a/src/platform/nmp-netns.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/*
- * Copyright (C) 2016 Red Hat, Inc.
- */
-
-#ifndef __NMP_NETNS_UTILS_H__
-#define __NMP_NETNS_UTILS_H__
-
-/*****************************************************************************/
-
-#define NMP_TYPE_NETNS            (nmp_netns_get_type())
-#define NMP_NETNS(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), NMP_TYPE_NETNS, NMPNetns))
-#define NMP_NETNS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), NMP_TYPE_NETNS, NMPNetnsClass))
-#define NMP_IS_NETNS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMP_TYPE_NETNS))
-#define NMP_IS_NETNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMP_TYPE_NETNS))
-#define NMP_NETNS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), NMP_TYPE_NETNS, NMPNetnsClass))
-
-#define NMP_NETNS_FD_NET "fd-net"
-#define NMP_NETNS_FD_MNT "fd-mnt"
-
-typedef struct _NMPNetnsClass NMPNetnsClass;
-
-GType nmp_netns_get_type(void);
-
-NMPNetns *nmp_netns_new(void);
-
-gboolean nmp_netns_push(NMPNetns *self);
-gboolean nmp_netns_push_type(NMPNetns *self, int ns_types);
-gboolean nmp_netns_pop(NMPNetns *self);
-
-NMPNetns *nmp_netns_get_current(void);
-NMPNetns *nmp_netns_get_initial(void);
-gboolean  nmp_netns_is_initial(void);
-
-int nmp_netns_get_fd_net(NMPNetns *self);
-int nmp_netns_get_fd_mnt(NMPNetns *self);
-
-static inline void
-_nm_auto_pop_netns(NMPNetns **p)
-{
-    if (*p) {
-        int errsv = errno;
-
-        nmp_netns_pop(*p);
-        errno = errsv;
-    }
-}
-#define nm_auto_pop_netns nm_auto(_nm_auto_pop_netns)
-
-gboolean nmp_netns_bind_to_path(NMPNetns *self, const char *filename, int *out_fd);
-gboolean nmp_netns_bind_to_path_destroy(NMPNetns *self, const char *filename);
-
-#endif /* __NMP_NETNS_UTILS_H__ */
diff --git a/src/settings/plugins/ifcfg-rh/tests/meson.build b/src/settings/plugins/ifcfg-rh/tests/meson.build
deleted file mode 100644
index 1ce61c90..00000000
--- a/src/settings/plugins/ifcfg-rh/tests/meson.build
+++ /dev/null
@@ -1,18 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-test_unit = 'test-ifcfg-rh'
-
-exe = executable(
-  test_unit,
-  test_unit + '.c',
-  dependencies: libnetwork_manager_test_dep,
-  c_args: test_c_flags,
-  link_with: libnms_ifcfg_rh_core,
-)
-
-test(
-  'ifcfg-rh/' + test_unit,
-  test_script,
-  timeout: 90,
-  args: test_args + [exe.full_path()],
-)
diff --git a/src/settings/plugins/ifupdown/tests/meson.build b/src/settings/plugins/ifupdown/tests/meson.build
deleted file mode 100644
index a39e4e21..00000000
--- a/src/settings/plugins/ifupdown/tests/meson.build
+++ /dev/null
@@ -1,17 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-test_unit = 'test-ifupdown'
-
-exe = executable(
-  test_unit,
-  test_unit + '.c',
-  dependencies: libnetwork_manager_test_dep,
-  c_args: test_c_flags,
-  link_with: libnms_ifupdown_core,
-)
-
-test(
-  'ifupdown/' + test_unit,
-  test_script,
-  args: test_args + [exe.full_path()],
-)
diff --git a/src/settings/plugins/keyfile/tests/meson.build b/src/settings/plugins/keyfile/tests/meson.build
deleted file mode 100644
index cd1dc3aa..00000000
--- a/src/settings/plugins/keyfile/tests/meson.build
+++ /dev/null
@@ -1,17 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-test_unit = 'test-keyfile-settings'
-
-exe = executable(
-  test_unit,
-  test_unit + '.c',
-  dependencies: libnetwork_manager_test_dep,
-  c_args: test_c_flags,
-)
-
-test(
-  test_unit,
-  test_script,
-  args: test_args + [exe.full_path()],
-  timeout: default_test_timeout,
-)
diff --git a/src/systemd/meson.build b/src/systemd/meson.build
deleted file mode 100644
index 9b87d772..00000000
--- a/src/systemd/meson.build
+++ /dev/null
@@ -1,50 +0,0 @@
-# SPDX-License-Identifier: LGPL-2.1+
-
-sources = files(
-  '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',
-  'nm-sd.c',
-  'nm-sd-utils-core.c',
-  'nm-sd-utils-dhcp.c',
-  'sd-adapt-core/nm-sd-adapt-core.c',
-)
-
-incs = include_directories(
-  'sd-adapt-core',
-  'src/libsystemd-network',
-  'src/libsystemd/sd-event',
-  'src/systemd',
-)
-
-deps = [
-  daemon_nm_default_dep,
-  libnm_systemd_shared_dep,
-]
-
-libnm_systemd_core = static_library(
-  'nm-systemd-core',
-  sources: sources,
-  include_directories: incs,
-  dependencies: deps,
-  c_args: '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD',
-  link_with: libc_siphash,
-)
diff --git a/src/systemd/src/libsystemd-network/network-internal.c b/src/systemd/src/libsystemd-network/network-internal.c
deleted file mode 100644
index 6a7382ac..00000000
--- a/src/systemd/src/libsystemd-network/network-internal.c
+++ /dev/null
@@ -1,890 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt-core.h"
-
-#include <arpa/inet.h>
-#include <linux/if.h>
-#include <netinet/ether.h>
-
-#include "sd-id128.h"
-#include "sd-ndisc.h"
-
-#include "alloc-util.h"
-#include "arphrd-list.h"
-#include "condition.h"
-#include "conf-parser.h"
-#include "device-util.h"
-#include "dhcp-lease-internal.h"
-#include "env-util.h"
-#include "ether-addr-util.h"
-#include "hexdecoct.h"
-#include "log.h"
-#include "network-internal.h"
-#include "parse-util.h"
-#include "siphash24.h"
-#include "socket-util.h"
-#include "string-table.h"
-#include "string-util.h"
-#include "strv.h"
-#include "utf8.h"
-#include "util.h"
-
-#if 0 /* NM_IGNORED */
-const char *net_get_name_persistent(sd_device *device) {
-        const char *name, *field;
-
-        assert(device);
-
-        /* fetch some persistent data unique (on this machine) to this device */
-        FOREACH_STRING(field, "ID_NET_NAME_ONBOARD", "ID_NET_NAME_SLOT", "ID_NET_NAME_PATH", "ID_NET_NAME_MAC")
-                if (sd_device_get_property_value(device, field, &name) >= 0)
-                        return name;
-
-        return NULL;
-}
-
-#define HASH_KEY SD_ID128_MAKE(d3,1e,48,fa,90,fe,4b,4c,9d,af,d5,d7,a1,b1,2e,8a)
-
-int net_get_unique_predictable_data(sd_device *device, bool use_sysname, uint64_t *result) {
-        size_t l, sz = 0;
-        const char *name;
-        int r;
-        uint8_t *v;
-
-        assert(device);
-
-        /* net_get_name_persistent() will return one of the device names based on stable information about
-         * the device. If this is not available, we fall back to using the actual device name. */
-        name = net_get_name_persistent(device);
-        if (!name && use_sysname)
-                (void) sd_device_get_sysname(device, &name);
-        if (!name)
-                return log_device_debug_errno(device, SYNTHETIC_ERRNO(ENODATA),
-                                              "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 = newa(uint8_t, sz);
-
-        /* 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. */
-        *result = htole64(siphash24(v, sz, HASH_KEY.bytes));
-        return 0;
-}
-
-static bool net_condition_test_strv(char * const *patterns, const char *string) {
-        char * const *p;
-        bool match = false, has_positive_rule = false;
-
-        if (strv_isempty(patterns))
-                return true;
-
-        STRV_FOREACH(p, patterns) {
-                const char *q = *p;
-                bool invert;
-
-                invert = *q == '!';
-                q += invert;
-
-                if (!invert)
-                        has_positive_rule = true;
-
-                if (string && fnmatch(q, string, 0) == 0) {
-                        if (invert)
-                                return false;
-                        else
-                                match = true;
-                }
-        }
-
-        return has_positive_rule ? match : true;
-}
-
-static bool net_condition_test_ifname(char * const *patterns, const char *ifname, char * const *alternative_names) {
-        if (net_condition_test_strv(patterns, ifname))
-                return true;
-
-        char * const *p;
-        STRV_FOREACH(p, alternative_names)
-                if (net_condition_test_strv(patterns, *p))
-                        return true;
-
-        return false;
-}
-
-static int net_condition_test_property(char * const *match_property, sd_device *device) {
-        char * const *p;
-
-        if (strv_isempty(match_property))
-                return true;
-
-        STRV_FOREACH(p, match_property) {
-                _cleanup_free_ char *key = NULL;
-                const char *val, *dev_val;
-                bool invert, v;
-
-                invert = **p == '!';
-
-                val = strchr(*p + invert, '=');
-                if (!val)
-                        return -EINVAL;
-
-                key = strndup(*p + invert, val - *p - invert);
-                if (!key)
-                        return -ENOMEM;
-
-                val++;
-
-                v = device &&
-                        sd_device_get_property_value(device, key, &dev_val) >= 0 &&
-                        fnmatch(val, dev_val, 0) == 0;
-
-                if (invert ? v : !v)
-                        return false;
-        }
-
-        return true;
-}
-
-static const char *const wifi_iftype_table[NL80211_IFTYPE_MAX+1] = {
-        [NL80211_IFTYPE_ADHOC] = "ad-hoc",
-        [NL80211_IFTYPE_STATION] = "station",
-        [NL80211_IFTYPE_AP] = "ap",
-        [NL80211_IFTYPE_AP_VLAN] = "ap-vlan",
-        [NL80211_IFTYPE_WDS] = "wds",
-        [NL80211_IFTYPE_MONITOR] = "monitor",
-        [NL80211_IFTYPE_MESH_POINT] = "mesh-point",
-        [NL80211_IFTYPE_P2P_CLIENT] = "p2p-client",
-        [NL80211_IFTYPE_P2P_GO] = "p2p-go",
-        [NL80211_IFTYPE_P2P_DEVICE] = "p2p-device",
-        [NL80211_IFTYPE_OCB] = "ocb",
-        [NL80211_IFTYPE_NAN] = "nan",
-};
-
-DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(wifi_iftype, enum nl80211_iftype);
-
-char *link_get_type_string(unsigned short iftype, sd_device *device) {
-        const char *t, *devtype;
-        char *p;
-
-        if (device &&
-            sd_device_get_devtype(device, &devtype) >= 0 &&
-            !isempty(devtype))
-                return strdup(devtype);
-
-        t = arphrd_to_name(iftype);
-        if (!t)
-                return NULL;
-
-        p = strdup(t);
-        if (!p)
-                return NULL;
-
-        ascii_strlower(p);
-        return p;
-}
-
-bool net_match_config(Set *match_mac,
-                      Set *match_permanent_mac,
-                      char * const *match_paths,
-                      char * const *match_drivers,
-                      char * const *match_iftypes,
-                      char * const *match_names,
-                      char * const *match_property,
-                      char * const *match_wifi_iftype,
-                      char * const *match_ssid,
-                      Set *match_bssid,
-                      sd_device *device,
-                      const struct ether_addr *dev_mac,
-                      const struct ether_addr *dev_permanent_mac,
-                      const char *dev_driver,
-                      unsigned short dev_iftype,
-                      const char *dev_name,
-                      char * const *alternative_names,
-                      enum nl80211_iftype dev_wifi_iftype,
-                      const char *dev_ssid,
-                      const struct ether_addr *dev_bssid) {
-
-        _cleanup_free_ char *dev_iftype_str;
-        const char *dev_path = NULL;
-
-        dev_iftype_str = link_get_type_string(dev_iftype, device);
-
-        if (device) {
-                const char *mac_str;
-
-                (void) sd_device_get_property_value(device, "ID_PATH", &dev_path);
-                if (!dev_driver)
-                        (void) sd_device_get_property_value(device, "ID_NET_DRIVER", &dev_driver);
-                if (!dev_name)
-                        (void) sd_device_get_sysname(device, &dev_name);
-                if (!dev_mac &&
-                    sd_device_get_sysattr_value(device, "address", &mac_str) >= 0)
-                        dev_mac = ether_aton(mac_str);
-        }
-
-        if (match_mac && (!dev_mac || !set_contains(match_mac, dev_mac)))
-                return false;
-
-        if (match_permanent_mac &&
-            (!dev_permanent_mac ||
-             ether_addr_is_null(dev_permanent_mac) ||
-             !set_contains(match_permanent_mac, dev_permanent_mac)))
-                return false;
-
-        if (!net_condition_test_strv(match_paths, dev_path))
-                return false;
-
-        if (!net_condition_test_strv(match_drivers, dev_driver))
-                return false;
-
-        if (!net_condition_test_strv(match_iftypes, dev_iftype_str))
-                return false;
-
-        if (!net_condition_test_ifname(match_names, dev_name, alternative_names))
-                return false;
-
-        if (!net_condition_test_property(match_property, device))
-                return false;
-
-        if (!net_condition_test_strv(match_wifi_iftype, wifi_iftype_to_string(dev_wifi_iftype)))
-                return false;
-
-        if (!net_condition_test_strv(match_ssid, dev_ssid))
-                return false;
-
-        if (match_bssid && (!dev_bssid || !set_contains(match_bssid, dev_bssid)))
-                return false;
-
-        return true;
-}
-
-int config_parse_net_condition(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) {
-
-        ConditionType cond = ltype;
-        Condition **list = data, *c;
-        bool negate;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        if (isempty(rvalue)) {
-                *list = condition_free_list_type(*list, cond);
-                return 0;
-        }
-
-        negate = rvalue[0] == '!';
-        if (negate)
-                rvalue++;
-
-        c = condition_new(cond, rvalue, false, negate);
-        if (!c)
-                return log_oom();
-
-        /* Drop previous assignment. */
-        *list = condition_free_list_type(*list, cond);
-
-        LIST_PREPEND(conditions, *list, c);
-        return 0;
-}
-
-int config_parse_match_strv(
-                const char *unit,
-                const char *filename,
-                unsigned line,
-                const char *section,
-                unsigned section_line,
-                const char *lvalue,
-                int ltype,
-                const char *rvalue,
-                void *data,
-                void *userdata) {
-
-        const char *p = rvalue;
-        char ***sv = data;
-        bool invert;
-        int r;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        if (isempty(rvalue)) {
-                *sv = strv_free(*sv);
-                return 0;
-        }
-
-        invert = *p == '!';
-        p += invert;
-
-        for (;;) {
-                _cleanup_free_ char *word = NULL, *k = NULL;
-
-                r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
-                if (r == 0)
-                        return 0;
-                if (r == -ENOMEM)
-                        return log_oom();
-                if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
-                        return 0;
-                }
-
-                if (invert) {
-                        k = strjoin("!", word);
-                        if (!k)
-                                return log_oom();
-                } else
-                        k = TAKE_PTR(word);
-
-                r = strv_consume(sv, TAKE_PTR(k));
-                if (r < 0)
-                        return log_oom();
-        }
-}
-
-int config_parse_match_ifnames(
-                const char *unit,
-                const char *filename,
-                unsigned line,
-                const char *section,
-                unsigned section_line,
-                const char *lvalue,
-                int ltype,
-                const char *rvalue,
-                void *data,
-                void *userdata) {
-
-        const char *p = rvalue;
-        char ***sv = data;
-        bool invert;
-        int r;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        invert = *p == '!';
-        p += invert;
-
-        for (;;) {
-                _cleanup_free_ char *word = NULL, *k = NULL;
-
-                r = extract_first_word(&p, &word, NULL, 0);
-                if (r == 0)
-                        return 0;
-                if (r == -ENOMEM)
-                        return log_oom();
-                if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0,
-                                   "Failed to parse interface name list: %s", rvalue);
-                        return 0;
-                }
-
-                if (!ifname_valid_full(word, ltype)) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0,
-                                   "Interface name is not valid or too long, ignoring assignment: %s", word);
-                        continue;
-                }
-
-                if (invert) {
-                        k = strjoin("!", word);
-                        if (!k)
-                                return log_oom();
-                } else
-                        k = TAKE_PTR(word);
-
-                r = strv_consume(sv, TAKE_PTR(k));
-                if (r < 0)
-                        return log_oom();
-        }
-}
-
-int config_parse_match_property(
-                const char *unit,
-                const char *filename,
-                unsigned line,
-                const char *section,
-                unsigned section_line,
-                const char *lvalue,
-                int ltype,
-                const char *rvalue,
-                void *data,
-                void *userdata) {
-
-        const char *p = rvalue;
-        char ***sv = data;
-        bool invert;
-        int r;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        invert = *p == '!';
-        p += invert;
-
-        for (;;) {
-                _cleanup_free_ char *word = NULL, *k = NULL;
-
-                r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
-                if (r == 0)
-                        return 0;
-                if (r == -ENOMEM)
-                        return log_oom();
-                if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0,
-                                   "Invalid syntax, ignoring: %s", rvalue);
-                        return 0;
-                }
-
-                if (!env_assignment_is_valid(word)) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0,
-                                   "Invalid property or value, ignoring assignment: %s", word);
-                        continue;
-                }
-
-                if (invert) {
-                        k = strjoin("!", word);
-                        if (!k)
-                                return log_oom();
-                } else
-                        k = TAKE_PTR(word);
-
-                r = strv_consume(sv, TAKE_PTR(k));
-                if (r < 0)
-                        return log_oom();
-        }
-}
-
-int config_parse_ifalias(const char *unit,
-                         const char *filename,
-                         unsigned line,
-                         const char *section,
-                         unsigned section_line,
-                         const char *lvalue,
-                         int ltype,
-                         const char *rvalue,
-                         void *data,
-                         void *userdata) {
-
-        char **s = data;
-        _cleanup_free_ char *n = NULL;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        n = strdup(rvalue);
-        if (!n)
-                return log_oom();
-
-        if (!ascii_is_valid(n) || strlen(n) >= IFALIASZ) {
-                log_syntax(unit, LOG_ERR, filename, line, 0, "Interface alias is not ASCII clean or is too long, ignoring assignment: %s", rvalue);
-                return 0;
-        }
-
-        if (isempty(n))
-                *s = mfree(*s);
-        else
-                free_and_replace(*s, n);
-
-        return 0;
-}
-
-int config_parse_hwaddr(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) {
-
-        _cleanup_free_ struct ether_addr *n = NULL;
-        struct ether_addr **hwaddr = data;
-        int r;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        n = new0(struct ether_addr, 1);
-        if (!n)
-                return log_oom();
-
-        r = ether_addr_from_string(rvalue, n);
-        if (r < 0) {
-                log_syntax(unit, LOG_ERR, filename, line, r, "Not a valid MAC address, ignoring assignment: %s", rvalue);
-                return 0;
-        }
-
-        free_and_replace(*hwaddr, n);
-
-        return 0;
-}
-
-int config_parse_hwaddrs(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) {
-
-        _cleanup_set_free_free_ Set *s = NULL;
-        const char *p = rvalue;
-        Set **hwaddrs = data;
-        int r;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        if (isempty(rvalue)) {
-                /* Empty assignment resets the list */
-                *hwaddrs = set_free_free(*hwaddrs);
-                return 0;
-        }
-
-        s = set_new(&ether_addr_hash_ops);
-        if (!s)
-                return log_oom();
-
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
-                _cleanup_free_ struct ether_addr *n = NULL;
-
-                r = extract_first_word(&p, &word, NULL, 0);
-                if (r == 0)
-                        break;
-                if (r == -ENOMEM)
-                        return log_oom();
-                if (r < 0) {
-                        log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
-                        return 0;
-                }
-
-                n = new(struct ether_addr, 1);
-                if (!n)
-                        return log_oom();
-
-                r = ether_addr_from_string(word, n);
-                if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0, "Not a valid MAC address, ignoring: %s", word);
-                        continue;
-                }
-
-                r = set_put(s, n);
-                if (r < 0)
-                        return log_oom();
-                if (r > 0)
-                        n = NULL; /* avoid cleanup */
-        }
-
-        r = set_ensure_allocated(hwaddrs, &ether_addr_hash_ops);
-        if (r < 0)
-                return log_oom();
-
-        r = set_move(*hwaddrs, s);
-        if (r < 0)
-                return log_oom();
-
-        return 0;
-}
-
-int config_parse_bridge_port_priority(
-                const char *unit,
-                const char *filename,
-                unsigned line,
-                const char *section,
-                unsigned section_line,
-                const char *lvalue,
-                int ltype,
-                const char *rvalue,
-                void *data,
-                void *userdata) {
-
-        uint16_t i;
-        int r;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(data);
-
-        r = safe_atou16(rvalue, &i);
-        if (r < 0) {
-                log_syntax(unit, LOG_ERR, filename, line, r,
-                           "Failed to parse bridge port priority, ignoring: %s", rvalue);
-                return 0;
-        }
-
-        if (i > LINK_BRIDGE_PORT_PRIORITY_MAX) {
-                log_syntax(unit, LOG_ERR, filename, line, r,
-                           "Bridge port priority is larger than maximum %u, ignoring: %s", LINK_BRIDGE_PORT_PRIORITY_MAX, rvalue);
-                return 0;
-        }
-
-        *((uint16_t *)data) = i;
-
-        return 0;
-}
-#endif /* NM_IGNORED */
-
-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)) {
-        assert(f);
-        assert(addresses);
-
-        size_t count = 0;
-        bool _space = false;
-        if (!with_leading_space)
-                with_leading_space = &_space;
-
-        for (size_t i = 0; i < size; i++) {
-                char sbuf[INET_ADDRSTRLEN];
-
-                if (predicate && !predicate(&addresses[i]))
-                        continue;
-
-                if (*with_leading_space)
-                        fputc(' ', f);
-                fputs(inet_ntop(AF_INET, &addresses[i], sbuf, sizeof(sbuf)), f);
-                count++;
-                *with_leading_space = true;
-        }
-
-        return count;
-}
-
-int deserialize_in_addrs(struct in_addr **ret, const char *string) {
-        _cleanup_free_ struct in_addr *addresses = NULL;
-        int size = 0;
-
-        assert(ret);
-        assert(string);
-
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
-                struct in_addr *new_addresses;
-                int r;
-
-                r = extract_first_word(&string, &word, NULL, 0);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
-
-                new_addresses = reallocarray(addresses, size + 1, sizeof(struct in_addr));
-                if (!new_addresses)
-                        return -ENOMEM;
-                else
-                        addresses = new_addresses;
-
-                r = inet_pton(AF_INET, word, &(addresses[size]));
-                if (r <= 0)
-                        continue;
-
-                size++;
-        }
-
-        *ret = size > 0 ? TAKE_PTR(addresses) : NULL;
-
-        return size;
-}
-
-void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size, bool *with_leading_space) {
-        assert(f);
-        assert(addresses);
-        assert(size);
-
-        bool _space = false;
-        if (!with_leading_space)
-                with_leading_space = &_space;
-
-        for (size_t i = 0; i < size; i++) {
-                char buffer[INET6_ADDRSTRLEN];
-
-                if (*with_leading_space)
-                        fputc(' ', f);
-                fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f);
-                *with_leading_space = true;
-        }
-}
-
-int deserialize_in6_addrs(struct in6_addr **ret, const char *string) {
-        _cleanup_free_ struct in6_addr *addresses = NULL;
-        int size = 0;
-
-        assert(ret);
-        assert(string);
-
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
-                struct in6_addr *new_addresses;
-                int r;
-
-                r = extract_first_word(&string, &word, NULL, 0);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
-
-                new_addresses = reallocarray(addresses, size + 1, sizeof(struct in6_addr));
-                if (!new_addresses)
-                        return -ENOMEM;
-                else
-                        addresses = new_addresses;
-
-                r = inet_pton(AF_INET6, word, &(addresses[size]));
-                if (r <= 0)
-                        continue;
-
-                size++;
-        }
-
-        *ret = TAKE_PTR(addresses);
-
-        return size;
-}
-
-void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) {
-        assert(f);
-        assert(key);
-        assert(routes);
-        assert(size);
-
-        fprintf(f, "%s=", key);
-
-        for (size_t i = 0; i < size; i++) {
-                char sbuf[INET_ADDRSTRLEN];
-                struct in_addr dest, gw;
-                uint8_t length;
-
-                assert_se(sd_dhcp_route_get_destination(routes[i], &dest) >= 0);
-                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_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);
-}
-
-int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string) {
-        _cleanup_free_ struct sd_dhcp_route *routes = NULL;
-        size_t size = 0, allocated = 0;
-
-        assert(ret);
-        assert(ret_size);
-        assert(ret_allocated);
-        assert(string);
-
-         /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */
-        for (;;) {
-                _cleanup_free_ char *word = NULL;
-                char *tok, *tok_end;
-                unsigned n;
-                int r;
-
-                r = extract_first_word(&string, &word, NULL, 0);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
-
-                if (!GREEDY_REALLOC(routes, allocated, size + 1))
-                        return -ENOMEM;
-
-                tok = word;
-
-                /* get the subnet */
-                tok_end = strchr(tok, '/');
-                if (!tok_end)
-                        continue;
-                *tok_end = '\0';
-
-                r = inet_aton(tok, &routes[size].dst_addr);
-                if (r == 0)
-                        continue;
-
-                tok = tok_end + 1;
-
-                /* get the prefixlen */
-                tok_end = strchr(tok, ',');
-                if (!tok_end)
-                        continue;
-
-                *tok_end = '\0';
-
-                r = safe_atou(tok, &n);
-                if (r < 0 || n > 32)
-                        continue;
-
-                routes[size].dst_prefixlen = (uint8_t) n;
-                tok = tok_end + 1;
-
-                /* get the gateway */
-                r = inet_aton(tok, &routes[size].gw_addr);
-                if (r == 0)
-                        continue;
-
-                size++;
-        }
-
-        *ret_size = size;
-        *ret_allocated = allocated;
-        *ret = TAKE_PTR(routes);
-
-        return 0;
-}
-
-int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size) {
-        _cleanup_free_ char *hex_buf = NULL;
-
-        assert(f);
-        assert(key);
-        assert(data);
-
-        hex_buf = hexmem(data, size);
-        if (!hex_buf)
-                return -ENOMEM;
-
-        fprintf(f, "%s=%s\n", key, hex_buf);
-
-        return 0;
-}
diff --git a/src/systemd/src/libsystemd-network/network-internal.h b/src/systemd/src/libsystemd-network/network-internal.h
deleted file mode 100644
index 1959474e..00000000
--- a/src/systemd/src/libsystemd-network/network-internal.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <linux/nl80211.h>
-#include <stdbool.h>
-
-#include "sd-device.h"
-#include "sd-dhcp-lease.h"
-
-#include "conf-parser.h"
-#include "set.h"
-#include "strv.h"
-
-#define LINK_BRIDGE_PORT_PRIORITY_INVALID 128
-#define LINK_BRIDGE_PORT_PRIORITY_MAX 63
-
-#if 0 /* NM_IGNORED */
-bool net_match_config(Set *match_mac,
-                      Set *match_permanent_mac,
-                      char * const *match_paths,
-                      char * const *match_drivers,
-                      char * const *match_iftypes,
-                      char * const *match_names,
-                      char * const *match_property,
-                      char * const *match_wifi_iftype,
-                      char * const *match_ssid,
-                      Set *match_bssid,
-                      sd_device *device,
-                      const struct ether_addr *dev_mac,
-                      const struct ether_addr *dev_permanent_mac,
-                      const char *dev_driver,
-                      unsigned short dev_iftype,
-                      const char *dev_name,
-                      char * const *alternative_names,
-                      enum nl80211_iftype dev_wifi_iftype,
-                      const char *dev_ssid,
-                      const struct ether_addr *dev_bssid);
-
-CONFIG_PARSER_PROTOTYPE(config_parse_net_condition);
-CONFIG_PARSER_PROTOTYPE(config_parse_hwaddr);
-CONFIG_PARSER_PROTOTYPE(config_parse_hwaddrs);
-CONFIG_PARSER_PROTOTYPE(config_parse_match_strv);
-CONFIG_PARSER_PROTOTYPE(config_parse_match_ifnames);
-CONFIG_PARSER_PROTOTYPE(config_parse_match_property);
-CONFIG_PARSER_PROTOTYPE(config_parse_ifalias);
-CONFIG_PARSER_PROTOTYPE(config_parse_bridge_port_priority);
-
-int net_get_unique_predictable_data(sd_device *device, bool use_sysname, uint64_t *result);
-const char *net_get_name_persistent(sd_device *device);
-#endif /* NM_IGNORED */
-
-size_t serialize_in_addrs(FILE *f,
-                          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,
-                         bool *with_leading_space);
-int deserialize_in6_addrs(struct in6_addr **addresses, const char *string);
-
-/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */
-struct sd_dhcp_route;
-struct sd_dhcp_lease;
-
-void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size);
-int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string);
-
-/* 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);
-
-int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file);
-int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file);
diff --git a/src/tests/test-l3cfg.c b/src/tests/test-l3cfg.c
deleted file mode 100644
index d55dcfbc..00000000
--- a/src/tests/test-l3cfg.c
+++ /dev/null
@@ -1,362 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-default.h"
-
-#include "nm-l3cfg.h"
-#include "nm-netns.h"
-#include "platform/nm-platform.h"
-
-#include "platform/tests/test-common.h"
-
-/*****************************************************************************/
-
-typedef struct {
-    int                test_idx;
-    NMPlatform *       platform;
-    NMNetns *          netns;
-    NMDedupMultiIndex *multiidx;
-    const char *       ifname0;
-    const char *       ifname1;
-    NMPLinkAddress     hwaddr0;
-    NMPLinkAddress     hwaddr1;
-    int                ifindex0;
-    int                ifindex1;
-} TestFixture1;
-
-static const TestFixture1 *
-_test_fixture_1_setup(TestFixture1 *f, int test_idx)
-{
-    const NMPlatformLink *l0;
-    const NMPlatformLink *l1;
-
-    g_assert_cmpint(test_idx, >, 0);
-    g_assert_cmpint(f->test_idx, ==, 0);
-
-    f->test_idx = test_idx;
-
-    f->ifname0 = "nm-test-veth0";
-    f->ifname1 = "nm-test-veth1";
-
-    f->platform = g_object_ref(NM_PLATFORM_GET);
-    f->multiidx = nm_dedup_multi_index_ref(nm_platform_get_multi_idx(f->platform));
-    f->netns    = nm_netns_new(f->platform);
-
-    l0 = nmtstp_link_veth_add(f->platform, -1, f->ifname0, f->ifname1);
-    l1 = nmtstp_link_get_typed(f->platform, -1, f->ifname1, NM_LINK_TYPE_VETH);
-
-    f->ifindex0 = l0->ifindex;
-    f->hwaddr0  = l0->l_address;
-
-    f->ifindex1 = l1->ifindex;
-    f->hwaddr1  = l1->l_address;
-
-    g_assert(nm_platform_link_set_up(f->platform, f->ifindex0, NULL));
-    g_assert(nm_platform_link_set_up(f->platform, f->ifindex1, NULL));
-
-    return f;
-}
-
-static void
-_test_fixture_1_teardown(TestFixture1 *f)
-{
-    g_assert(f);
-
-    if (f->test_idx == 0)
-        return;
-
-    _LOGD("test teatdown");
-
-    nmtstp_link_delete(f->platform, -1, f->ifindex0, f->ifname0, TRUE);
-    g_assert(!nm_platform_link_get(f->platform, f->ifindex0));
-    g_assert(!nm_platform_link_get(f->platform, f->ifindex1));
-    g_assert(!nm_platform_link_get_by_ifname(f->platform, f->ifname0));
-    g_assert(!nm_platform_link_get_by_ifname(f->platform, f->ifname1));
-
-    g_object_unref(f->netns);
-    g_object_unref(f->platform);
-    nm_dedup_multi_index_unref(f->multiidx);
-
-    *f = (TestFixture1){
-        .test_idx = 0,
-    };
-}
-
-/*****************************************************************************/
-
-typedef enum {
-    TEST_L3CFG_NOTIFY_TYPE_NONE,
-    TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL,
-    TEST_L3CFG_NOTIFY_TYPE_COMMIT_1,
-    TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1,
-} TestL3cfgNotifyType;
-
-typedef struct {
-    const TestFixture1 *f;
-
-    TestL3cfgNotifyType notify_type;
-    guint               post_commit_event_count;
-    guint               general_event_count;
-    union {
-        struct {
-            int  cb_count;
-            bool expected_probe_result : 1;
-        } wait_for_acd_ready_1;
-    } notify_data;
-} TestL3cfgData;
-
-static void
-_test_l3cfg_data_set_notify_type(TestL3cfgData *tdata, TestL3cfgNotifyType notify_type)
-{
-    g_assert(tdata);
-
-    tdata->notify_type             = notify_type;
-    tdata->post_commit_event_count = 0;
-    tdata->general_event_count     = 0;
-    memset(&tdata->notify_data, 0, sizeof(tdata->notify_data));
-}
-
-static void
-_test_l3cfg_signal_notify(NML3Cfg *                   l3cfg,
-                          const NML3ConfigNotifyData *notify_data,
-                          TestL3cfgData *             tdata)
-{
-    g_assert(NM_IS_L3CFG(l3cfg));
-    g_assert(tdata);
-    g_assert(notify_data);
-    g_assert(_NM_INT_NOT_NEGATIVE(notify_data->notify_type));
-    g_assert(notify_data->notify_type < _NM_L3_CONFIG_NOTIFY_TYPE_NUM);
-
-    if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE)
-        g_assert(notify_data->platform_change_on_idle.obj_type_flags != 0u);
-    else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE) {
-        g_assert(NMP_OBJECT_IS_VALID(notify_data->platform_change.obj));
-        g_assert(notify_data->platform_change.change_type != 0);
-    }
-
-    switch (tdata->notify_type) {
-    case TEST_L3CFG_NOTIFY_TYPE_NONE:
-        g_assert_not_reached();
-        break;
-    case TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL:
-        if (NM_IN_SET(notify_data->notify_type,
-                      NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE,
-                      NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE))
-            return;
-        g_assert_not_reached();
-        return;
-    case TEST_L3CFG_NOTIFY_TYPE_COMMIT_1:
-        g_assert_cmpint(tdata->post_commit_event_count, ==, 0);
-        switch (notify_data->notify_type) {
-        case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT:
-            tdata->post_commit_event_count++;
-            return;
-        case NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED:
-            switch (tdata->f->test_idx) {
-            case 2:
-                nmtst_assert_ip4_address(notify_data->acd_completed.addr, "192.167.133.45");
-                g_assert(notify_data->acd_completed.probe_result);
-                g_assert(tdata->general_event_count == 0);
-                tdata->general_event_count++;
-                return;
-            default:
-                g_assert_not_reached();
-                return;
-            }
-        case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE:
-            return;
-        default:
-            g_assert_not_reached();
-            return;
-        }
-    case TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1:
-        if (NM_IN_SET(notify_data->notify_type,
-                      NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE,
-                      NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE))
-            return;
-        if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED) {
-            g_assert(tdata->notify_data.wait_for_acd_ready_1.cb_count == 0);
-            tdata->notify_data.wait_for_acd_ready_1.cb_count++;
-            return;
-        }
-        if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT) {
-            g_assert(tdata->notify_data.wait_for_acd_ready_1.cb_count == 1);
-            tdata->notify_data.wait_for_acd_ready_1.cb_count++;
-            nmtstp_platform_ip_addresses_assert(tdata->f->platform,
-                                                tdata->f->ifindex0,
-                                                TRUE,
-                                                TRUE,
-                                                TRUE,
-                                                "192.167.133.45",
-                                                "1:2:3:4::45");
-            return;
-        }
-        g_assert_not_reached();
-        return;
-    }
-
-    g_assert_not_reached();
-}
-
-static void
-test_l3cfg(gconstpointer test_data)
-{
-    nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {};
-    const TestFixture1 *                           f;
-    NML3CfgCommitTypeHandle *                      commit_type_1;
-    NML3CfgCommitTypeHandle *                      commit_type_2;
-    gs_unref_object NML3Cfg *l3cfg0                           = NULL;
-    nm_auto_unref_l3cd const NML3ConfigData *l3cd_a           = NULL;
-    guint32                                  acd_timeout_msec = 0;
-    TestL3cfgData                            tdata_stack      = {
-        .f = NULL,
-    };
-    TestL3cfgData *const tdata = &tdata_stack;
-
-    _LOGD("test start (/l3cfg/%d)", GPOINTER_TO_INT(test_data));
-
-    if (nmtst_test_quick()) {
-        gs_free char *msg =
-            g_strdup_printf("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n",
-                            g_get_prgname() ?: "test-l3cfg");
-
-        g_test_skip(msg);
-        return;
-    }
-
-    f = _test_fixture_1_setup(&test_fixture, GPOINTER_TO_INT(test_data));
-
-    tdata->f = f;
-
-    l3cfg0 = nm_netns_access_l3cfg(f->netns, f->ifindex0);
-    g_assert(NM_IS_L3CFG(l3cfg0));
-
-    g_signal_connect(l3cfg0, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_test_l3cfg_signal_notify), tdata);
-
-    commit_type_1 = nm_l3cfg_commit_type_register(l3cfg0, NM_L3_CFG_COMMIT_TYPE_UPDATE, NULL);
-
-    if ((nmtst_get_rand_uint32() % 4u) != 0) {
-        commit_type_2 =
-            nm_l3cfg_commit_type_register(l3cfg0,
-                                          nmtst_rand_select(NM_L3_CFG_COMMIT_TYPE_NONE,
-                                                            NM_L3_CFG_COMMIT_TYPE_ASSUME,
-                                                            NM_L3_CFG_COMMIT_TYPE_UPDATE),
-                                          NULL);
-    } else
-        commit_type_2 = NULL;
-
-    switch (f->test_idx) {
-    case 1:
-        break;
-    case 2:
-    case 3:
-    {
-        nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
-
-        l3cd = nm_l3_config_data_new(f->multiidx, f->ifindex0);
-
-        nm_l3_config_data_add_address_4(
-            l3cd,
-            NM_PLATFORM_IP4_ADDRESS_INIT(.address      = nmtst_inet4_from_string("192.167.133.45"),
-                                         .peer_address = nmtst_inet4_from_string("192.167.133.45"),
-                                         .plen         = 24, ));
-
-        nm_l3_config_data_add_address_6(
-            l3cd,
-            NM_PLATFORM_IP6_ADDRESS_INIT(.address = *nmtst_inet6_from_string("1:2:3:4::45"),
-                                         .plen    = 64, ));
-
-        if (nmtst_get_rand_bool())
-            nm_l3_config_data_seal(l3cd);
-        l3cd_a = g_steal_pointer(&l3cd);
-        break;
-    }
-    }
-
-    acd_timeout_msec = (f->test_idx == 3) ? 2000u : 0u;
-
-    if (l3cd_a) {
-        nm_l3cfg_add_config(l3cfg0,
-                            GINT_TO_POINTER('a'),
-                            nmtst_get_rand_bool(),
-                            l3cd_a,
-                            'a',
-                            0,
-                            0,
-                            NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4,
-                            NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
-                            0,
-                            0,
-                            acd_timeout_msec,
-                            NM_L3_CONFIG_MERGE_FLAGS_NONE);
-    }
-
-    nm_l3_config_data_log(nm_l3cfg_get_combined_l3cd(l3cfg0, FALSE),
-                          "test",
-                          "platform-test: l3cfg0: ",
-                          LOGL_DEBUG,
-                          LOGD_PLATFORM);
-
-    _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_COMMIT_1);
-    nm_l3cfg_commit(l3cfg0, NM_L3_CFG_COMMIT_TYPE_REAPPLY);
-    g_assert_cmpint(tdata->post_commit_event_count, ==, 1);
-    _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE);
-
-    nmtstp_platform_ip_addresses_assert(tdata->f->platform,
-                                        tdata->f->ifindex0,
-                                        TRUE,
-                                        TRUE,
-                                        TRUE,
-                                        NM_IN_SET(f->test_idx, 2) ? "192.167.133.45" : NULL,
-                                        NM_IN_SET(f->test_idx, 2, 3) ? "1:2:3:4::45" : NULL);
-
-    if (NM_IN_SET(f->test_idx, 1, 2)) {
-        _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL);
-        _LOGT("poll 1 start");
-        nmtst_main_context_iterate_until(NULL, nmtst_get_rand_uint32() % 5000u, FALSE);
-        _LOGT("poll 1 end");
-        _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE);
-    }
-
-    if (NM_IN_SET(f->test_idx, 3)) {
-        _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1);
-        tdata->notify_data.wait_for_acd_ready_1.expected_probe_result = TRUE;
-        _LOGT("poll 2 start");
-        nmtst_main_context_iterate_until(NULL, 2500u + (nmtst_get_rand_uint32() % 4000u), FALSE);
-        _LOGT("poll 2 end");
-        g_assert_cmpint(tdata->notify_data.wait_for_acd_ready_1.cb_count, ==, 2);
-        _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE);
-    }
-
-    g_signal_handlers_disconnect_by_func(l3cfg0, G_CALLBACK(_test_l3cfg_signal_notify), tdata);
-
-    nm_l3cfg_commit_type_unregister(l3cfg0, commit_type_1);
-    nm_l3cfg_commit_type_unregister(l3cfg0, commit_type_2);
-
-    if ((nmtst_get_rand_uint32() % 3) == 0)
-        _test_fixture_1_teardown(&test_fixture);
-
-    nm_l3cfg_remove_config_all(l3cfg0, GINT_TO_POINTER('a'), FALSE);
-
-    if ((nmtst_get_rand_uint32() % 3) == 0)
-        _test_fixture_1_teardown(&test_fixture);
-
-    _LOGD("test end (/l3cfg/%d)", f->test_idx);
-}
-
-/*****************************************************************************/
-
-NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup;
-
-void
-_nmtstp_init_tests(int *argc, char ***argv)
-{
-    nmtst_init_with_logging(argc, argv, NULL, "ALL");
-}
-
-void
-_nmtstp_setup_tests(void)
-{
-    g_test_add_data_func("/l3cfg/1", GINT_TO_POINTER(1), test_l3cfg);
-    g_test_add_data_func("/l3cfg/2", GINT_TO_POINTER(2), test_l3cfg);
-    g_test_add_data_func("/l3cfg/3", GINT_TO_POINTER(3), test_l3cfg);
-}