summary refs log tree commit diff
path: root/src/core/devices
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/devices')
-rw-r--r--src/core/devices/adsl/meson.build1
-rw-r--r--src/core/devices/adsl/nm-atm-manager.c4
-rw-r--r--src/core/devices/adsl/nm-device-adsl.c10
-rw-r--r--src/core/devices/bluetooth/meson.build2
-rw-r--r--src/core/devices/bluetooth/nm-bluez-manager.c18
-rw-r--r--src/core/devices/bluetooth/nm-device-bt.c18
-rw-r--r--src/core/devices/nm-acd-manager.c2
-rw-r--r--src/core/devices/nm-device-6lowpan.c2
-rw-r--r--src/core/devices/nm-device-bond.c16
-rw-r--r--src/core/devices/nm-device-bridge.c18
-rw-r--r--src/core/devices/nm-device-dummy.c12
-rw-r--r--src/core/devices/nm-device-ethernet.c86
-rw-r--r--src/core/devices/nm-device-factory.c4
-rw-r--r--src/core/devices/nm-device-generic.c12
-rw-r--r--src/core/devices/nm-device-infiniband.c14
-rw-r--r--src/core/devices/nm-device-ip-tunnel.c62
-rw-r--r--src/core/devices/nm-device-macsec.c66
-rw-r--r--src/core/devices/nm-device-macvlan.c18
-rw-r--r--src/core/devices/nm-device-ppp.c7
-rw-r--r--src/core/devices/nm-device-private.h10
-rw-r--r--src/core/devices/nm-device-tun.c30
-rw-r--r--src/core/devices/nm-device-utils.c356
-rw-r--r--src/core/devices/nm-device-utils.h98
-rw-r--r--src/core/devices/nm-device-veth.c10
-rw-r--r--src/core/devices/nm-device-vlan.c33
-rw-r--r--src/core/devices/nm-device-vrf.c4
-rw-r--r--src/core/devices/nm-device-vxlan.c62
-rw-r--r--src/core/devices/nm-device-wireguard.c22
-rw-r--r--src/core/devices/nm-device-wpan.c4
-rw-r--r--src/core/devices/nm-device.c1392
-rw-r--r--src/core/devices/nm-device.h63
-rw-r--r--src/core/devices/nm-lldp-listener.c402
-rw-r--r--src/core/devices/nm-lldp-listener.h31
-rw-r--r--src/core/devices/ovs/meson.build1
-rw-r--r--src/core/devices/ovs/nm-device-ovs-bridge.c6
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.c5
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.c4
-rw-r--r--src/core/devices/ovs/nm-ovs-factory.c4
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c6
-rw-r--r--src/core/devices/team/meson.build1
-rw-r--r--src/core/devices/team/nm-device-team.c32
-rw-r--r--src/core/devices/team/nm-team-factory.c4
-rw-r--r--src/core/devices/tests/test-acd.c4
-rw-r--r--src/core/devices/tests/test-lldp.c66
-rw-r--r--src/core/devices/wifi/meson.build2
-rw-r--r--src/core/devices/wifi/nm-device-iwd.c191
-rw-r--r--src/core/devices/wifi/nm-device-olpc-mesh.c32
-rw-r--r--src/core/devices/wifi/nm-device-wifi-p2p.c9
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c137
-rw-r--r--src/core/devices/wifi/nm-device-wifi.h2
-rw-r--r--src/core/devices/wifi/nm-iwd-manager.c434
-rw-r--r--src/core/devices/wifi/nm-iwd-manager.h1
-rw-r--r--src/core/devices/wifi/nm-wifi-ap.c153
-rw-r--r--src/core/devices/wifi/nm-wifi-ap.h3
-rw-r--r--src/core/devices/wifi/nm-wifi-common.c38
-rw-r--r--src/core/devices/wifi/nm-wifi-factory.c15
-rw-r--r--src/core/devices/wifi/nm-wifi-p2p-peer.c7
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.c902
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.h9
-rw-r--r--src/core/devices/wifi/tests/test-devices-wifi.c58
-rw-r--r--src/core/devices/wwan/meson.build2
-rw-r--r--src/core/devices/wwan/nm-device-modem.c36
-rw-r--r--src/core/devices/wwan/nm-modem-broadband.c4
-rw-r--r--src/core/devices/wwan/nm-modem-manager.c2
-rw-r--r--src/core/devices/wwan/nm-modem-ofono.c172
-rw-r--r--src/core/devices/wwan/nm-modem-ofono.h2
-rw-r--r--src/core/devices/wwan/nm-modem.c13
-rw-r--r--src/core/devices/wwan/nm-wwan-factory.c2
68 files changed, 3499 insertions, 1749 deletions
diff --git a/src/core/devices/adsl/meson.build b/src/core/devices/adsl/meson.build
index 95f61d95..ef87c6e8 100644
--- a/src/core/devices/adsl/meson.build
+++ b/src/core/devices/adsl/meson.build
@@ -7,7 +7,6 @@ libnm_device_plugin_adsl = shared_module(
     'nm-device-adsl.c',
   ),
   dependencies: core_plugin_dep,
-  c_args: daemon_c_flags,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
diff --git a/src/core/devices/adsl/nm-atm-manager.c b/src/core/devices/adsl/nm-atm-manager.c
index 9be9b5ce..bd9ed8cc 100644
--- a/src/core/devices/adsl/nm-atm-manager.c
+++ b/src/core/devices/adsl/nm-atm-manager.c
@@ -11,8 +11,8 @@
 #include "nm-setting-adsl.h"
 #include "nm-device-adsl.h"
 #include "devices/nm-device-factory.h"
-#include "platform/nm-platform.h"
-#include "nm-udev-aux/nm-udev-utils.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-udev-aux/nm-udev-utils.h"
 
 /*****************************************************************************/
 
diff --git a/src/core/devices/adsl/nm-device-adsl.c b/src/core/devices/adsl/nm-device-adsl.c
index 34c062a8..4e4c9dcc 100644
--- a/src/core/devices/adsl/nm-device-adsl.c
+++ b/src/core/devices/adsl/nm-device-adsl.c
@@ -17,7 +17,7 @@
 
 #include "nm-ip4-config.h"
 #include "devices/nm-device-private.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "ppp/nm-ppp-manager-call.h"
 #include "ppp/nm-ppp-status.h"
 #include "nm-setting-adsl.h"
@@ -265,7 +265,7 @@ pppoe_vcc_config(NMDeviceAdsl *self)
     _LOGD(LOGD_ADSL, "ATM setup successful");
 
     /* otherwise we're good for stage3 */
-    nm_platform_link_set_up(nm_device_get_platform(device), priv->nas_ifindex, NULL);
+    nm_platform_link_change_flags(nm_device_get_platform(device), priv->nas_ifindex, IFF_UP, TRUE);
 
     return TRUE;
 }
@@ -674,12 +674,8 @@ dispose(GObject *object)
 static const NMDBusInterfaceInfoExtended interface_info_device_adsl = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_ADSL,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier",
-                                                             "b",
-                                                             NM_DEVICE_CARRIER), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER), ), ),
 };
 
 static void
diff --git a/src/core/devices/bluetooth/meson.build b/src/core/devices/bluetooth/meson.build
index d5f26068..5b196d08 100644
--- a/src/core/devices/bluetooth/meson.build
+++ b/src/core/devices/bluetooth/meson.build
@@ -8,11 +8,11 @@ libnm_device_plugin_bluetooth_static = static_library(
     'nm-device-bt.c',
   ) + (enable_bluez5_dun ? files('nm-bluez5-dun.c') : files()),
   dependencies: [
+    libnm_core_public_dep,
     core_default_dep,
     libnm_wwan_dep,
     bluez5_dep,
   ],
-  c_args: daemon_c_flags,
 )
 
 libnm_device_plugin_bluetooth_static_dep = declare_dependency(
diff --git a/src/core/devices/bluetooth/nm-bluez-manager.c b/src/core/devices/bluetooth/nm-bluez-manager.c
index 459ea409..fc081580 100644
--- a/src/core/devices/bluetooth/nm-bluez-manager.c
+++ b/src/core/devices/bluetooth/nm-bluez-manager.c
@@ -12,8 +12,9 @@
 #include <gmodule.h>
 #include <linux/if_ether.h>
 
-#include "nm-glib-aux/nm-dbus-aux.h"
-#include "nm-glib-aux/nm-c-list.h"
+#include "libnm-glib-aux/nm-uuid.h"
+#include "libnm-glib-aux/nm-dbus-aux.h"
+#include "libnm-glib-aux/nm-c-list.h"
 #include "nm-dbus-manager.h"
 #include "devices/nm-device-factory.h"
 #include "devices/nm-device-bridge.h"
@@ -23,9 +24,9 @@
 #include "nm-device-bt.h"
 #include "nm-manager.h"
 #include "nm-bluez5-dun.h"
-#include "nm-core-internal.h"
-#include "platform/nm-platform.h"
-#include "nm-std-aux/nm-dbus-compat.h"
+#include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-std-aux/nm-dbus-compat.h"
 
 /*****************************************************************************/
 
@@ -574,11 +575,12 @@ _bzobj_to_string(const BzDBusObj *bzobj, char *buf, gsize len)
 #define _LOG_bzobj(bzobj, context)                           \
     G_STMT_START                                             \
     {                                                        \
-        const BzDBusObj *const _bzobj = (bzobj);             \
+        const BzDBusObj *const _bzobj   = (bzobj);           \
+        const char *const      _context = (context);         \
         char                   _buf[500];                    \
                                                              \
         _LOGT("change %-21s %s : { %s }",                    \
-              (context),                                     \
+              _context,                                      \
               _bzobj->object_path,                           \
               _bzobj_to_string(_bzobj, _buf, sizeof(_buf))); \
     }                                                        \
@@ -1321,7 +1323,7 @@ _conn_create_panu_connection(NMBluezManager *self, BzDBusObj *bzobj)
     char                          uuid[37];
     gs_free_error GError *error = NULL;
 
-    nm_utils_uuid_generate_buf(uuid);
+    nm_uuid_generate_random_str_arr(uuid);
     id = g_strdup_printf(_("%s Network"), bzobj->d_device.name);
 
     connection = nm_simple_connection_new();
diff --git a/src/core/devices/bluetooth/nm-device-bt.c b/src/core/devices/bluetooth/nm-device-bt.c
index c07be2d3..8f4ceba7 100644
--- a/src/core/devices/bluetooth/nm-device-bt.c
+++ b/src/core/devices/bluetooth/nm-device-bt.c
@@ -10,7 +10,7 @@
 #include <stdio.h>
 #include <linux/if_ether.h>
 
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "nm-bluez-common.h"
 #include "nm-bluez-manager.h"
 #include "devices/nm-device-private.h"
@@ -26,7 +26,7 @@
 #include "nm-utils.h"
 #include "nm-bt-error.h"
 #include "nm-ip4-config.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 
 #include "devices/wwan/nm-modem-manager.h"
 #include "devices/wwan/nm-modem.h"
@@ -1317,16 +1317,12 @@ finalize(GObject *object)
 static const NMDBusInterfaceInfoExtended interface_info_device_bluetooth = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_BLUETOOTH,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Name", "s", NM_DEVICE_BT_NAME),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("BtCapabilities",
-                                                             "u",
-                                                             NM_DEVICE_BT_CAPABILITIES), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Name", "s", NM_DEVICE_BT_NAME),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("BtCapabilities",
+                                                           "u",
+                                                           NM_DEVICE_BT_CAPABILITIES), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-acd-manager.c b/src/core/devices/nm-acd-manager.c
index 81a28797..145947bb 100644
--- a/src/core/devices/nm-acd-manager.c
+++ b/src/core/devices/nm-acd-manager.c
@@ -12,7 +12,7 @@
 #include <sys/wait.h>
 #include <linux/if_ether.h>
 
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-utils.h"
 #include "NetworkManagerUtils.h"
 #include "n-acd/src/n-acd.h"
diff --git a/src/core/devices/nm-device-6lowpan.c b/src/core/devices/nm-device-6lowpan.c
index fe116dd4..96218658 100644
--- a/src/core/devices/nm-device-6lowpan.c
+++ b/src/core/devices/nm-device-6lowpan.c
@@ -9,7 +9,7 @@
 
 #include "nm-device-private.h"
 #include "settings/nm-settings.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-6lowpan.h"
 #include "nm-utils.h"
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index 9c1c484f..b208e2c0 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -12,9 +12,9 @@
 
 #include "NetworkManagerUtils.h"
 #include "nm-device-private.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "nm-ip4-config.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceBond
@@ -601,16 +601,10 @@ nm_device_bond_init(NMDeviceBond *self)
 static const NMDBusInterfaceInfoExtended interface_info_device_bond = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_BOND,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier", "b", NM_DEVICE_CARRIER),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Slaves",
-                                                             "ao",
-                                                             NM_DEVICE_SLAVES), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c
index c919d85d..a095dc3e 100644
--- a/src/core/devices/nm-device-bridge.c
+++ b/src/core/devices/nm-device-bridge.c
@@ -12,9 +12,9 @@
 
 #include "NetworkManagerUtils.h"
 #include "nm-device-private.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceBridge
 #include "nm-device-logging.h"
@@ -1033,7 +1033,7 @@ create_and_realize(NMDevice *             device,
     const char *        iface = nm_device_get_iface(device);
     const char *        hwaddr;
     gs_free char *      hwaddr_cloned = NULL;
-    guint8              mac_address[NM_UTILS_HWADDR_LEN_MAX];
+    guint8              mac_address[_NM_UTILS_HWADDR_LEN_MAX];
     NMPlatformLnkBridge props;
     int                 r;
     guint32             mtu = 0;
@@ -1142,16 +1142,10 @@ nm_device_bridge_init(NMDeviceBridge *self)
 static const NMDBusInterfaceInfoExtended interface_info_device_bridge = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_BRIDGE,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier", "b", NM_DEVICE_CARRIER),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Slaves",
-                                                             "ao",
-                                                             NM_DEVICE_SLAVES), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-dummy.c b/src/core/devices/nm-device-dummy.c
index 13cfd3b0..488f0ba0 100644
--- a/src/core/devices/nm-device-dummy.c
+++ b/src/core/devices/nm-device-dummy.c
@@ -13,10 +13,10 @@
 #include "nm-act-request.h"
 #include "nm-device-private.h"
 #include "nm-ip4-config.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-dummy.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceDummy
 #include "nm-device-logging.h"
@@ -121,12 +121,10 @@ nm_device_dummy_init(NMDeviceDummy *self)
 static const NMDBusInterfaceInfoExtended interface_info_device_dummy = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_DUMMY,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress",
+                                                           "s",
+                                                           NM_DEVICE_HW_ADDRESS), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c
index 59f6c613..95336c7a 100644
--- a/src/core/devices/nm-device-ethernet.c
+++ b/src/core/devices/nm-device-ethernet.c
@@ -14,6 +14,7 @@
 #include <libudev.h>
 #include <linux/if_ether.h>
 
+#include "libnm-glib-aux/nm-uuid.h"
 #include "nm-device-private.h"
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
@@ -24,17 +25,17 @@
 #include "ppp/nm-ppp-manager.h"
 #include "ppp/nm-ppp-manager-call.h"
 #include "ppp/nm-ppp-status.h"
-#include "platform/nm-platform.h"
-#include "nm-platform/nm-platform-utils.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-platform/nm-platform-utils.h"
 #include "nm-dcb.h"
 #include "settings/nm-settings-connection.h"
 #include "nm-config.h"
 #include "nm-device-ethernet-utils.h"
 #include "settings/nm-settings.h"
 #include "nm-device-factory.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "NetworkManagerUtils.h"
-#include "nm-udev-aux/nm-udev-utils.h"
+#include "libnm-udev-aux/nm-udev-utils.h"
 #include "nm-device-veth.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceEthernet
@@ -922,31 +923,33 @@ link_negotiation_set(NMDevice *device)
         return;
     }
 
-    /* If link negotiation setting are already in place do nothing and return with success */
-    if (!!autoneg == !!link_autoneg && speed == link_speed && duplex == link_duplex) {
-        _LOGD(LOGD_DEVICE, "set-link: link negotiation is already configured");
-        return;
-    }
-
     if (autoneg && !speed && !duplex)
         _LOGD(LOGD_DEVICE, "set-link: configure auto-negotiation");
     else {
         _LOGD(LOGD_DEVICE,
-              "set-link: configure %snegotiation (%u Mbit%s, %s duplex%s)",
+              "set-link: configure %snegotiation (%u Mbit, %s duplex)",
               autoneg ? "auto-" : "static ",
-              speed ?: link_speed,
-              speed ? "" : "*",
-              duplex ? nm_platform_link_duplex_type_to_string(duplex)
-                     : nm_platform_link_duplex_type_to_string(link_duplex),
-              duplex ? "" : "*");
+              speed,
+              nm_platform_link_duplex_type_to_string(duplex));
     }
 
     if (!priv->ethtool_prev_set) {
         /* remember the values we had before setting it. */
         priv->ethtool_prev_autoneg = link_autoneg;
-        priv->ethtool_prev_speed   = link_speed;
-        priv->ethtool_prev_duplex  = link_duplex;
-        priv->ethtool_prev_set     = TRUE;
+        if (link_autoneg) {
+            /* with autoneg, we only support advertising one speed/duplex. Likewise
+             * our nm_platform_ethtool_get_link_settings() can only return the current
+             * speed/duplex, but not all the modes that we were advertising.
+             *
+             * Do the best we can do: remember to re-enable autoneg, but don't restrict
+             * the mode. */
+            priv->ethtool_prev_speed  = 0;
+            priv->ethtool_prev_duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN;
+        } else {
+            priv->ethtool_prev_speed  = link_speed;
+            priv->ethtool_prev_duplex = link_duplex;
+        }
+        priv->ethtool_prev_set = TRUE;
     }
 
     if (!nm_platform_ethtool_set_link_settings(nm_device_get_platform(device),
@@ -1718,11 +1721,11 @@ new_default_connection(NMDevice *self)
 
     /* Create a stable UUID. The UUID is also the Network_ID for stable-privacy addr-gen-mode,
      * thus when it changes we will also generate different IPv6 addresses. */
-    uuid = _nm_utils_uuid_generate_from_strings("default-wired",
-                                                nm_utils_machine_id_str(),
-                                                defname,
-                                                perm_hw_addr ?: iface,
-                                                NULL);
+    uuid = nm_uuid_generate_from_strings("default-wired",
+                                         nm_utils_machine_id_str(),
+                                         defname,
+                                         perm_hw_addr ?: iface,
+                                         NULL);
 
     g_object_set(setting,
                  NM_SETTING_CONNECTION_ID,
@@ -1747,7 +1750,7 @@ new_default_connection(NMDevice *self)
     if (dev)
         uprop = udev_device_get_property_value(dev, "NM_AUTO_DEFAULT_LINK_LOCAL_ONLY");
 
-    if (nm_udev_utils_property_as_boolean(uprop)) {
+    if (_nm_utils_ascii_str_to_bool(uprop, FALSE)) {
         setting = nm_setting_ip4_config_new();
         g_object_set(setting,
                      NM_SETTING_IP_CONFIG_METHOD,
@@ -1786,7 +1789,8 @@ update_connection(NMDevice *device, NMConnection *connection)
     const char *             mac      = nm_device_get_hw_address(device);
     const char *             mac_prop = NM_SETTING_WIRED_MAC_ADDRESS;
     GHashTableIter           iter;
-    gpointer                 key, value;
+    const char *             key;
+    const char *             value;
 
     if (!s_wired) {
         s_wired = (NMSettingWired *) nm_setting_wired_new();
@@ -1824,8 +1828,8 @@ update_connection(NMDevice *device, NMConnection *connection)
 
     _nm_setting_wired_clear_s390_options(s_wired);
     g_hash_table_iter_init(&iter, priv->s390_options);
-    while (g_hash_table_iter_next(&iter, &key, &value))
-        nm_setting_wired_add_s390_option(s_wired, (const char *) key, (const char *) value);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value))
+        nm_setting_wired_add_s390_option(s_wired, key, value);
 }
 
 static void
@@ -2000,24 +2004,16 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
 static const NMDBusInterfaceInfoExtended interface_info_device_wired = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_WIRED,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("PermHwAddress",
-                                                             "s",
-                                                             NM_DEVICE_PERM_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Speed",
-                                                             "u",
-                                                             NM_DEVICE_ETHERNET_SPEED),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("S390Subchannels",
-                                                             "as",
-                                                             NM_DEVICE_ETHERNET_S390_SUBCHANNELS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier",
-                                                             "b",
-                                                             NM_DEVICE_CARRIER), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("PermHwAddress",
+                                                           "s",
+                                                           NM_DEVICE_PERM_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Speed", "u", NM_DEVICE_ETHERNET_SPEED),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("S390Subchannels",
+                                                           "as",
+                                                           NM_DEVICE_ETHERNET_S390_SUBCHANNELS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-factory.c b/src/core/devices/nm-device-factory.c
index 81124a8d..bdc64135 100644
--- a/src/core/devices/nm-device-factory.c
+++ b/src/core/devices/nm-device-factory.c
@@ -11,9 +11,9 @@
 #include <sys/stat.h>
 #include <gmodule.h>
 
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-utils.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "nm-setting-bluetooth.h"
 
 #define PLUGIN_PREFIX "libnm-device-plugin-"
diff --git a/src/core/devices/nm-device-generic.c b/src/core/devices/nm-device-generic.c
index a319666a..08ea5ac5 100644
--- a/src/core/devices/nm-device-generic.c
+++ b/src/core/devices/nm-device-generic.c
@@ -8,8 +8,8 @@
 #include "nm-device-generic.h"
 
 #include "nm-device-private.h"
-#include "platform/nm-platform.h"
-#include "nm-core-internal.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-core-intern/nm-core-internal.h"
 
 /*****************************************************************************/
 
@@ -191,16 +191,12 @@ dispose(GObject *object)
 static const NMDBusInterfaceInfoExtended interface_info_device_generic = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_GENERIC,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L(
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
                 "TypeDescription",
                 "s",
                 NM_DEVICE_GENERIC_TYPE_DESCRIPTION), ), ),
-    .legacy_property_changed = TRUE,
 };
 
 static void
diff --git a/src/core/devices/nm-device-infiniband.c b/src/core/devices/nm-device-infiniband.c
index f54ffcf0..37065bc8 100644
--- a/src/core/devices/nm-device-infiniband.c
+++ b/src/core/devices/nm-device-infiniband.c
@@ -14,9 +14,9 @@
 #include "nm-device-private.h"
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 
 #define NM_DEVICE_INFINIBAND_IS_PARTITION "is-partition"
 
@@ -366,15 +366,9 @@ nm_device_infiniband_init(NMDeviceInfiniband *self)
 static const NMDBusInterfaceInfoExtended interface_info_device_infiniband = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_INFINIBAND,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier",
-                                                             "b",
-                                                             NM_DEVICE_CARRIER), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-ip-tunnel.c b/src/core/devices/nm-device-ip-tunnel.c
index da6afb3b..c55dd9f6 100644
--- a/src/core/devices/nm-device-ip-tunnel.c
+++ b/src/core/devices/nm-device-ip-tunnel.c
@@ -16,9 +16,9 @@
 
 #include "nm-device-private.h"
 #include "nm-manager.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "settings/nm-settings.h"
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
@@ -1050,39 +1050,33 @@ dispose(GObject *object)
 static const NMDBusInterfaceInfoExtended interface_info_device_ip_tunnel = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_IP_TUNNEL,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "u", NM_DEVICE_IP_TUNNEL_MODE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Parent", "o", NM_DEVICE_PARENT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Local",
-                                                             "s",
-                                                             NM_DEVICE_IP_TUNNEL_LOCAL),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Remote",
-                                                             "s",
-                                                             NM_DEVICE_IP_TUNNEL_REMOTE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ttl", "y", NM_DEVICE_IP_TUNNEL_TTL),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Tos", "y", NM_DEVICE_IP_TUNNEL_TOS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L(
-                "PathMtuDiscovery",
-                "b",
-                NM_DEVICE_IP_TUNNEL_PATH_MTU_DISCOVERY),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("InputKey",
-                                                             "s",
-                                                             NM_DEVICE_IP_TUNNEL_INPUT_KEY),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("OutputKey",
-                                                             "s",
-                                                             NM_DEVICE_IP_TUNNEL_OUTPUT_KEY),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L(
-                "EncapsulationLimit",
-                "y",
-                NM_DEVICE_IP_TUNNEL_ENCAPSULATION_LIMIT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("FlowLabel",
-                                                             "u",
-                                                             NM_DEVICE_IP_TUNNEL_FLOW_LABEL),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Flags",
-                                                             "u",
-                                                             NM_DEVICE_IP_TUNNEL_FLAGS), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Mode", "u", NM_DEVICE_IP_TUNNEL_MODE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Parent", "o", NM_DEVICE_PARENT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Local", "s", NM_DEVICE_IP_TUNNEL_LOCAL),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Remote",
+                                                           "s",
+                                                           NM_DEVICE_IP_TUNNEL_REMOTE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ttl", "y", NM_DEVICE_IP_TUNNEL_TTL),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Tos", "y", NM_DEVICE_IP_TUNNEL_TOS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("PathMtuDiscovery",
+                                                           "b",
+                                                           NM_DEVICE_IP_TUNNEL_PATH_MTU_DISCOVERY),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("InputKey",
+                                                           "s",
+                                                           NM_DEVICE_IP_TUNNEL_INPUT_KEY),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("OutputKey",
+                                                           "s",
+                                                           NM_DEVICE_IP_TUNNEL_OUTPUT_KEY),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("EncapsulationLimit",
+                                                           "y",
+                                                           NM_DEVICE_IP_TUNNEL_ENCAPSULATION_LIMIT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("FlowLabel",
+                                                           "u",
+                                                           NM_DEVICE_IP_TUNNEL_FLOW_LABEL),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Flags",
+                                                           "u",
+                                                           NM_DEVICE_IP_TUNNEL_FLAGS), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-macsec.c b/src/core/devices/nm-device-macsec.c
index 51b820a1..51b7225b 100644
--- a/src/core/devices/nm-device-macsec.c
+++ b/src/core/devices/nm-device-macsec.c
@@ -11,11 +11,11 @@
 
 #include "nm-act-request.h"
 #include "nm-device-private.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-manager.h"
 #include "nm-setting-macsec.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "supplicant/nm-supplicant-manager.h"
 #include "supplicant/nm-supplicant-interface.h"
 #include "supplicant/nm-supplicant-config.h"
@@ -870,40 +870,36 @@ dispose(GObject *object)
 static const NMDBusInterfaceInfoExtended interface_info_device_macsec = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_MACSEC,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Parent", "o", NM_DEVICE_PARENT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Sci", "t", NM_DEVICE_MACSEC_SCI),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("IcvLength",
-                                                             "y",
-                                                             NM_DEVICE_MACSEC_ICV_LENGTH),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("CipherSuite",
-                                                             "t",
-                                                             NM_DEVICE_MACSEC_CIPHER_SUITE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Window",
-                                                             "u",
-                                                             NM_DEVICE_MACSEC_WINDOW),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("EncodingSa",
-                                                             "y",
-                                                             NM_DEVICE_MACSEC_ENCODING_SA),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Validation",
-                                                             "s",
-                                                             NM_DEVICE_MACSEC_VALIDATION),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Encrypt",
-                                                             "b",
-                                                             NM_DEVICE_MACSEC_ENCRYPT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Protect",
-                                                             "b",
-                                                             NM_DEVICE_MACSEC_PROTECT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("IncludeSci",
-                                                             "b",
-                                                             NM_DEVICE_MACSEC_INCLUDE_SCI),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Es", "b", NM_DEVICE_MACSEC_ES),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Scb", "b", NM_DEVICE_MACSEC_SCB),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ReplayProtect",
-                                                             "b",
-                                                             NM_DEVICE_MACSEC_REPLAY_PROTECT), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Parent", "o", NM_DEVICE_PARENT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Sci", "t", NM_DEVICE_MACSEC_SCI),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("IcvLength",
+                                                           "y",
+                                                           NM_DEVICE_MACSEC_ICV_LENGTH),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("CipherSuite",
+                                                           "t",
+                                                           NM_DEVICE_MACSEC_CIPHER_SUITE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Window", "u", NM_DEVICE_MACSEC_WINDOW),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("EncodingSa",
+                                                           "y",
+                                                           NM_DEVICE_MACSEC_ENCODING_SA),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Validation",
+                                                           "s",
+                                                           NM_DEVICE_MACSEC_VALIDATION),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Encrypt",
+                                                           "b",
+                                                           NM_DEVICE_MACSEC_ENCRYPT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Protect",
+                                                           "b",
+                                                           NM_DEVICE_MACSEC_PROTECT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("IncludeSci",
+                                                           "b",
+                                                           NM_DEVICE_MACSEC_INCLUDE_SCI),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Es", "b", NM_DEVICE_MACSEC_ES),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Scb", "b", NM_DEVICE_MACSEC_SCB),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ReplayProtect",
+                                                           "b",
+                                                           NM_DEVICE_MACSEC_REPLAY_PROTECT), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-macvlan.c b/src/core/devices/nm-device-macvlan.c
index e8b39ed6..7302ecdf 100644
--- a/src/core/devices/nm-device-macvlan.c
+++ b/src/core/devices/nm-device-macvlan.c
@@ -13,7 +13,7 @@
 #include "settings/nm-settings.h"
 #include "nm-act-request.h"
 #include "nm-manager.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-macvlan.h"
 #include "nm-setting-wired.h"
@@ -502,17 +502,13 @@ dispose(GObject *object)
 static const NMDBusInterfaceInfoExtended interface_info_device_macvlan = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_MACVLAN,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Parent", "o", NM_DEVICE_PARENT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "s", NM_DEVICE_MACVLAN_MODE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("NoPromisc",
-                                                             "b",
-                                                             NM_DEVICE_MACVLAN_NO_PROMISC),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Tab",
-                                                             "b",
-                                                             NM_DEVICE_MACVLAN_TAP), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Parent", "o", NM_DEVICE_PARENT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Mode", "s", NM_DEVICE_MACVLAN_MODE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("NoPromisc",
+                                                           "b",
+                                                           NM_DEVICE_MACVLAN_NO_PROMISC),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Tab", "b", NM_DEVICE_MACVLAN_TAP), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-ppp.c b/src/core/devices/nm-device-ppp.c
index 4040f2d3..8df245bb 100644
--- a/src/core/devices/nm-device-ppp.c
+++ b/src/core/devices/nm-device-ppp.c
@@ -13,7 +13,7 @@
 #include "nm-device-private.h"
 #include "nm-manager.h"
 #include "nm-setting-pppoe.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "ppp/nm-ppp-manager.h"
 #include "ppp/nm-ppp-manager-call.h"
 #include "ppp/nm-ppp-status.h"
@@ -274,10 +274,7 @@ dispose(GObject *object)
 }
 
 static const NMDBusInterfaceInfoExtended interface_info_device_ppp = {
-    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
-        NM_DBUS_INTERFACE_DEVICE_PPP,
-        .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), ),
-    .legacy_property_changed = TRUE,
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(NM_DBUS_INTERFACE_DEVICE_PPP, ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-private.h b/src/core/devices/nm-device-private.h
index 8675a699..eb37b14f 100644
--- a/src/core/devices/nm-device-private.h
+++ b/src/core/devices/nm-device-private.h
@@ -17,14 +17,6 @@ typedef enum {
     NM_DEVICE_STAGE_STATE_COMPLETED = 2,
 } NMDeviceStageState;
 
-typedef enum {
-    NM_DEVICE_IP_STATE_NONE,
-    NM_DEVICE_IP_STATE_WAIT,
-    NM_DEVICE_IP_STATE_CONF,
-    NM_DEVICE_IP_STATE_DONE,
-    NM_DEVICE_IP_STATE_FAIL,
-} NMDeviceIPState;
-
 enum NMActStageReturn {
     NM_ACT_STAGE_RETURN_FAILURE = 0, /* Hard failure of activation */
     NM_ACT_STAGE_RETURN_SUCCESS,     /* Activation stage done */
@@ -117,8 +109,6 @@ nm_device_activate_ip6_state_done(NMDevice *self)
     return nm_device_activate_get_ip_state(self, AF_INET6) == NM_DEVICE_IP_STATE_DONE;
 }
 
-void nm_device_set_dhcp_anycast_address(NMDevice *device, const char *addr);
-
 gboolean nm_device_dhcp4_renew(NMDevice *device, gboolean release);
 gboolean nm_device_dhcp6_renew(NMDevice *device, gboolean release);
 
diff --git a/src/core/devices/nm-device-tun.c b/src/core/devices/nm-device-tun.c
index edca69e9..ebccddfc 100644
--- a/src/core/devices/nm-device-tun.c
+++ b/src/core/devices/nm-device-tun.c
@@ -14,10 +14,10 @@
 #include "nm-act-request.h"
 #include "nm-device-private.h"
 #include "nm-ip4-config.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-tun.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceTun
 #include "nm-device-logging.h"
@@ -444,22 +444,18 @@ nm_device_tun_init(NMDeviceTun *self)
 static const NMDBusInterfaceInfoExtended interface_info_device_tun = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_TUN,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Owner", "x", NM_DEVICE_TUN_OWNER),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Group", "x", NM_DEVICE_TUN_GROUP),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "s", NM_DEVICE_TUN_MODE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("NoPi", "b", NM_DEVICE_TUN_NO_PI),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("VnetHdr",
-                                                             "b",
-                                                             NM_DEVICE_TUN_VNET_HDR),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("MultiQueue",
-                                                             "b",
-                                                             NM_DEVICE_TUN_MULTI_QUEUE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Owner", "x", NM_DEVICE_TUN_OWNER),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Group", "x", NM_DEVICE_TUN_GROUP),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Mode", "s", NM_DEVICE_TUN_MODE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("NoPi", "b", NM_DEVICE_TUN_NO_PI),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("VnetHdr", "b", NM_DEVICE_TUN_VNET_HDR),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("MultiQueue",
+                                                           "b",
+                                                           NM_DEVICE_TUN_MULTI_QUEUE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress",
+                                                           "s",
+                                                           NM_DEVICE_HW_ADDRESS), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-utils.c b/src/core/devices/nm-device-utils.c
new file mode 100644
index 00000000..f40ca570
--- /dev/null
+++ b/src/core/devices/nm-device-utils.c
@@ -0,0 +1,356 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "src/core/nm-default-daemon.h"
+#include "src/core/dns/nm-dns-manager.h"
+#include "src/core/dns/nm-dns-systemd-resolved.h"
+
+#include "nm-device-utils.h"
+#include "nm-core-utils.h"
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE(
+    nm_device_state_queued_state_to_str,
+    NMDeviceState,
+    NM_UTILS_LOOKUP_DEFAULT(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "???"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNKNOWN,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unknown"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNMANAGED,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unmanaged"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNAVAILABLE,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unavailable"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_DISCONNECTED,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "disconnected"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_PREPARE,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "prepare"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_CONFIG,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "config"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_NEED_AUTH,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "need-auth"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_IP_CONFIG,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "ip-config"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_IP_CHECK,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "ip-check"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_SECONDARIES,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "secondaries"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_ACTIVATED,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "activated"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_DEACTIVATING,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "deactivating"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_FAILED,
+                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "failed"), );
+
+const char *
+nm_device_state_to_str(NMDeviceState state)
+{
+    return nm_device_state_queued_state_to_str(state)
+           + NM_STRLEN(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE);
+}
+
+NM_UTILS_LOOKUP_STR_DEFINE(
+    nm_device_state_reason_to_str,
+    NMDeviceStateReason,
+    NM_UTILS_LOOKUP_DEFAULT(NULL),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNKNOWN, "unknown"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NONE, "none"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NOW_MANAGED, "managed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NOW_UNMANAGED, "unmanaged"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONFIG_FAILED, "config-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE, "ip-config-unavailable"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED, "ip-config-expired"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NO_SECRETS, "no-secrets"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, "supplicant-disconnect"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED,
+                             "supplicant-config-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED, "supplicant-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT, "supplicant-timeout"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_START_FAILED, "ppp-start-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_DISCONNECT, "ppp-disconnect"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_FAILED, "ppp-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_START_FAILED, "dhcp-start-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_ERROR, "dhcp-error"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_FAILED, "dhcp-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SHARED_START_FAILED, "sharing-start-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SHARED_FAILED, "sharing-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED, "autoip-start-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_ERROR, "autoip-error"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_FAILED, "autoip-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_BUSY, "modem-busy"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE, "modem-no-dialtone"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER, "modem-no-carrier"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT, "modem-dial-timeout"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED, "modem-dial-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED, "modem-init-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_APN_FAILED, "gsm-apn-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING,
+                             "gsm-registration-idle"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED,
+                             "gsm-registration-denied"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT,
+                             "gsm-registration-timeout"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED,
+                             "gsm-registration-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED, "gsm-pin-check-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_FIRMWARE_MISSING, "firmware-missing"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_REMOVED, "removed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SLEEPING, "sleeping"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_REMOVED, "connection-removed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_USER_REQUESTED, "user-requested"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CARRIER, "carrier-changed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED, "connection-assumed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, "supplicant-available"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND, "modem-not-found"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_BT_FAILED, "bluetooth-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED, "gsm-sim-not-inserted"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED, "gsm-sim-pin-required"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED, "gsm-sim-puk-required"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_WRONG, "gsm-sim-wrong"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_INFINIBAND_MODE, "infiniband-mode"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED, "dependency-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_BR2684_FAILED, "br2684-bridge-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE,
+                             "modem-manager-unavailable"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SSID_NOT_FOUND, "ssid-not-found"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED,
+                             "secondary-connection-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED, "dcb-fcoe-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED, "teamd-control-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_FAILED, "modem-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_AVAILABLE, "modem-available"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT, "sim-pin-incorrect"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NEW_ACTIVATION, "new-activation"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PARENT_CHANGED, "parent-changed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED,
+                             "parent-managed-changed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_OVSDB_FAILED, "ovsdb-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE, "ip-address-duplicate"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED, "ip-method-unsupported"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED,
+                             "sriov-configuration-failed"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PEER_NOT_FOUND, "peer-not-found"), );
+
+NM_UTILS_LOOKUP_STR_DEFINE(nm_device_mtu_source_to_str,
+                           NMDeviceMtuSource,
+                           NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_NONE, "none"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_PARENT, "parent"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_IP_CONFIG, "ip-config"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_CONNECTION,
+                                                    "connection"), );
+
+NM_UTILS_LOOKUP_STR_DEFINE(nm_device_sys_iface_state_to_str,
+                           NMDeviceSysIfaceState,
+                           NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, "external"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_ASSUME, "assume"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_MANAGED, "managed"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_REMOVED,
+                                                    "removed"), );
+
+NM_UTILS_LOOKUP_STR_DEFINE(nm_device_ip_state_to_str,
+                           NMDeviceIPState,
+                           NM_UTILS_LOOKUP_DEFAULT_WARN("unknown"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_NONE, "none"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_WAIT, "wait"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_CONF, "conf"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_DONE, "done"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_FAIL, "fail"), );
+
+/*****************************************************************************/
+
+#define SD_RESOLVED_DNS (1UL << 0)
+/* Don't answer request from locally synthesized records (which includes /etc/hosts) */
+#define SD_RESOLVED_NO_SYNTHESIZE (1UL << 11)
+
+typedef struct {
+    int                                addr_family;
+    NMIPAddr                           address;
+    gulong                             cancellable_id;
+    GTask *                            task;
+    NMDnsSystemdResolvedResolveHandle *resolved_handle;
+} ResolveAddrInfo;
+
+#define _NMLOG_PREFIX_NAME "resolve-addr"
+#define _NMLOG_DOMAIN      LOGD_CORE
+#define _NMLOG2(level, info, ...)                                                      \
+    G_STMT_START                                                                       \
+    {                                                                                  \
+        if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) {                            \
+            ResolveAddrInfo *_info = (info);                                           \
+            char             _addr_str[NM_UTILS_INET_ADDRSTRLEN];                      \
+                                                                                       \
+            _nm_log((level),                                                           \
+                    (_NMLOG_DOMAIN),                                                   \
+                    0,                                                                 \
+                    NULL,                                                              \
+                    NULL,                                                              \
+                    _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT                   \
+                                       ",%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),    \
+                    NM_HASH_OBFUSCATE_PTR(_info),                                      \
+                    nm_utils_inet_ntop(_info->addr_family, &_info->address, _addr_str) \
+                        _NM_UTILS_MACRO_REST(__VA_ARGS__));                            \
+        }                                                                              \
+    }                                                                                  \
+    G_STMT_END
+
+static void
+resolve_addr_info_free(ResolveAddrInfo *info)
+{
+    nm_assert(info->cancellable_id == 0);
+    nm_assert(!info->resolved_handle);
+    g_object_unref(info->task);
+    g_free(info);
+}
+
+static void
+resolve_addr_complete(ResolveAddrInfo *info, char *hostname_take, GError *error_take)
+{
+    nm_assert(!!hostname_take != !!error_take);
+
+    nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task), &info->cancellable_id);
+    if (error_take)
+        g_task_return_error(info->task, error_take);
+    else
+        g_task_return_pointer(info->task, hostname_take, g_free);
+
+    resolve_addr_info_free(info);
+}
+
+static void
+resolve_addr_helper_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+    ResolveAddrInfo *info        = user_data;
+    gs_free_error GError *error  = NULL;
+    gs_free char *        output = NULL;
+
+    output = nm_utils_spawn_helper_finish(result, &error);
+    if (nm_utils_error_is_cancelled(error))
+        return;
+
+    _LOG2D(info, "helper returned hostname '%s'", output);
+
+    resolve_addr_complete(info, g_steal_pointer(&output), g_steal_pointer(&error));
+}
+
+static void
+resolve_addr_spawn_helper(ResolveAddrInfo *info)
+{
+    char addr_str[NM_UTILS_INET_ADDRSTRLEN];
+
+    nm_utils_inet_ntop(info->addr_family, &info->address, addr_str);
+    _LOG2D(info, "start lookup via nm-daemon-helper");
+    nm_utils_spawn_helper(NM_MAKE_STRV("resolve-address", addr_str),
+                          g_task_get_cancellable(info->task),
+                          resolve_addr_helper_cb,
+                          info);
+}
+
+static void
+resolve_addr_resolved_cb(NMDnsSystemdResolved *                   resolved,
+                         NMDnsSystemdResolvedResolveHandle *      handle,
+                         const NMDnsSystemdResolvedAddressResult *names,
+                         guint                                    names_len,
+                         guint64                                  flags,
+                         GError *                                 error,
+                         gpointer                                 user_data)
+{
+    ResolveAddrInfo *info = user_data;
+
+    info->resolved_handle = NULL;
+
+    if (nm_utils_error_is_cancelled(error))
+        return;
+
+    if (error) {
+        gs_free char *dbus_error = NULL;
+
+        _LOG2D(info, "error resolving via systemd-resolved: %s", error->message);
+
+        dbus_error = g_dbus_error_get_remote_error(error);
+        if (nm_streq0(dbus_error, "org.freedesktop.resolve1.DnsError.NXDOMAIN")) {
+            resolve_addr_complete(info, NULL, g_error_copy(error));
+            return;
+        }
+
+        resolve_addr_spawn_helper(info);
+        return;
+    }
+
+    if (names_len == 0) {
+        _LOG2D(info, "systemd-resolved returned no result");
+        resolve_addr_complete(info, g_strdup(""), NULL);
+        return;
+    }
+
+    _LOG2D(info, "systemd-resolved returned hostname '%s'", names[0].name);
+    resolve_addr_complete(info, g_strdup(names[0].name), NULL);
+}
+
+static void
+resolve_addr_cancelled(GObject *object, gpointer user_data)
+{
+    ResolveAddrInfo *info  = user_data;
+    GError *         error = NULL;
+
+    nm_clear_g_signal_handler(g_task_get_cancellable(info->task), &info->cancellable_id);
+    nm_clear_pointer(&info->resolved_handle, nm_dns_systemd_resolved_resolve_cancel);
+    nm_utils_error_set_cancelled(&error, FALSE, NULL);
+    resolve_addr_complete(info, NULL, error);
+}
+
+void
+nm_device_resolve_address(int                 addr_family,
+                          gconstpointer       address,
+                          GCancellable *      cancellable,
+                          GAsyncReadyCallback callback,
+                          gpointer            cb_data)
+{
+    ResolveAddrInfo *     info;
+    NMDnsSystemdResolved *resolved;
+
+    info  = g_new(ResolveAddrInfo, 1);
+    *info = (ResolveAddrInfo){
+        .task = nm_g_task_new(NULL, cancellable, nm_device_resolve_address, callback, cb_data),
+        .addr_family = addr_family,
+        .address     = nm_ip_addr_init(addr_family, address),
+    };
+
+    if (cancellable) {
+        gulong signal_id;
+
+        signal_id =
+            g_cancellable_connect(cancellable, G_CALLBACK(resolve_addr_cancelled), info, NULL);
+        if (signal_id == 0) {
+            /* the request is already cancelled. Return. */
+            return;
+        }
+        info->cancellable_id = signal_id;
+    }
+
+    resolved = (NMDnsSystemdResolved *) nm_dns_manager_get_systemd_resolved(nm_dns_manager_get());
+    if (resolved) {
+        _LOG2D(info, "start lookup via systemd-resolved");
+        info->resolved_handle =
+            nm_dns_systemd_resolved_resolve_address(resolved,
+                                                    0,
+                                                    addr_family,
+                                                    address,
+                                                    SD_RESOLVED_DNS | SD_RESOLVED_NO_SYNTHESIZE,
+                                                    20000,
+                                                    resolve_addr_resolved_cb,
+                                                    info);
+        return;
+    }
+
+    resolve_addr_spawn_helper(info);
+}
+
+char *
+nm_device_resolve_address_finish(GAsyncResult *result, GError **error)
+{
+    GTask *task = G_TASK(result);
+
+    nm_assert(nm_g_task_is_valid(result, NULL, nm_device_resolve_address));
+
+    return g_task_propagate_pointer(task, error);
+}
diff --git a/src/core/devices/nm-device-utils.h b/src/core/devices/nm-device-utils.h
new file mode 100644
index 00000000..8bc957a4
--- /dev/null
+++ b/src/core/devices/nm-device-utils.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __DEVICES_NM_DEVICE_UTILS_H__
+#define __DEVICES_NM_DEVICE_UTILS_H__
+
+/*****************************************************************************/
+
+const char *nm_device_state_to_str(NMDeviceState state);
+const char *nm_device_state_reason_to_str(NMDeviceStateReason reason);
+
+#define nm_device_state_reason_to_str_a(reason) \
+    NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_str, reason)
+
+static inline NMDeviceStateReason
+nm_device_state_reason_check(NMDeviceStateReason reason)
+{
+    /* the device-state-reason serves mostly informational purpose during a state
+     * change. In some cases however, decisions are made based on the reason.
+     * I tend to think that interpreting the state reason to derive some behaviors
+     * is confusing, because the cause and effect are so far apart.
+     *
+     * This function is here to mark source that inspects the reason to make
+     * a decision -- contrary to places that set the reason. Thus, by grepping
+     * for nm_device_state_reason_check() you can find the "effect" to a certain
+     * reason.
+     */
+    return reason;
+}
+
+/*****************************************************************************/
+
+#define NM_PENDING_ACTION_AUTOACTIVATE           "autoactivate"
+#define NM_PENDING_ACTION_IN_STATE_CHANGE        "in-state-change"
+#define NM_PENDING_ACTION_RECHECK_AVAILABLE      "recheck-available"
+#define NM_PENDING_ACTION_CARRIER_WAIT           "carrier-wait"
+#define NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT "waiting-for-supplicant"
+#define NM_PENDING_ACTION_WIFI_SCAN              "wifi-scan"
+#define NM_PENDING_ACTION_WAITING_FOR_COMPANION  "waiting-for-companion"
+#define NM_PENDING_ACTION_LINK_INIT              "link-init"
+
+#define NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "queued-state-change-"
+#define NM_PENDING_ACTIONPREFIX_ACTIVATION          "activation-"
+
+const char *nm_device_state_queued_state_to_str(NMDeviceState state);
+
+/*****************************************************************************/
+
+typedef enum {
+    NM_DEVICE_MTU_SOURCE_NONE,
+    NM_DEVICE_MTU_SOURCE_PARENT,
+    NM_DEVICE_MTU_SOURCE_IP_CONFIG,
+    NM_DEVICE_MTU_SOURCE_CONNECTION,
+} NMDeviceMtuSource;
+
+const char *nm_device_mtu_source_to_str(NMDeviceMtuSource mtu_source);
+
+/*****************************************************************************/
+
+typedef enum _nm_packed {
+    NM_DEVICE_SYS_IFACE_STATE_EXTERNAL,
+    NM_DEVICE_SYS_IFACE_STATE_ASSUME,
+    NM_DEVICE_SYS_IFACE_STATE_MANAGED,
+
+    /* the REMOVED state applies when the device is manually set to unmanaged
+     * or the link was externally removed. In both cases, we move the device
+     * to UNMANAGED state, without touching the link -- be it, because the link
+     * is already gone or because we want to release it (give it up).
+     */
+    NM_DEVICE_SYS_IFACE_STATE_REMOVED,
+} NMDeviceSysIfaceState;
+
+const char *nm_device_sys_iface_state_to_str(NMDeviceSysIfaceState sys_iface_state);
+
+/*****************************************************************************/
+
+typedef enum {
+    NM_DEVICE_IP_STATE_NONE,
+    NM_DEVICE_IP_STATE_WAIT,
+    NM_DEVICE_IP_STATE_CONF,
+    NM_DEVICE_IP_STATE_DONE,
+    NM_DEVICE_IP_STATE_FAIL,
+} NMDeviceIPState;
+
+const char *nm_device_ip_state_to_str(NMDeviceIPState ip_state);
+
+/*****************************************************************************/
+
+/*****************************************************************************/
+
+void nm_device_resolve_address(int                 addr_family,
+                               gconstpointer       address,
+                               GCancellable *      cancellable,
+                               GAsyncReadyCallback callback,
+                               gpointer            cb_data);
+
+char *nm_device_resolve_address_finish(GAsyncResult *result, GError **error);
+
+#endif /* __DEVICES_NM_DEVICE_UTILS_H__ */
diff --git a/src/core/devices/nm-device-veth.c b/src/core/devices/nm-device-veth.c
index e0ba843d..28831b75 100644
--- a/src/core/devices/nm-device-veth.c
+++ b/src/core/devices/nm-device-veth.c
@@ -7,11 +7,11 @@
 
 #include <stdlib.h>
 
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "nm-device-veth.h"
 #include "nm-device-private.h"
 #include "nm-manager.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-veth.h"
 
@@ -161,12 +161,8 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 static const NMDBusInterfaceInfoExtended interface_info_device_veth = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_VETH,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Peer",
-                                                             "o",
-                                                             NM_DEVICE_VETH_PEER), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Peer", "o", NM_DEVICE_VETH_PEER), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-vlan.c b/src/core/devices/nm-device-vlan.c
index bfde60ef..548245f8 100644
--- a/src/core/devices/nm-device-vlan.c
+++ b/src/core/devices/nm-device-vlan.c
@@ -16,11 +16,12 @@
 #include "settings/nm-settings.h"
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-manager.h"
-#include "nm-core-internal.h"
-#include "platform/nmp-object.h"
+#include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-platform/nmp-object.h"
+#include "libnm-platform/nm-platform-utils.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceVlan
 #include "nm-device-logging.h"
@@ -436,7 +437,7 @@ update_connection(NMDevice *device, NMConnection *connection)
     const NMPlatformLink *plink;
     const NMPObject *     polnk;
     guint                 vlan_id;
-    guint                 vlan_flags;
+    _NMVlanFlags          vlan_flags;
 
     if (!s_vlan) {
         s_vlan = (NMSettingVlan *) nm_setting_vlan_new();
@@ -463,9 +464,9 @@ update_connection(NMDevice *device, NMConnection *connection)
     if (polnk)
         vlan_flags = polnk->lnk_vlan.flags;
     else
-        vlan_flags = NM_VLAN_FLAG_REORDER_HEADERS;
-    if (vlan_flags != nm_setting_vlan_get_flags(s_vlan))
-        g_object_set(s_vlan, NM_SETTING_VLAN_FLAGS, (NMVlanFlags) vlan_flags, NULL);
+        vlan_flags = _NM_VLAN_FLAG_REORDER_HEADERS;
+    if (NM_VLAN_FLAGS_CAST(vlan_flags) != nm_setting_vlan_get_flags(s_vlan))
+        g_object_set(s_vlan, NM_SETTING_VLAN_FLAGS, NM_VLAN_FLAGS_CAST(vlan_flags), NULL);
 
     if (polnk) {
         _nm_setting_vlan_set_priorities(s_vlan,
@@ -507,7 +508,7 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
         nm_platform_link_vlan_change(nm_device_get_platform(device),
                                      nm_device_get_ifindex(device),
-                                     NM_VLAN_FLAGS_ALL,
+                                     _NM_VLAN_FLAGS_ALL,
                                      nm_setting_vlan_get_flags(s_vlan),
                                      TRUE,
                                      ingress_map,
@@ -546,17 +547,11 @@ nm_device_vlan_init(NMDeviceVlan *self)
 static const NMDBusInterfaceInfoExtended interface_info_device_vlan = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_VLAN,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier", "b", NM_DEVICE_CARRIER),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Parent", "o", NM_DEVICE_PARENT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("VlanId",
-                                                             "u",
-                                                             NM_DEVICE_VLAN_ID), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Parent", "o", NM_DEVICE_PARENT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("VlanId", "u", NM_DEVICE_VLAN_ID), ), ),
 };
 
 static void
@@ -674,7 +669,7 @@ get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const c
      * device, we create one for it using the VLAN ID and the parent
      * interface's name.
      */
-    return nm_utils_new_vlan_name(parent_iface, nm_setting_vlan_get_id(s_vlan));
+    return nmp_utils_new_vlan_name(parent_iface, nm_setting_vlan_get_id(s_vlan));
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL(
diff --git a/src/core/devices/nm-device-vrf.c b/src/core/devices/nm-device-vrf.c
index 4fec59ba..2a1f42c6 100644
--- a/src/core/devices/nm-device-vrf.c
+++ b/src/core/devices/nm-device-vrf.c
@@ -4,12 +4,12 @@
 
 #include "nm-device-vrf.h"
 
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "nm-device-factory.h"
 #include "nm-device-private.h"
 #include "nm-manager.h"
 #include "nm-setting-vrf.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "settings/nm-settings.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceVrf
diff --git a/src/core/devices/nm-device-vxlan.c b/src/core/devices/nm-device-vxlan.c
index f16a52c4..f9dfad2a 100644
--- a/src/core/devices/nm-device-vxlan.c
+++ b/src/core/devices/nm-device-vxlan.c
@@ -9,7 +9,7 @@
 
 #include "nm-device-private.h"
 #include "nm-manager.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-utils.h"
 #include "nm-device-factory.h"
 #include "nm-setting-vxlan.h"
@@ -17,7 +17,7 @@
 #include "settings/nm-settings.h"
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceVxlan
 #include "nm-device-logging.h"
@@ -576,38 +576,34 @@ nm_device_vxlan_init(NMDeviceVxlan *self)
 static const NMDBusInterfaceInfoExtended interface_info_device_vxlan = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_VXLAN,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Parent", "o", NM_DEVICE_PARENT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Id", "u", NM_DEVICE_VXLAN_ID),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Group", "s", NM_DEVICE_VXLAN_GROUP),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Local", "s", NM_DEVICE_VXLAN_LOCAL),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Tos", "y", NM_DEVICE_VXLAN_TOS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ttl", "y", NM_DEVICE_VXLAN_TTL),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Learning",
-                                                             "b",
-                                                             NM_DEVICE_VXLAN_LEARNING),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ageing", "u", NM_DEVICE_VXLAN_AGEING),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Limit", "u", NM_DEVICE_VXLAN_LIMIT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DstPort",
-                                                             "q",
-                                                             NM_DEVICE_VXLAN_DST_PORT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("SrcPortMin",
-                                                             "q",
-                                                             NM_DEVICE_VXLAN_SRC_PORT_MIN),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("SrcPortMax",
-                                                             "q",
-                                                             NM_DEVICE_VXLAN_SRC_PORT_MAX),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Proxy", "b", NM_DEVICE_VXLAN_PROXY),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Rsc", "b", NM_DEVICE_VXLAN_RSC),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("L2miss", "b", NM_DEVICE_VXLAN_L2MISS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("L3miss",
-                                                             "b",
-                                                             NM_DEVICE_VXLAN_L3MISS), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Parent", "o", NM_DEVICE_PARENT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Id", "u", NM_DEVICE_VXLAN_ID),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Group", "s", NM_DEVICE_VXLAN_GROUP),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Local", "s", NM_DEVICE_VXLAN_LOCAL),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Tos", "y", NM_DEVICE_VXLAN_TOS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ttl", "y", NM_DEVICE_VXLAN_TTL),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Learning",
+                                                           "b",
+                                                           NM_DEVICE_VXLAN_LEARNING),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ageing", "u", NM_DEVICE_VXLAN_AGEING),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Limit", "u", NM_DEVICE_VXLAN_LIMIT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("DstPort",
+                                                           "q",
+                                                           NM_DEVICE_VXLAN_DST_PORT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("SrcPortMin",
+                                                           "q",
+                                                           NM_DEVICE_VXLAN_SRC_PORT_MIN),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("SrcPortMax",
+                                                           "q",
+                                                           NM_DEVICE_VXLAN_SRC_PORT_MAX),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Proxy", "b", NM_DEVICE_VXLAN_PROXY),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Rsc", "b", NM_DEVICE_VXLAN_RSC),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("L2miss", "b", NM_DEVICE_VXLAN_L2MISS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("L3miss",
+                                                           "b",
+                                                           NM_DEVICE_VXLAN_L3MISS), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-wireguard.c b/src/core/devices/nm-device-wireguard.c
index 5bee09e6..aa9c46eb 100644
--- a/src/core/devices/nm-device-wireguard.c
+++ b/src/core/devices/nm-device-wireguard.c
@@ -11,12 +11,12 @@
 #include <linux/fib_rules.h>
 
 #include "nm-setting-wireguard.h"
-#include "nm-core-internal.h"
-#include "nm-glib-aux/nm-secret-utils.h"
+#include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-glib-aux/nm-secret-utils.h"
 #include "nm-device-private.h"
-#include "platform/nm-platform.h"
-#include "platform/nmp-object.h"
-#include "platform/nmp-rules-manager.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-platform/nmp-object.h"
+#include "libnm-platform/nmp-rules-manager.h"
 #include "nm-device-factory.h"
 #include "nm-active-connection.h"
 #include "nm-act-request.h"
@@ -66,7 +66,7 @@ G_STATIC_ASSERT(NM_WIREGUARD_SYMMETRIC_KEY_LEN == NMP_WIREGUARD_SYMMETRIC_KEY_LE
  * timestamp. */
 #define RETRY_IN_MSEC_ASAP ((gint64) G_MAXINT64)
 
-#define RETRY_IN_MSEC_MAX ((gint64)(30 * 60 * 1000))
+#define RETRY_IN_MSEC_MAX ((gint64) (30 * 60 * 1000))
 
 typedef enum {
     LINK_CONFIG_MODE_FULL,
@@ -669,7 +669,7 @@ _peers_resolve_retry_reschedule(NMDeviceWireGuard *self, gint64 new_next_try_at_
      * than expected. Also, rate-limit to 500 msec. */
     interval_ms = NM_CLAMP((new_next_try_at_nsec - now) / NM_UTILS_NSEC_PER_MSEC,
                            (gint64) 500,
-                           (gint64)(24 * 60 * 60 * 1000));
+                           (gint64) (24 * 60 * 60 * 1000));
 
     _LOGT(LOGD_DEVICE,
           "wireguard-peers: schedule rechecking peer endpoints in %u msec",
@@ -1939,11 +1939,9 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 
     switch (prop_id) {
     case PROP_PUBLIC_KEY:
-        g_value_take_variant(value,
-                             g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
-                                                       priv->lnk_curr.public_key,
-                                                       sizeof(priv->lnk_curr.public_key),
-                                                       1));
+        g_value_take_variant(
+            value,
+            nm_g_variant_new_ay(priv->lnk_curr.public_key, sizeof(priv->lnk_curr.public_key)));
         break;
     case PROP_LISTEN_PORT:
         g_value_set_uint(value, priv->lnk_curr.listen_port);
diff --git a/src/core/devices/nm-device-wpan.c b/src/core/devices/nm-device-wpan.c
index 2f3b16ff..73d79410 100644
--- a/src/core/devices/nm-device-wpan.c
+++ b/src/core/devices/nm-device-wpan.c
@@ -15,10 +15,10 @@
 #include "nm-act-request.h"
 #include "nm-device-private.h"
 #include "nm-ip4-config.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-wpan.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceWpan
 #include "nm-device-logging.h"
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 3874e586..50386d00 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -23,22 +23,23 @@
 #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-base/nm-ethtool-base.h"
-#include "nm-libnm-core-intern/nm-common-macros.h"
+#include "libnm-std-aux/unaligned.h"
+#include "libnm-glib-aux/nm-uuid.h"
+#include "libnm-glib-aux/nm-dedup-multi.h"
+#include "libnm-glib-aux/nm-random-utils.h"
+#include "libnm-systemd-shared/nm-sd-utils-shared.h"
+
+#include "libnm-base/nm-ethtool-base.h"
+#include "libnm-core-aux-intern/nm-common-macros.h"
 #include "nm-device-private.h"
 #include "nm-l3cfg.h"
 #include "nm-l3-config-data.h"
 #include "NetworkManagerUtils.h"
 #include "nm-manager.h"
-#include "platform/nm-platform.h"
-#include "nm-platform/nm-platform-utils.h"
-#include "platform/nmp-object.h"
-#include "platform/nmp-rules-manager.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-platform/nm-platform-utils.h"
+#include "libnm-platform/nmp-object.h"
+#include "libnm-platform/nmp-rules-manager.h"
 #include "ndisc/nm-ndisc.h"
 #include "ndisc/nm-lndp-ndisc.h"
 #include "dhcp/nm-dhcp-manager.h"
@@ -51,7 +52,8 @@
 #include "dnsmasq/nm-dnsmasq-manager.h"
 #include "nm-dhcp-config.h"
 #include "nm-rfkill-manager.h"
-#include "nm-firewall-manager.h"
+#include "nm-firewall-utils.h"
+#include "nm-firewalld-manager.h"
 #include "settings/nm-settings-connection.h"
 #include "settings/nm-settings.h"
 #include "nm-setting-ethtool.h"
@@ -65,12 +67,13 @@
 #include "c-list/src/c-list.h"
 #include "dns/nm-dns-manager.h"
 #include "nm-acd-manager.h"
-#include "nm-core-internal.h"
-#include "systemd/nm-sd.h"
+#include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-systemd-core/nm-sd.h"
 #include "nm-lldp-listener.h"
 #include "nm-audit-manager.h"
 #include "nm-connectivity.h"
 #include "nm-dbus-interface.h"
+#include "nm-hostname-manager.h"
 
 #include "nm-device-generic.h"
 #include "nm-device-vlan.h"
@@ -207,6 +210,7 @@ typedef struct {
     NMOptionBool            requested[_NM_ETHTOOL_ID_FEATURE_NUM];
     NMEthtoolCoalesceState *coalesce;
     NMEthtoolRingState *    ring;
+    NMEthtoolPauseState *   pause;
 } EthtoolState;
 
 typedef enum {
@@ -217,7 +221,6 @@ typedef enum {
 
 typedef struct {
     ResolverState state;
-    GResolver *   resolver;
     GInetAddress *address;
     GCancellable *cancellable;
     char *        hostname;
@@ -496,11 +499,18 @@ typedef struct _NMDevicePrivate {
 
     NMDeviceStageState stage1_sriov_state : 3;
 
-    /* Generic DHCP stuff */
-    char *dhcp_anycast_address;
+    bool ip_config_started : 1;
 
     char *current_stable_id;
 
+    union {
+        struct {
+            GSource *ip_req_timeout_source_6;
+            GSource *ip_req_timeout_source_4;
+        };
+        GSource *ip_req_timeout_source_x[2];
+    };
+
     /* Proxy Configuration */
     NMProxyConfig *    proxy_config;
     NMPacrunnerConfId *pacrunner_conf_id;
@@ -592,9 +602,9 @@ typedef struct _NMDevicePrivate {
     gulong            dnsmasq_state_id;
 
     /* Firewall */
-    FirewallState            fw_state : 4;
-    NMFirewallManager *      fw_mgr;
-    NMFirewallManagerCallId *fw_call;
+    FirewallState             fw_state : 4;
+    NMFirewalldManager *      fw_mgr;
+    NMFirewalldManagerCallId *fw_call;
 
     /* IPv4LL stuff */
     sd_ipv4ll *ipv4ll;
@@ -693,6 +703,8 @@ typedef struct _NMDevicePrivate {
     } stats;
 
     bool mtu_force_set_done : 1;
+
+    NMOptionBool promisc_reset;
 } NMDevicePrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
@@ -764,139 +776,7 @@ static void sriov_op_cb(GError *error, gpointer user_data);
 
 static void device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self);
 static gboolean device_link_changed(NMDevice *self);
-
-/*****************************************************************************/
-
-static NM_UTILS_LOOKUP_STR_DEFINE(
-    queued_state_to_string,
-    NMDeviceState,
-    NM_UTILS_LOOKUP_DEFAULT(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "???"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNKNOWN,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unknown"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNMANAGED,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unmanaged"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNAVAILABLE,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unavailable"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_DISCONNECTED,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "disconnected"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_PREPARE,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "prepare"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_CONFIG,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "config"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_NEED_AUTH,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "need-auth"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_IP_CONFIG,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "ip-config"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_IP_CHECK,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "ip-check"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_SECONDARIES,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "secondaries"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_ACTIVATED,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "activated"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_DEACTIVATING,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "deactivating"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_FAILED,
-                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "failed"), );
-
-const char *
-nm_device_state_to_str(NMDeviceState state)
-{
-    return queued_state_to_string(state) + NM_STRLEN(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE);
-}
-
-NM_UTILS_LOOKUP_STR_DEFINE(
-    nm_device_state_reason_to_str,
-    NMDeviceStateReason,
-    NM_UTILS_LOOKUP_DEFAULT(NULL),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNKNOWN, "unknown"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NONE, "none"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NOW_MANAGED, "managed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NOW_UNMANAGED, "unmanaged"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONFIG_FAILED, "config-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE, "ip-config-unavailable"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED, "ip-config-expired"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NO_SECRETS, "no-secrets"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, "supplicant-disconnect"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED,
-                             "supplicant-config-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED, "supplicant-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT, "supplicant-timeout"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_START_FAILED, "ppp-start-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_DISCONNECT, "ppp-disconnect"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_FAILED, "ppp-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_START_FAILED, "dhcp-start-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_ERROR, "dhcp-error"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_FAILED, "dhcp-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SHARED_START_FAILED, "sharing-start-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SHARED_FAILED, "sharing-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED, "autoip-start-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_ERROR, "autoip-error"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_FAILED, "autoip-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_BUSY, "modem-busy"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE, "modem-no-dialtone"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER, "modem-no-carrier"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT, "modem-dial-timeout"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED, "modem-dial-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED, "modem-init-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_APN_FAILED, "gsm-apn-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING,
-                             "gsm-registration-idle"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED,
-                             "gsm-registration-denied"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT,
-                             "gsm-registration-timeout"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED,
-                             "gsm-registration-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED, "gsm-pin-check-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_FIRMWARE_MISSING, "firmware-missing"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_REMOVED, "removed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SLEEPING, "sleeping"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_REMOVED, "connection-removed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_USER_REQUESTED, "user-requested"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CARRIER, "carrier-changed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED, "connection-assumed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, "supplicant-available"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND, "modem-not-found"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_BT_FAILED, "bluetooth-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED, "gsm-sim-not-inserted"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED, "gsm-sim-pin-required"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED, "gsm-sim-puk-required"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_WRONG, "gsm-sim-wrong"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_INFINIBAND_MODE, "infiniband-mode"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED, "dependency-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_BR2684_FAILED, "br2684-bridge-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE,
-                             "modem-manager-unavailable"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SSID_NOT_FOUND, "ssid-not-found"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED,
-                             "secondary-connection-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED, "dcb-fcoe-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED, "teamd-control-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_FAILED, "modem-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_AVAILABLE, "modem-available"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT, "sim-pin-incorrect"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NEW_ACTIVATION, "new-activation"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PARENT_CHANGED, "parent-changed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED,
-                             "parent-managed-changed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_OVSDB_FAILED, "ovsdb-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE, "ip-address-duplicate"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED, "ip-method-unsupported"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED,
-                             "sriov-configuration-failed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PEER_NOT_FOUND, "peer-not-found"), );
-
-#define reason_to_string_a(reason) NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_str, reason)
-
-static NM_UTILS_LOOKUP_STR_DEFINE(mtu_source_to_str,
-                                  NMDeviceMtuSource,
-                                  NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
-                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_NONE, "none"),
-                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_PARENT, "parent"),
-                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_IP_CONFIG,
-                                                           "ip-config"),
-                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_CONNECTION,
-                                                           "connection"), );
+static void     check_ip_state(NMDevice *self, gboolean may_fail, gboolean full_state_update);
 
 /*****************************************************************************/
 
@@ -908,7 +788,6 @@ _hostname_resolver_free(HostnameResolver *resolver)
 
     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);
@@ -952,8 +831,7 @@ _prop_get_connection_stable_id(NMDevice *         self,
      * Especially with ${RANDOM} stable-id we want to generate *one* configuration
      * for each activation. */
     if (G_UNLIKELY(!priv->current_stable_id)) {
-        gs_free char *       default_id = NULL;
-        gs_free char *       generated  = NULL;
+        gs_free char *       generated = NULL;
         NMUtilsStableType    stable_type;
         NMSettingConnection *s_con;
         gboolean             hwaddr_is_fake;
@@ -966,11 +844,10 @@ _prop_get_connection_stable_id(NMDevice *         self,
         stable_id = nm_setting_connection_get_stable_id(s_con);
 
         if (!stable_id) {
-            default_id =
+            stable_id =
                 nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
                                                       NM_CON_DEFAULT("connection.stable-id"),
                                                       self);
-            stable_id = default_id;
         }
 
         uuid = nm_connection_get_uuid(connection);
@@ -1029,7 +906,6 @@ _prop_get_ipv6_dhcp_duid(NMDevice *    self,
 {
     NMSettingIPConfig *s_ip6;
     const char *       duid;
-    gs_free char *     duid_default = NULL;
     const char *       duid_error;
     GBytes *           duid_out;
     gboolean           duid_enforce = TRUE;
@@ -1042,10 +918,9 @@ _prop_get_ipv6_dhcp_duid(NMDevice *    self,
     duid  = nm_setting_ip6_config_get_dhcp_duid(NM_SETTING_IP6_CONFIG(s_ip6));
 
     if (!duid) {
-        duid_default = nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
-                                                             NM_CON_DEFAULT("ipv6.dhcp-duid"),
-                                                             self);
-        duid         = duid_default;
+        duid = nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
+                                                     NM_CON_DEFAULT("ipv6.dhcp-duid"),
+                                                     self);
         if (!duid)
             duid = "lease";
     }
@@ -1396,7 +1271,7 @@ _prop_get_ipvx_route_table(NMDevice *self, int addr_family)
     _LOGT(LOGD_DEVICE,
           "ipv%c.route-table = %u%s",
           nm_utils_addr_family_to_char(addr_family),
-          (guint)(route_table ?: RT_TABLE_MAIN),
+          (guint) (route_table ?: RT_TABLE_MAIN),
           route_table != 0u ? "" : " (policy routing not enabled)");
 
     return route_table;
@@ -1500,6 +1375,40 @@ out:
     return timeout;
 }
 
+static guint32
+_prop_get_ipvx_required_timeout(NMDevice *self, int addr_family)
+{
+    NMConnection *     connection;
+    NMSettingIPConfig *s_ip;
+    int                timeout;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert_addr_family(addr_family);
+
+    connection = nm_device_get_applied_connection(self);
+    if (!connection)
+        return 0;
+
+    s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
+    if (!s_ip)
+        return 0;
+
+    timeout = nm_setting_ip_config_get_required_timeout(s_ip);
+    nm_assert(timeout >= -1);
+
+    if (timeout > -1)
+        return (guint32) timeout;
+
+    return nm_config_data_get_connection_default_int64(
+        NM_CONFIG_GET_DATA,
+        NM_IS_IPv4(addr_family) ? NM_CON_DEFAULT("ipv4.required-timeout")
+                                : NM_CON_DEFAULT("ipv6.required-timeout"),
+        self,
+        0,
+        G_MAXINT32,
+        0);
+}
+
 /**
  * _prop_get_ipvx_dhcp_iaid:
  * @self: the #NMDevice
@@ -1522,7 +1431,6 @@ _prop_get_ipvx_dhcp_iaid(NMDevice *    self,
     const int          IS_IPv4 = NM_IS_IPv4(addr_family);
     NMSettingIPConfig *s_ip;
     const char *       iaid_str;
-    gs_free char *     iaid_str_free = NULL;
     guint32            iaid;
     const char *       iface;
     const char *       fail_reason;
@@ -1531,11 +1439,10 @@ _prop_get_ipvx_dhcp_iaid(NMDevice *    self,
     s_ip     = nm_connection_get_setting_ip_config(connection, addr_family);
     iaid_str = nm_setting_ip_config_get_dhcp_iaid(s_ip);
     if (!iaid_str) {
-        iaid_str_free = nm_config_data_get_connection_default(
-            NM_CONFIG_GET_DATA,
-            IS_IPv4 ? NM_CON_DEFAULT("ipv4.dhcp-iaid") : NM_CON_DEFAULT("ipv6.dhcp-iaid"),
-            self);
-        iaid_str = iaid_str_free;
+        iaid_str = nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
+                                                         IS_IPv4 ? NM_CON_DEFAULT("ipv4.dhcp-iaid")
+                                                                 : NM_CON_DEFAULT("ipv6.dhcp-iaid"),
+                                                         self);
         if (!iaid_str) {
             iaid_str    = NM_IAID_IFNAME;
             is_explicit = FALSE;
@@ -1566,7 +1473,7 @@ _prop_get_ipvx_dhcp_iaid(NMDevice *    self,
         iaid = unaligned_read_be32(&pllink->l_address.data[pllink->l_address.len - 4]);
         goto out_good;
     } else if (nm_streq0(iaid_str, NM_IAID_PERM_MAC)) {
-        guint8      hwaddr_buf[NM_UTILS_HWADDR_LEN_MAX];
+        guint8      hwaddr_buf[_NM_UTILS_HWADDR_LEN_MAX];
         const char *hwaddr_str;
         gsize       hwaddr_len;
 
@@ -1692,12 +1599,10 @@ _prop_get_ipvx_dhcp_hostname_flags(NMDevice *self, int addr_family)
 }
 
 static const char *
-_prop_get_connection_mud_url(NMDevice *self, NMSettingConnection *s_con, char **out_mud_url)
+_prop_get_connection_mud_url(NMDevice *self, NMSettingConnection *s_con)
 {
-    const char *  mud_url;
-    gs_free char *s = NULL;
-
-    nm_assert(out_mud_url && !*out_mud_url);
+    const char *mud_url;
+    const char *s;
 
     mud_url = nm_setting_connection_get_mud_url(s_con);
 
@@ -1714,7 +1619,7 @@ _prop_get_connection_mud_url(NMDevice *self, NMSettingConnection *s_con, char **
         if (nm_streq(s, NM_CONNECTION_MUD_URL_NONE))
             return NULL;
         if (nm_sd_http_url_is_valid_https(s))
-            return (*out_mud_url = g_steal_pointer(&s));
+            return s;
     }
 
     return NULL;
@@ -1725,10 +1630,9 @@ _prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *
 {
     NMSettingIPConfig *s_ip4;
     const char *       client_id;
-    gs_free char *     client_id_default = NULL;
     guint8 *           client_id_buf;
     const char *       fail_reason;
-    guint8             hwaddr_bin_buf[NM_UTILS_HWADDR_LEN_MAX];
+    guint8             hwaddr_bin_buf[_NM_UTILS_HWADDR_LEN_MAX];
     const guint8 *     hwaddr_bin;
     int                arp_type;
     gsize              hwaddr_len;
@@ -1739,13 +1643,12 @@ _prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *
     client_id = nm_setting_ip4_config_get_dhcp_client_id(NM_SETTING_IP4_CONFIG(s_ip4));
 
     if (!client_id) {
-        client_id_default =
-            nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
-                                                  NM_CON_DEFAULT("ipv4.dhcp-client-id"),
-                                                  self);
-        if (client_id_default && client_id_default[0]) {
+        client_id = nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
+                                                          NM_CON_DEFAULT("ipv4.dhcp-client-id"),
+                                                          self);
+        if (client_id && !client_id[0]) {
             /* a non-empty client-id is always valid, see nm_dhcp_utils_client_id_string_to_bytes().  */
-            client_id = client_id_default;
+            client_id = NULL;
         }
     }
 
@@ -1877,8 +1780,7 @@ out_good:
 static GBytes *
 _prop_get_ipv4_dhcp_vendor_class_identifier(NMDevice *self, NMSettingIP4Config *s_ip4)
 {
-    gs_free char *config_data_prop = NULL;
-    gs_free char *to_free          = NULL;
+    gs_free char *to_free = NULL;
     const char *  conn_prop;
     GBytes *      bytes = NULL;
     const char *  bin;
@@ -1888,13 +1790,13 @@ _prop_get_ipv4_dhcp_vendor_class_identifier(NMDevice *self, NMSettingIP4Config *
 
     if (!conn_prop) {
         /* set in NetworkManager.conf ? */
-        config_data_prop = nm_config_data_get_connection_default(
+        conn_prop = nm_config_data_get_connection_default(
             NM_CONFIG_GET_DATA,
             NM_CON_DEFAULT("ipv4.dhcp-vendor-class-identifier"),
             self);
 
-        if (config_data_prop && nm_utils_validate_dhcp4_vendor_class_id(config_data_prop, NULL))
-            conn_prop = config_data_prop;
+        if (conn_prop && !nm_utils_validate_dhcp4_vendor_class_id(conn_prop, NULL))
+            conn_prop = NULL;
     }
 
     if (conn_prop) {
@@ -1960,16 +1862,11 @@ _prop_get_ipv6_ip6_privacy(NMDevice *self)
 }
 
 static const char *
-_prop_get_x_cloned_mac_address(NMDevice *    self,
-                               NMConnection *connection,
-                               gboolean      is_wifi,
-                               char **       out_addr)
+_prop_get_x_cloned_mac_address(NMDevice *self, NMConnection *connection, gboolean is_wifi)
 {
     NMSetting * setting;
     const char *addr = NULL;
 
-    nm_assert(out_addr && !*out_addr);
-
     setting = nm_connection_get_setting(connection,
                                         is_wifi ? NM_TYPE_SETTING_WIRELESS : NM_TYPE_SETTING_WIRED);
     if (setting) {
@@ -1978,7 +1875,7 @@ _prop_get_x_cloned_mac_address(NMDevice *    self,
     }
 
     if (!addr) {
-        gs_free char *a = NULL;
+        const char *a;
 
         a = nm_config_data_get_connection_default(
             NM_CONFIG_GET_DATA,
@@ -1993,36 +1890,28 @@ _prop_get_x_cloned_mac_address(NMDevice *    self,
                 NMSettingMacRandomization v;
 
                 /* for backward compatibility, read the deprecated wifi.mac-address-randomization setting. */
-                a = nm_config_data_get_connection_default(
+                v = nm_config_data_get_connection_default_int64(
                     NM_CONFIG_GET_DATA,
                     NM_CON_DEFAULT("wifi.mac-address-randomization"),
-                    self);
-                v = _nm_utils_ascii_str_to_int64(a,
-                                                 10,
-                                                 NM_SETTING_MAC_RANDOMIZATION_DEFAULT,
-                                                 NM_SETTING_MAC_RANDOMIZATION_ALWAYS,
-                                                 NM_SETTING_MAC_RANDOMIZATION_DEFAULT);
+                    self,
+                    NM_SETTING_MAC_RANDOMIZATION_DEFAULT,
+                    NM_SETTING_MAC_RANDOMIZATION_ALWAYS,
+                    NM_SETTING_MAC_RANDOMIZATION_DEFAULT);
                 if (v == NM_SETTING_MAC_RANDOMIZATION_ALWAYS)
                     addr = NM_CLONED_MAC_RANDOM;
             }
         } else if (NM_CLONED_MAC_IS_SPECIAL(a) || nm_utils_hwaddr_valid(a, ETH_ALEN))
-            addr = *out_addr = g_steal_pointer(&a);
+            addr = a;
     }
 
     return addr;
 }
 
 static const char *
-_prop_get_x_generate_mac_address_mask(NMDevice *    self,
-                                      NMConnection *connection,
-                                      gboolean      is_wifi,
-                                      char **       out_value)
+_prop_get_x_generate_mac_address_mask(NMDevice *self, NMConnection *connection, gboolean is_wifi)
 {
     NMSetting * setting;
-    const char *value = NULL;
-    char *      a;
-
-    nm_assert(out_value && !*out_value);
+    const char *value;
 
     setting = nm_connection_get_setting(connection,
                                         is_wifi ? NM_TYPE_SETTING_WIRELESS : NM_TYPE_SETTING_WIRED);
@@ -2035,15 +1924,11 @@ _prop_get_x_generate_mac_address_mask(NMDevice *    self,
             return value;
     }
 
-    a = nm_config_data_get_connection_default(
+    return nm_config_data_get_connection_default(
         NM_CONFIG_GET_DATA,
         is_wifi ? NM_CON_DEFAULT("wifi.generate-mac-address-mask")
                 : NM_CON_DEFAULT("ethernet.generate-mac-address-mask"),
         self);
-    if (!a)
-        return NULL;
-    *out_value = a;
-    return a;
 }
 
 /*****************************************************************************/
@@ -2272,6 +2157,113 @@ _ethtool_ring_set(NMDevice *        self,
 }
 
 static void
+_ethtool_pause_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
+{
+    gs_free NMEthtoolPauseState *pause = NULL;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(NM_IS_PLATFORM(platform));
+    nm_assert(ethtool_state);
+
+    pause = g_steal_pointer(&ethtool_state->pause);
+    if (!pause)
+        return;
+
+    if (!nm_platform_ethtool_set_pause(platform, ethtool_state->ifindex, pause))
+        _LOGW(LOGD_DEVICE, "ethtool: failure resetting one or more pause settings");
+    else
+        _LOGD(LOGD_DEVICE, "ethtool: pause settings successfully reset");
+}
+
+static void
+_ethtool_pause_set(NMDevice *        self,
+                   NMPlatform *      platform,
+                   EthtoolState *    ethtool_state,
+                   NMSettingEthtool *s_ethtool)
+{
+    NMEthtoolPauseState pause_old;
+    NMEthtoolPauseState pause_new;
+    GHashTable *        hash;
+    GHashTableIter      iter;
+    const char *        name;
+    GVariant *          variant;
+    gboolean            has_old       = FALSE;
+    NMTernary           pause_autoneg = NM_TERNARY_DEFAULT;
+    NMTernary           pause_rx      = NM_TERNARY_DEFAULT;
+    NMTernary           pause_tx      = NM_TERNARY_DEFAULT;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(NM_IS_PLATFORM(platform));
+    nm_assert(NM_IS_SETTING_ETHTOOL(s_ethtool));
+    nm_assert(ethtool_state);
+    nm_assert(!ethtool_state->pause);
+
+    hash = _nm_setting_option_hash(NM_SETTING(s_ethtool), FALSE);
+    if (!hash)
+        return;
+
+    g_hash_table_iter_init(&iter, hash);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) {
+        NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name);
+
+        if (!nm_ethtool_id_is_pause(ethtool_id))
+            continue;
+
+        nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN));
+
+        if (!has_old) {
+            if (!nm_platform_ethtool_get_link_pause(platform, ethtool_state->ifindex, &pause_old)) {
+                _LOGW(LOGD_DEVICE,
+                      "ethtool: failure setting pause options (cannot read "
+                      "existing setting)");
+                return;
+            }
+            has_old = TRUE;
+        }
+
+        switch (ethtool_id) {
+        case NM_ETHTOOL_ID_PAUSE_AUTONEG:
+            pause_autoneg = g_variant_get_boolean(variant);
+            break;
+        case NM_ETHTOOL_ID_PAUSE_RX:
+            pause_rx = g_variant_get_boolean(variant);
+            break;
+        case NM_ETHTOOL_ID_PAUSE_TX:
+            pause_tx = g_variant_get_boolean(variant);
+            break;
+        default:
+            nm_assert_not_reached();
+        }
+    }
+
+    if (!has_old)
+        return;
+
+    if (pause_rx != NM_TERNARY_DEFAULT || pause_tx != NM_TERNARY_DEFAULT) {
+        /* this implies to explicitly disable autoneg. */
+        nm_assert(pause_autoneg != NM_TERNARY_TRUE);
+        pause_autoneg = NM_TERNARY_FALSE;
+    }
+
+    pause_new = pause_old;
+    if (pause_autoneg != NM_TERNARY_DEFAULT)
+        pause_new.autoneg = !!pause_autoneg;
+    if (pause_rx != NM_TERNARY_DEFAULT)
+        pause_new.rx = !!pause_rx;
+    if (pause_tx != NM_TERNARY_DEFAULT)
+        pause_new.tx = !!pause_tx;
+
+    ethtool_state->pause = nm_memdup(&pause_old, sizeof(pause_old));
+
+    if (!nm_platform_ethtool_set_pause(platform, ethtool_state->ifindex, &pause_new)) {
+        _LOGW(LOGD_DEVICE, "ethtool: failure setting pause settings");
+        return;
+    }
+
+    _LOGD(LOGD_DEVICE, "ethtool: pause settings successfully set");
+}
+
+static void
 _ethtool_state_reset(NMDevice *self)
 {
     NMPlatform *     platform           = nm_device_get_platform(self);
@@ -2287,6 +2279,8 @@ _ethtool_state_reset(NMDevice *self)
         _ethtool_coalesce_reset(self, platform, ethtool_state);
     if (ethtool_state->ring)
         _ethtool_ring_reset(self, platform, ethtool_state);
+    if (ethtool_state->pause)
+        _ethtool_pause_reset(self, platform, ethtool_state);
 }
 
 static void
@@ -2320,8 +2314,10 @@ _ethtool_state_set(NMDevice *self)
     _ethtool_features_set(self, platform, ethtool_state, s_ethtool);
     _ethtool_coalesce_set(self, platform, ethtool_state, s_ethtool);
     _ethtool_ring_set(self, platform, ethtool_state, s_ethtool);
+    _ethtool_pause_set(self, platform, ethtool_state, s_ethtool);
 
-    if (ethtool_state->features || ethtool_state->coalesce || ethtool_state->ring)
+    if (ethtool_state->features || ethtool_state->coalesce || ethtool_state->ring
+        || ethtool_state->pause)
         priv->ethtool_state = g_steal_pointer(&ethtool_state);
 }
 
@@ -2520,15 +2516,6 @@ applied_config_reset_nameservers(AppliedConfig *config)
 
 /*****************************************************************************/
 
-static NM_UTILS_LOOKUP_STR_DEFINE(
-    _sys_iface_state_to_str,
-    NMDeviceSysIfaceState,
-    NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, "external"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_ASSUME, "assume"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_MANAGED, "managed"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_REMOVED, "removed"), );
-
 NMDeviceSysIfaceState
 nm_device_sys_iface_state_get(NMDevice *self)
 {
@@ -2567,8 +2554,8 @@ nm_device_sys_iface_state_set(NMDevice *self, NMDeviceSysIfaceState sys_iface_st
     if (priv->sys_iface_state != sys_iface_state) {
         _LOGT(LOGD_DEVICE,
               "sys-iface-state: %s -> %s",
-              _sys_iface_state_to_str(priv->sys_iface_state),
-              _sys_iface_state_to_str(sys_iface_state));
+              nm_device_sys_iface_state_to_str(priv->sys_iface_state),
+              nm_device_sys_iface_state_to_str(sys_iface_state));
         priv->sys_iface_state_ = sys_iface_state;
     }
 
@@ -2599,6 +2586,42 @@ _active_connection_set_state_flags(NMDevice *self, NMActivationStateFlags flags)
 
 /*****************************************************************************/
 
+static gboolean
+set_interface_flags_full(NMDevice *             self,
+                         NMDeviceInterfaceFlags mask,
+                         NMDeviceInterfaceFlags interface_flags,
+                         gboolean               notify)
+{
+    NMDevicePrivate *      priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDeviceInterfaceFlags f;
+
+    nm_assert(!!mask);
+    nm_assert(!NM_FLAGS_ANY(mask, ~_NM_DEVICE_INTERFACE_FLAG_ALL));
+    nm_assert(!NM_FLAGS_ANY(interface_flags, ~mask));
+
+    f = (priv->interface_flags & ~mask) | (interface_flags & mask);
+
+    if (f == priv->interface_flags)
+        return FALSE;
+
+    priv->interface_flags = f;
+    if (notify)
+        _notify(self, PROP_INTERFACE_FLAGS);
+    return TRUE;
+}
+
+static gboolean
+set_interface_flags(NMDevice *             self,
+                    NMDeviceInterfaceFlags interface_flags,
+                    gboolean               set,
+                    gboolean               notify)
+{
+    return set_interface_flags_full(self,
+                                    interface_flags,
+                                    set ? interface_flags : NM_DEVICE_INTERFACE_FLAG_NONE,
+                                    notify);
+}
+
 void
 nm_device_assume_state_get(NMDevice *   self,
                            gboolean *   out_assume_state_guess_assume,
@@ -2809,23 +2832,72 @@ _add_capabilities(NMDevice *self, NMDeviceCapabilities capabilities)
 
 /*****************************************************************************/
 
-static NM_UTILS_LOOKUP_STR_DEFINE(_ip_state_to_string,
-                                  NMDeviceIPState,
-                                  NM_UTILS_LOOKUP_DEFAULT_WARN("unknown"),
-                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_NONE, "none"),
-                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_WAIT, "wait"),
-                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_CONF, "conf"),
-                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_DONE, "done"),
-                                  NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_FAIL, "fail"), );
+static gboolean
+ip_required_timeout_x(NMDevice *self, int addr_family)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    _LOGD(LOGD_CORE,
+          "required-timeout expired for IPv%c",
+          nm_utils_addr_family_to_char(addr_family));
+    nm_clear_g_source_inst(&priv->ip_req_timeout_source_x[NM_IS_IPv4(addr_family)]);
+    check_ip_state(self, FALSE, TRUE);
+    return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+ip_required_timeout_4(gpointer data)
+{
+    return ip_required_timeout_x(data, AF_INET);
+}
+
+static gboolean
+ip_required_timeout_6(gpointer data)
+{
+    return ip_required_timeout_x(data, AF_INET6);
+}
 
 static void
 _set_ip_state(NMDevice *self, int addr_family, NMDeviceIPState new_state)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
     const int        IS_IPv4 = NM_IS_IPv4(addr_family);
+    guint            timeout_msec;
+    int              v4;
 
     nm_assert_addr_family(addr_family);
 
+    if (new_state == NM_DEVICE_IP_STATE_CONF && !priv->ip_config_started) {
+        /* Start the required-timeout timers when one of IPv4/IPv6
+         * enters the CONF state. This means that if there is no carrier and
+         * ipv4.method=auto,ipv6.method=manual, the timeout for IPv4 will
+         * start as soon as connection is activated, even if DHCPv4 did not
+         * start yet.
+         */
+        priv->ip_config_started = TRUE;
+
+        for (v4 = 1; v4 >= 0; v4--) {
+            char buf[32];
+
+            nm_assert(!priv->ip_req_timeout_source_x[v4]);
+            if ((timeout_msec = _prop_get_ipvx_required_timeout(self, v4 ? AF_INET : AF_INET6))) {
+                _LOGD(LOGD_CORE,
+                      "required-timeout in %s msec for IPv%c",
+                      timeout_msec == G_MAXINT32 ? "∞" : nm_sprintf_buf(buf, "%u", timeout_msec),
+                      v4 ? '4' : '6');
+
+                if (timeout_msec == G_MAXINT32) {
+                    priv->ip_req_timeout_source_x[v4] = g_source_ref(nm_g_source_sentinel_get(0));
+                } else {
+                    priv->ip_req_timeout_source_x[v4] =
+                        nm_g_timeout_add_source(timeout_msec,
+                                                v4 ? ip_required_timeout_4 : ip_required_timeout_6,
+                                                self);
+                }
+            }
+        }
+    }
+
     if (priv->ip_state_x[IS_IPv4] == new_state)
         return;
 
@@ -2833,7 +2905,7 @@ _set_ip_state(NMDevice *self, int addr_family, NMDeviceIPState new_state)
           "ip%c-state: set to %d (%s)",
           nm_utils_addr_family_to_char(addr_family),
           (int) new_state,
-          _ip_state_to_string(new_state));
+          nm_device_ip_state_to_str(new_state));
 
     priv->ip_state_x_[IS_IPv4] = new_state;
 
@@ -2942,10 +3014,10 @@ nm_device_take_over_link(NMDevice *self, int ifindex, char **old_name, GError **
 
         /* Rename the link to the device ifname */
         if (up)
-            nm_platform_link_set_down(platform, ifindex);
+            nm_platform_link_change_flags(platform, ifindex, IFF_UP, FALSE);
         success = nm_platform_link_set_name(platform, ifindex, nm_device_get_iface(self));
         if (up)
-            nm_platform_link_set_up(platform, ifindex, NULL);
+            nm_platform_link_change_flags(platform, ifindex, IFF_UP, TRUE);
 
         if (!success) {
             nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "failure renaming link %d", ifindex);
@@ -3073,7 +3145,7 @@ _set_ip_ifindex(NMDevice *self, int ifindex, const char *ifname)
             nm_platform_link_set_user_ipv6ll_enabled(platform, priv->ip_ifindex, TRUE);
 
         if (!nm_platform_link_is_up(platform, priv->ip_ifindex))
-            nm_platform_link_set_up(platform, priv->ip_ifindex, NULL);
+            nm_platform_link_change_flags(platform, priv->ip_ifindex, IFF_UP, TRUE);
     }
 
     /* We don't care about any saved values from the old iface */
@@ -4865,7 +4937,6 @@ void
 nm_device_update_dynamic_ip_setup(NMDevice *self)
 {
     NMDevicePrivate *priv;
-    GError *         error = NULL;
 
     g_return_if_fail(NM_IS_DEVICE(self));
 
@@ -4898,17 +4969,6 @@ nm_device_update_dynamic_ip_setup(NMDevice *self)
     if (priv->dnsmasq_manager) {
         /* FIXME: todo */
     }
-
-    if (priv->lldp_listener && nm_lldp_listener_is_running(priv->lldp_listener)) {
-        nm_lldp_listener_stop(priv->lldp_listener);
-        if (!nm_lldp_listener_start(priv->lldp_listener, nm_device_get_ifindex(self), &error)) {
-            _LOGD(LOGD_DEVICE,
-                  "LLDP listener %p could not be restarted: %s",
-                  priv->lldp_listener,
-                  error->message);
-            g_clear_error(&error);
-        }
-    }
 }
 
 /*****************************************************************************/
@@ -5025,18 +5085,12 @@ nm_device_set_carrier(NMDevice *self, gboolean carrier)
 
     if (NM_FLAGS_ALL(priv->capabilities,
                      NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_NONSTANDARD_CARRIER)) {
-        if (carrier)
-            priv->interface_flags |= NM_DEVICE_INTERFACE_FLAG_CARRIER;
-        else
-            priv->interface_flags &= ~NM_DEVICE_INTERFACE_FLAG_CARRIER;
-        notify_flags = TRUE;
+        notify_flags = set_interface_flags(self, NM_DEVICE_INTERFACE_FLAG_CARRIER, carrier, FALSE);
     }
 
     priv->carrier = carrier;
-    if (notify_flags)
-        nm_gobject_notify_together(self, PROP_CARRIER, PROP_INTERFACE_FLAGS);
-    else
-        _notify(self, PROP_CARRIER);
+
+    nm_gobject_notify_together(self, PROP_CARRIER, notify_flags ? PROP_INTERFACE_FLAGS : PROP_0);
 
     if (priv->carrier) {
         _LOGI(LOGD_DEVICE, "carrier: link connected");
@@ -5215,11 +5269,11 @@ ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
         if (addr->plen != 64)
             continue;
 
-        lifetime = nm_utils_lifetime_get(addr->timestamp,
-                                         addr->lifetime,
-                                         addr->preferred,
-                                         NM_NDISC_EXPIRY_BASE_TIMESTAMP / 1000,
-                                         &preferred);
+        lifetime = nmp_utils_lifetime_get(addr->timestamp,
+                                          addr->lifetime,
+                                          addr->preferred,
+                                          NM_NDISC_EXPIRY_BASE_TIMESTAMP / 1000,
+                                          &preferred);
         if (!lifetime)
             continue;
 
@@ -5271,6 +5325,8 @@ device_update_interface_flags(NMDevice *self, const NMPlatformLink *plink)
         flags |= NM_DEVICE_INTERFACE_FLAG_UP;
     if (plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_LOWER_UP))
         flags |= NM_DEVICE_INTERFACE_FLAG_LOWER_UP;
+    if (plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_PROMISC))
+        flags |= NM_DEVICE_INTERFACE_FLAG_PROMISC;
 
     if (NM_FLAGS_ALL(priv->capabilities,
                      NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_NONSTANDARD_CARRIER)) {
@@ -5281,10 +5337,12 @@ device_update_interface_flags(NMDevice *self, const NMPlatformLink *plink)
             flags |= NM_DEVICE_INTERFACE_FLAG_CARRIER;
     }
 
-    if (flags != priv->interface_flags) {
-        priv->interface_flags = flags;
-        _notify(self, PROP_INTERFACE_FLAGS);
-    }
+    set_interface_flags_full(self,
+                             NM_DEVICE_INTERFACE_FLAG_UP | NM_DEVICE_INTERFACE_FLAG_LOWER_UP
+                                 | NM_DEVICE_INTERFACE_FLAG_CARRIER
+                                 | NM_DEVICE_INTERFACE_FLAG_PROMISC,
+                             flags,
+                             TRUE);
 }
 
 static gboolean
@@ -5891,16 +5949,19 @@ sriov_op_queue(NMDevice *              self,
 static void
 device_init_static_sriov_num_vfs(NMDevice *self)
 {
-    NMDevicePrivate *priv  = NM_DEVICE_GET_PRIVATE(self);
-    gs_free char *   value = NULL;
-    int              num_vfs;
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     if (priv->ifindex > 0 && nm_device_has_capability(self, NM_DEVICE_CAP_SRIOV)) {
-        value   = nm_config_data_get_device_config(NM_CONFIG_GET_DATA,
-                                                 NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
-                                                 self,
-                                                 NULL);
-        num_vfs = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXINT32, -1);
+        int num_vfs;
+
+        num_vfs = nm_config_data_get_device_config_int64(NM_CONFIG_GET_DATA,
+                                                         NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
+                                                         self,
+                                                         10,
+                                                         0,
+                                                         G_MAXINT32,
+                                                         -1,
+                                                         -1);
         if (num_vfs >= 0)
             sriov_op_queue(self, num_vfs, NM_OPTION_BOOL_DEFAULT, NULL, NULL);
     }
@@ -6618,6 +6679,7 @@ check_ip_state(NMDevice *self, gboolean may_fail, gboolean full_state_update)
     gboolean           ip4_disabled = FALSE, ip6_disabled = FALSE;
     NMSettingIPConfig *s_ip4, *s_ip6;
     NMDeviceState      state;
+    int                IS_IPv4;
 
     if (full_state_update && nm_device_get_state(self) != NM_DEVICE_STATE_IP_CONFIG)
         return;
@@ -6647,6 +6709,13 @@ check_ip_state(NMDevice *self, gboolean may_fail, gboolean full_state_update)
         return;
     }
 
+    for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
+        if (priv->ip_state_x[IS_IPv4] == NM_DEVICE_IP_STATE_CONF
+            && priv->ip_req_timeout_source_x[IS_IPv4]) {
+            return;
+        }
+    }
+
     if ((priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL
          || (ip4_disabled && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE))
         && (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL
@@ -7170,6 +7239,16 @@ _get_maybe_ipv6_disabled(NMDevice *self)
     return (nm_platform_sysctl_get_int32(platform, NMP_SYSCTL_PATHID_ABSOLUTE(path), 0) == 0);
 }
 
+/*
+ * nm_device_generate_connection:
+ *
+ * Generates a connection from an existing interface.
+ *
+ * If the device doesn't have an IP configuration and it's not a port or a
+ * controller, then no connection gets generated and the function returns
+ * %NULL. In such case, @maybe_later is set to %TRUE if a connection can be
+ * generated later when an IP address is assigned to the interface.
+ */
 NMConnection *
 nm_device_generate_connection(NMDevice *self,
                               NMDevice *master,
@@ -7214,7 +7293,7 @@ nm_device_generate_connection(NMDevice *self,
 
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_UUID,
-                 nm_utils_uuid_generate_buf(uuid),
+                 nm_uuid_generate_random_str_arr(uuid),
                  NM_SETTING_CONNECTION_ID,
                  ifname,
                  NM_SETTING_CONNECTION_AUTOCONNECT,
@@ -7426,6 +7505,8 @@ check_connection_compatible(NMDevice *self, NMConnection *connection, GError **e
     gs_free char *        conn_iface = NULL;
     NMDeviceClass *       klass;
     NMSettingMatch *      s_match;
+    const GSList *        specs;
+    gboolean              has_match = FALSE;
 
     klass = NM_DEVICE_GET_CLASS(self);
     if (klass->connection_type_check_compatible) {
@@ -7466,11 +7547,10 @@ check_connection_compatible(NMDevice *self, NMConnection *connection, GError **e
     s_match = (NMSettingMatch *) nm_connection_get_setting(connection, NM_TYPE_SETTING_MATCH);
     if (s_match) {
         const char *const *patterns;
-        const char *       device_driver;
         guint              num_patterns = 0;
 
         patterns = nm_setting_match_get_interface_names(s_match, &num_patterns);
-        if (!nm_wildcard_match_check(device_iface, patterns, num_patterns)) {
+        if (num_patterns > 0 && !nm_wildcard_match_check(device_iface, patterns, num_patterns)) {
             nm_utils_error_set_literal(error,
                                        NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
                                        "device does not satisfy match.interface-name property");
@@ -7485,9 +7565,9 @@ check_connection_compatible(NMDevice *self, NMConnection *connection, GError **e
                                                     error))
             return FALSE;
 
-        device_driver = nm_device_get_driver(self);
-        patterns      = nm_setting_match_get_drivers(s_match, &num_patterns);
-        if (!nm_wildcard_match_check(device_driver, patterns, num_patterns)) {
+        patterns = nm_setting_match_get_drivers(s_match, &num_patterns);
+        if (num_patterns > 0
+            && !nm_wildcard_match_check(nm_device_get_driver(self), patterns, num_patterns)) {
             nm_utils_error_set_literal(error,
                                        NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
                                        "device does not satisfy match.driver property");
@@ -7495,7 +7575,7 @@ check_connection_compatible(NMDevice *self, NMConnection *connection, GError **e
         }
 
         patterns = nm_setting_match_get_paths(s_match, &num_patterns);
-        if (!nm_wildcard_match_check(priv->path, patterns, num_patterns)) {
+        if (num_patterns > 0 && !nm_wildcard_match_check(priv->path, patterns, num_patterns)) {
             nm_utils_error_set_literal(error,
                                        NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
                                        "device does not satisfy match.path property");
@@ -7503,6 +7583,15 @@ check_connection_compatible(NMDevice *self, NMConnection *connection, GError **e
         }
     }
 
+    specs =
+        nm_config_data_get_device_allowed_connections_specs(NM_CONFIG_GET_DATA, self, &has_match);
+    if (has_match && !nm_utils_connection_match_spec_list(connection, specs, FALSE)) {
+        nm_utils_error_set_literal(error,
+                                   NM_UTILS_ERROR_CONNECTION_AVAILABLE_DISALLOWED,
+                                   "device configuration doesn't allow this connection");
+        return FALSE;
+    }
+
     return TRUE;
 }
 
@@ -7569,7 +7658,7 @@ nm_device_check_slave_connection_compatible(NMDevice *self, NMConnection *slave)
  *
  * Returns: %TRUE if the device is capable of assuming connections, %FALSE if not
  */
-static gboolean
+gboolean
 nm_device_can_assume_connections(NMDevice *self)
 {
     return !!NM_DEVICE_GET_CLASS(self)->update_connection;
@@ -7924,14 +8013,6 @@ master_ready_cb(NMActiveConnection *active, GParamSpec *pspec, NMDevice *self)
         nm_device_activate_schedule_stage1_device_prepare(self, FALSE);
 }
 
-static void
-lldp_neighbors_changed(NMLldpListener *lldp_listener, GParamSpec *pspec, gpointer user_data)
-{
-    NMDevice *self = NM_DEVICE(user_data);
-
-    _notify(self, PROP_LLDP_NEIGHBORS);
-}
-
 static NMPlatformVF *
 sriov_vf_config_to_platform(NMDevice *self, NMSriovVF *vf, GError **error)
 {
@@ -8219,38 +8300,56 @@ act_stage2_config(NMDevice *self, NMDeviceStateReason *out_failure_reason)
 }
 
 static void
-lldp_init(NMDevice *self, gboolean restart)
+_lldp_neighbors_changed_cb(NMLldpListener *lldp_listener, gpointer user_data)
+{
+    _notify(user_data, PROP_LLDP_NEIGHBORS);
+}
+
+static void
+lldp_setup(NMDevice *self, NMTernary enabled)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    int              ifindex;
+    gboolean         notify_lldp_neighbors  = FALSE;
+    gboolean         notify_interface_flags = FALSE;
 
-    if (priv->ifindex > 0 && _prop_get_connection_lldp(self)) {
-        gs_free_error GError *error = NULL;
+    ifindex = nm_device_get_ifindex(self);
 
-        if (priv->lldp_listener) {
-            if (restart && nm_lldp_listener_is_running(priv->lldp_listener))
-                nm_lldp_listener_stop(priv->lldp_listener);
-        } else {
-            priv->lldp_listener = nm_lldp_listener_new();
-            g_signal_connect(priv->lldp_listener,
-                             "notify::" NM_LLDP_LISTENER_NEIGHBORS,
-                             G_CALLBACK(lldp_neighbors_changed),
-                             self);
-        }
+    if (ifindex <= 0)
+        enabled = FALSE;
+    else if (enabled == NM_TERNARY_DEFAULT)
+        enabled = _prop_get_connection_lldp(self);
 
-        if (!nm_lldp_listener_is_running(priv->lldp_listener)) {
-            if (nm_lldp_listener_start(priv->lldp_listener, nm_device_get_ifindex(self), &error))
-                _LOGD(LOGD_DEVICE, "LLDP listener %p started", priv->lldp_listener);
-            else {
-                _LOGD(LOGD_DEVICE,
-                      "LLDP listener %p could not be started: %s",
-                      priv->lldp_listener,
-                      error->message);
-            }
+    if (priv->lldp_listener) {
+        if (!enabled || nm_lldp_listener_get_ifindex(priv->lldp_listener) != ifindex) {
+            nm_clear_pointer(&priv->lldp_listener, nm_lldp_listener_destroy);
+            notify_lldp_neighbors = TRUE;
         }
-    } else {
-        if (priv->lldp_listener)
-            nm_lldp_listener_stop(priv->lldp_listener);
     }
+
+    if (enabled && !priv->lldp_listener) {
+        gs_free_error GError *error = NULL;
+
+        priv->lldp_listener =
+            nm_lldp_listener_new(ifindex, _lldp_neighbors_changed_cb, self, &error);
+        if (!priv->lldp_listener) {
+            /* This really shouldn't happen. It's likely a bug. Investigate when this happens! */
+            _LOGW(LOGD_DEVICE,
+                  "LLDP listener for ifindex %d could not be started: %s",
+                  ifindex,
+                  error->message);
+        } else
+            notify_lldp_neighbors = TRUE;
+    }
+
+    notify_interface_flags = set_interface_flags(self,
+                                                 NM_DEVICE_INTERFACE_FLAG_LLDP_CLIENT_ENABLED,
+                                                 !!priv->lldp_listener,
+                                                 FALSE);
+
+    nm_gobject_notify_together(self,
+                               notify_lldp_neighbors ? PROP_LLDP_NEIGHBORS : PROP_0,
+                               notify_interface_flags ? PROP_INTERFACE_FLAGS : PROP_0);
 }
 
 /* set-mode can be:
@@ -8401,8 +8500,10 @@ activate_stage2_device_config(NMDevice *self)
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
     NMDeviceClass *  klass;
     NMActStageReturn ret;
+    NMSettingWired * s_wired;
     gboolean         no_firmware = FALSE;
     CList *          iter;
+    NMTernary        accept_all_mac_addresses;
 
     nm_device_state_changed(self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
@@ -8460,7 +8561,30 @@ activate_stage2_device_config(NMDevice *self)
             nm_device_queue_recheck_assume(info->slave);
     }
 
-    lldp_init(self, TRUE);
+    s_wired = nm_device_get_applied_setting(self, NM_TYPE_SETTING_WIRED);
+    accept_all_mac_addresses =
+        s_wired ? nm_setting_wired_get_accept_all_mac_addresses(s_wired) : NM_TERNARY_DEFAULT;
+    if (accept_all_mac_addresses != NM_TERNARY_DEFAULT) {
+        int ifindex = nm_device_get_ip_ifindex(self);
+
+        if (ifindex > 0) {
+            int ifi_flags =
+                nm_platform_link_get_ifi_flags(nm_device_get_platform(self), ifindex, IFF_PROMISC);
+
+            if (ifi_flags >= 0 && ((!!ifi_flags) != (!!accept_all_mac_addresses))) {
+                nm_platform_link_change_flags(nm_device_get_platform(self),
+                                              ifindex,
+                                              IFF_PROMISC,
+                                              !!accept_all_mac_addresses);
+                if (priv->promisc_reset == NM_OPTION_BOOL_DEFAULT)
+                    priv->promisc_reset = !accept_all_mac_addresses;
+            }
+        }
+    }
+
+    lldp_setup(self, NM_TERNARY_DEFAULT);
+
+    _commit_mtu(self, NULL);
 
     nm_device_activate_schedule_stage3_ip_config_start(self);
 }
@@ -8877,6 +9001,21 @@ ensure_con_ip_config(NMDevice *self, int addr_family)
 
 /*****************************************************************************/
 
+static const char *
+_device_get_dhcp_anycast_address(NMDevice *self)
+{
+    NMDeviceClass *klass;
+
+    nm_assert(NM_IS_DEVICE(self));
+
+    klass = NM_DEVICE_GET_CLASS(self);
+
+    if (klass->get_dhcp_anycast_address)
+        return klass->get_dhcp_anycast_address(self);
+
+    return NULL;
+}
+
 static void
 dhcp4_cleanup(NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
@@ -9234,7 +9373,7 @@ dhcp4_fail(NMDevice *self, NMDhcpState dhcp_state)
 
     _LOGD(LOGD_DHCP4,
           "DHCPv4 failed (ip_state %s, was_active %d)",
-          _ip_state_to_string(priv->ip_state_4),
+          nm_device_ip_state_to_str(priv->ip_state_4),
           priv->dhcp_data_4.was_active);
 
     /* The client is always left running after a failure. */
@@ -9382,12 +9521,13 @@ dhcp4_start(NMDevice *self)
     gs_unref_bytes GBytes *hwaddr                  = NULL;
     gs_unref_bytes GBytes *bcast_hwaddr            = NULL;
     gs_unref_bytes GBytes *client_id               = NULL;
-    gs_free char *         mud_url_free            = NULL;
     NMConnection *         connection;
     NMSettingConnection *  s_con;
     GError *               error = NULL;
     const NMPlatformLink * pllink;
     const char *const *    reject_servers;
+    gboolean               request_broadcast;
+    const char *           str;
 
     connection = nm_device_get_applied_connection(self);
     g_return_val_if_fail(connection, FALSE);
@@ -9401,10 +9541,22 @@ dhcp4_start(NMDevice *self)
     nm_dbus_object_clear_and_unexport(&priv->dhcp_data_4.config);
     priv->dhcp_data_4.config = nm_dhcp_config_new(AF_INET);
 
+    request_broadcast = FALSE;
+
     pllink = nm_platform_link_get(nm_device_get_platform(self), nm_device_get_ip_ifindex(self));
     if (pllink) {
         hwaddr       = nmp_link_address_get_as_bytes(&pllink->l_address);
         bcast_hwaddr = nmp_link_address_get_as_bytes(&pllink->l_broadcast);
+
+        str = nmp_object_link_udev_device_get_property_value(NMP_OBJECT_UP_CAST(pllink),
+                                                             "ID_NET_DHCP_BROADCAST");
+        if (str && _nm_utils_ascii_str_to_bool(str, FALSE)) {
+            /* Use the device property ID_NET_DHCP_BROADCAST setting, which may be set for interfaces
+             * requiring that the DHCPOFFER message is being broadcast because they can't handle unicast
+             * messages while not fully configured.
+             */
+            request_broadcast = TRUE;
+        }
     }
 
     client_id = _prop_get_ipv4_dhcp_client_id(self, connection, hwaddr);
@@ -9413,28 +9565,29 @@ dhcp4_start(NMDevice *self)
     reject_servers = nm_setting_ip_config_get_dhcp_reject_servers(s_ip4, NULL);
 
     g_warn_if_fail(priv->dhcp_data_4.client == NULL);
-    priv->dhcp_data_4.client =
-        nm_dhcp_manager_start_ip4(nm_dhcp_manager_get(),
-                                  nm_netns_get_multi_idx(nm_device_get_netns(self)),
-                                  nm_device_get_ip_iface(self),
-                                  nm_device_get_ip_ifindex(self),
-                                  hwaddr,
-                                  bcast_hwaddr,
-                                  nm_connection_get_uuid(connection),
-                                  nm_device_get_route_table(self, AF_INET),
-                                  nm_device_get_route_metric(self, AF_INET),
-                                  nm_setting_ip_config_get_dhcp_send_hostname(s_ip4),
-                                  nm_setting_ip_config_get_dhcp_hostname(s_ip4),
-                                  nm_setting_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip4)),
-                                  _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET),
-                                  _prop_get_connection_mud_url(self, s_con, &mud_url_free),
-                                  client_id,
-                                  _prop_get_ipvx_dhcp_timeout(self, AF_INET),
-                                  priv->dhcp_anycast_address,
-                                  NULL,
-                                  vendor_class_identifier,
-                                  reject_servers,
-                                  &error);
+    priv->dhcp_data_4.client = nm_dhcp_manager_start_ip4(
+        nm_dhcp_manager_get(),
+        nm_netns_get_multi_idx(nm_device_get_netns(self)),
+        nm_device_get_ip_iface(self),
+        nm_device_get_ip_ifindex(self),
+        hwaddr,
+        bcast_hwaddr,
+        nm_connection_get_uuid(connection),
+        nm_device_get_route_table(self, AF_INET),
+        nm_device_get_route_metric(self, AF_INET),
+        request_broadcast ? NM_DHCP_CLIENT_FLAGS_REQUEST_BROADCAST : NM_DHCP_CLIENT_FLAGS_NONE,
+        nm_setting_ip_config_get_dhcp_send_hostname(s_ip4),
+        nm_setting_ip_config_get_dhcp_hostname(s_ip4),
+        nm_setting_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip4)),
+        _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET),
+        _prop_get_connection_mud_url(self, s_con),
+        client_id,
+        _prop_get_ipvx_dhcp_timeout(self, AF_INET),
+        _device_get_dhcp_anycast_address(self),
+        NULL,
+        vendor_class_identifier,
+        reject_servers,
+        &error);
     if (!priv->dhcp_data_4.client) {
         _LOGW(LOGD_DHCP4, "failure to start DHCP: %s", error->message);
         g_clear_error(&error);
@@ -9665,7 +9818,7 @@ dhcp6_fail(NMDevice *self, NMDhcpState dhcp_state)
 
     _LOGD(LOGD_DHCP6,
           "DHCPv6 failed (ip_state %s, was_active %d)",
-          _ip_state_to_string(priv->ip_state_6),
+          nm_device_ip_state_to_str(priv->ip_state_6),
           priv->dhcp_data_6.was_active);
 
     /* The client is always left running after a failure. */
@@ -9831,12 +9984,12 @@ dhcp6_start_with_link_ready(NMDevice *self, NMConnection *connection)
     gs_unref_bytes GBytes *     duid         = NULL;
     gboolean                    enforce_duid = FALSE;
     const NMPlatformLink *      pllink;
-    gs_free char *              mud_url_free = NULL;
-    GError *                    error        = NULL;
+    GError *                    error = NULL;
     guint32                     iaid;
     gboolean                    iaid_explicit;
     NMSettingConnection *       s_con;
     const NMPlatformIP6Address *ll_addr = NULL;
+    int                         ip_ifindex;
 
     g_return_val_if_fail(connection, FALSE);
 
@@ -9846,9 +9999,9 @@ dhcp6_start_with_link_ready(NMDevice *self, NMConnection *connection)
     nm_assert(s_con);
 
     if (priv->ext_ip6_config_captured) {
-        ll_addr = nm_ip6_config_find_first_address(priv->ext_ip6_config_captured,
-                                                   NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
-                                                       | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL);
+        ll_addr = nm_ip_config_find_first_address(NM_IP_CONFIG(priv->ext_ip6_config_captured),
+                                                  NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+                                                      | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL);
     }
 
     if (!ll_addr) {
@@ -9856,7 +10009,13 @@ dhcp6_start_with_link_ready(NMDevice *self, NMConnection *connection)
         return FALSE;
     }
 
-    pllink = nm_platform_link_get(nm_device_get_platform(self), nm_device_get_ip_ifindex(self));
+    ip_ifindex = nm_device_get_ip_ifindex(self);
+    if (ip_ifindex <= 0) {
+        _LOGD(LOGD_DHCP6, "can't start DHCPv6: interface is gone");
+        return FALSE;
+    }
+
+    pllink = nm_platform_link_get(nm_device_get_platform(self), ip_ifindex);
     if (pllink)
         hwaddr = nmp_link_address_get_as_bytes(&pllink->l_address);
 
@@ -9867,22 +10026,23 @@ dhcp6_start_with_link_ready(NMDevice *self, NMConnection *connection)
         nm_dhcp_manager_get(),
         nm_device_get_multi_index(self),
         nm_device_get_ip_iface(self),
-        nm_device_get_ip_ifindex(self),
+        ip_ifindex,
         &ll_addr->address,
         nm_connection_get_uuid(connection),
         nm_device_get_route_table(self, AF_INET6),
         nm_device_get_route_metric(self, AF_INET6),
+        (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? NM_DHCP_CLIENT_FLAGS_INFO_ONLY
+                                                            : NM_DHCP_CLIENT_FLAGS_NONE,
         nm_setting_ip_config_get_dhcp_send_hostname(s_ip6),
         nm_setting_ip_config_get_dhcp_hostname(s_ip6),
         _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET6),
-        _prop_get_connection_mud_url(self, s_con, &mud_url_free),
+        _prop_get_connection_mud_url(self, s_con),
         duid,
         enforce_duid,
         iaid,
         iaid_explicit,
         _prop_get_ipvx_dhcp_timeout(self, AF_INET6),
-        priv->dhcp_anycast_address,
-        (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
+        _device_get_dhcp_anycast_address(self),
         nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip6)),
         priv->dhcp6.needed_prefixes,
         &error);
@@ -10088,9 +10248,9 @@ linklocal6_check_complete(NMDevice *self)
     }
 
     if (!priv->ext_ip6_config_captured
-        || !nm_ip6_config_find_first_address(priv->ext_ip6_config_captured,
-                                             NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
-                                                 | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL)) {
+        || !nm_ip_config_find_first_address(NM_IP_CONFIG(priv->ext_ip6_config_captured),
+                                            NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+                                                | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL)) {
         /* we don't have a non-tentative link local address yet. Wait longer. */
         return;
     }
@@ -10136,10 +10296,10 @@ check_and_add_ipv6ll_addr(NMDevice *self)
         return;
 
     if (priv->ext_ip6_config_captured
-        && nm_ip6_config_find_first_address(priv->ext_ip6_config_captured,
-                                            NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
-                                                | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL
-                                                | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE)) {
+        && nm_ip_config_find_first_address(NM_IP_CONFIG(priv->ext_ip6_config_captured),
+                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+                                               | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL
+                                               | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE)) {
         /* Already have an LL address, nothing to do */
         return;
     }
@@ -10209,9 +10369,9 @@ linklocal6_start(NMDevice *self)
     nm_clear_g_source(&priv->linklocal6_timeout_id);
 
     if (priv->ext_ip6_config_captured
-        && nm_ip6_config_find_first_address(priv->ext_ip6_config_captured,
-                                            NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
-                                                | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL))
+        && nm_ip_config_find_first_address(NM_IP_CONFIG(priv->ext_ip6_config_captured),
+                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+                                               | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL))
         return TRUE;
 
     _LOGD(LOGD_DEVICE,
@@ -10456,9 +10616,9 @@ _commit_mtu(NMDevice *self, const NMIP4Config *config)
             _LOGT(LOGD_DEVICE,
                   "mtu: value %u from source '%s' (%u), current source '%s' (%u)%s",
                   (guint) mtu,
-                  mtu_source_to_str(source),
+                  nm_device_mtu_source_to_str(source),
                   (guint) source,
-                  mtu_source_to_str(priv->mtu_source),
+                  nm_device_mtu_source_to_str(priv->mtu_source),
                   (guint) priv->mtu_source,
                   force ? " (forced)" : "");
         }
@@ -10522,10 +10682,30 @@ _commit_mtu(NMDevice *self, const NMIP4Config *config)
         }
     }
 
+#define _IP6_MTU_SYS()                                                                         \
+    ({                                                                                         \
+        if (!ip6_mtu_sysctl.initialized) {                                                     \
+            ip6_mtu_sysctl.value       = nm_device_sysctl_ip_conf_get_int_checked(self,        \
+                                                                            AF_INET6,    \
+                                                                            "mtu",       \
+                                                                            10,          \
+                                                                            0,           \
+                                                                            G_MAXUINT32, \
+                                                                            0);          \
+            ip6_mtu_sysctl.initialized = TRUE;                                                 \
+        }                                                                                      \
+        ip6_mtu_sysctl.value;                                                                  \
+    })
+
     if (mtu_desired && NM_DEVICE_GET_CLASS(self)->mtu_force_set && !priv->mtu_force_set_done) {
         priv->mtu_force_set_done = TRUE;
-
         if (mtu_desired == mtu_plat) {
+            if (!priv->mtu_initial && !priv->ip6_mtu_initial) {
+                /* before touching any of the MTU parameters, record the
+                 * original setting to restore on deactivation. */
+                priv->mtu_initial     = mtu_plat;
+                priv->ip6_mtu_initial = _IP6_MTU_SYS();
+            }
             mtu_plat--;
             if (NM_DEVICE_GET_CLASS(self)->set_platform_mtu(self, mtu_desired - 1)) {
                 _LOGD(LOGD_DEVICE, "mtu: force-set MTU to %u", mtu_desired - 1);
@@ -10544,20 +10724,6 @@ _commit_mtu(NMDevice *self, const NMIP4Config *config)
           ip6_mtu == ip6_mtu_orig ? "" : nm_sprintf_buf(sbuf2, " (was %u)", (guint) ip6_mtu_orig),
           ifindex);
 
-#define _IP6_MTU_SYS()                                                                         \
-    ({                                                                                         \
-        if (!ip6_mtu_sysctl.initialized) {                                                     \
-            ip6_mtu_sysctl.value       = nm_device_sysctl_ip_conf_get_int_checked(self,        \
-                                                                            AF_INET6,    \
-                                                                            "mtu",       \
-                                                                            10,          \
-                                                                            0,           \
-                                                                            G_MAXUINT32, \
-                                                                            0);          \
-            ip6_mtu_sysctl.initialized = TRUE;                                                 \
-        }                                                                                      \
-        ip6_mtu_sysctl.value;                                                                  \
-    })
     if ((mtu_desired && mtu_desired != mtu_plat) || (ip6_mtu && ip6_mtu != _IP6_MTU_SYS())) {
         gboolean anticipated_failure = FALSE;
 
@@ -10790,9 +10956,9 @@ ndisc_ra_timeout(NMNDisc *ndisc, NMDevice *self)
          *        addresses we find inside priv->ip_config_6.
          */
         if (priv->ip_config_6
-            && nm_ip6_config_find_first_address(priv->ip_config_6,
-                                                NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
-                                                    | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))
+            && nm_ip_config_find_first_address(NM_IP_CONFIG(priv->ip_config_6),
+                                               NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
+                                                   | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))
             nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
         else
             nm_device_activate_schedule_ip_config_timeout(self, AF_INET6);
@@ -11395,10 +11561,10 @@ activate_stage3_ip_config_start(NMDevice *self)
 }
 
 static void
-fw_change_zone_cb(NMFirewallManager *      firewall_manager,
-                  NMFirewallManagerCallId *call_id,
-                  GError *                 error,
-                  gpointer                 user_data)
+fw_change_zone_cb(NMFirewalldManager *      firewalld_manager,
+                  NMFirewalldManagerCallId *call_id,
+                  GError *                  error,
+                  gpointer                  user_data)
 {
     NMDevice *       self = user_data;
     NMDevicePrivate *priv;
@@ -11450,12 +11616,12 @@ fw_change_zone(NMDevice *self)
     nm_assert(s_con);
 
     if (priv->fw_call) {
-        nm_firewall_manager_cancel_call(priv->fw_call);
+        nm_firewalld_manager_cancel_call(priv->fw_call);
         nm_assert(!priv->fw_call);
     }
 
     if (G_UNLIKELY(!priv->fw_mgr))
-        priv->fw_mgr = g_object_ref(nm_firewall_manager_get());
+        priv->fw_mgr = g_object_ref(nm_firewalld_manager_get());
 
     zone = nm_setting_connection_get_zone(s_con);
 #if WITH_FIREWALLD_ZONE
@@ -11467,12 +11633,12 @@ fw_change_zone(NMDevice *self)
             zone = "nm-shared";
     }
 #endif
-    priv->fw_call = nm_firewall_manager_add_or_change_zone(priv->fw_mgr,
-                                                           nm_device_get_ip_iface(self),
-                                                           zone,
-                                                           FALSE, /* change zone */
-                                                           fw_change_zone_cb,
-                                                           self);
+    priv->fw_call = nm_firewalld_manager_add_or_change_zone(priv->fw_mgr,
+                                                            nm_device_get_ip_iface(self),
+                                                            zone,
+                                                            FALSE, /* change zone */
+                                                            fw_change_zone_cb,
+                                                            self);
 }
 
 /*
@@ -11632,7 +11798,7 @@ share_init(NMDevice *self, GError **error)
     }
 
     for (i = 0; i < G_N_ELEMENTS(modules); i++)
-        nm_utils_modprobe(NULL, FALSE, modules[i], NULL);
+        nmp_utils_modprobe(NULL, FALSE, modules[i], NULL);
 
     return TRUE;
 }
@@ -11648,7 +11814,7 @@ start_sharing(NMDevice *self, NMIP4Config *config, GError **error)
     NMConnection *              conn;
     NMSettingConnection *       s_con;
     gboolean                    announce_android_metered;
-    NMUtilsShareRules *         share_rules;
+    NMFirewallConfig *          firewall_config;
 
     g_return_val_if_fail(config, FALSE);
 
@@ -11673,11 +11839,9 @@ start_sharing(NMDevice *self, NMIP4Config *config, GError **error)
     req = nm_device_get_act_request(self);
     g_return_val_if_fail(req, FALSE);
 
-    share_rules = nm_utils_share_rules_new();
-
-    nm_utils_share_rules_add_all_rules(share_rules, ip_iface, ip4_addr->address, ip4_addr->plen);
+    firewall_config = nm_firewall_config_new(ip_iface, ip4_addr->address, ip4_addr->plen);
 
-    nm_act_request_set_shared(req, share_rules);
+    nm_act_request_set_shared(req, firewall_config);
 
     conn  = nm_act_request_get_applied_connection(req);
     s_con = nm_connection_get_setting_connection(conn);
@@ -11792,13 +11956,15 @@ activate_stage5_ip_config_result_x(NMDevice *self, int addr_family)
     req = nm_device_get_act_request(self);
     g_assert(req);
 
+    nm_clear_g_source_inst(&priv->ip_req_timeout_source_x[IS_IPv4]);
+
     /* 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);
+        nm_platform_link_change_flags(nm_device_get_platform(self), ip_ifindex, IFF_UP, TRUE);
         if (!nm_platform_link_is_up(nm_device_get_platform(self), ip_ifindex))
             _LOGW(LOGD_DEVICE,
                   "interface %s not up for IP configuration",
@@ -12660,7 +12826,7 @@ check_and_reapply_connection(NMDevice *    self,
     klass->reapply_connection(self, con_old, con_new);
 
     if (priv->state >= NM_DEVICE_STATE_CONFIG)
-        lldp_init(self, FALSE);
+        lldp_setup(self, NM_TERNARY_DEFAULT);
 
     if (priv->state >= NM_DEVICE_STATE_IP_CONFIG) {
         s_ip4_old = nm_connection_get_setting_ip4_config(con_old);
@@ -12891,9 +13057,10 @@ impl_device_get_applied_connection(NMDBusObject *                     obj,
         return;
     }
 
-    var_settings = nm_connection_to_dbus(applied_connection, NM_CONNECTION_SERIALIZE_NO_SECRETS);
+    var_settings =
+        nm_connection_to_dbus(applied_connection, NM_CONNECTION_SERIALIZE_WITH_NON_SECRET);
     if (!var_settings)
-        var_settings = g_variant_new_array(G_VARIANT_TYPE("{sa{sv}}"), NULL, 0);
+        var_settings = nm_g_variant_singleton_aLsaLsvII();
 
     g_dbus_method_invocation_return_value(
         invocation,
@@ -13568,7 +13735,7 @@ nm_device_set_ip_config(NMDevice *  self,
                                           NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
                                           NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
                                           NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
-                                          NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE,
+                                          NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET,
                                           "update-external",
                                           NULL);
         }
@@ -13660,18 +13827,24 @@ void
 nm_device_replace_vpn6_config(NMDevice *self, NMIP6Config *old, NMIP6Config *config)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDeviceState    state;
 
     nm_assert(!old || NM_IS_IP6_CONFIG(old));
+    nm_assert(!old || nm_ip6_config_get_ifindex(old) > 0);
+    nm_assert(!old || nm_device_get_ip_ifindex(self) == 0
+              || nm_device_get_ip_ifindex(self) == nm_ip6_config_get_ifindex(old));
     nm_assert(!config || NM_IS_IP6_CONFIG(config));
-    nm_assert(!old || nm_ip6_config_get_ifindex(old) == nm_device_get_ip_ifindex(self));
-    nm_assert(!config || nm_ip6_config_get_ifindex(config) == nm_device_get_ip_ifindex(self));
+    nm_assert(!config || nm_ip6_config_get_ifindex(config) > 0);
+    nm_assert(!config || nm_device_get_ip_ifindex(self) == nm_ip6_config_get_ifindex(config));
 
     if (!_replace_vpn_config_in_list(&priv->vpn_configs_6, (GObject *) old, (GObject *) config))
         return;
 
-    /* NULL to use existing configs */
-    if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
-        _LOGW(LOGD_IP6, "failed to set VPN routes for device");
+    state = nm_device_get_state(self);
+    if (state >= NM_DEVICE_STATE_IP_CONFIG && state <= NM_DEVICE_STATE_ACTIVATED) {
+        if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
+            _LOGW(LOGD_IP6, "failed to set VPN routes for device");
+    }
 }
 
 NMIP6Config *
@@ -13979,13 +14152,14 @@ nm_device_is_up(NMDevice *self)
 static gint64
 _get_carrier_wait_ms(NMDevice *self)
 {
-    gs_free char *value = NULL;
-
-    value = nm_config_data_get_device_config(NM_CONFIG_GET_DATA,
-                                             NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT,
-                                             self,
-                                             NULL);
-    return _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXINT32, CARRIER_WAIT_TIME_MS);
+    return nm_config_data_get_device_config_int64(NM_CONFIG_GET_DATA,
+                                                  NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT,
+                                                  self,
+                                                  10,
+                                                  0,
+                                                  G_MAXINT32,
+                                                  CARRIER_WAIT_TIME_MS,
+                                                  CARRIER_WAIT_TIME_MS);
 }
 
 gboolean
@@ -13995,6 +14169,7 @@ nm_device_bring_up(NMDevice *self, gboolean block, gboolean *no_firmware)
     gboolean             device_is_up = FALSE;
     NMDeviceCapabilities capabilities;
     int                  ifindex;
+    int                  r;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
 
@@ -14010,7 +14185,9 @@ nm_device_bring_up(NMDevice *self, gboolean block, gboolean *no_firmware)
     if (ifindex <= 0) {
         /* assume success. */
     } else {
-        if (!nm_platform_link_set_up(nm_device_get_platform(self), ifindex, no_firmware))
+        r = nm_platform_link_change_flags(nm_device_get_platform(self), ifindex, IFF_UP, TRUE);
+        NM_SET_OUT(no_firmware, (r == -NME_PL_NO_FIRMWARE));
+        if (r < 0)
             return FALSE;
     }
 
@@ -14097,7 +14274,7 @@ nm_device_take_down(NMDevice *self, gboolean block)
         return;
     }
 
-    if (!nm_platform_link_set_down(nm_device_get_platform(self), ifindex))
+    if (!nm_platform_link_change_flags(nm_device_get_platform(self), ifindex, IFF_UP, FALSE))
         return;
 
     device_is_up = nm_device_is_up(self);
@@ -14827,7 +15004,7 @@ _set_unmanaged_flags(NMDevice *          self,
           flags,
           NM_PRINT_FMT_QUOTED(allow_state_transition,
                               ", reason ",
-                              reason_to_string_a(reason),
+                              nm_device_state_reason_to_str_a(reason),
                               transition_state ? ", transition-state" : "",
                               ""));
 
@@ -15017,20 +15194,6 @@ nm_device_set_unmanaged_by_quitting(NMDevice *self)
 /*****************************************************************************/
 
 void
-nm_device_set_dhcp_anycast_address(NMDevice *self, const char *addr)
-{
-    NMDevicePrivate *priv;
-
-    g_return_if_fail(NM_IS_DEVICE(self));
-    g_return_if_fail(!addr || nm_utils_hwaddr_valid(addr, ETH_ALEN));
-
-    priv = NM_DEVICE_GET_PRIVATE(self);
-
-    g_free(priv->dhcp_anycast_address);
-    priv->dhcp_anycast_address = g_strdup(addr);
-}
-
-void
 nm_device_reapply_settings_immediately(NMDevice *self)
 {
     NMConnection *        applied_connection;
@@ -15702,7 +15865,7 @@ _cancel_activation(NMDevice *self)
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     if (priv->fw_call) {
-        nm_firewall_manager_cancel_call(priv->fw_call);
+        nm_firewalld_manager_cancel_call(priv->fw_call);
         nm_assert(!priv->fw_call);
         priv->fw_call  = NULL;
         priv->fw_state = FIREWALL_STATE_INITIALIZED;
@@ -15732,11 +15895,11 @@ _cleanup_generic_pre(NMDevice *self, CleanupType cleanup_type)
 
     if (cleanup_type == CLEANUP_TYPE_DECONFIGURE && priv->fw_state >= FIREWALL_STATE_INITIALIZED
         && priv->fw_mgr && !nm_device_sys_iface_state_is_external(self)) {
-        nm_firewall_manager_remove_from_zone(priv->fw_mgr,
-                                             nm_device_get_ip_iface(self),
-                                             NULL,
-                                             NULL,
-                                             NULL);
+        nm_firewalld_manager_remove_from_zone(priv->fw_mgr,
+                                              nm_device_get_ip_iface(self),
+                                              NULL,
+                                              NULL,
+                                              NULL);
     }
     priv->fw_state = FIREWALL_STATE_UNMANAGED;
     g_clear_object(&priv->fw_mgr);
@@ -15750,6 +15913,10 @@ _cleanup_generic_pre(NMDevice *self, CleanupType cleanup_type)
 
     _cleanup_ip_pre(self, AF_INET, cleanup_type);
     _cleanup_ip_pre(self, AF_INET6, cleanup_type);
+
+    priv->ip_config_started = FALSE;
+    nm_clear_g_source_inst(&priv->ip_req_timeout_source_4);
+    nm_clear_g_source_inst(&priv->ip_req_timeout_source_6);
 }
 
 static void
@@ -15847,7 +16014,7 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
     else
         _LOGD(LOGD_DEVICE,
               "deactivating device (reason '%s') [%d]",
-              reason_to_string_a(reason),
+              nm_device_state_reason_to_str_a(reason),
               reason);
 
     /* Save whether or not we tried IPv6 for later */
@@ -15902,8 +16069,7 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
                                            FALSE,
                                            NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
 
-    if (priv->lldp_listener)
-        nm_lldp_listener_stop(priv->lldp_listener);
+    lldp_setup(self, NM_TERNARY_FALSE);
 
     nm_device_update_metered(self);
 
@@ -15954,6 +16120,14 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
 
     _ethtool_state_reset(self);
 
+    if (priv->promisc_reset != NM_OPTION_BOOL_DEFAULT && ifindex > 0) {
+        nm_platform_link_change_flags(nm_device_get_platform(self),
+                                      ifindex,
+                                      IFF_PROMISC,
+                                      !!priv->promisc_reset);
+        priv->promisc_reset = NM_OPTION_BOOL_DEFAULT;
+    }
+
     _cleanup_generic_post(self, cleanup_type);
 }
 
@@ -16068,7 +16242,8 @@ nm_device_spawn_iface_helper(NMDevice *self)
 
             hostname = nm_dhcp_client_get_hostname(priv->dhcp_data_4.client);
             if (hostname) {
-                if (nm_dhcp_client_get_use_fqdn(priv->dhcp_data_4.client))
+                if (NM_FLAGS_HAS(nm_dhcp_client_get_client_flags(priv->dhcp_data_4.client),
+                                 NM_DHCP_CLIENT_FLAGS_USE_FQDN))
                     g_ptr_array_add(argv, g_strdup("--dhcp4-fqdn"));
                 else
                     g_ptr_array_add(argv, g_strdup("--dhcp4-hostname"));
@@ -16335,8 +16510,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
               "state change: %s -> %s (reason '%s', sys-iface-state: '%s'%s)",
               nm_device_state_to_str(old_state),
               nm_device_state_to_str(state),
-              reason_to_string_a(reason),
-              _sys_iface_state_to_str(priv->sys_iface_state),
+              nm_device_state_reason_to_str_a(reason),
+              nm_device_sys_iface_state_to_str(priv->sys_iface_state),
               priv->firmware_missing ? ", missing firmware" : "");
         return;
     }
@@ -16345,8 +16520,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
           "state change: %s -> %s (reason '%s', sys-iface-state: '%s')",
           nm_device_state_to_str(old_state),
           nm_device_state_to_str(state),
-          reason_to_string_a(reason),
-          _sys_iface_state_to_str(priv->sys_iface_state));
+          nm_device_state_reason_to_str_a(reason),
+          nm_device_sys_iface_state_to_str(priv->sys_iface_state));
 
     /* in order to prevent triggering any callback caused
      * by the device not having any pending action anymore
@@ -16692,7 +16867,7 @@ queued_state_set(gpointer user_data)
     _LOGD(LOGD_DEVICE,
           "queue-state[%s, reason:%s, id:%u]: %s",
           nm_device_state_to_str(priv->queued_state.state),
-          reason_to_string_a(priv->queued_state.reason),
+          nm_device_state_reason_to_str_a(priv->queued_state.reason),
           priv->queued_state.id,
           "change state");
 
@@ -16704,7 +16879,7 @@ queued_state_set(gpointer user_data)
     new_reason            = priv->queued_state.reason;
 
     nm_device_state_changed(self, new_state, new_reason);
-    nm_device_remove_pending_action(self, queued_state_to_string(new_state), TRUE);
+    nm_device_remove_pending_action(self, nm_device_state_queued_state_to_str(new_state), TRUE);
 
     return G_SOURCE_REMOVE;
 }
@@ -16722,12 +16897,12 @@ nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateReason r
         _LOGD(LOGD_DEVICE,
               "queue-state[%s, reason:%s, id:%u]: %s%s%s%s",
               nm_device_state_to_str(priv->queued_state.state),
-              reason_to_string_a(priv->queued_state.reason),
+              nm_device_state_reason_to_str_a(priv->queued_state.reason),
               priv->queued_state.id,
               "ignore queuing same state change",
               NM_PRINT_FMT_QUOTED(priv->queued_state.reason != reason,
                                   " (reason differs: ",
-                                  reason_to_string_a(reason),
+                                  nm_device_state_reason_to_str_a(reason),
                                   ")",
                                   ""));
         return;
@@ -16735,20 +16910,21 @@ nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateReason r
 
     /* Add pending action for the new state before clearing the queued states, so
      * that we don't accidentally pop all pending states and reach 'startup complete'  */
-    nm_device_add_pending_action(self, queued_state_to_string(state), TRUE);
+    nm_device_add_pending_action(self, nm_device_state_queued_state_to_str(state), TRUE);
 
     /* We should only ever have one delayed state transition at a time */
     if (priv->queued_state.id) {
         _LOGW(LOGD_DEVICE,
               "queue-state[%s, reason:%s, id:%u]: %s",
               nm_device_state_to_str(priv->queued_state.state),
-              reason_to_string_a(priv->queued_state.reason),
+              nm_device_state_reason_to_str_a(priv->queued_state.reason),
               priv->queued_state.id,
               "replace previously queued state change");
         nm_clear_g_source(&priv->queued_state.id);
-        nm_device_remove_pending_action(self,
-                                        queued_state_to_string(priv->queued_state.state),
-                                        TRUE);
+        nm_device_remove_pending_action(
+            self,
+            nm_device_state_queued_state_to_str(priv->queued_state.state),
+            TRUE);
     }
 
     priv->queued_state.state  = state;
@@ -16758,7 +16934,7 @@ nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateReason r
     _LOGD(LOGD_DEVICE,
           "queue-state[%s, reason:%s, id:%u]: %s",
           nm_device_state_to_str(state),
-          reason_to_string_a(reason),
+          nm_device_state_reason_to_str_a(reason),
           priv->queued_state.id,
           "queue state change");
 }
@@ -16774,11 +16950,13 @@ queued_state_clear(NMDevice *self)
     _LOGD(LOGD_DEVICE,
           "queue-state[%s, reason:%s, id:%u]: %s",
           nm_device_state_to_str(priv->queued_state.state),
-          reason_to_string_a(priv->queued_state.reason),
+          nm_device_state_reason_to_str_a(priv->queued_state.reason),
           priv->queued_state.id,
           "clear queued state change");
     nm_clear_g_source(&priv->queued_state.id);
-    nm_device_remove_pending_action(self, queued_state_to_string(priv->queued_state.state), TRUE);
+    nm_device_remove_pending_action(self,
+                                    nm_device_state_queued_state_to_str(priv->queued_state.state),
+                                    TRUE);
 }
 
 NMDeviceState
@@ -16796,7 +16974,7 @@ const char *
 nm_device_get_hw_address(NMDevice *self)
 {
     NMDevicePrivate *priv;
-    char             buf[NM_UTILS_HWADDR_LEN_MAX];
+    char             buf[_NM_UTILS_HWADDR_LEN_MAX];
     gsize            l;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
@@ -16890,7 +17068,7 @@ void
 nm_device_update_permanent_hw_address(NMDevice *self, gboolean force_freeze)
 {
     NMDevicePrivate *              priv = NM_DEVICE_GET_PRIVATE(self);
-    guint8                         buf[NM_UTILS_HWADDR_LEN_MAX];
+    guint8                         buf[_NM_UTILS_HWADDR_LEN_MAX];
     size_t                         len = 0;
     gboolean                       success_read;
     int                            ifindex;
@@ -17008,7 +17186,7 @@ _hw_addr_set(NMDevice *        self,
     NMDevicePrivate *priv;
     gboolean         success = FALSE;
     int              r;
-    guint8           addr_bytes[NM_UTILS_HWADDR_LEN_MAX];
+    guint8           addr_bytes[_NM_UTILS_HWADDR_LEN_MAX];
     gsize            addr_len;
     gboolean         was_taken_down = FALSE;
     gboolean         retry_down;
@@ -17209,14 +17387,13 @@ _hw_addr_get_cloned(NMDevice *    self,
                     gboolean *    preserve,
                     char **       hwaddr,
                     HwAddrType *  hwaddr_type,
-                    char **       hwaddr_detail,
+                    const char ** hwaddr_detail,
                     GError **     error)
 {
     NMDevicePrivate *priv;
-    gs_free char *   addr_setting_free             = NULL;
-    gs_free char *   hw_addr_generated             = NULL;
-    gs_free char *   generate_mac_address_mask_tmp = NULL;
-    const char *     addr, *addr_setting;
+    gs_free char *   hw_addr_generated = NULL;
+    const char *     addr;
+    const char *     addr_setting;
     char *           addr_out;
     HwAddrType       type_out;
 
@@ -17229,15 +17406,16 @@ _hw_addr_get_cloned(NMDevice *    self,
     if (!connection)
         g_return_val_if_reached(FALSE);
 
-    addr = addr_setting =
-        _prop_get_x_cloned_mac_address(self, connection, is_wifi, &addr_setting_free);
+    addr_setting = _prop_get_x_cloned_mac_address(self, connection, is_wifi);
+
+    addr = addr_setting;
 
     if (nm_streq(addr, NM_CLONED_MAC_PRESERVE)) {
         /* "preserve" means to reset the initial MAC address. */
         NM_SET_OUT(preserve, TRUE);
         NM_SET_OUT(hwaddr, NULL);
         NM_SET_OUT(hwaddr_type, HW_ADDR_TYPE_UNSET);
-        NM_SET_OUT(hwaddr_detail, g_steal_pointer(&addr_setting_free) ?: g_strdup(addr_setting));
+        NM_SET_OUT(hwaddr_detail, addr_setting);
         return TRUE;
     }
 
@@ -17250,8 +17428,7 @@ _hw_addr_get_cloned(NMDevice *    self,
             NM_SET_OUT(preserve, TRUE);
             NM_SET_OUT(hwaddr, NULL);
             NM_SET_OUT(hwaddr_type, HW_ADDR_TYPE_UNSET);
-            NM_SET_OUT(hwaddr_detail,
-                       g_steal_pointer(&addr_setting_free) ?: g_strdup(addr_setting));
+            NM_SET_OUT(hwaddr_detail, addr_setting);
             return TRUE;
         } else if (!addr) {
             g_set_error_literal(error,
@@ -17271,10 +17448,7 @@ _hw_addr_get_cloned(NMDevice *    self,
         }
         hw_addr_generated = nm_utils_hw_addr_gen_random_eth(
             nm_device_get_initial_hw_address(self),
-            _prop_get_x_generate_mac_address_mask(self,
-                                                  connection,
-                                                  is_wifi,
-                                                  &generate_mac_address_mask_tmp));
+            _prop_get_x_generate_mac_address_mask(self, connection, is_wifi));
         if (!hw_addr_generated) {
             g_set_error(error,
                         NM_DEVICE_ERROR,
@@ -17302,10 +17476,7 @@ _hw_addr_get_cloned(NMDevice *    self,
             stable_id,
             nm_device_get_ip_iface(self),
             nm_device_get_initial_hw_address(self),
-            _prop_get_x_generate_mac_address_mask(self,
-                                                  connection,
-                                                  is_wifi,
-                                                  &generate_mac_address_mask_tmp));
+            _prop_get_x_generate_mac_address_mask(self, connection, is_wifi));
         if (!hw_addr_generated) {
             g_set_error(error,
                         NM_DEVICE_ERROR,
@@ -17329,7 +17500,7 @@ _hw_addr_get_cloned(NMDevice *    self,
     NM_SET_OUT(preserve, FALSE);
     NM_SET_OUT(hwaddr, addr_out);
     NM_SET_OUT(hwaddr_type, type_out);
-    NM_SET_OUT(hwaddr_detail, g_steal_pointer(&addr_setting_free) ?: g_strdup(addr_setting));
+    NM_SET_OUT(hwaddr_detail, addr_setting);
     return TRUE;
 out_no_action:
     NM_SET_OUT(preserve, FALSE);
@@ -17359,7 +17530,7 @@ nm_device_hw_addr_set_cloned(NMDevice *self, NMConnection *connection, gboolean
     NMDevicePrivate *priv;
     gboolean         preserve   = FALSE;
     gs_free char *   hwaddr     = NULL;
-    gs_free char *   detail     = NULL;
+    const char *     detail     = NULL;
     HwAddrType       type       = HW_ADDR_TYPE_UNSET;
     gs_free_error GError *error = NULL;
 
@@ -17599,23 +17770,38 @@ hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer use
 {
     HostnameResolver *resolver;
     NMDevice *        self;
-    gs_free char *    hostname  = NULL;
     gs_free char *    addr_str  = NULL;
+    gs_free char *    output    = NULL;
     gs_free_error GError *error = NULL;
 
-    hostname = g_resolver_lookup_by_address_finish(G_RESOLVER(source), result, &error);
+    output = nm_device_resolve_address_finish(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);
+    resolver        = user_data;
+    self            = resolver->device;
+    resolver->state = RESOLVER_DONE;
+
+    if (error) {
+        _LOGD(LOGD_DNS,
+              "hostname-from-dns: lookup error for %s: %s",
+              (addr_str = g_inet_address_to_string(resolver->address)),
+              error->message);
+    } else {
+        gboolean valid;
+
+        resolver->hostname = g_steal_pointer(&output);
+        valid              = nm_hostname_manager_validate_hostname(resolver->hostname);
+
+        _LOGD(LOGD_DNS,
+              "hostname-from-dns: lookup done for %s, result %s%s%s%s",
+              (addr_str = g_inet_address_to_string(resolver->address)),
+              NM_PRINT_FMT_QUOTE_STRING(resolver->hostname),
+              valid ? "" : " (invalid)");
 
-    _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));
+        if (!valid)
+            g_clear_pointer(&resolver->hostname, g_free);
+    }
 
     nm_clear_g_cancellable(&resolver->cancellable);
     g_signal_emit(self, signals[DNS_LOOKUP_DONE], 0);
@@ -17713,7 +17899,6 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean
     if (!resolver) {
         resolver  = g_slice_new(HostnameResolver);
         *resolver = (HostnameResolver){
-            .resolver    = g_resolver_get_default(),
             .device      = self,
             .addr_family = addr_family,
             .state       = RESOLVER_WAIT_ADDRESS,
@@ -17721,13 +17906,36 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean
         priv->hostname_resolver_x[IS_IPv4] = resolver;
     }
 
-    /* Determine the first address of the interface and
-     * whether it changed from the previous lookup */
+    /* Determine the most suitable 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;
+        const NMPlatformIPAddress *addr = NULL;
+
+        if (IS_IPv4) {
+            addr = nm_ip_config_get_first_address(ip_config);
+        } else {
+            /* For IPv6 prefer, in order:
+             * - !link-local, !deprecated
+             * - !link-local, deprecated
+             * - link-local
+             */
+            addr = nm_ip_config_find_first_address(ip_config,
+                                                   NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
+                                                       | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL);
+            if (!addr) {
+                addr = nm_ip_config_find_first_address(
+                    ip_config,
+                    NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
+                        | NM_PLATFORM_MATCH_WITH_ADDRSTATE_DEPRECATED);
+            }
+            if (!addr) {
+                addr = nm_ip_config_find_first_address(ip_config,
+                                                       NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
+                                                           | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY);
+            }
+        }
 
-        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
@@ -17763,20 +17971,15 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean
     }
 
     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_device_resolve_address(addr_family,
+                                  g_inet_address_to_bytes(resolver->address),
+                                  resolver->cancellable,
+                                  hostname_dns_lookup_callback,
+                                  resolver);
         nm_clear_g_source(&resolver->timeout_id);
     }
 
@@ -17826,7 +18029,6 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
     NMDevice *       self = NM_DEVICE(object);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    GVariantBuilder  array_builder;
 
     switch (prop_id) {
     case PROP_UDI:
@@ -17977,12 +18179,10 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
         g_value_set_uint(value, priv->metered);
         break;
     case PROP_LLDP_NEIGHBORS:
-        if (priv->lldp_listener)
-            g_value_set_variant(value, nm_lldp_listener_get_neighbors(priv->lldp_listener));
-        else {
-            g_variant_builder_init(&array_builder, G_VARIANT_TYPE("aa{sv}"));
-            g_value_take_variant(value, g_variant_builder_end(&array_builder));
-        }
+        g_value_set_variant(value,
+                            priv->lldp_listener
+                                ? nm_lldp_listener_get_neighbors(priv->lldp_listener)
+                                : nm_g_variant_singleton_aaLsvI());
         break;
     case PROP_REAL:
         g_value_set_boolean(value, nm_device_is_real(self));
@@ -18162,6 +18362,8 @@ nm_device_init(NMDevice *self)
 
     priv->v4_commit_first_time = TRUE;
     priv->v6_commit_first_time = TRUE;
+
+    priv->promisc_reset = NM_OPTION_BOOL_DEFAULT;
 }
 
 static GObject *
@@ -18191,7 +18393,7 @@ constructor(GType type, guint n_construct_params, GObjectConstructParam *constru
     }
 
     if (priv->hw_addr_perm) {
-        guint8 buf[NM_UTILS_HWADDR_LEN_MAX];
+        guint8 buf[_NM_UTILS_HWADDR_LEN_MAX];
         gsize  l;
 
         if (!_nm_utils_hwaddr_aton(priv->hw_addr_perm, buf, sizeof(buf), &l)) {
@@ -18339,13 +18541,7 @@ dispose(GObject *object)
     nm_clear_g_source(&priv->device_link_changed_id);
     nm_clear_g_source(&priv->device_ip_link_changed_id);
 
-    if (priv->lldp_listener) {
-        g_signal_handlers_disconnect_by_func(priv->lldp_listener,
-                                             G_CALLBACK(lldp_neighbors_changed),
-                                             self);
-        nm_lldp_listener_stop(priv->lldp_listener);
-        g_clear_object(&priv->lldp_listener);
-    }
+    lldp_setup(self, FALSE);
 
     nm_clear_g_source(&priv->concheck_x[0].p_cur_id);
     nm_clear_g_source(&priv->concheck_x[1].p_cur_id);
@@ -18388,7 +18584,6 @@ finalize(GObject *object)
     g_free(priv->driver_version);
     g_free(priv->firmware_version);
     g_free(priv->type_desc);
-    g_free(priv->dhcp_anycast_address);
     g_free(priv->current_stable_id);
 
     g_hash_table_unref(priv->ip6_saved_properties);
@@ -18443,75 +18638,69 @@ static const NMDBusInterfaceInfoExtended interface_info_device = {
                                                 .handle = impl_device_delete, ), ),
         .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&signal_info_state_changed, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Udi", "s", NM_DEVICE_UDI),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Path", "s", NM_DEVICE_PATH),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Interface", "s", NM_DEVICE_IFACE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("IpInterface",
-                                                             "s",
-                                                             NM_DEVICE_IP_IFACE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Driver", "s", NM_DEVICE_DRIVER),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DriverVersion",
-                                                             "s",
-                                                             NM_DEVICE_DRIVER_VERSION),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("FirmwareVersion",
-                                                             "s",
-                                                             NM_DEVICE_FIRMWARE_VERSION),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Capabilities",
-                                                             "u",
-                                                             NM_DEVICE_CAPABILITIES),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ip4Address",
-                                                             "u",
-                                                             NM_DEVICE_IP4_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("State", "u", NM_DEVICE_STATE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("StateReason",
-                                                             "(uu)",
-                                                             NM_DEVICE_STATE_REASON),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ActiveConnection",
-                                                             "o",
-                                                             NM_DEVICE_ACTIVE_CONNECTION),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ip4Config",
-                                                             "o",
-                                                             NM_DEVICE_IP4_CONFIG),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Dhcp4Config",
-                                                             "o",
-                                                             NM_DEVICE_DHCP4_CONFIG),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ip6Config",
-                                                             "o",
-                                                             NM_DEVICE_IP6_CONFIG),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Dhcp6Config",
-                                                             "o",
-                                                             NM_DEVICE_DHCP6_CONFIG),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L("Managed",
-                                                                 "b",
-                                                                 NM_DEVICE_MANAGED,
-                                                                 NM_AUTH_PERMISSION_NETWORK_CONTROL,
-                                                                 NM_AUDIT_OP_DEVICE_MANAGED),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L("Autoconnect",
-                                                                 "b",
-                                                                 NM_DEVICE_AUTOCONNECT,
-                                                                 NM_AUTH_PERMISSION_NETWORK_CONTROL,
-                                                                 NM_AUDIT_OP_DEVICE_AUTOCONNECT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("FirmwareMissing",
-                                                             "b",
-                                                             NM_DEVICE_FIRMWARE_MISSING),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("NmPluginMissing",
-                                                             "b",
-                                                             NM_DEVICE_NM_PLUGIN_MISSING),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DeviceType",
-                                                             "u",
-                                                             NM_DEVICE_DEVICE_TYPE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("AvailableConnections",
-                                                             "ao",
-                                                             NM_DEVICE_AVAILABLE_CONNECTIONS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("PhysicalPortId",
-                                                             "s",
-                                                             NM_DEVICE_PHYSICAL_PORT_ID),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mtu", "u", NM_DEVICE_MTU),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Metered", "u", NM_DEVICE_METERED),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("LldpNeighbors",
-                                                             "aa{sv}",
-                                                             NM_DEVICE_LLDP_NEIGHBORS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Real", "b", NM_DEVICE_REAL),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Udi", "s", NM_DEVICE_UDI),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Path", "s", NM_DEVICE_PATH),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Interface", "s", NM_DEVICE_IFACE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("IpInterface", "s", NM_DEVICE_IP_IFACE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Driver", "s", NM_DEVICE_DRIVER),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("DriverVersion",
+                                                           "s",
+                                                           NM_DEVICE_DRIVER_VERSION),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("FirmwareVersion",
+                                                           "s",
+                                                           NM_DEVICE_FIRMWARE_VERSION),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Capabilities",
+                                                           "u",
+                                                           NM_DEVICE_CAPABILITIES),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip4Address",
+                                                           "u",
+                                                           NM_DEVICE_IP4_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("State", "u", NM_DEVICE_STATE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("StateReason",
+                                                           "(uu)",
+                                                           NM_DEVICE_STATE_REASON),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ActiveConnection",
+                                                           "o",
+                                                           NM_DEVICE_ACTIVE_CONNECTION),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip4Config", "o", NM_DEVICE_IP4_CONFIG),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Dhcp4Config",
+                                                           "o",
+                                                           NM_DEVICE_DHCP4_CONFIG),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip6Config", "o", NM_DEVICE_IP6_CONFIG),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Dhcp6Config",
+                                                           "o",
+                                                           NM_DEVICE_DHCP6_CONFIG),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE("Managed",
+                                                               "b",
+                                                               NM_DEVICE_MANAGED,
+                                                               NM_AUTH_PERMISSION_NETWORK_CONTROL,
+                                                               NM_AUDIT_OP_DEVICE_MANAGED),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE("Autoconnect",
+                                                               "b",
+                                                               NM_DEVICE_AUTOCONNECT,
+                                                               NM_AUTH_PERMISSION_NETWORK_CONTROL,
+                                                               NM_AUDIT_OP_DEVICE_AUTOCONNECT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("FirmwareMissing",
+                                                           "b",
+                                                           NM_DEVICE_FIRMWARE_MISSING),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("NmPluginMissing",
+                                                           "b",
+                                                           NM_DEVICE_NM_PLUGIN_MISSING),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("DeviceType",
+                                                           "u",
+                                                           NM_DEVICE_DEVICE_TYPE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("AvailableConnections",
+                                                           "ao",
+                                                           NM_DEVICE_AVAILABLE_CONNECTIONS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("PhysicalPortId",
+                                                           "s",
+                                                           NM_DEVICE_PHYSICAL_PORT_ID),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Mtu", "u", NM_DEVICE_MTU),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Metered", "u", NM_DEVICE_METERED),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("LldpNeighbors",
+                                                           "aa{sv}",
+                                                           NM_DEVICE_LLDP_NEIGHBORS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Real", "b", NM_DEVICE_REAL),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip4Connectivity",
                                                            "u",
                                                            NM_DEVICE_IP4_CONNECTIVITY),
@@ -18526,10 +18715,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device = {
                                                            NM_DEVICE_HW_ADDRESS), ), ),
 };
 
-const NMDBusInterfaceInfoExtended nm_interface_info_device_statistics = {
+static const NMDBusInterfaceInfoExtended interface_info_device_statistics = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_STATISTICS,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE(
                 "RefreshRateMs",
@@ -18555,7 +18743,7 @@ nm_device_class_init(NMDeviceClass *klass)
 
     dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/Devices");
     dbus_object_class->interface_infos =
-        NM_DBUS_INTERFACE_INFOS(&interface_info_device, &nm_interface_info_device_statistics);
+        NM_DBUS_INTERFACE_INFOS(&interface_info_device, &interface_info_device_statistics);
 
     object_class->dispose      = dispose;
     object_class->finalize     = finalize;
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index 68343ef5..f59b6fa8 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -15,54 +15,7 @@
 #include "nm-connection.h"
 #include "nm-rfkill-manager.h"
 #include "NetworkManagerUtils.h"
-
-typedef enum _nm_packed {
-    NM_DEVICE_SYS_IFACE_STATE_EXTERNAL,
-    NM_DEVICE_SYS_IFACE_STATE_ASSUME,
-    NM_DEVICE_SYS_IFACE_STATE_MANAGED,
-
-    /* the REMOVED state applies when the device is manually set to unmanaged
-     * or the link was externally removed. In both cases, we move the device
-     * to UNMANAGED state, without touching the link -- be it, because the link
-     * is already gone or because we want to release it (give it up).
-     */
-    NM_DEVICE_SYS_IFACE_STATE_REMOVED,
-} NMDeviceSysIfaceState;
-
-typedef enum {
-    NM_DEVICE_MTU_SOURCE_NONE,
-    NM_DEVICE_MTU_SOURCE_PARENT,
-    NM_DEVICE_MTU_SOURCE_IP_CONFIG,
-    NM_DEVICE_MTU_SOURCE_CONNECTION,
-} NMDeviceMtuSource;
-
-static inline NMDeviceStateReason
-nm_device_state_reason_check(NMDeviceStateReason reason)
-{
-    /* the device-state-reason serves mostly informational purpose during a state
-     * change. In some cases however, decisions are made based on the reason.
-     * I tend to think that interpreting the state reason to derive some behaviors
-     * is confusing, because the cause and effect are so far apart.
-     *
-     * This function is here to mark source that inspects the reason to make
-     * a decision -- contrary to places that set the reason. Thus, by grepping
-     * for nm_device_state_reason_check() you can find the "effect" to a certain
-     * reason.
-     */
-    return reason;
-}
-
-#define NM_PENDING_ACTION_AUTOACTIVATE           "autoactivate"
-#define NM_PENDING_ACTION_IN_STATE_CHANGE        "in-state-change"
-#define NM_PENDING_ACTION_RECHECK_AVAILABLE      "recheck-available"
-#define NM_PENDING_ACTION_CARRIER_WAIT           "carrier-wait"
-#define NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT "waiting-for-supplicant"
-#define NM_PENDING_ACTION_WIFI_SCAN              "wifi-scan"
-#define NM_PENDING_ACTION_WAITING_FOR_COMPANION  "waiting-for-companion"
-#define NM_PENDING_ACTION_LINK_INIT              "link-init"
-
-#define NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "queued-state-change-"
-#define NM_PENDING_ACTIONPREFIX_ACTIVATION          "activation-"
+#include "nm-device-utils.h"
 
 /* Properties */
 #define NM_DEVICE_UDI                   "udi"
@@ -109,10 +62,10 @@ nm_device_state_reason_check(NMDeviceStateReason reason)
  * NMDeviceOvsBridge and NMDeviceOvsPort. */
 #define NM_DEVICE_SLAVES "slaves" /* partially internal */
 
-#define NM_DEVICE_TYPE_DESC          "type-desc" /* Internal only */
-#define NM_DEVICE_RFKILL_TYPE        "rfkill-type" /* Internal only */
-#define NM_DEVICE_IFINDEX            "ifindex" /* Internal only */
-#define NM_DEVICE_MASTER             "master" /* Internal only */
+#define NM_DEVICE_TYPE_DESC          "type-desc"          /* Internal only */
+#define NM_DEVICE_RFKILL_TYPE        "rfkill-type"        /* Internal only */
+#define NM_DEVICE_IFINDEX            "ifindex"            /* Internal only */
+#define NM_DEVICE_MASTER             "master"             /* Internal only */
 #define NM_DEVICE_HAS_PENDING_ACTION "has-pending-action" /* Internal only */
 
 /* Internal signals */
@@ -459,6 +412,8 @@ typedef struct _NMDeviceClass {
 
     gboolean (*set_platform_mtu)(NMDevice *self, guint32 mtu);
 
+    const char *(*get_dhcp_anycast_address)(NMDevice *self);
+
 } NMDeviceClass;
 
 GType nm_device_get_type(void);
@@ -572,6 +527,7 @@ nm_device_check_connection_compatible(NMDevice *device, NMConnection *connection
 
 gboolean nm_device_check_slave_connection_compatible(NMDevice *device, NMConnection *connection);
 
+gboolean nm_device_can_assume_connections(NMDevice *self);
 gboolean nm_device_unmanage_on_quit(NMDevice *self);
 
 gboolean nm_device_spec_match_list(NMDevice *device, const GSList *specs);
@@ -862,9 +818,6 @@ struct _NMBtVTableNetworkServer {
     gboolean (*unregister_bridge)(const NMBtVTableNetworkServer *vtable, NMDevice *device);
 };
 
-const char *nm_device_state_to_str(NMDeviceState state);
-const char *nm_device_state_reason_to_str(NMDeviceStateReason reason);
-
 gboolean nm_device_is_vpn(NMDevice *self);
 
 const char *
diff --git a/src/core/devices/nm-lldp-listener.c b/src/core/devices/nm-lldp-listener.c
index c60fb3ad..aba972ae 100644
--- a/src/core/devices/nm-lldp-listener.c
+++ b/src/core/devices/nm-lldp-listener.c
@@ -9,12 +9,15 @@
 
 #include <net/ethernet.h>
 
-#include "nm-std-aux/unaligned.h"
-#include "platform/nm-platform.h"
-#include "nm-glib-aux/nm-c-list.h"
+#include "NetworkManagerUtils.h"
+#include "libnm-core-public/nm-errors.h"
+#include "libnm-glib-aux/nm-time-utils.h"
+#include "libnm-std-aux/unaligned.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-glib-aux/nm-c-list.h"
 #include "nm-utils.h"
 
-#include "systemd/nm-sd.h"
+#include "libnm-systemd-core/nm-sd.h"
 
 #define MAX_NEIGHBORS            128
 #define MIN_UPDATE_INTERVAL_NSEC (2 * NM_UTILS_NSEC_PER_SEC)
@@ -28,34 +31,21 @@
 
 /*****************************************************************************/
 
-NM_GOBJECT_PROPERTIES_DEFINE(NMLldpListener, PROP_NEIGHBORS, );
-
-typedef struct {
+struct _NMLldpListener {
     sd_lldp *   lldp_handle;
     GHashTable *lldp_neighbors;
     GVariant *  variant;
 
+    NMLldpListenerNotify notify_callback;
+    gpointer             notify_user_data;
+
     /* the timestamp in nsec until which we delay updates. */
-    gint64 ratelimit_next_nsec;
-    guint  ratelimit_id;
+    GSource *ratelimit_source;
+    gint64   ratelimit_next_nsec;
 
     int ifindex;
-} NMLldpListenerPrivate;
-
-struct _NMLldpListener {
-    GObject               parent;
-    NMLldpListenerPrivate _priv;
-};
-
-struct _NMLldpListenerClass {
-    GObjectClass parent;
 };
 
-G_DEFINE_TYPE(NMLldpListener, nm_lldp_listener, G_TYPE_OBJECT)
-
-#define NM_LLDP_LISTENER_GET_PRIVATE(self) \
-    _NM_GET_PRIVATE(self, NMLldpListener, NM_IS_LLDP_LISTENER)
-
 /*****************************************************************************/
 
 typedef struct {
@@ -71,27 +61,28 @@ typedef struct {
 
 #define _NMLOG_PREFIX_NAME "lldp"
 #define _NMLOG_DOMAIN      LOGD_DEVICE
-#define _NMLOG(level, ...)                                                                      \
-    G_STMT_START                                                                                \
-    {                                                                                           \
-        const NMLogLevel _level = (level);                                                      \
-                                                                                                \
-        if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) {                                        \
-            char _sbuf[64];                                                                     \
-            int  _ifindex = (self) ? NM_LLDP_LISTENER_GET_PRIVATE(self)->ifindex : 0;           \
-                                                                                                \
-            _nm_log(_level,                                                                     \
-                    _NMLOG_DOMAIN,                                                              \
-                    0,                                                                          \
-                    _ifindex > 0 ? nm_platform_link_get_name(NM_PLATFORM_GET, _ifindex) : NULL, \
-                    NULL,                                                                       \
-                    "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                                \
-                    _NMLOG_PREFIX_NAME,                                                         \
-                    ((_ifindex > 0) ? nm_sprintf_buf(_sbuf, "[%p,%d]", (self), _ifindex)        \
-                                    : ((self) ? nm_sprintf_buf(_sbuf, "[%p]", (self)) : ""))    \
-                        _NM_UTILS_MACRO_REST(__VA_ARGS__));                                     \
-        }                                                                                       \
-    }                                                                                           \
+#define _NMLOG(level, ...)                                                                       \
+    G_STMT_START                                                                                 \
+    {                                                                                            \
+        const NMLogLevel _level = (level);                                                       \
+                                                                                                 \
+        if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) {                                         \
+            char _sbuf[100];                                                                     \
+                                                                                                 \
+            _nm_log(_level,                                                                      \
+                    _NMLOG_DOMAIN,                                                               \
+                    0,                                                                           \
+                    (self) ? nm_platform_link_get_name(NM_PLATFORM_GET, (self)->ifindex) : NULL, \
+                    NULL,                                                                        \
+                    "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                                 \
+                    _NMLOG_PREFIX_NAME,                                                          \
+                    ((self) ? nm_sprintf_buf(_sbuf,                                              \
+                                             "[" NM_HASH_OBFUSCATE_PTR_FMT ",%d]",               \
+                                             NM_HASH_OBFUSCATE_PTR(self),                        \
+                                             (self)->ifindex)                                    \
+                            : "") _NM_UTILS_MACRO_REST(__VA_ARGS__));                            \
+        }                                                                                        \
+    }                                                                                            \
     G_STMT_END
 
 #define LOG_NEIGH_FMT "CHASSIS=%u/%s PORT=%u/%s"
@@ -809,80 +800,73 @@ nmtst_lldp_parse_from_raw(const guint8 *raw_data, gsize raw_len)
 /*****************************************************************************/
 
 static void
-data_changed_notify(NMLldpListener *self, NMLldpListenerPrivate *priv)
+data_changed_notify(NMLldpListener *self)
 {
-    nm_clear_g_variant(&priv->variant);
-    _notify(self, PROP_NEIGHBORS);
+    nm_clear_g_variant(&self->variant);
+
+    self->notify_callback(self, self->notify_user_data);
 }
 
 static gboolean
 data_changed_timeout(gpointer user_data)
 {
-    NMLldpListener *       self = user_data;
-    NMLldpListenerPrivate *priv;
+    NMLldpListener *self = user_data;
 
-    g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), G_SOURCE_REMOVE);
-
-    priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
-
-    priv->ratelimit_id        = 0;
-    priv->ratelimit_next_nsec = nm_utils_get_monotonic_timestamp_nsec() + MIN_UPDATE_INTERVAL_NSEC;
-    data_changed_notify(self, priv);
-    return G_SOURCE_REMOVE;
+    nm_clear_g_source_inst(&self->ratelimit_source);
+    self->ratelimit_next_nsec = nm_utils_get_monotonic_timestamp_nsec() + MIN_UPDATE_INTERVAL_NSEC;
+    data_changed_notify(self);
+    return G_SOURCE_CONTINUE;
 }
 
 static void
 data_changed_schedule(NMLldpListener *self)
 {
-    NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
-    gint64                 now_nsec;
+    gint64 now_nsec;
 
-    if (priv->ratelimit_id != 0)
+    if (self->ratelimit_source)
         return;
 
     now_nsec = nm_utils_get_monotonic_timestamp_nsec();
-    if (now_nsec < priv->ratelimit_next_nsec) {
-        priv->ratelimit_id =
-            g_timeout_add_full(G_PRIORITY_LOW,
-                               NM_UTILS_NSEC_TO_MSEC_CEIL(priv->ratelimit_next_nsec - now_nsec),
-                               data_changed_timeout,
-                               self,
-                               NULL);
-        return;
+    if (now_nsec < self->ratelimit_next_nsec) {
+        self->ratelimit_source = nm_g_timeout_source_new(
+            NM_UTILS_NSEC_TO_MSEC_CEIL(self->ratelimit_next_nsec - now_nsec),
+            G_PRIORITY_LOW,
+            data_changed_timeout,
+            self,
+            NULL);
+    } else {
+        self->ratelimit_source =
+            nm_g_idle_source_new(G_PRIORITY_LOW, data_changed_timeout, self, NULL);
     }
 
-    priv->ratelimit_id = g_idle_add_full(G_PRIORITY_LOW, data_changed_timeout, self, NULL);
+    g_source_attach(self->ratelimit_source, NULL);
 }
 
 static void
 process_lldp_neighbor(NMLldpListener *self, sd_lldp_neighbor *neighbor_sd, gboolean remove)
 {
-    NMLldpListenerPrivate *                    priv;
     nm_auto(lldp_neighbor_freep) LldpNeighbor *neigh = NULL;
     LldpNeighbor *                             neigh_old;
 
-    g_return_if_fail(NM_IS_LLDP_LISTENER(self));
+    nm_assert(self);
+    nm_assert(self->lldp_handle);
+    nm_assert(self->lldp_neighbors);
 
-    priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
-
-    g_return_if_fail(priv->lldp_handle);
     g_return_if_fail(neighbor_sd);
 
-    nm_assert(priv->lldp_neighbors);
-
     neigh = lldp_neighbor_new(neighbor_sd);
     if (!neigh) {
         _LOGT("process: failed to parse neighbor");
         return;
     }
 
-    neigh_old = g_hash_table_lookup(priv->lldp_neighbors, neigh);
+    neigh_old = g_hash_table_lookup(self->lldp_neighbors, neigh);
 
     if (remove) {
         if (neigh_old) {
             _LOGT("process: %s neigh: " LOG_NEIGH_FMT, "remove", LOG_NEIGH_ARG(neigh));
 
-            g_hash_table_remove(priv->lldp_neighbors, neigh_old);
+            g_hash_table_remove(self->lldp_neighbors, neigh_old);
             goto handle_changed;
         }
         return;
@@ -893,14 +877,14 @@ process_lldp_neighbor(NMLldpListener *self, sd_lldp_neighbor *neighbor_sd, gbool
 
     _LOGD("process: %s neigh: " LOG_NEIGH_FMT, neigh_old ? "update" : "new", LOG_NEIGH_ARG(neigh));
 
-    g_hash_table_add(priv->lldp_neighbors, g_steal_pointer(&neigh));
+    g_hash_table_add(self->lldp_neighbors, g_steal_pointer(&neigh));
 
 handle_changed:
     data_changed_schedule(self);
 }
 
 static void
-lldp_event_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata)
+lldp_event_handler(sd_lldp *lldp, sd_lldp_event_t event, sd_lldp_neighbor *n, void *userdata)
 {
     process_lldp_neighbor(
         userdata,
@@ -908,25 +892,57 @@ lldp_event_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void
         !NM_IN_SET(event, SD_LLDP_EVENT_ADDED, SD_LLDP_EVENT_UPDATED, SD_LLDP_EVENT_REFRESHED));
 }
 
-gboolean
-nm_lldp_listener_start(NMLldpListener *self, int ifindex, GError **error)
+/*****************************************************************************/
+
+int
+nm_lldp_listener_get_ifindex(NMLldpListener *self)
 {
-    NMLldpListenerPrivate *priv;
-    int                    ret;
+    g_return_val_if_fail(self, 0);
 
-    g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), FALSE);
-    g_return_val_if_fail(ifindex > 0, FALSE);
-    g_return_val_if_fail(!error || !*error, FALSE);
+    return self->ifindex;
+}
 
-    priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
+/*****************************************************************************/
 
-    if (priv->lldp_handle) {
-        g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "already running");
-        return FALSE;
+GVariant *
+nm_lldp_listener_get_neighbors(NMLldpListener *self)
+{
+    g_return_val_if_fail(self, FALSE);
+
+    if (G_UNLIKELY(!self->variant)) {
+        gs_free LldpNeighbor **neighbors = NULL;
+        GVariantBuilder        array_builder;
+        guint                  i, n;
+
+        g_variant_builder_init(&array_builder, G_VARIANT_TYPE("aa{sv}"));
+        neighbors = (LldpNeighbor **)
+            nm_utils_hash_keys_to_array(self->lldp_neighbors, lldp_neighbor_id_cmp_p, NULL, &n);
+        for (i = 0; i < n; i++)
+            g_variant_builder_add_value(&array_builder, lldp_neighbor_to_variant(neighbors[i]));
+        self->variant = g_variant_ref_sink(g_variant_builder_end(&array_builder));
     }
 
-    ret = sd_lldp_new(&priv->lldp_handle);
-    if (ret < 0) {
+    return self->variant;
+}
+
+/*****************************************************************************/
+
+NMLldpListener *
+nm_lldp_listener_new(int                  ifindex,
+                     NMLldpListenerNotify notify_callback,
+                     gpointer             notify_user_data,
+                     GError **            error)
+{
+    NMLldpListener *self = NULL;
+    sd_lldp *       lldp_handle;
+    int             r;
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+    g_return_val_if_fail(notify_callback, FALSE);
+
+    r = sd_lldp_new(&lldp_handle);
+    if (r < 0) {
         g_set_error_literal(error,
                             NM_DEVICE_ERROR,
                             NM_DEVICE_ERROR_FAILED,
@@ -934,189 +950,77 @@ nm_lldp_listener_start(NMLldpListener *self, int ifindex, GError **error)
         return FALSE;
     }
 
-    ret = sd_lldp_set_ifindex(priv->lldp_handle, ifindex);
-    if (ret < 0) {
+    r = sd_lldp_set_ifindex(lldp_handle, ifindex);
+    if (r < 0) {
         g_set_error_literal(error,
                             NM_DEVICE_ERROR,
                             NM_DEVICE_ERROR_FAILED,
                             "failed setting ifindex");
-        goto err;
+        goto fail_handle;
     }
 
-    ret = sd_lldp_set_callback(priv->lldp_handle, lldp_event_handler, self);
-    if (ret < 0) {
-        g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "set callback failed");
-        goto err;
-    }
+    r = sd_lldp_set_neighbors_max(lldp_handle, MAX_NEIGHBORS);
+    nm_assert(r == 0);
 
-    ret = sd_lldp_set_neighbors_max(priv->lldp_handle, MAX_NEIGHBORS);
-    nm_assert(ret == 0);
+    self  = g_slice_new(NMLldpListener);
+    *self = (NMLldpListener){
+        .ifindex          = ifindex,
+        .notify_callback  = notify_callback,
+        .notify_user_data = notify_user_data,
+    };
 
-    priv->ifindex = ifindex;
+    r = sd_lldp_set_callback(lldp_handle, lldp_event_handler, self);
+    if (r < 0) {
+        g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "set callback failed");
+        goto fail_handle;
+    }
 
-    ret = sd_lldp_attach_event(priv->lldp_handle, NULL, 0);
-    if (ret < 0) {
+    r = sd_lldp_attach_event(lldp_handle, NULL, 0);
+    if (r < 0) {
         g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "attach event failed");
-        goto err_free;
+        goto fail_attached;
     }
 
-    ret = sd_lldp_start(priv->lldp_handle);
-    if (ret < 0) {
+    r = sd_lldp_start(lldp_handle);
+    if (r < 0) {
         g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "start failed");
-        goto err;
+        goto fail_attached;
     }
 
-    priv->lldp_neighbors = g_hash_table_new_full(lldp_neighbor_id_hash,
+    self->lldp_neighbors = g_hash_table_new_full(lldp_neighbor_id_hash,
                                                  lldp_neighbor_id_equal,
                                                  (GDestroyNotify) lldp_neighbor_free,
                                                  NULL);
-
-    _LOGD("start");
-
-    return TRUE;
-
-err:
-    sd_lldp_detach_event(priv->lldp_handle);
-err_free:
-    sd_lldp_unref(priv->lldp_handle);
-    priv->lldp_handle = NULL;
-    priv->ifindex     = 0;
-    return FALSE;
+    self->lldp_handle    = lldp_handle;
+
+    _LOGD("start lldp listener");
+    return self;
+
+fail_attached:
+    sd_lldp_detach_event(lldp_handle);
+fail_handle:
+    if (self)
+        nm_g_slice_free(self);
+    sd_lldp_unref(lldp_handle);
+    return NULL;
 }
 
 void
-nm_lldp_listener_stop(NMLldpListener *self)
+nm_lldp_listener_destroy(NMLldpListener *self)
 {
-    NMLldpListenerPrivate *priv;
-    guint                  size;
-    gboolean               changed = FALSE;
-
-    g_return_if_fail(NM_IS_LLDP_LISTENER(self));
-    priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
-
-    if (priv->lldp_handle) {
-        _LOGD("stop");
-        sd_lldp_stop(priv->lldp_handle);
-        sd_lldp_detach_event(priv->lldp_handle);
-        sd_lldp_unref(priv->lldp_handle);
-        priv->lldp_handle = NULL;
-
-        size = g_hash_table_size(priv->lldp_neighbors);
-        g_hash_table_remove_all(priv->lldp_neighbors);
-        nm_clear_pointer(&priv->lldp_neighbors, g_hash_table_unref);
-        if (size > 0 || priv->ratelimit_id != 0)
-            changed = TRUE;
-    }
+    g_return_if_fail(self);
 
-    nm_clear_g_source(&priv->ratelimit_id);
-    priv->ratelimit_next_nsec = 0;
-    priv->ifindex             = 0;
+    sd_lldp_stop(self->lldp_handle);
+    sd_lldp_detach_event(self->lldp_handle);
+    sd_lldp_unref(self->lldp_handle);
 
-    if (changed)
-        data_changed_notify(self, priv);
-}
-
-gboolean
-nm_lldp_listener_is_running(NMLldpListener *self)
-{
-    NMLldpListenerPrivate *priv;
-
-    g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), FALSE);
-
-    priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
-    return !!priv->lldp_handle;
-}
-
-GVariant *
-nm_lldp_listener_get_neighbors(NMLldpListener *self)
-{
-    NMLldpListenerPrivate *priv;
-
-    g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), FALSE);
-
-    priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
-
-    if (G_UNLIKELY(!priv->variant)) {
-        gs_free LldpNeighbor **neighbors = NULL;
-        GVariantBuilder        array_builder;
-        guint                  i, n;
-
-        g_variant_builder_init(&array_builder, G_VARIANT_TYPE("aa{sv}"));
-        neighbors = (LldpNeighbor **)
-            nm_utils_hash_keys_to_array(priv->lldp_neighbors, lldp_neighbor_id_cmp_p, NULL, &n);
-        for (i = 0; i < n; i++)
-            g_variant_builder_add_value(&array_builder, lldp_neighbor_to_variant(neighbors[i]));
-        priv->variant = g_variant_ref_sink(g_variant_builder_end(&array_builder));
-    }
-    return priv->variant;
-}
-
-static void
-get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
-{
-    NMLldpListener *self = NM_LLDP_LISTENER(object);
-
-    switch (prop_id) {
-    case PROP_NEIGHBORS:
-        g_value_set_variant(value, nm_lldp_listener_get_neighbors(self));
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-static void
-nm_lldp_listener_init(NMLldpListener *self)
-{
-    _LOGT("lldp listener created");
-}
+    nm_clear_g_source_inst(&self->ratelimit_source);
 
-NMLldpListener *
-nm_lldp_listener_new(void)
-{
-    return g_object_new(NM_TYPE_LLDP_LISTENER, NULL);
-}
+    g_hash_table_destroy(self->lldp_neighbors);
 
-static void
-dispose(GObject *object)
-{
-    nm_lldp_listener_stop(NM_LLDP_LISTENER(object));
-
-    G_OBJECT_CLASS(nm_lldp_listener_parent_class)->dispose(object);
-}
-
-static void
-finalize(GObject *object)
-{
-    NMLldpListener *       self = NM_LLDP_LISTENER(object);
-    NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
-
-    nm_lldp_listener_stop(self);
-
-    nm_clear_g_variant(&priv->variant);
+    nm_g_variant_unref(self->variant);
 
     _LOGT("lldp listener destroyed");
 
-    G_OBJECT_CLASS(nm_lldp_listener_parent_class)->finalize(object);
-}
-
-static void
-nm_lldp_listener_class_init(NMLldpListenerClass *klass)
-{
-    GObjectClass *object_class = G_OBJECT_CLASS(klass);
-
-    object_class->dispose      = dispose;
-    object_class->finalize     = finalize;
-    object_class->get_property = get_property;
-
-    obj_properties[PROP_NEIGHBORS] =
-        g_param_spec_variant(NM_LLDP_LISTENER_NEIGHBORS,
-                             "",
-                             "",
-                             G_VARIANT_TYPE("aa{sv}"),
-                             NULL,
-                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
-
-    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+    nm_g_slice_free(self);
 }
diff --git a/src/core/devices/nm-lldp-listener.h b/src/core/devices/nm-lldp-listener.h
index 9d3e2436..762eb3d2 100644
--- a/src/core/devices/nm-lldp-listener.h
+++ b/src/core/devices/nm-lldp-listener.h
@@ -6,28 +6,21 @@
 #ifndef __NM_LLDP_LISTENER__
 #define __NM_LLDP_LISTENER__
 
-#define NM_TYPE_LLDP_LISTENER (nm_lldp_listener_get_type())
-#define NM_LLDP_LISTENER(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_LLDP_LISTENER, NMLldpListener))
-#define NM_LLDP_LISTENER_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_LLDP_LISTENER, NMLldpListenerClass))
-#define NM_IS_LLDP_LISTENER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_LLDP_LISTENER))
-#define NM_IS_LLDP_LISTENER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_LLDP_LISTENER))
-#define NM_LLDP_LISTENER_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_LLDP_LISTENER, NMLldpListenerClass))
-
-#define NM_LLDP_LISTENER_NEIGHBORS "neighbors"
-
-typedef struct _NMLldpListenerClass NMLldpListenerClass;
-
-GType           nm_lldp_listener_get_type(void);
-NMLldpListener *nm_lldp_listener_new(void);
-gboolean        nm_lldp_listener_start(NMLldpListener *self, int ifindex, GError **error);
-void            nm_lldp_listener_stop(NMLldpListener *self);
-gboolean        nm_lldp_listener_is_running(NMLldpListener *self);
+/*****************************************************************************/
 
+typedef void (*NMLldpListenerNotify)(NMLldpListener *self, gpointer user_data);
+
+NMLldpListener *nm_lldp_listener_new(int                  ifindex,
+                                     NMLldpListenerNotify notify_callback,
+                                     gpointer             notify_user_data,
+                                     GError **            error);
+void            nm_lldp_listener_destroy(NMLldpListener *self);
+
+int       nm_lldp_listener_get_ifindex(NMLldpListener *self);
 GVariant *nm_lldp_listener_get_neighbors(NMLldpListener *self);
 
+/*****************************************************************************/
+
 GVariant *nmtst_lldp_parse_from_raw(const guint8 *raw_data, gsize raw_len);
 
 #endif /* __NM_LLDP_LISTENER__ */
diff --git a/src/core/devices/ovs/meson.build b/src/core/devices/ovs/meson.build
index 81c29bd6..1974ff4c 100644
--- a/src/core/devices/ovs/meson.build
+++ b/src/core/devices/ovs/meson.build
@@ -13,7 +13,6 @@ libnm_device_plugin_ovs = shared_module(
     core_plugin_dep,
     jansson_dep,
   ],
-  c_args: daemon_c_flags,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.c b/src/core/devices/ovs/nm-device-ovs-bridge.c
index 3ae8a481..26a5ba00 100644
--- a/src/core/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/core/devices/ovs/nm-device-ovs-bridge.c
@@ -16,7 +16,7 @@
 #include "nm-setting-connection.h"
 #include "nm-setting-ovs-bridge.h"
 #include "nm-setting-ovs-external-ids.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceOvsBridge
 #include "devices/nm-device-logging.h"
@@ -134,9 +134,7 @@ static const NMDBusInterfaceInfoExtended interface_info_device_ovs_bridge = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_OVS_BRIDGE,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ),
-        .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ), ),
 };
 
 static void
diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c
index 0a537f7a..46a612ac 100644
--- a/src/core/devices/ovs/nm-device-ovs-interface.c
+++ b/src/core/devices/ovs/nm-device-ovs-interface.c
@@ -414,10 +414,7 @@ dispose(GObject *object)
 }
 
 static const NMDBusInterfaceInfoExtended interface_info_device_ovs_interface = {
-    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
-        NM_DBUS_INTERFACE_DEVICE_OVS_INTERFACE,
-        .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), ),
-    .legacy_property_changed = TRUE,
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(NM_DBUS_INTERFACE_DEVICE_OVS_INTERFACE, ),
 };
 
 static void
diff --git a/src/core/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c
index 2ecb95e8..f11c5852 100644
--- a/src/core/devices/ovs/nm-device-ovs-port.c
+++ b/src/core/devices/ovs/nm-device-ovs-port.c
@@ -167,9 +167,7 @@ static const NMDBusInterfaceInfoExtended interface_info_device_ovs_port = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_OVS_PORT,
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ),
-        .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES), ), ),
 };
 
 static void
diff --git a/src/core/devices/ovs/nm-ovs-factory.c b/src/core/devices/ovs/nm-ovs-factory.c
index e7af38d8..6f5d8832 100644
--- a/src/core/devices/ovs/nm-ovs-factory.c
+++ b/src/core/devices/ovs/nm-ovs-factory.c
@@ -10,8 +10,8 @@
 #include "nm-device-ovs-interface.h"
 #include "nm-device-ovs-port.h"
 #include "nm-device-ovs-bridge.h"
-#include "platform/nm-platform.h"
-#include "nm-core-internal.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "settings/nm-settings.h"
 #include "devices/nm-device-factory.h"
 #include "devices/nm-device-private.h"
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index da3a7989..b8d5311a 100644
--- a/src/core/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -10,10 +10,10 @@
 #include <gmodule.h>
 #include <gio/gunixsocketaddress.h>
 
-#include "nm-glib-aux/nm-jansson.h"
-#include "nm-glib-aux/nm-str-buf.h"
+#include "libnm-glib-aux/nm-jansson.h"
+#include "libnm-glib-aux/nm-str-buf.h"
 #include "nm-core-utils.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "devices/nm-device.h"
 #include "nm-manager.h"
 #include "nm-setting-ovs-external-ids.h"
diff --git a/src/core/devices/team/meson.build b/src/core/devices/team/meson.build
index d0ff4caa..b1ee9103 100644
--- a/src/core/devices/team/meson.build
+++ b/src/core/devices/team/meson.build
@@ -11,7 +11,6 @@ libnm_device_plugin_team = shared_module(
     jansson_dep,
     libteamdctl_dep,
   ],
-  c_args: daemon_c_flags,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
diff --git a/src/core/devices/team/nm-device-team.c b/src/core/devices/team/nm-device-team.c
index d2d71729..0f1a8c4c 100644
--- a/src/core/devices/team/nm-device-team.c
+++ b/src/core/devices/team/nm-device-team.c
@@ -15,15 +15,15 @@
 #include <teamdctl.h>
 #include <stdlib.h>
 
-#include "nm-glib-aux/nm-jansson.h"
+#include "libnm-glib-aux/nm-jansson.h"
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-config.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "nm-dbus-manager.h"
 #include "nm-ip4-config.h"
-#include "nm-std-aux/nm-dbus-compat.h"
+#include "libnm-std-aux/nm-dbus-compat.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceTeam
 #include "devices/nm-device-logging.h"
@@ -829,12 +829,12 @@ enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gbool
                           "enslaved team port %s config not changed, not connected to teamd",
                           slave_iface);
                 } else {
-                    int   err;
-                    char *sanitized_config;
+                    gs_free char *sanitized_config = NULL;
+                    int           err;
 
-                    sanitized_config = g_strdelimit(g_strdup(config), "\r\n", ' ');
+                    sanitized_config = g_strdup(config);
+                    g_strdelimit(sanitized_config, "\r\n", ' ');
                     err = teamdctl_port_config_update_raw(priv->tdc, slave_iface, sanitized_config);
-                    g_free(sanitized_config);
                     if (err != 0) {
                         _LOGE(LOGD_TEAM,
                               "failed to update config for port %s (err=%d)",
@@ -1042,17 +1042,13 @@ dispose(GObject *object)
 static const NMDBusInterfaceInfoExtended interface_info_device_team = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_TEAM,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Carrier", "b", NM_DEVICE_CARRIER),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Slaves", "ao", NM_DEVICE_SLAVES),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Config",
-                                                             "s",
-                                                             NM_DEVICE_TEAM_CONFIG), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Carrier", "b", NM_DEVICE_CARRIER),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Slaves", "ao", NM_DEVICE_SLAVES),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Config",
+                                                           "s",
+                                                           NM_DEVICE_TEAM_CONFIG), ), ),
 };
 
 static void
diff --git a/src/core/devices/team/nm-team-factory.c b/src/core/devices/team/nm-team-factory.c
index 57b51bf8..e2040d19 100644
--- a/src/core/devices/team/nm-team-factory.c
+++ b/src/core/devices/team/nm-team-factory.c
@@ -10,8 +10,8 @@
 #include "nm-manager.h"
 #include "devices/nm-device-factory.h"
 #include "nm-device-team.h"
-#include "platform/nm-platform.h"
-#include "nm-core-internal.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-core-intern/nm-core-internal.h"
 
 /*****************************************************************************/
 
diff --git a/src/core/devices/tests/test-acd.c b/src/core/devices/tests/test-acd.c
index b0af59db..b4b65165 100644
--- a/src/core/devices/tests/test-acd.c
+++ b/src/core/devices/tests/test-acd.c
@@ -78,8 +78,8 @@ fixture_setup(test_fixture *fixture, gconstpointer user_data)
     fixture->ifindex1 =
         nmtstp_link_get_typed(NM_PLATFORM_GET, -1, IFACE_VETH1, NM_LINK_TYPE_VETH)->ifindex;
 
-    g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, fixture->ifindex0, NULL));
-    g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, fixture->ifindex1, NULL));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, fixture->ifindex0, IFF_UP, TRUE) >= 0);
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, fixture->ifindex1, IFF_UP, TRUE) >= 0);
 
     fixture->hwaddr0 =
         nm_platform_link_get_address(NM_PLATFORM_GET, fixture->ifindex0, &fixture->hwaddr0_len);
diff --git a/src/core/devices/tests/test-lldp.c b/src/core/devices/tests/test-lldp.c
index ef0b1549..c64c7c11 100644
--- a/src/core/devices/tests/test-lldp.c
+++ b/src/core/devices/tests/test-lldp.c
@@ -13,7 +13,7 @@
 #include <sys/types.h>
 
 #include "devices/nm-lldp-listener.h"
-#include "systemd/nm-sd.h"
+#include "libnm-systemd-core/nm-sd.h"
 
 #include "platform/tests/test-common.h"
 
@@ -83,6 +83,7 @@ typedef struct {
     const uint8_t *frame;
     const char *   as_variant;
 } TestRecvFrame;
+
 #define TEST_RECV_FRAME_DEFINE(name, _as_variant, ...)        \
     static const guint8        _##name##_v[] = {__VA_ARGS__}; \
     static const TestRecvFrame name          = {              \
@@ -92,11 +93,17 @@ typedef struct {
     }
 
 typedef struct {
+    int        num_called;
+    GMainLoop *loop_to_quit;
+} TestRecvCallbackInfo;
+
+typedef struct {
     guint                expected_num_called;
     gsize                frames_len;
     const TestRecvFrame *frames[10];
-    void (*check)(GMainLoop *loop, NMLldpListener *listener);
+    void (*check)(GMainLoop *loop, NMLldpListener *listener, TestRecvCallbackInfo *info);
 } TestRecvData;
+
 #define TEST_RECV_DATA_DEFINE(name, _expected_num_called, _check, ...) \
     static const TestRecvData name = {                                 \
         .expected_num_called = _expected_num_called,                   \
@@ -213,7 +220,7 @@ _test_recv_data0_check_do(GMainLoop *loop, NMLldpListener *listener, const TestR
 }
 
 static void
-_test_recv_data0_check(GMainLoop *loop, NMLldpListener *listener)
+_test_recv_data0_check(GMainLoop *loop, NMLldpListener *listener, TestRecvCallbackInfo *info)
 {
     _test_recv_data0_check_do(loop, listener, &_test_recv_data0_frame0);
 }
@@ -528,7 +535,7 @@ TEST_RECV_FRAME_DEFINE(
 );
 
 static void
-_test_recv_data1_check(GMainLoop *loop, NMLldpListener *listener)
+_test_recv_data1_check(GMainLoop *loop, NMLldpListener *listener, TestRecvCallbackInfo *info)
 {
     GVariant *       neighbors, *attr, *child;
     gs_unref_variant GVariant *neighbor = NULL;
@@ -756,21 +763,17 @@ TEST_RECV_FRAME_DEFINE(
 );
 
 static void
-_test_recv_data2_ttl1_check(GMainLoop *loop, NMLldpListener *listener)
+_test_recv_data2_ttl1_check(GMainLoop *loop, NMLldpListener *listener, TestRecvCallbackInfo *info)
 {
-    gulong    notify_id;
     GVariant *neighbors;
 
     _test_recv_data0_check_do(loop, listener, &_test_recv_data2_frame0_ttl1);
 
     /* wait for signal. */
-    notify_id = g_signal_connect(listener,
-                                 "notify::" NM_LLDP_LISTENER_NEIGHBORS,
-                                 nmtst_main_loop_quit_on_notify,
-                                 loop);
+    info->loop_to_quit = loop;
     if (!nmtst_main_loop_run(loop, 5000))
         g_assert_not_reached();
-    nm_clear_g_signal_handler(listener, &notify_id);
+    info->loop_to_quit = NULL;
 
     neighbors = nm_lldp_listener_get_neighbors(listener);
     nmtst_assert_variant_is_of_type(neighbors, G_VARIANT_TYPE("aa{sv}"));
@@ -852,46 +855,37 @@ again:
     memcpy(fixture->mac, link->l_address.data, ETH_ALEN);
 }
 
-typedef struct {
-    int num_called;
-} TestRecvCallbackInfo;
-
 static void
-lldp_neighbors_changed(NMLldpListener *lldp_listener, GParamSpec *pspec, gpointer user_data)
+lldp_neighbors_changed(NMLldpListener *lldp_listener, gpointer user_data)
 {
     TestRecvCallbackInfo *info = user_data;
 
     info->num_called++;
+    if (info->loop_to_quit)
+        g_main_loop_quit(info->loop_to_quit);
 }
 
 static void
 test_recv(TestRecvFixture *fixture, gconstpointer user_data)
 {
-    const TestRecvData *data                 = user_data;
-    gs_unref_object NMLldpListener *listener = NULL;
-    GMainLoop *                     loop;
-    TestRecvCallbackInfo            info = {};
-    gsize                           i_frames;
-    gulong                          notify_id;
-    GError *                        error = NULL;
-    guint                           sd_id;
+    const TestRecvData * data = user_data;
+    NMLldpListener *     listener;
+    GMainLoop *          loop;
+    TestRecvCallbackInfo info = {};
+    gsize                i_frames;
+    GError *             error = NULL;
+    guint                sd_id;
 
     if (fixture->ifindex == 0) {
         g_test_skip("Tun device not available");
         return;
     }
 
-    listener = nm_lldp_listener_new();
-    g_assert(listener != NULL);
-    g_assert(nm_lldp_listener_start(listener, fixture->ifindex, &error));
-    g_assert_no_error(error);
+    listener = nm_lldp_listener_new(fixture->ifindex, lldp_neighbors_changed, &info, &error);
+    nmtst_assert_success(listener, error);
 
-    notify_id = g_signal_connect(listener,
-                                 "notify::" NM_LLDP_LISTENER_NEIGHBORS,
-                                 (GCallback) lldp_neighbors_changed,
-                                 &info);
-    loop      = g_main_loop_new(NULL, FALSE);
-    sd_id     = nm_sd_event_attach_default();
+    loop  = g_main_loop_new(NULL, FALSE);
+    sd_id = nm_sd_event_attach_default();
 
     for (i_frames = 0; i_frames < data->frames_len; i_frames++) {
         const TestRecvFrame *f = data->frames[i_frames];
@@ -904,9 +898,9 @@ test_recv(TestRecvFixture *fixture, gconstpointer user_data)
 
     g_assert_cmpint(info.num_called, ==, data->expected_num_called);
 
-    nm_clear_g_signal_handler(listener, &notify_id);
+    data->check(loop, listener, &info);
 
-    data->check(loop, listener);
+    nm_clear_pointer(&listener, nm_lldp_listener_destroy);
 
     nm_clear_g_source(&sd_id);
     nm_clear_pointer(&loop, g_main_loop_unref);
diff --git a/src/core/devices/wifi/meson.build b/src/core/devices/wifi/meson.build
index 743937db..85553c53 100644
--- a/src/core/devices/wifi/meson.build
+++ b/src/core/devices/wifi/meson.build
@@ -22,7 +22,6 @@ libnm_device_plugin_wifi_static = static_library(
   dependencies: [
     core_plugin_dep,
   ],
-  c_args: daemon_c_flags,
 )
 
 libnm_device_plugin_wifi_static_dep = declare_dependency(
@@ -38,7 +37,6 @@ libnm_device_plugin_wifi = shared_module(
     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,
   install: true,
diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c
index 01f2a303..56338743 100644
--- a/src/core/devices/wifi/nm-device-iwd.c
+++ b/src/core/devices/wifi/nm-device-iwd.c
@@ -13,16 +13,16 @@
 #include "devices/nm-device.h"
 #include "nm-act-request.h"
 #include "nm-config.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "nm-dbus-manager.h"
-#include "nm-glib-aux/nm-ref-string.h"
+#include "libnm-glib-aux/nm-ref-string.h"
 #include "nm-iwd-manager.h"
-#include "nm-libnm-core-intern/nm-common-macros.h"
+#include "libnm-core-aux-intern/nm-common-macros.h"
 #include "nm-setting-8021x.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-wireless-security.h"
 #include "nm-setting-wireless.h"
-#include "nm-std-aux/nm-dbus-compat.h"
+#include "libnm-std-aux/nm-dbus-compat.h"
 #include "nm-utils.h"
 #include "nm-wifi-common.h"
 #include "nm-wifi-utils.h"
@@ -55,7 +55,7 @@ typedef struct {
     CList                         aps_lst_head;
     NMWifiAP *                    current_ap;
     GCancellable *                cancellable;
-    NMDeviceWifiCapabilities      capabilities;
+    _NMDeviceWifiCapabilities     capabilities;
     NMActRequestGetSecretsCallId *wifi_secrets_id;
     guint                         periodic_scan_id;
     guint                         periodic_update_id;
@@ -265,7 +265,7 @@ ap_from_network(NMDeviceIwd *self,
         .bss_path       = bss_path,
         .last_seen_msec = last_seen_msec,
         .bssid_valid    = TRUE,
-        .mode           = NM_802_11_MODE_INFRA,
+        .mode           = _NM_802_11_MODE_INFRA,
         .rsn_flags      = ap_security_flags_from_network_type(type),
         .ssid           = ssid,
         .signal_percent = nm_wifi_utils_level_to_quality(signal / 100),
@@ -812,7 +812,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
         NMSettingWirelessSecurity *s_wireless_sec =
             nm_connection_get_setting_wireless_security(connection);
 
-        if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) {
+        if (!(priv->capabilities & _NM_WIFI_DEVICE_CAP_AP)) {
             nm_utils_error_set_literal(error,
                                        NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
                                        "device does not support Access Point mode");
@@ -830,7 +830,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
         NMSettingWirelessSecurity *s_wireless_sec =
             nm_connection_get_setting_wireless_security(connection);
 
-        if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) {
+        if (!(priv->capabilities & _NM_WIFI_DEVICE_CAP_ADHOC)) {
             nm_utils_error_set_literal(error,
                                        NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
                                        "device does not support Ad-Hoc mode");
@@ -1045,16 +1045,15 @@ complete_connection(NMDevice *           device,
     }
 
     ssid_utf8 = iwd_ssid_to_str(ssid);
-    nm_utils_complete_generic(
-        nm_device_get_platform(device),
-        connection,
-        NM_SETTING_WIRELESS_SETTING_NAME,
-        existing_connections,
-        ssid_utf8,
-        ssid_utf8,
-        NULL,
-        nm_setting_wireless_get_mac_address(s_wifi) ? NULL : nm_device_get_iface(device),
-        TRUE);
+    nm_utils_complete_generic(nm_device_get_platform(device),
+                              connection,
+                              NM_SETTING_WIRELESS_SETTING_NAME,
+                              existing_connections,
+                              ssid_utf8,
+                              ssid_utf8,
+                              NULL,
+                              NULL,
+                              TRUE);
 
     if (hidden)
         g_object_set(s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL);
@@ -1317,6 +1316,7 @@ static gboolean
 try_reply_agent_request(NMDeviceIwd *          self,
                         NMConnection *         connection,
                         GDBusMethodInvocation *invocation,
+                        gboolean               allow_existing,
                         const char **          setting_name,
                         const char **          setting_key,
                         gboolean *             replied)
@@ -1331,56 +1331,64 @@ try_reply_agent_request(NMDeviceIwd *          self,
     *replied = FALSE;
 
     if (nm_streq(method_name, "RequestPassphrase")) {
-        const char *psk;
-
         if (!s_wireless_sec)
             return FALSE;
 
-        psk = nm_setting_wireless_security_get_psk(s_wireless_sec);
-        if (psk) {
-            _LOGD(LOGD_DEVICE | LOGD_WIFI, "Returning the PSK to the IWD Agent");
+        if (allow_existing) {
+            const char *psk = nm_setting_wireless_security_get_psk(s_wireless_sec);
 
-            g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", psk));
-            *replied = TRUE;
-            return TRUE;
+            if (psk) {
+                _LOGD(LOGD_DEVICE | LOGD_WIFI, "Returning the PSK to the IWD Agent");
+
+                g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", psk));
+                *replied = TRUE;
+                return TRUE;
+            }
         }
 
         *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
         *setting_key  = NM_SETTING_WIRELESS_SECURITY_PSK;
         return TRUE;
     } else if (nm_streq(method_name, "RequestPrivateKeyPassphrase")) {
-        const char *password;
-
         if (!s_8021x)
             return FALSE;
 
-        password = nm_setting_802_1x_get_private_key_password(s_8021x);
-        if (password) {
-            _LOGD(LOGD_DEVICE | LOGD_WIFI, "Returning the private key password to the IWD Agent");
+        if (allow_existing) {
+            const char *password = nm_setting_802_1x_get_private_key_password(s_8021x);
 
-            g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", password));
-            *replied = TRUE;
-            return TRUE;
+            if (password) {
+                _LOGD(LOGD_DEVICE | LOGD_WIFI,
+                      "Returning the private key password to the IWD Agent");
+
+                g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", password));
+                *replied = TRUE;
+                return TRUE;
+            }
         }
 
         *setting_name = NM_SETTING_802_1X_SETTING_NAME;
         *setting_key  = NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD;
         return TRUE;
     } else if (nm_streq(method_name, "RequestUserNameAndPassword")) {
-        const char *identity, *password;
+        const char *identity;
 
         if (!s_8021x)
             return FALSE;
 
         identity = nm_setting_802_1x_get_identity(s_8021x);
-        password = nm_setting_802_1x_get_password(s_8021x);
-        if (identity && password) {
-            _LOGD(LOGD_DEVICE | LOGD_WIFI, "Returning the username and password to the IWD Agent");
 
-            g_dbus_method_invocation_return_value(invocation,
-                                                  g_variant_new("(ss)", identity, password));
-            *replied = TRUE;
-            return TRUE;
+        if (allow_existing) {
+            const char *password = nm_setting_802_1x_get_password(s_8021x);
+
+            if (identity && password) {
+                _LOGD(LOGD_DEVICE | LOGD_WIFI,
+                      "Returning the username and password to the IWD Agent");
+
+                g_dbus_method_invocation_return_value(invocation,
+                                                      g_variant_new("(ss)", identity, password));
+                *replied = TRUE;
+                return TRUE;
+            }
         }
 
         *setting_name = NM_SETTING_802_1X_SETTING_NAME;
@@ -1390,18 +1398,19 @@ try_reply_agent_request(NMDeviceIwd *          self,
             *setting_key = NM_SETTING_802_1X_PASSWORD;
         return TRUE;
     } else if (nm_streq(method_name, "RequestUserPassword")) {
-        const char *password;
-
         if (!s_8021x)
             return FALSE;
 
-        password = nm_setting_802_1x_get_password(s_8021x);
-        if (password) {
-            _LOGD(LOGD_DEVICE | LOGD_WIFI, "Returning the user password to the IWD Agent");
+        if (allow_existing) {
+            const char *password = nm_setting_802_1x_get_password(s_8021x);
 
-            g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", password));
-            *replied = TRUE;
-            return TRUE;
+            if (password) {
+                _LOGD(LOGD_DEVICE | LOGD_WIFI, "Returning the user password to the IWD Agent");
+
+                g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", password));
+                *replied = TRUE;
+                return TRUE;
+            }
         }
 
         *setting_name = NM_SETTING_802_1X_SETTING_NAME;
@@ -1452,6 +1461,8 @@ wifi_secrets_cb(NMActRequest *                req,
     gboolean                     replied;
     NMSecretAgentGetSecretsFlags get_secret_flags =
         NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
+    NMIwdNetworkSecurity security;
+    NMConnection *       connection;
 
     nm_utils_user_data_unpack(user_data, &self, &invocation);
 
@@ -1484,9 +1495,18 @@ wifi_secrets_cb(NMActRequest *                req,
         goto secrets_error;
     }
 
+    connection = nm_device_get_applied_connection(device);
+
+    if (nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security)
+        && security == NM_IWD_NETWORK_SECURITY_PSK) {
+        if (nm_settings_connection_get_timestamp(nm_device_get_settings_connection(device), NULL))
+            get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
+    }
+
     if (!try_reply_agent_request(self,
-                                 nm_act_request_get_applied_connection(req),
+                                 connection,
                                  invocation,
+                                 TRUE,
                                  &setting_name,
                                  &setting_key,
                                  &replied))
@@ -1512,9 +1532,6 @@ wifi_secrets_cb(NMActRequest *                req,
         return;
     }
 
-    if (nm_settings_connection_get_timestamp(nm_act_request_get_settings_connection(req), NULL))
-        get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
-
     /* Request further secrets if we still need something */
     wifi_secrets_get_one(self, setting_name, get_secret_flags, setting_key, invocation);
     return;
@@ -1606,8 +1623,6 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
             dbus_error = g_dbus_error_get_remote_error(error);
 
         if (nm_streq0(dbus_error, "net.connman.iwd.Failed")) {
-            nm_connection_clear_secrets(connection);
-
             /* 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") && priv->secrets_failed) {
@@ -2621,9 +2636,9 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
     switch (prop_id) {
     case PROP_MODE:
         if (!priv->current_ap)
-            g_value_set_uint(value, NM_802_11_MODE_UNKNOWN);
+            g_value_set_uint(value, _NM_802_11_MODE_UNKNOWN);
         else if (nm_wifi_ap_is_hotspot(priv->current_ap))
-            g_value_set_uint(value, NM_802_11_MODE_AP);
+            g_value_set_uint(value, _NM_802_11_MODE_AP);
         else
             g_value_set_uint(value, nm_wifi_ap_get_mode(priv->current_ap));
 
@@ -3045,7 +3060,7 @@ nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object)
     GVariantIter *              iter;
     const char *                mode;
     gboolean                    powered;
-    NMDeviceWifiCapabilities    capabilities;
+    _NMDeviceWifiCapabilities   capabilities;
 
     if (!nm_g_object_ref_set(&priv->dbus_obj, object))
         return;
@@ -3107,14 +3122,14 @@ nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object)
         goto error;
     }
 
-    capabilities = NM_WIFI_DEVICE_CAP_CIPHER_CCMP | NM_WIFI_DEVICE_CAP_RSN;
+    capabilities = _NM_WIFI_DEVICE_CAP_CIPHER_CCMP | _NM_WIFI_DEVICE_CAP_RSN;
 
     g_variant_get(value, "as", &iter);
     while (g_variant_iter_next(iter, "&s", &mode)) {
         if (nm_streq(mode, "ap"))
-            capabilities |= NM_WIFI_DEVICE_CAP_AP;
+            capabilities |= _NM_WIFI_DEVICE_CAP_AP;
         else if (nm_streq(mode, "ad-hoc"))
-            capabilities |= NM_WIFI_DEVICE_CAP_ADHOC;
+            capabilities |= _NM_WIFI_DEVICE_CAP_ADHOC;
     }
     g_variant_iter_free(iter);
 
@@ -3156,8 +3171,11 @@ nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation)
     const char *                 setting_key;
     gboolean                     replied;
     NMWifiAP *                   ap;
+    gboolean                     allow_existing = FALSE;
     NMSecretAgentGetSecretsFlags get_secret_flags =
         NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
+    NMIwdNetworkSecurity security;
+    NMConnection *       connection;
     nm_auto_ref_string NMRefString *network_path = NULL;
 
     if (!invocation) {
@@ -3261,9 +3279,37 @@ nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation)
         /* Otherwise handle as usual */
     }
 
+    /* Normally for PSK networks require new secret every time IWD asks for
+     * it.  IWD only queries us if it has not saved the PSK (e.g. by policy)
+     * or a previous attempt has failed with current secrets so it wants a
+     * fresh value.  It doesn't know about agent-owned secrets so whenever
+     * possible and the PSK is saved and not asked from NM.  However if this
+     * is a new connection it may include all of the needed settings already
+     * so allow using these, too.  Connection timestamp is set after
+     * activation or after first activation failure (to 0).
+     *
+     * For 802.1x, since IWD assumes the network is pre-provisioned by an
+     * admin and tested, there's no reason for IWD to save secrets in
+     * the network config file and there's no reason to ask for a new value
+     * of a saved (i.e. system-owned) secret because it can't be wrong.
+     * Since NM has a richer set of secret storage options we never specify
+     * NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW and let
+     * nm_settings_connection_get_secrets decide.
+     */
+    connection = nm_device_get_applied_connection(device);
+
+    if (nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security)
+        && security == NM_IWD_NETWORK_SECURITY_PSK) {
+        if (nm_settings_connection_get_timestamp(nm_device_get_settings_connection(device), NULL))
+            get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
+        else
+            allow_existing = TRUE;
+    }
+
     if (!try_reply_agent_request(self,
-                                 nm_device_get_applied_connection(device),
+                                 connection,
                                  invocation,
+                                 allow_existing,
                                  &setting_name,
                                  &setting_key,
                                  &replied)) {
@@ -3274,17 +3320,6 @@ nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation)
     if (replied)
         return TRUE;
 
-    /* Normally require new secrets every time IWD asks for them.
-     * IWD only queries us if it has not saved the secrets (e.g. by policy)
-     * or a previous attempt has failed with current secrets so it wants
-     * a fresh set.  However if this is a new connection it may include
-     * all of the needed settings already so allow using these, too.
-     * Connection timestamp is set after activation or after first
-     * activation failure (to 0).
-     */
-    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(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);
 
@@ -3463,9 +3498,9 @@ nm_device_iwd_class_init(NMDeviceIwdClass *klass)
     obj_properties[PROP_MODE] = g_param_spec_uint(NM_DEVICE_IWD_MODE,
                                                   "",
                                                   "",
-                                                  NM_802_11_MODE_UNKNOWN,
-                                                  NM_802_11_MODE_AP,
-                                                  NM_802_11_MODE_INFRA,
+                                                  _NM_802_11_MODE_UNKNOWN,
+                                                  _NM_802_11_MODE_AP,
+                                                  _NM_802_11_MODE_INFRA,
                                                   G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
     obj_properties[PROP_BITRATE] = g_param_spec_uint(NM_DEVICE_IWD_BITRATE,
@@ -3496,7 +3531,7 @@ nm_device_iwd_class_init(NMDeviceIwdClass *klass)
                           "",
                           0,
                           G_MAXUINT32,
-                          NM_WIFI_DEVICE_CAP_NONE,
+                          _NM_WIFI_DEVICE_CAP_NONE,
                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
     obj_properties[PROP_SCANNING] = g_param_spec_boolean(NM_DEVICE_IWD_SCANNING,
diff --git a/src/core/devices/wifi/nm-device-olpc-mesh.c b/src/core/devices/wifi/nm-device-olpc-mesh.c
index af83c4a3..040350d5 100644
--- a/src/core/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/core/devices/wifi/nm-device-olpc-mesh.c
@@ -28,7 +28,7 @@
 #include "nm-setting-connection.h"
 #include "nm-setting-olpc-mesh.h"
 #include "nm-manager.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceOlpcMesh
 #include "devices/nm-device-logging.h"
@@ -122,6 +122,17 @@ complete_connection(NMDevice *           device,
 
 /*****************************************************************************/
 
+static const char *
+get_dhcp_anycast_address(NMDevice *device)
+{
+    NMSettingOlpcMesh *s_mesh;
+
+    s_mesh = nm_device_get_applied_setting(device, NM_TYPE_SETTING_OLPC_MESH);
+    return s_mesh ? nm_setting_olpc_mesh_get_dhcp_anycast_address(s_mesh) : NULL;
+}
+
+/*****************************************************************************/
+
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
@@ -178,7 +189,6 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     NMDeviceOlpcMesh * self = NM_DEVICE_OLPC_MESH(device);
     NMSettingOlpcMesh *s_mesh;
     GBytes *           ssid;
-    const char *       anycast_addr;
     gboolean           success;
 
     s_mesh = nm_device_get_applied_setting(device, NM_TYPE_SETTING_OLPC_MESH);
@@ -197,9 +207,6 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         return NM_ACT_STAGE_RETURN_FAILURE;
     }
 
-    anycast_addr = nm_setting_olpc_mesh_get_dhcp_anycast_address(s_mesh);
-    nm_device_set_dhcp_anycast_address(device, anycast_addr);
-
     if (!_mesh_set_channel(self, nm_setting_olpc_mesh_get_channel(s_mesh))) {
         _LOGW(LOGD_WIFI, "Unable to set the mesh channel");
         return NM_ACT_STAGE_RETURN_FAILURE;
@@ -492,19 +499,15 @@ dispose(GObject *object)
 static const NMDBusInterfaceInfoExtended interface_info_device_olpc_mesh = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_OLPC_MESH,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Companion",
-                                                             "o",
-                                                             NM_DEVICE_OLPC_MESH_COMPANION),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L(
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Companion",
+                                                           "o",
+                                                           NM_DEVICE_OLPC_MESH_COMPANION),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
                 "ActiveChannel",
                 "u",
                 NM_DEVICE_OLPC_MESH_ACTIVE_CHANNEL), ), ),
-    .legacy_property_changed = TRUE,
 };
 
 static void
@@ -531,6 +534,7 @@ nm_device_olpc_mesh_class_init(NMDeviceOlpcMeshClass *klass)
     device_class->act_stage2_config           = act_stage2_config;
     device_class->state_changed               = state_changed;
     device_class->get_dhcp_timeout_for_device = get_dhcp_timeout_for_device;
+    device_class->get_dhcp_anycast_address    = get_dhcp_anycast_address;
 
     obj_properties[PROP_COMPANION] = g_param_spec_string(NM_DEVICE_OLPC_MESH_COMPANION,
                                                          "",
diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c
index fb987600..a8ea2ed7 100644
--- a/src/core/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/core/devices/wifi/nm-device-wifi-p2p.c
@@ -15,16 +15,16 @@
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
 #include "nm-act-request.h"
-#include "nm-core-internal.h"
-#include "nm-glib-aux/nm-ref-string.h"
+#include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-glib-aux/nm-ref-string.h"
 #include "nm-ip4-config.h"
 #include "nm-manager.h"
 #include "nm-manager.h"
 #include "nm-setting-wifi-p2p.h"
 #include "nm-utils.h"
 #include "nm-wifi-p2p-peer.h"
-#include "platform/nm-platform.h"
-#include "platform/nmp-object.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-platform/nmp-object.h"
 #include "settings/nm-settings.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceWifiP2P
@@ -1149,7 +1149,6 @@ static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p = {
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Peers",
                                                            "ao",
                                                            NM_DEVICE_WIFI_P2P_PEERS), ), ),
-    .legacy_property_changed = FALSE,
 };
 
 /*****************************************************************************/
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index c9655eea..fca2fde5 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -12,11 +12,11 @@
 #include <unistd.h>
 #include <linux/if_ether.h>
 
-#include "nm-glib-aux/nm-ref-string.h"
-#include "nm-glib-aux/nm-c-list.h"
+#include "libnm-glib-aux/nm-ref-string.h"
+#include "libnm-glib-aux/nm-c-list.h"
 #include "nm-device-wifi-p2p.h"
 #include "nm-wifi-ap.h"
-#include "nm-libnm-core-intern/nm-common-macros.h"
+#include "libnm-core-aux-intern/nm-common-macros.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-private.h"
 #include "nm-dbus-manager.h"
@@ -33,13 +33,13 @@
 #include "nm-setting-ip4-config.h"
 #include "nm-ip4-config.h"
 #include "nm-setting-ip6-config.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-auth-utils.h"
 #include "settings/nm-settings-connection.h"
 #include "settings/nm-settings.h"
 #include "nm-wifi-utils.h"
 #include "nm-wifi-common.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "nm-config.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceWifi
@@ -114,11 +114,11 @@ typedef struct {
     guint wps_timeout_id;
     guint sup_timeout_id; /* supplicant association timeout */
 
-    NMDeviceWifiCapabilities    capabilities;
-    NMSettingWirelessWakeOnWLan wowlan_restore;
+    _NMDeviceWifiCapabilities    capabilities;
+    _NMSettingWirelessWakeOnWLan wowlan_restore;
 
     NMDeviceWifiP2P *p2p_device;
-    NM80211Mode      mode;
+    _NM80211Mode     mode;
 
     guint32 failed_iface_count;
     gint32  hw_addr_scan_expire;
@@ -487,7 +487,7 @@ _scan_notify_allowed(NMDeviceWifi *self, NMTernary do_kickoff)
 
     if (!c_list_is_empty(&priv->scanning_prohibited_lst_head)) {
         /* something prohibits scanning. */
-    } else if (NM_IN_SET(priv->mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_AP)) {
+    } else if (NM_IN_SET(priv->mode, _NM_802_11_MODE_ADHOC, _NM_802_11_MODE_AP)) {
         /* Don't scan when a an AP or Ad-Hoc connection is active as it will
          * disrupt connected clients or peers. */
     } else if (NM_IN_SET(state, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_FAILED)) {
@@ -678,7 +678,7 @@ update_seen_bssids_cache(NMDeviceWifi *self, NMWifiAP *ap)
         return;
 
     /* Don't cache the BSSID for Ad-Hoc APs */
-    if (nm_wifi_ap_get_mode(ap) != NM_802_11_MODE_INFRA)
+    if (nm_wifi_ap_get_mode(ap) != _NM_802_11_MODE_INFRA)
         return;
 
     if (nm_device_get_state(NM_DEVICE(self)) == NM_DEVICE_STATE_ACTIVATED
@@ -712,10 +712,11 @@ set_current_ap(NMDeviceWifi *self, NMWifiAP *new_ap, gboolean recheck_available_
         priv->current_ap = NULL;
 
     if (old_ap) {
-        NM80211Mode mode = nm_wifi_ap_get_mode(old_ap);
+        _NM80211Mode mode = nm_wifi_ap_get_mode(old_ap);
 
         /* Remove any AP from the internal list if it was created by NM or isn't known to the supplicant */
-        if (NM_IN_SET(mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_AP) || nm_wifi_ap_get_fake(old_ap))
+        if (NM_IN_SET(mode, _NM_802_11_MODE_ADHOC, _NM_802_11_MODE_AP)
+            || nm_wifi_ap_get_fake(old_ap))
             ap_add_remove(self, FALSE, old_ap, recheck_available_connections);
         g_object_unref(old_ap);
     }
@@ -750,7 +751,7 @@ periodic_update(NMDeviceWifi *self)
         return;
     }
 
-    if (priv->mode == NM_802_11_MODE_AP) {
+    if (priv->mode == _NM_802_11_MODE_AP) {
         /* In AP mode we currently have nothing to do. */
         return;
     }
@@ -845,14 +846,14 @@ remove_all_aps(NMDeviceWifi *self)
 static gboolean
 wake_on_wlan_restore(NMDeviceWifi *self)
 {
-    NMDeviceWifiPrivate *       priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMSettingWirelessWakeOnWLan w;
+    NMDeviceWifiPrivate *        priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    _NMSettingWirelessWakeOnWLan w;
 
     w = priv->wowlan_restore;
-    if (w == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE)
+    if (w == _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE)
         return TRUE;
 
-    priv->wowlan_restore = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
+    priv->wowlan_restore = _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
     return nm_platform_wifi_set_wake_on_wlan(NM_PLATFORM_GET,
                                              nm_device_get_ifindex(NM_DEVICE(self)),
                                              w);
@@ -937,14 +938,14 @@ deactivate(NMDevice *device)
      * (usually older ones) don't scan well in adhoc mode.
      */
     if (nm_platform_wifi_get_mode(nm_device_get_platform(device), ifindex)
-        != NM_802_11_MODE_INFRA) {
+        != _NM_802_11_MODE_INFRA) {
         nm_device_take_down(NM_DEVICE(self), TRUE);
-        nm_platform_wifi_set_mode(nm_device_get_platform(device), ifindex, NM_802_11_MODE_INFRA);
+        nm_platform_wifi_set_mode(nm_device_get_platform(device), ifindex, _NM_802_11_MODE_INFRA);
         nm_device_bring_up(NM_DEVICE(self), TRUE, NULL);
     }
 
-    if (priv->mode != NM_802_11_MODE_INFRA) {
-        priv->mode = NM_802_11_MODE_INFRA;
+    if (priv->mode != _NM_802_11_MODE_INFRA) {
+        priv->mode = _NM_802_11_MODE_INFRA;
         _notify(self, PROP_MODE);
     }
 
@@ -1010,14 +1011,14 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
     /* Early exit if supplicant or device doesn't support requested mode */
     mode = nm_setting_wireless_get_mode(s_wireless);
     if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0) {
-        if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) {
+        if (!(priv->capabilities & _NM_WIFI_DEVICE_CAP_ADHOC)) {
             nm_utils_error_set_literal(error,
                                        NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
                                        "the device does not support Ad-Hoc networks");
             return FALSE;
         }
     } else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_AP) == 0) {
-        if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) {
+        if (!(priv->capabilities & _NM_WIFI_DEVICE_CAP_AP)) {
             nm_utils_error_set_literal(error,
                                        NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
                                        "the device does not support Access Point mode");
@@ -1034,7 +1035,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
             }
         }
     } else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_MESH) == 0) {
-        if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_MESH)) {
+        if (!(priv->capabilities & _NM_WIFI_DEVICE_CAP_MESH)) {
             nm_utils_error_set_literal(error,
                                        NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
                                        "the device does not support Mesh mode");
@@ -1381,8 +1382,8 @@ _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset)
     now = nm_utils_get_monotonic_timestamp_sec();
 
     if (now >= priv->hw_addr_scan_expire) {
-        gs_free char *generate_mac_address_mask = NULL;
-        gs_free char *hw_addr_scan              = NULL;
+        gs_free char *hw_addr_scan = NULL;
+        const char *  generate_mac_address_mask;
 
         /* the random MAC address for scanning expires after a while.
          *
@@ -1433,7 +1434,7 @@ ssids_options_to_ptrarray(GVariant *value, GError **error)
 
             v     = g_variant_get_child_value(value, i);
             bytes = g_variant_get_fixed_array(v, &len, sizeof(guint8));
-            if (len > 32) {
+            if (len > NM_IW_ESSID_MAX_SIZE) {
                 g_set_error(error,
                             NM_DEVICE_ERROR,
                             NM_DEVICE_ERROR_INVALID_ARGUMENT,
@@ -1822,7 +1823,7 @@ _scan_kickoff(NMDeviceWifi *self)
 
             strv = g_new(char *, ssids->len + 1u);
             for (i = 0; i < ssids->len; i++)
-                strv[i] = _nm_utils_ssid_to_string(ssids->pdata[i]);
+                strv[i] = _nm_utils_ssid_to_string_gbytes(ssids->pdata[i]);
             strv[i] = NULL;
 
             nm_assert(ssids->len > 0);
@@ -1884,13 +1885,13 @@ ap_list_dump(gpointer user_data)
 
         _LOGD(LOGD_WIFI_SCAN,
               "APs: [now:%u.%03u, last:%s]",
-              (guint)(now_msec / NM_UTILS_MSEC_PER_SEC),
-              (guint)(now_msec % NM_UTILS_MSEC_PER_SEC),
+              (guint) (now_msec / NM_UTILS_MSEC_PER_SEC),
+              (guint) (now_msec % NM_UTILS_MSEC_PER_SEC),
               priv->scan_last_complete_msec > 0
                   ? nm_sprintf_buf(str_buf,
                                    "%u.%03u",
-                                   (guint)(priv->scan_last_complete_msec / NM_UTILS_MSEC_PER_SEC),
-                                   (guint)(priv->scan_last_complete_msec % NM_UTILS_MSEC_PER_SEC))
+                                   (guint) (priv->scan_last_complete_msec / NM_UTILS_MSEC_PER_SEC),
+                                   (guint) (priv->scan_last_complete_msec % NM_UTILS_MSEC_PER_SEC))
                   : "-1");
         c_list_for_each_entry (ap, &priv->aps_lst_head, aps_lst)
             _ap_dump(self, LOGL_DEBUG, ap, "dump", now_msec);
@@ -1986,19 +1987,19 @@ supplicant_iface_bss_changed_cb(NMSupplicantInterface *iface,
 
         /* Let the manager try to fill in the SSID from seen-bssids lists */
         ssid = nm_wifi_ap_get_ssid(ap);
-        if (!ssid || _nm_utils_is_empty_ssid(ssid)) {
+        if (!ssid || _nm_utils_is_empty_ssid_gbytes(ssid)) {
             /* Try to fill the SSID from the AP database */
             try_fill_ssid_for_hidden_ap(self, ap);
 
             ssid = nm_wifi_ap_get_ssid(ap);
-            if (ssid && !_nm_utils_is_empty_ssid(ssid)) {
+            if (ssid && !_nm_utils_is_empty_ssid_gbytes(ssid)) {
                 gs_free char *s = NULL;
 
                 /* Yay, matched it, no longer treat as hidden */
                 _LOGD(LOGD_WIFI,
                       "matched hidden AP %s => %s",
                       nm_wifi_ap_get_address(ap),
-                      (s = _nm_utils_ssid_to_string(ssid)));
+                      (s = _nm_utils_ssid_to_string_gbytes(ssid)));
             } else {
                 /* Didn't have an entry for this AP in the database */
                 _LOGD(LOGD_WIFI, "failed to match hidden AP %s", nm_wifi_ap_get_address(ap));
@@ -2481,9 +2482,9 @@ supplicant_iface_state(NMDeviceWifi *             self,
 
             _LOGI(LOGD_DEVICE | LOGD_WIFI,
                   "Activation: (wifi) Stage 2 of 5 (Device Configure) successful. %s %s",
-                  priv->mode == NM_802_11_MODE_AP ? "Started Wi-Fi Hotspot"
-                                                  : "Connected to wireless network",
-                  (ssid_str = _nm_utils_ssid_to_string(ssid)));
+                  priv->mode == _NM_802_11_MODE_AP ? "Started Wi-Fi Hotspot"
+                                                   : "Connected to wireless network",
+                  (ssid_str = _nm_utils_ssid_to_string_gbytes(ssid)));
             nm_device_activate_schedule_stage3_ip_config_start(device);
         } else if (devstate == NM_DEVICE_STATE_ACTIVATED)
             periodic_update(self);
@@ -2599,9 +2600,9 @@ supplicant_iface_notify_current_bss(NMSupplicantInterface *iface,
         _LOGD(LOGD_WIFI,
               "roamed from BSSID %s (%s) to %s (%s)",
               old_bssid ?: "(none)",
-              (old_ssid_s = _nm_utils_ssid_to_string(old_ssid)),
+              (old_ssid_s = _nm_utils_ssid_to_string_gbytes(old_ssid)),
               new_bssid ?: "(none)",
-              (new_ssid_s = _nm_utils_ssid_to_string(new_ssid)));
+              (new_ssid_s = _nm_utils_ssid_to_string_gbytes(new_ssid)));
 
         if (new_bssid) {
             /* The new AP could be in a different layer 3 network
@@ -2790,21 +2791,21 @@ supplicant_connection_timeout_cb(gpointer user_data)
     connection = nm_act_request_get_applied_connection(req);
     g_assert(connection);
 
-    if (NM_IN_SET(priv->mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_MESH, NM_802_11_MODE_AP)) {
+    if (NM_IN_SET(priv->mode, _NM_802_11_MODE_ADHOC, _NM_802_11_MODE_MESH, _NM_802_11_MODE_AP)) {
         /* In Ad-Hoc and AP modes there's nothing to check the encryption key
          * (if any), so supplicant timeouts here are almost certainly the wifi
          * driver being really stupid.
          */
         _LOGW(LOGD_DEVICE | LOGD_WIFI,
               "Activation: (wifi) %s network creation took too long, failing activation",
-              priv->mode == NM_802_11_MODE_ADHOC ? "Ad-Hoc" : "Hotspot");
+              priv->mode == _NM_802_11_MODE_ADHOC ? "Ad-Hoc" : "Hotspot");
         nm_device_state_changed(device,
                                 NM_DEVICE_STATE_FAILED,
                                 NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
         return FALSE;
     }
 
-    g_assert(priv->mode == NM_802_11_MODE_INFRA);
+    g_assert(priv->mode == _NM_802_11_MODE_INFRA);
 
     if (priv->ssid_found && nm_connection_get_setting_wireless_security(connection)) {
         guint64  timestamp   = 0;
@@ -2956,9 +2957,10 @@ error:
 static gboolean
 wake_on_wlan_enable(NMDeviceWifi *self)
 {
-    NMDeviceWifiPrivate *       priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMSettingWirelessWakeOnWLan wowl;
-    NMSettingWireless *         s_wireless;
+    NMDeviceWifiPrivate *        priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    NMSettingWirelessWakeOnWLan  wowl;
+    _NMSettingWirelessWakeOnWLan wowl2;
+    NMSettingWireless *          s_wireless;
 
     s_wireless = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_WIRELESS);
     if (s_wireless) {
@@ -2990,9 +2992,11 @@ wake_on_wlan_enable(NMDeviceWifi *self)
         goto found;
 
     wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
+
 found:
-    if (wowl == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE) {
-        priv->wowlan_restore = wowl;
+    wowl2 = _NM_SETTING_WIRELESS_WAKE_ON_WLAN_CAST(wowl);
+    if (wowl2 == _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE) {
+        priv->wowlan_restore = wowl2;
         return TRUE;
     }
 
@@ -3001,7 +3005,7 @@ found:
 
     return nm_platform_wifi_set_wake_on_wlan(NM_PLATFORM_GET,
                                              nm_device_get_ifindex(NM_DEVICE(self)),
-                                             wowl);
+                                             wowl2);
 }
 
 static NMActStageReturn
@@ -3030,16 +3034,16 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
     mode = nm_setting_wireless_get_mode(s_wireless);
     if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_INFRA) == 0)
-        priv->mode = NM_802_11_MODE_INFRA;
+        priv->mode = _NM_802_11_MODE_INFRA;
     else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0)
-        priv->mode = NM_802_11_MODE_ADHOC;
+        priv->mode = _NM_802_11_MODE_ADHOC;
     else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_AP) == 0) {
-        priv->mode = NM_802_11_MODE_AP;
+        priv->mode = _NM_802_11_MODE_AP;
 
         /* Scanning not done in AP mode; clear the scan list */
         remove_all_aps(self);
     } else if (g_strcmp0(mode, NM_SETTING_WIRELESS_MODE_MESH) == 0)
-        priv->mode = NM_802_11_MODE_MESH;
+        priv->mode = _NM_802_11_MODE_MESH;
     _notify(self, PROP_MODE);
 
     /* expire the temporary MAC address used during scanning */
@@ -3052,7 +3056,7 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     }
 
     /* AP and Mesh modes never use a specific object or existing scanned AP */
-    if (!NM_IN_SET(priv->mode, NM_802_11_MODE_AP, NM_802_11_MODE_MESH)) {
+    if (!NM_IN_SET(priv->mode, _NM_802_11_MODE_AP, _NM_802_11_MODE_MESH)) {
         ap_path = nm_active_connection_get_specific_object(NM_ACTIVE_CONNECTION(req));
         ap      = ap_path ? nm_wifi_ap_lookup_for_device(NM_DEVICE(self), ap_path) : NULL;
     }
@@ -3154,7 +3158,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     NMDeviceWifi *       self                  = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv                  = NM_DEVICE_WIFI_GET_PRIVATE(self);
     gs_unref_object NMSupplicantConfig *config = NULL;
-    NM80211Mode                         ap_mode;
+    _NM80211Mode                        ap_mode;
     NMActRequest *                      req;
     NMWifiAP *                          ap;
     NMConnection *                      connection;
@@ -3221,10 +3225,11 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
      * if the user didn't specify one and we didn't find an AP that matched
      * the connection, just pick a frequency the device supports.
      */
-    if (NM_IN_SET(ap_mode, NM_802_11_MODE_ADHOC, NM_802_11_MODE_MESH) || nm_wifi_ap_is_hotspot(ap))
+    if (NM_IN_SET(ap_mode, _NM_802_11_MODE_ADHOC, _NM_802_11_MODE_MESH)
+        || nm_wifi_ap_is_hotspot(ap))
         ensure_hotspot_frequency(self, s_wireless, ap);
 
-    if (ap_mode == NM_802_11_MODE_INFRA)
+    if (ap_mode == _NM_802_11_MODE_INFRA)
         set_powersave(device);
 
     /* Build up the supplicant configuration */
@@ -3341,7 +3346,7 @@ act_stage4_ip_config_timeout(NMDevice *           device,
     s_ip       = nm_connection_get_setting_ip_config(connection, addr_family);
     may_fail   = nm_setting_ip_config_get_may_fail(s_ip);
 
-    if (priv->mode == NM_802_11_MODE_AP)
+    if (priv->mode == _NM_802_11_MODE_AP)
         goto call_parent;
 
     if (may_fail || !is_static_wep(connection)) {
@@ -3694,8 +3699,8 @@ nm_device_wifi_init(NMDeviceWifi *self)
 
     priv->scan_last_request_started_at_msec = G_MININT64;
     priv->hidden_probe_scan_warn            = TRUE;
-    priv->mode                              = NM_802_11_MODE_INFRA;
-    priv->wowlan_restore                    = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
+    priv->mode                              = _NM_802_11_MODE_INFRA;
+    priv->wowlan_restore                    = _NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
 }
 
 static void
@@ -3706,7 +3711,7 @@ constructed(GObject *object)
 
     G_OBJECT_CLASS(nm_device_wifi_parent_class)->constructed(object);
 
-    if (priv->capabilities & NM_WIFI_DEVICE_CAP_AP)
+    if (priv->capabilities & _NM_WIFI_DEVICE_CAP_AP)
         _LOGI(LOGD_PLATFORM | LOGD_WIFI, "driver supports Access Point (AP) mode");
 
     /* Connect to the supplicant manager */
@@ -3714,7 +3719,7 @@ constructed(GObject *object)
 }
 
 NMDevice *
-nm_device_wifi_new(const char *iface, NMDeviceWifiCapabilities capabilities)
+nm_device_wifi_new(const char *iface, _NMDeviceWifiCapabilities capabilities)
 {
     return g_object_new(NM_TYPE_DEVICE_WIFI,
                         NM_DEVICE_IFACE,
@@ -3822,9 +3827,9 @@ nm_device_wifi_class_init(NMDeviceWifiClass *klass)
     obj_properties[PROP_MODE] = g_param_spec_uint(NM_DEVICE_WIFI_MODE,
                                                   "",
                                                   "",
-                                                  NM_802_11_MODE_UNKNOWN,
-                                                  NM_802_11_MODE_AP,
-                                                  NM_802_11_MODE_INFRA,
+                                                  _NM_802_11_MODE_UNKNOWN,
+                                                  _NM_802_11_MODE_AP,
+                                                  _NM_802_11_MODE_INFRA,
                                                   G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
     obj_properties[PROP_BITRATE] = g_param_spec_uint(NM_DEVICE_WIFI_BITRATE,
@@ -3855,7 +3860,7 @@ nm_device_wifi_class_init(NMDeviceWifiClass *klass)
                           "",
                           0,
                           G_MAXUINT32,
-                          NM_WIFI_DEVICE_CAP_NONE,
+                          _NM_WIFI_DEVICE_CAP_NONE,
                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
     obj_properties[PROP_SCANNING] = g_param_spec_boolean(NM_DEVICE_WIFI_SCANNING,
diff --git a/src/core/devices/wifi/nm-device-wifi.h b/src/core/devices/wifi/nm-device-wifi.h
index d9e9038c..c7ff1a14 100644
--- a/src/core/devices/wifi/nm-device-wifi.h
+++ b/src/core/devices/wifi/nm-device-wifi.h
@@ -33,7 +33,7 @@ typedef struct _NMDeviceWifiClass NMDeviceWifiClass;
 
 GType nm_device_wifi_get_type(void);
 
-NMDevice *nm_device_wifi_new(const char *iface, NMDeviceWifiCapabilities capabilities);
+NMDevice *nm_device_wifi_new(const char *iface, _NMDeviceWifiCapabilities capabilities);
 
 const CList *_nm_device_wifi_get_aps(NMDeviceWifi *self);
 
diff --git a/src/core/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c
index 68a74438..56386c57 100644
--- a/src/core/devices/wifi/nm-iwd-manager.c
+++ b/src/core/devices/wifi/nm-iwd-manager.c
@@ -8,14 +8,20 @@
 #include "nm-iwd-manager.h"
 
 #include <net/if.h>
+#include <glib/gstdio.h>
+#include <errno.h>
+#include <sys/stat.h>
 
-#include "nm-core-internal.h"
+#include "libnm-core-intern/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 "libnm-glib-aux/nm-uuid.h"
+#include "libnm-glib-aux/nm-random-utils.h"
+#include "libnm-glib-aux/nm-io-utils.h"
 #include "settings/nm-settings.h"
-#include "nm-std-aux/nm-dbus-compat.h"
+#include "libnm-std-aux/nm-dbus-compat.h"
+#include "nm-config.h"
 
 /*****************************************************************************/
 
@@ -28,6 +34,7 @@ typedef struct {
 typedef struct {
     GDBusProxy *          known_network;
     NMSettingsConnection *mirror_connection;
+    const KnownNetworkId *id;
 } KnownNetworkData;
 
 typedef struct {
@@ -40,6 +47,8 @@ typedef struct {
     char *              agent_path;
     GHashTable *        known_networks;
     NMDeviceIwd *       last_agent_call_device;
+    char *              last_state_dir;
+    char *              warned_state_dir;
 } NMIwdManagerPrivate;
 
 struct _NMIwdManager {
@@ -419,25 +428,94 @@ known_network_update_cb(GObject *source, GAsyncResult *res, gpointer user_data)
     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",
+                    "iwd: 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 gboolean
+iwd_config_write(GKeyFile *             config,
+                 const char *           filepath,
+                 const struct timespec *mtime,
+                 GError **              error)
+{
+    gsize           length;
+    gs_free char *  data     = g_key_file_to_data(config, &length, NULL);
+    struct timespec times[2] = {{.tv_nsec = UTIME_OMIT}, *mtime};
+
+    /* Atomically write or replace the file with the right permission bits
+     * and timestamps set.  We rely on the temporary file created by
+     * nm_utils_file_set_contents having only upper-case letters and digits
+     * in the last few filename characters -- it cannot end in .open, .psk
+     * or .8021x.
+     */
+    return nm_utils_file_set_contents(filepath, data, length, 0600, times, NULL, error);
+}
+
+static const char *
+get_config_path(NMIwdManager *self)
+{
+    NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+    const char *         path;
+
+    path = nm_config_data_get_iwd_config_path(NM_CONFIG_GET_DATA);
+    if (path && path[0] == '\0') {
+        nm_clear_g_free(&priv->warned_state_dir);
+        return NULL;
+    }
+
+    if (!path || nm_streq(path, "auto")) {
+        path = priv->last_state_dir;
+        if (!path) {
+            nm_clear_g_free(&priv->warned_state_dir);
+            return NULL;
+        }
+    }
+
+    if (priv->warned_state_dir && !nm_streq(priv->warned_state_dir, path))
+        nm_clear_g_free(&priv->warned_state_dir);
+
+    if (path && (path[0] != '/' || !g_file_test(path, G_FILE_TEST_IS_DIR))) {
+        if (!priv->warned_state_dir) {
+            priv->warned_state_dir = g_strdup(path);
+            _LOGW("IWD StateDirectory '%s' not accessible", priv->warned_state_dir);
+        }
+        return NULL;
+    }
+
+    return path;
+}
+
 static void
-sett_conn_changed(NMSettingsConnection *sett_conn, guint update_reason, KnownNetworkData *data)
+sett_conn_changed(NMSettingsConnection *  sett_conn,
+                  guint                   update_reason,
+                  const 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);
+    NMConnection *               conn          = nm_settings_connection_get_connection(sett_conn);
+    NMSettingConnection *        s_conn        = nm_connection_get_setting_connection(conn);
+    NMSettingWireless *          s_wifi        = nm_connection_get_setting_wireless(conn);
+    nm_auto_unref_keyfile GKeyFile *iwd_config = NULL;
+    const char *                    iwd_dir;
+    gs_free char *                  filename  = NULL;
+    gs_free char *                  full_path = NULL;
+    gs_free_error GError *error               = NULL;
+    NMIwdNetworkSecurity  security;
+    GBytes *              ssid;
+    const guint8 *        ssid_data;
+    gsize                 ssid_len;
+    gboolean              removed;
+    GStatBuf              statbuf;
+    gboolean              have_mtime;
 
     nm_assert(sett_conn == data->mirror_connection);
 
-    if (iwd_autoconnectable == nm_autoconnectable)
+    if (!NM_FLAGS_ANY(update_reason,
+                      NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET
+                          | NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_SYSTEM_SECRETS
+                          | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS))
         return;
 
     /* If this is a generated connection it may be ourselves updating it */
@@ -445,21 +523,131 @@ sett_conn_changed(NMSettingsConnection *sett_conn, guint update_reason, KnownNet
     if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
         return;
 
+    iwd_dir = get_config_path(nm_iwd_manager_get());
+    if (!iwd_dir) {
+        gboolean nm_autoconnectable  = nm_setting_connection_get_autoconnect(s_conn);
+        gboolean iwd_autoconnectable = get_property_bool(data->known_network, "AutoConnect", TRUE);
+
+        if (iwd_autoconnectable != nm_autoconnectable) {
+            nm_log_dbg(LOGD_WIFI,
+                       "iwd: 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");
+        }
+
+        return;
+    }
+
+    /* If the SSID and the security type in the NMSettingsConnection haven't
+     * changed, we just need to overwrite the original IWD config file.
+     * Otherwise we need to call Forget on the original KnownNetwork or
+     * remove its file.  IWD will have to delete one D-Bus object and
+     * create another anyway because the SSID and security type are in the
+     * D-Bus object path, so no point renaming the file.
+     */
+    ssid       = nm_setting_wireless_get_ssid(s_wifi);
+    ssid_data  = ssid ? g_bytes_get_data(ssid, &ssid_len) : NULL;
+    removed    = FALSE;
+    have_mtime = FALSE;
+
+    if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &security)
+        || security != data->id->security || !ssid_data || ssid_len != strlen(data->id->name)
+        || memcmp(ssid_data, data->id->name, ssid_len)) {
+        gs_free char *orig_filename =
+            nm_wifi_utils_get_iwd_config_filename(data->id->name, -1, data->id->security);
+        gs_free char *orig_full_path = g_strdup_printf("%s/%s", iwd_dir, orig_filename);
+
+        if (g_stat(orig_full_path, &statbuf) == 0)
+            have_mtime = TRUE;
+
+        if (g_remove(orig_full_path) == 0)
+            nm_log_dbg(LOGD_WIFI, "iwd: profile at %s removed", orig_full_path);
+        else if (errno != ENOENT)
+            nm_log_dbg(LOGD_WIFI,
+                       "iwd: profile at %s not removed: %s (%i)",
+                       orig_full_path,
+                       strerror(errno),
+                       errno);
+
+        removed = TRUE;
+    }
+
+    if (!nm_streq(nm_settings_connection_get_connection_type(sett_conn), "802-11-wireless")
+        || !s_wifi)
+        return;
+
+    /* If the connection has any permissions other than the default we don't
+     * want to save it as an IWD profile.  IWD will make it available for
+     * everybody to attempt a connection, remove, or toggle "autoconnectable".
+     */
+    if (s_conn && nm_setting_connection_get_num_permissions(s_conn)) {
+        nm_log_dbg(
+            LOGD_WIFI,
+            "iwd: changed Wi-Fi connection %s not mirrored as IWD profile because of non-default "
+            "permissions",
+            nm_settings_connection_get_id(sett_conn));
+        return;
+    }
+
+    iwd_config = nm_wifi_utils_connection_to_iwd_config(conn, &filename, &error);
+    if (!iwd_config) {
+        /* The error message here is not translated and it only goes in
+         * the logs.
+         */
+        nm_log_dbg(LOGD_WIFI,
+                   "iwd: changed Wi-Fi connection %s not mirrored as IWD profile: %s",
+                   nm_settings_connection_get_id(sett_conn),
+                   error->message);
+        return;
+    }
+
+    full_path = g_strdup_printf("%s/%s", iwd_dir, filename);
+    if (removed && g_file_test(full_path, G_FILE_TEST_EXISTS)) {
+        nm_log_dbg(LOGD_WIFI,
+                   "iwd: changed Wi-Fi connection %s not mirrored as IWD profile because %s "
+                   "already exists",
+                   nm_settings_connection_get_id(sett_conn),
+                   full_path);
+        return;
+    }
+
+    if (!removed && g_stat(full_path, &statbuf) == 0)
+        have_mtime = TRUE;
+
+    /* If modifying an existing network try to preserve the file mtime,
+     * otherwise use a small non-zero timespec value to signal that the
+     * network is autoconnectable (according to its AutoConnect value)
+     * but hasn't recently been connected to and thus shouldn't be
+     * prioritized by autoconnect.
+     */
+    if (!have_mtime) {
+        statbuf.st_mtim.tv_sec  = 1;
+        statbuf.st_mtim.tv_nsec = 0;
+    }
+
+    if (!iwd_config_write(iwd_config, full_path, &statbuf.st_mtim, &error)) {
+        nm_log_dbg(LOGD_WIFI,
+                   "iwd: changed Wi-Fi connection %s not mirrored as IWD profile: save error: %s",
+                   nm_settings_connection_get_id(sett_conn),
+                   error->message);
+        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");
+               "iwd: changed Wi-Fi connection %s mirrored as IWD profile %s",
+               nm_settings_connection_get_id(sett_conn),
+               full_path);
 }
 
 /* Look up an existing NMSettingsConnection for a network that has been
@@ -590,8 +778,10 @@ mirror_connection(NMIwdManager *        self,
                          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);
+            KnownNetworkData data = {known_network, settings_connection, id};
+            sett_conn_changed(settings_connection,
+                              NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET,
+                              &data);
         }
     }
 
@@ -621,7 +811,7 @@ mirror_connection(NMIwdManager *        self,
                            NM_SETTING_CONNECTION_ID,
                            id->name,
                            NM_SETTING_CONNECTION_UUID,
-                           nm_utils_uuid_generate_buf(uuid),
+                           nm_uuid_generate_random_str_arr(uuid),
                            NM_SETTING_CONNECTION_AUTOCONNECT,
                            autoconnectable,
                            NULL);
@@ -779,6 +969,7 @@ interface_added(GDBusObjectManager *object_manager,
         } else {
             data                = g_slice_new0(KnownNetworkData);
             data->known_network = g_object_ref(proxy);
+            data->id            = id;
             g_hash_table_insert(priv->known_networks, id, data);
         }
 
@@ -907,6 +1098,9 @@ connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpoint
     const guint8 *        ssid_bytes;
     gsize                 ssid_len;
     NMSettingsConnection *new_mirror_conn;
+    const char *          iwd_dir;
+    gs_free char *        filename  = NULL;
+    gs_free char *        full_path = NULL;
 
     if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &id.security))
         return;
@@ -923,8 +1117,12 @@ connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpoint
     ssid_buf[ssid_len] = '\0';
     id.name            = ssid_buf;
     data               = g_hash_table_lookup(priv->known_networks, &id);
-    if (!data)
-        return;
+    if (!data) {
+        if (!g_utf8_validate((const char *) ssid_bytes, ssid_len, NULL))
+            return;
+
+        goto try_delete_file;
+    }
 
     if (data->mirror_connection != sett_conn)
         return;
@@ -941,7 +1139,7 @@ connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpoint
     }
 
     if (!priv->running)
-        return;
+        goto try_delete_file;
 
     g_dbus_proxy_call(data->known_network,
                       "Forget",
@@ -951,6 +1149,91 @@ connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpoint
                       NULL,
                       NULL,
                       NULL);
+    return;
+
+try_delete_file:
+    if (mirror_connection(self, &id, FALSE, NULL))
+        return;
+
+    iwd_dir = get_config_path(self);
+    if (!iwd_dir)
+        return;
+
+    filename  = nm_wifi_utils_get_iwd_config_filename(id.name, ssid_len, id.security);
+    full_path = g_strdup_printf("%s/%s", iwd_dir, filename);
+    if (g_remove(full_path) == 0)
+        _LOGD("IWD profile at %s removed", full_path);
+    else if (errno != ENOENT)
+        _LOGD("IWD profile at %s not removed: %s (%i)", full_path, strerror(errno), errno);
+}
+
+static void
+connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data)
+{
+    NMIwdManager *       self   = user_data;
+    NMConnection *       conn   = nm_settings_connection_get_connection(sett_conn);
+    NMSettingConnection *s_conn = nm_connection_get_setting_connection(conn);
+    const char *         iwd_dir;
+    gs_free char *       filename              = NULL;
+    gs_free char *       full_path             = NULL;
+    gs_free_error GError *error                = NULL;
+    nm_auto_unref_keyfile GKeyFile *iwd_config = NULL;
+    NMSettingsConnectionIntFlags    flags;
+
+    if (!nm_streq(nm_settings_connection_get_connection_type(sett_conn), "802-11-wireless"))
+        return;
+
+    iwd_dir = get_config_path(self);
+    if (!iwd_dir)
+        return;
+
+    /* If this is a generated connection it may be ourselves creating it and
+     * directly assigning it to a KnownNetwork's .mirror_connection.
+     */
+    flags = nm_settings_connection_get_flags(sett_conn);
+    if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
+        return;
+
+    /* If the connection has any permissions other than the default we don't
+     * want to save it as an IWD profile.  IWD will make it available for
+     * everybody to attempt a connection, remove, or toggle "autoconnectable".
+     */
+    if (s_conn && nm_setting_connection_get_num_permissions(s_conn)) {
+        _LOGD("New Wi-Fi connection %s not mirrored as IWD profile because of non-default "
+              "permissions",
+              nm_settings_connection_get_id(sett_conn));
+        return;
+    }
+
+    iwd_config = nm_wifi_utils_connection_to_iwd_config(conn, &filename, &error);
+    if (!iwd_config) {
+        /* The error message here is not translated and it only goes in
+         * the logs.
+         */
+        _LOGD("New Wi-Fi connection %s not mirrored as IWD profile: %s",
+              nm_settings_connection_get_id(sett_conn),
+              error->message);
+        return;
+    }
+
+    full_path = g_strdup_printf("%s/%s", iwd_dir, filename);
+    if (g_file_test(full_path, G_FILE_TEST_EXISTS)) {
+        _LOGD("New Wi-Fi connection %s not mirrored as IWD profile because %s already exists",
+              nm_settings_connection_get_id(sett_conn),
+              full_path);
+        return;
+    }
+
+    if (!g_key_file_save_to_file(iwd_config, full_path, &error)) {
+        _LOGD("New Wi-Fi connection %s not mirrored as IWD profile: save error: %s",
+              nm_settings_connection_get_id(sett_conn),
+              error->message);
+        return;
+    }
+
+    _LOGD("New Wi-Fi connection %s mirrored as IWD profile %s",
+          nm_settings_connection_get_id(sett_conn),
+          full_path);
 }
 
 static gboolean
@@ -1115,7 +1398,7 @@ device_removed(NMManager *manager, NMDevice *device, gpointer user_data)
 static int
 object_compare_interfaces(gconstpointer a, gconstpointer b)
 {
-    static const char *interface_order[] = {
+    static const char *const interface_order[] = {
         NM_IWD_KNOWN_NETWORK_INTERFACE,
         NM_IWD_NETWORK_INTERFACE,
         NM_IWD_DEVICE_INTERFACE,
@@ -1146,6 +1429,56 @@ object_compare_interfaces(gconstpointer a, gconstpointer b)
 }
 
 static void
+get_daemon_info_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+    NMIwdManager *       self = user_data;
+    NMIwdManagerPrivate *priv;
+    gs_unref_variant GVariant *properties = NULL;
+    gs_free_error GError *error           = NULL;
+    GVariantIter *        properties_iter;
+    const char *          key;
+    GVariant *            value;
+
+    properties = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
+    if (!properties) {
+        if (nm_utils_error_is_cancelled(error))
+            return;
+
+        nm_log_warn(LOGD_WIFI, "iwd: Daemon.GetInfo() failed: %s", error->message);
+        return;
+    }
+
+    priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+
+    if (!g_variant_is_of_type(properties, G_VARIANT_TYPE("(a{sv})"))) {
+        _LOGE("Daemon.GetInfo returned type %s instead of (a{sv})",
+              g_variant_get_type_string(properties));
+        return;
+    }
+
+    g_variant_get(properties, "(a{sv})", &properties_iter);
+
+    while (g_variant_iter_next(properties_iter, "{&sv}", &key, &value)) {
+        if (nm_streq(key, "StateDirectory")) {
+            if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) {
+                _LOGE("Daemon.GetInfo property %s is typed '%s' instead of 's'",
+                      key,
+                      g_variant_get_type_string(value));
+                goto next;
+            }
+
+            nm_clear_g_free(&priv->last_state_dir);
+            priv->last_state_dir = g_variant_dup_string(value, NULL);
+        }
+
+next:
+        g_variant_unref(value);
+    }
+
+    g_variant_iter_free(properties_iter);
+}
+
+static void
 got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data)
 {
     NMIwdManager *       self  = user_data;
@@ -1182,7 +1515,8 @@ got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data)
     }
 
     if (_om_has_name_owner(object_manager)) {
-        GList *objects, *iter;
+        GList *         objects, *iter;
+        gs_unref_object GDBusInterface *daemon = NULL;
 
         priv->running = true;
 
@@ -1208,6 +1542,19 @@ got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data)
 
         if (priv->agent_id)
             register_agent(self);
+
+        daemon = g_dbus_object_manager_get_interface(object_manager,
+                                                     "/net/connman/iwd", /* IWD 1.15+ */
+                                                     NM_IWD_DAEMON_INTERFACE);
+        if (daemon)
+            g_dbus_proxy_call(G_DBUS_PROXY(daemon),
+                              "GetInfo",
+                              g_variant_new("()"),
+                              G_DBUS_CALL_FLAGS_NONE,
+                              -1,
+                              priv->cancellable,
+                              get_daemon_info_cb,
+                              self);
     }
 }
 
@@ -1305,11 +1652,31 @@ nm_iwd_manager_init(NMIwdManager *self)
     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);
 
+    /* The current logic is that we track all creations and removals but
+     * for modifications we only listen to those connections that are
+     * currently a KnownNetwork's mirror_connection.  There may be multiple
+     * NMSettingsConnections referring to the same SSID+Security type tuple
+     * so to the same KnownNetwork.  So to make connection profile editing
+     * work at least for the simple cases, we track one NMSettingsConnection
+     * out of those, and we map its changes to the IWD KnownNetwork.
+     *
+     * When an NMSettingsConnection is created by a user for a completely
+     * new network and the settings are compatible with IWD, we create an
+     * IWD KnownNetwork config file for it.  IWD will notice that and a
+     * KnownNetwork objects pops up on D-Bus.  We look up a suitable
+     * mirror_connection for it and only then subscribe to modification
+     * signals.  There are various different ways that this could be done,
+     * it's not clear which one's the best.
+     */
     priv->settings = g_object_ref(NM_SETTINGS_GET);
     g_signal_connect(priv->settings,
                      NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
                      G_CALLBACK(connection_removed),
                      self);
+    g_signal_connect(priv->settings,
+                     NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
+                     G_CALLBACK(connection_added),
+                     self);
 
     priv->cancellable = g_cancellable_new();
 
@@ -1348,6 +1715,9 @@ dispose(GObject *object)
 
     priv->last_agent_call_device = NULL;
 
+    nm_clear_g_free(&priv->last_state_dir);
+    nm_clear_g_free(&priv->warned_state_dir);
+
     G_OBJECT_CLASS(nm_iwd_manager_parent_class)->dispose(object);
 }
 
diff --git a/src/core/devices/wifi/nm-iwd-manager.h b/src/core/devices/wifi/nm-iwd-manager.h
index 466f67c7..e794e661 100644
--- a/src/core/devices/wifi/nm-iwd-manager.h
+++ b/src/core/devices/wifi/nm-iwd-manager.h
@@ -13,6 +13,7 @@
 #define NM_IWD_BUS_TYPE G_BUS_TYPE_SYSTEM
 #define NM_IWD_SERVICE  "net.connman.iwd"
 
+#define NM_IWD_DAEMON_INTERFACE        "net.connman.iwd.Daemon"
 #define NM_IWD_AGENT_MANAGER_INTERFACE "net.connman.iwd.AgentManager"
 #define NM_IWD_WIPHY_INTERFACE         "net.connman.iwd.Adapter"
 #define NM_IWD_DEVICE_INTERFACE        "net.connman.iwd.Device"
diff --git a/src/core/devices/wifi/nm-wifi-ap.c b/src/core/devices/wifi/nm-wifi-ap.c
index 08fa10ec..20ed6833 100644
--- a/src/core/devices/wifi/nm-wifi-ap.c
+++ b/src/core/devices/wifi/nm-wifi-ap.c
@@ -13,13 +13,13 @@
 
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "nm-dbus-manager.h"
-#include "nm-glib-aux/nm-ref-string.h"
+#include "libnm-glib-aux/nm-ref-string.h"
 #include "nm-setting-wireless.h"
 #include "nm-utils.h"
 #include "nm-wifi-utils.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "supplicant/nm-supplicant-interface.h"
 
 #define PROTO_WPA "wpa"
@@ -41,12 +41,12 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMWifiAP,
 
 struct _NMWifiAPPrivate {
     /* Scanned or cached values */
-    GBytes *    ssid;
-    char *      address;
-    NM80211Mode mode;
-    guint8      strength;
-    guint32     freq;        /* Frequency in MHz; ie 2412 (== 2.412 GHz) */
-    guint32     max_bitrate; /* Maximum bitrate of the AP in Kbit/s (ie 54000 Kb/s == 54Mbit/s) */
+    GBytes *     ssid;
+    char *       address;
+    _NM80211Mode mode;
+    guint8       strength;
+    guint32      freq;        /* Frequency in MHz; ie 2412 (== 2.412 GHz) */
+    guint32      max_bitrate; /* Maximum bitrate of the AP in Kbit/s (ie 54000 Kb/s == 54Mbit/s) */
 
     gint64
         last_seen_msec; /* Timestamp when the AP was seen lastly (in nm_utils_get_monotonic_timestamp_*() scale).
@@ -98,7 +98,7 @@ nm_wifi_ap_set_ssid(NMWifiAP *ap, GBytes *ssid)
     }
 
     l = g_bytes_get_size(ssid);
-    if (l == 0 || l > 32)
+    if (l == 0 || l > NM_IW_ESSID_MAX_SIZE)
         g_return_val_if_reached(FALSE);
 
     priv = NM_WIFI_AP_GET_PRIVATE(ap);
@@ -191,24 +191,24 @@ nm_wifi_ap_set_address(NMWifiAP *ap, const char *addr)
     return nm_wifi_ap_set_address_bin(ap, &addr_buf);
 }
 
-NM80211Mode
+_NM80211Mode
 nm_wifi_ap_get_mode(NMWifiAP *ap)
 {
-    g_return_val_if_fail(NM_IS_WIFI_AP(ap), NM_802_11_MODE_UNKNOWN);
+    g_return_val_if_fail(NM_IS_WIFI_AP(ap), _NM_802_11_MODE_UNKNOWN);
 
     return NM_WIFI_AP_GET_PRIVATE(ap)->mode;
 }
 
 static gboolean
-nm_wifi_ap_set_mode(NMWifiAP *ap, NM80211Mode mode)
+nm_wifi_ap_set_mode(NMWifiAP *ap, _NM80211Mode mode)
 {
     NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(ap);
 
     nm_assert(NM_IN_SET(mode,
-                        NM_802_11_MODE_UNKNOWN,
-                        NM_802_11_MODE_ADHOC,
-                        NM_802_11_MODE_INFRA,
-                        NM_802_11_MODE_MESH));
+                        _NM_802_11_MODE_UNKNOWN,
+                        _NM_802_11_MODE_ADHOC,
+                        _NM_802_11_MODE_INFRA,
+                        _NM_802_11_MODE_MESH));
 
     if (priv->mode != mode) {
         priv->mode = mode;
@@ -513,34 +513,35 @@ nm_wifi_ap_to_string(const NMWifiAP *self, char *str_buf, gulong buf_len, gint64
 
     nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
 
-    g_snprintf(str_buf,
-               buf_len,
-               "%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
-                    ? '*'
-                    : (priv->hotspot
-                           ? '#'
-                           : (priv->fake ? 'f' : (priv->mode == NM_802_11_MODE_MESH ? 'm' : 'a')))),
-               chan,
-               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
-                   ? nm_sprintf_buf(str_buf_ts,
-                                    "%3u.%03us",
-                                    (guint)((now_msec - priv->last_seen_msec) / 1000),
-                                    (guint)((now_msec - priv->last_seen_msec) % 1000))
-                   : "        ",
-               supplicant_id,
-               export_path);
+    g_snprintf(
+        str_buf,
+        buf_len,
+        "%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_gbytes(priv->ssid)),
+        (priv->mode == _NM_802_11_MODE_ADHOC
+             ? '*'
+             : (priv->hotspot
+                    ? '#'
+                    : (priv->fake ? 'f' : (priv->mode == _NM_802_11_MODE_MESH ? 'm' : 'a')))),
+        chan,
+        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
+            ? nm_sprintf_buf(str_buf_ts,
+                             "%3u.%03us",
+                             (guint) ((now_msec - priv->last_seen_msec) / 1000),
+                             (guint) ((now_msec - priv->last_seen_msec) % 1000))
+            : "        ",
+        supplicant_id,
+        export_path);
     return str_buf;
 }
 
@@ -589,13 +590,13 @@ nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection)
 
     mode = nm_setting_wireless_get_mode(s_wireless);
     if (mode) {
-        if (!strcmp(mode, "infrastructure") && (priv->mode != NM_802_11_MODE_INFRA))
+        if (!strcmp(mode, "infrastructure") && (priv->mode != _NM_802_11_MODE_INFRA))
             return FALSE;
-        if (!strcmp(mode, "adhoc") && (priv->mode != NM_802_11_MODE_ADHOC))
+        if (!strcmp(mode, "adhoc") && (priv->mode != _NM_802_11_MODE_ADHOC))
             return FALSE;
-        if (!strcmp(mode, "ap") && (priv->mode != NM_802_11_MODE_INFRA || priv->hotspot != TRUE))
+        if (!strcmp(mode, "ap") && (priv->mode != _NM_802_11_MODE_INFRA || priv->hotspot != TRUE))
             return FALSE;
-        if (!strcmp(mode, "mesh") && (priv->mode != NM_802_11_MODE_MESH))
+        if (!strcmp(mode, "mesh") && (priv->mode != _NM_802_11_MODE_MESH))
             return FALSE;
     }
 
@@ -624,7 +625,7 @@ nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection)
                                                       priv->flags,
                                                       priv->wpa_flags,
                                                       priv->rsn_flags,
-                                                      priv->mode);
+                                                      NM_802_11_MODE_CAST(priv->mode));
 }
 
 gboolean
@@ -713,7 +714,7 @@ nm_wifi_ap_init(NMWifiAP *self)
 
     c_list_init(&self->aps_lst);
 
-    priv->mode           = NM_802_11_MODE_INFRA;
+    priv->mode           = _NM_802_11_MODE_INFRA;
     priv->flags          = NM_802_11_AP_FLAGS_NONE;
     priv->wpa_flags      = NM_802_11_AP_SEC_NONE;
     priv->rsn_flags      = NM_802_11_AP_SEC_NONE;
@@ -758,19 +759,19 @@ nm_wifi_ap_new_fake_from_connection(NMConnection *connection)
     mode = nm_setting_wireless_get_mode(s_wireless);
     if (mode) {
         if (!strcmp(mode, "infrastructure"))
-            nm_wifi_ap_set_mode(ap, NM_802_11_MODE_INFRA);
+            nm_wifi_ap_set_mode(ap, _NM_802_11_MODE_INFRA);
         else if (!strcmp(mode, "adhoc")) {
-            nm_wifi_ap_set_mode(ap, NM_802_11_MODE_ADHOC);
+            nm_wifi_ap_set_mode(ap, _NM_802_11_MODE_ADHOC);
             adhoc = TRUE;
         } else if (!strcmp(mode, "mesh"))
-            nm_wifi_ap_set_mode(ap, NM_802_11_MODE_MESH);
+            nm_wifi_ap_set_mode(ap, _NM_802_11_MODE_MESH);
         else if (!strcmp(mode, "ap")) {
-            nm_wifi_ap_set_mode(ap, NM_802_11_MODE_INFRA);
+            nm_wifi_ap_set_mode(ap, _NM_802_11_MODE_INFRA);
             NM_WIFI_AP_GET_PRIVATE(ap)->hotspot = TRUE;
         } else
             goto error;
     } else {
-        nm_wifi_ap_set_mode(ap, NM_802_11_MODE_INFRA);
+        nm_wifi_ap_set_mode(ap, _NM_802_11_MODE_INFRA);
     }
 
     band    = nm_setting_wireless_get_band(s_wireless);
@@ -861,27 +862,21 @@ finalize(GObject *object)
 static const NMDBusInterfaceInfoExtended interface_info_access_point = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_ACCESS_POINT,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Flags", "u", NM_WIFI_AP_FLAGS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("WpaFlags", "u", NM_WIFI_AP_WPA_FLAGS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("RsnFlags", "u", NM_WIFI_AP_RSN_FLAGS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ssid", "ay", NM_WIFI_AP_SSID),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Frequency",
-                                                             "u",
-                                                             NM_WIFI_AP_FREQUENCY),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_WIFI_AP_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "u", NM_WIFI_AP_MODE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("MaxBitrate",
-                                                             "u",
-                                                             NM_WIFI_AP_MAX_BITRATE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Strength", "y", NM_WIFI_AP_STRENGTH),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("LastSeen",
-                                                             "i",
-                                                             NM_WIFI_AP_LAST_SEEN), ), ),
-    .legacy_property_changed = TRUE,
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Flags", "u", NM_WIFI_AP_FLAGS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("WpaFlags", "u", NM_WIFI_AP_WPA_FLAGS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("RsnFlags", "u", NM_WIFI_AP_RSN_FLAGS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ssid", "ay", NM_WIFI_AP_SSID),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Frequency", "u", NM_WIFI_AP_FREQUENCY),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_WIFI_AP_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Mode", "u", NM_WIFI_AP_MODE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("MaxBitrate",
+                                                           "u",
+                                                           NM_WIFI_AP_MAX_BITRATE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Strength", "y", NM_WIFI_AP_STRENGTH),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("LastSeen",
+                                                           "i",
+                                                           NM_WIFI_AP_LAST_SEEN), ), ),
 };
 
 static void
@@ -955,9 +950,9 @@ nm_wifi_ap_class_init(NMWifiAPClass *ap_class)
     obj_properties[PROP_MODE] = g_param_spec_uint(NM_WIFI_AP_MODE,
                                                   "",
                                                   "",
-                                                  NM_802_11_MODE_ADHOC,
-                                                  NM_802_11_MODE_INFRA,
-                                                  NM_802_11_MODE_INFRA,
+                                                  _NM_802_11_MODE_ADHOC,
+                                                  _NM_802_11_MODE_INFRA,
+                                                  _NM_802_11_MODE_INFRA,
                                                   G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
     obj_properties[PROP_MAX_BITRATE] = g_param_spec_uint(NM_WIFI_AP_MAX_BITRATE,
diff --git a/src/core/devices/wifi/nm-wifi-ap.h b/src/core/devices/wifi/nm-wifi-ap.h
index bdd72415..bffd28a8 100644
--- a/src/core/devices/wifi/nm-wifi-ap.h
+++ b/src/core/devices/wifi/nm-wifi-ap.h
@@ -10,6 +10,7 @@
 #include "nm-dbus-object.h"
 #include "nm-dbus-interface.h"
 #include "nm-connection.h"
+#include "libnm-base/nm-base.h"
 
 #define NM_TYPE_WIFI_AP            (nm_wifi_ap_get_type())
 #define NM_WIFI_AP(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIFI_AP, NMWifiAP))
@@ -69,7 +70,7 @@ 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);
+_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);
diff --git a/src/core/devices/wifi/nm-wifi-common.c b/src/core/devices/wifi/nm-wifi-common.c
index c715c07c..b98ef222 100644
--- a/src/core/devices/wifi/nm-wifi-common.c
+++ b/src/core/devices/wifi/nm-wifi-common.c
@@ -146,31 +146,25 @@ const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless = {
                     .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
                         NM_DEFINE_GDBUS_ARG_INFO("options", "a{sv}"), ), ),
                 .handle = impl_device_wifi_request_scan, ), ),
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy,
-                                                &nm_signal_info_wireless_access_point_added,
+        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_wireless_access_point_added,
                                                 &nm_signal_info_wireless_access_point_removed, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("HwAddress",
-                                                             "s",
-                                                             NM_DEVICE_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("PermHwAddress",
-                                                             "s",
-                                                             NM_DEVICE_PERM_HW_ADDRESS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "u", NM_DEVICE_WIFI_MODE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Bitrate",
-                                                             "u",
-                                                             NM_DEVICE_WIFI_BITRATE),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("AccessPoints",
-                                                             "ao",
-                                                             NM_DEVICE_WIFI_ACCESS_POINTS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ActiveAccessPoint",
-                                                             "o",
-                                                             NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("WirelessCapabilities",
-                                                             "u",
-                                                             NM_DEVICE_WIFI_CAPABILITIES),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("PermHwAddress",
+                                                           "s",
+                                                           NM_DEVICE_PERM_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Mode", "u", NM_DEVICE_WIFI_MODE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Bitrate", "u", NM_DEVICE_WIFI_BITRATE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("AccessPoints",
+                                                           "ao",
+                                                           NM_DEVICE_WIFI_ACCESS_POINTS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ActiveAccessPoint",
+                                                           "o",
+                                                           NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("WirelessCapabilities",
+                                                           "u",
+                                                           NM_DEVICE_WIFI_CAPABILITIES),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("LastScan",
                                                            "x",
                                                            NM_DEVICE_WIFI_LAST_SCAN), ), ),
-    .legacy_property_changed = TRUE,
 };
diff --git a/src/core/devices/wifi/nm-wifi-factory.c b/src/core/devices/wifi/nm-wifi-factory.c
index 40375e1c..d2269c74 100644
--- a/src/core/devices/wifi/nm-wifi-factory.c
+++ b/src/core/devices/wifi/nm-wifi-factory.c
@@ -15,7 +15,7 @@
 #include "nm-device-olpc-mesh.h"
 #include "nm-device-iwd.h"
 #include "settings/nm-settings-connection.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-config.h"
 
 /*****************************************************************************/
@@ -74,7 +74,8 @@ create_device(NMDeviceFactory *     factory,
               NMConnection *        connection,
               gboolean *            out_ignore)
 {
-    gs_free char *backend = NULL;
+    gs_free char *backend_free = NULL;
+    const char *  backend;
 
     g_return_val_if_fail(iface != NULL, NULL);
     g_return_val_if_fail(plink != NULL, NULL);
@@ -89,7 +90,7 @@ create_device(NMDeviceFactory *     factory,
                                                          plink,
                                                          "wifi",
                                                          NULL);
-    nm_strstrip(backend);
+    backend = nm_strstrip_avoid_copy_a(300, backend, &backend_free);
 
     nm_log_dbg(LOGD_PLATFORM | LOGD_WIFI,
                "(%s) config: backend is %s%s%s%s",
@@ -97,9 +98,9 @@ create_device(NMDeviceFactory *     factory,
                NM_PRINT_FMT_QUOTE_STRING(backend),
                WITH_IWD ? " (iwd support enabled)" : "");
     if (!backend || !g_ascii_strcasecmp(backend, "wpa_supplicant")) {
-        NMDevice *               device;
-        NMDeviceWifiCapabilities capabilities;
-        NM80211Mode              mode;
+        NMDevice *                device;
+        _NMDeviceWifiCapabilities capabilities;
+        _NM80211Mode              mode;
 
         if (!nm_platform_wifi_get_capabilities(NM_PLATFORM_GET, plink->ifindex, &capabilities)) {
             nm_log_warn(LOGD_PLATFORM | LOGD_WIFI,
@@ -114,7 +115,7 @@ create_device(NMDeviceFactory *     factory,
          * them if/when they change to a handled type.
          */
         mode = nm_platform_wifi_get_mode(NM_PLATFORM_GET, plink->ifindex);
-        if (mode == NM_802_11_MODE_UNKNOWN) {
+        if (mode == _NM_802_11_MODE_UNKNOWN) {
             *out_ignore = TRUE;
             return NULL;
         }
diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.c b/src/core/devices/wifi/nm-wifi-p2p-peer.c
index 8488f32d..433c2833 100644
--- a/src/core/devices/wifi/nm-wifi-p2p-peer.c
+++ b/src/core/devices/wifi/nm-wifi-p2p-peer.c
@@ -12,13 +12,13 @@
 
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "nm-dbus-manager.h"
-#include "nm-glib-aux/nm-ref-string.h"
+#include "libnm-glib-aux/nm-ref-string.h"
 #include "nm-setting-wireless.h"
 #include "nm-utils.h"
 #include "nm-wifi-utils.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "supplicant/nm-supplicant-types.h"
 
 /*****************************************************************************/
@@ -608,7 +608,6 @@ static const NMDBusInterfaceInfoExtended interface_info_p2p_peer = {
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("LastSeen",
                                                            "i",
                                                            NM_WIFI_P2P_PEER_LAST_SEEN), ), ),
-    .legacy_property_changed = FALSE,
 };
 
 static void
diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c
index aed236cc..15ced990 100644
--- a/src/core/devices/wifi/nm-wifi-utils.c
+++ b/src/core/devices/wifi/nm-wifi-utils.c
@@ -7,10 +7,14 @@
 
 #include "nm-wifi-utils.h"
 
+#include <arpa/inet.h>
+#include <netinet/if_ether.h>
 #include <stdlib.h>
 
 #include "nm-utils.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-core-aux-intern/nm-common-macros.h"
+#include "libnm-base/nm-config-base.h"
 
 static gboolean
 verify_no_wep(NMSettingWirelessSecurity *s_wsec, const char *tag, GError **error)
@@ -526,7 +530,7 @@ verify_adhoc(NMSettingWirelessSecurity *s_wsec,
 gboolean
 nm_wifi_utils_complete_connection(GBytes *      ap_ssid,
                                   const char *  bssid,
-                                  NM80211Mode   ap_mode,
+                                  _NM80211Mode  ap_mode,
                                   guint32       ap_freq,
                                   guint32       ap_flags,
                                   guint32       ap_wpa_flags,
@@ -575,14 +579,14 @@ nm_wifi_utils_complete_connection(GBytes *      ap_ssid,
         /* Make sure the supplied mode matches the AP's */
         if (!strcmp(mode, NM_SETTING_WIRELESS_MODE_INFRA)
             || !strcmp(mode, NM_SETTING_WIRELESS_MODE_AP)) {
-            if (ap_mode == NM_802_11_MODE_INFRA)
+            if (ap_mode == _NM_802_11_MODE_INFRA)
                 valid = TRUE;
         } else if (!strcmp(mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
-            if (ap_mode == NM_802_11_MODE_ADHOC)
+            if (ap_mode == _NM_802_11_MODE_ADHOC)
                 valid = TRUE;
             adhoc = TRUE;
         } else if (!strcmp(mode, NM_SETTING_WIRELESS_MODE_MESH)) {
-            if (ap_mode == NM_802_11_MODE_MESH)
+            if (ap_mode == _NM_802_11_MODE_MESH)
                 valid = TRUE;
             mesh = TRUE;
         }
@@ -600,10 +604,10 @@ nm_wifi_utils_complete_connection(GBytes *      ap_ssid,
         }
     } else {
         mode = NM_SETTING_WIRELESS_MODE_INFRA;
-        if (ap_mode == NM_802_11_MODE_ADHOC) {
+        if (ap_mode == _NM_802_11_MODE_ADHOC) {
             mode  = NM_SETTING_WIRELESS_MODE_ADHOC;
             adhoc = TRUE;
-        } else if (ap_mode == NM_802_11_MODE_MESH) {
+        } else if (ap_mode == _NM_802_11_MODE_MESH) {
             mode = NM_SETTING_WIRELESS_MODE_MESH;
             mesh = TRUE;
         }
@@ -810,15 +814,24 @@ nm_wifi_utils_complete_connection(GBytes *      ap_ssid,
          * setting.  Since there's so much configuration required for it, there's
          * no way it can be automatically completed.
          */
-    } else if ((key_mgmt && !strcmp(key_mgmt, "sae"))
-               || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE)) {
+    } else if (nm_streq0(key_mgmt, "wpa-psk")
+               || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE
+                   && (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK
+                       || ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK))) {
+        g_object_set(s_wsec,
+                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                     "wpa-psk",
+                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                     "open",
+                     NULL);
+    } else if (nm_streq0(key_mgmt, "sae") || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE)) {
         g_object_set(s_wsec,
                      NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                      "sae",
                      NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
                      "open",
                      NULL);
-    } else if ((key_mgmt && !strcmp(key_mgmt, "owe"))
+    } else if (nm_streq0(key_mgmt, "owe")
                || NM_FLAGS_ANY(ap_rsn_flags,
                                NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) {
         g_object_set(s_wsec,
@@ -827,9 +840,8 @@ nm_wifi_utils_complete_connection(GBytes *      ap_ssid,
                      NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
                      "open",
                      NULL);
-    } else if ((key_mgmt && !strcmp(key_mgmt, "wpa-psk"))
-               || (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
-               || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) {
+    } else if (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK
+               || ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) {
         g_object_set(s_wsec,
                      NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                      "wpa-psk",
@@ -839,7 +851,7 @@ 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"))
+    } else if (nm_streq0(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,
@@ -943,3 +955,865 @@ nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *        connection,
 
     return TRUE;
 }
+
+/*****************************************************************************/
+
+/* Builds the IWD network configuration file name for a given SSID
+ * and security type pair.  The SSID should be valid UTF-8 and in
+ * any case must contain no NUL-bytes.  If @ssid is NUL-terminated,
+ * @ssid_len can be -1 instead of actual SSID length.
+ */
+char *
+nm_wifi_utils_get_iwd_config_filename(const char *         ssid,
+                                      gssize               ssid_len,
+                                      NMIwdNetworkSecurity security)
+{
+    const char *security_suffix;
+    const char *ptr;
+    gboolean    alnum_ssid = TRUE;
+
+    for (ptr = ssid; ssid_len != 0 && *ptr != '\0'; ptr++, ssid_len--)
+        if (!g_ascii_isalnum(*ptr) && !strchr("-_ ", *ptr))
+            alnum_ssid = FALSE;
+
+    g_return_val_if_fail(ptr != ssid && ptr - ssid <= NM_IW_ESSID_MAX_SIZE, NULL);
+
+    switch (security) {
+    case NM_IWD_NETWORK_SECURITY_OPEN:
+        security_suffix = "open";
+        break;
+    case NM_IWD_NETWORK_SECURITY_PSK:
+        security_suffix = "psk";
+        break;
+    case NM_IWD_NETWORK_SECURITY_8021X:
+        security_suffix = "8021x";
+        break;
+    default:
+        return NULL;
+    }
+
+    if (alnum_ssid) {
+        return g_strdup_printf("%.*s.%s", (int) (ptr - ssid), ssid, security_suffix);
+    } else {
+        char ssid_buf[NM_IW_ESSID_MAX_SIZE * 2 + 1];
+
+        return g_strdup_printf("=%s.%s",
+                               nm_utils_bin2hexstr_full(ssid, ptr - ssid, '\0', FALSE, ssid_buf),
+                               security_suffix);
+    }
+}
+
+/*****************************************************************************/
+
+#define SECRETS_DONT_STORE_FLAGS \
+    (NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED)
+
+static gboolean
+psk_setting_to_iwd_config(GKeyFile *file, NMSettingWirelessSecurity *s_wsec, GError **error)
+{
+    NMSettingSecretFlags psk_flags = nm_setting_wireless_security_get_psk_flags(s_wsec);
+    const char *         psk       = nm_setting_wireless_security_get_psk(s_wsec);
+    gsize                psk_len;
+    guint8               buffer[32];
+    const char *         key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec);
+
+    if (!psk || NM_FLAGS_ANY(psk_flags, SECRETS_DONT_STORE_FLAGS)) {
+        if (NM_FLAGS_ANY(psk_flags, SECRETS_DONT_STORE_FLAGS)) {
+            nm_log_info(
+                LOGD_WIFI,
+                "IWD network config is being created wihout the PSK but IWD will save the PSK on "
+                "successful activation not honoring the psk-flags property");
+        }
+        return TRUE;
+    }
+
+    psk_len = strlen(psk);
+    if (nm_streq0(key_mgmt, "sae")) {
+        g_key_file_set_string(file, "Security", "Passphrase", psk);
+    } else if (psk_len >= 8 && psk_len <= 63) {
+        g_key_file_set_string(file, "Security", "Passphrase", psk);
+    } else if (psk_len == 64 && nm_utils_hexstr2bin_buf(psk, FALSE, FALSE, NULL, buffer)) {
+        g_key_file_set_string(file, "Security", "PreSharedKey", psk);
+    } else {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "Unknown PSK format");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
+eap_certs_to_iwd_config(GKeyFile *      file,
+                        NMSetting8021x *s_8021x,
+                        bool            phase2,
+                        char *          iwd_prefix,
+                        GError **       error)
+{
+    NMSetting8021xCKScheme ca_cert_scheme =
+        phase2 ? nm_setting_802_1x_get_phase2_ca_cert_scheme(s_8021x)
+               : nm_setting_802_1x_get_ca_cert_scheme(s_8021x);
+    NMSetting8021xCKScheme client_cert_scheme =
+        phase2 ? nm_setting_802_1x_get_phase2_client_cert_scheme(s_8021x)
+               : nm_setting_802_1x_get_client_cert_scheme(s_8021x);
+    NMSetting8021xCKScheme key_scheme;
+    NMSettingSecretFlags   key_password_flags;
+    const char *           ca_path = phase2 ? nm_setting_802_1x_get_phase2_ca_path(s_8021x)
+                                            : nm_setting_802_1x_get_ca_path(s_8021x);
+    const char *           cert_path;
+    const char *           key_path = NULL;
+    const char *           key_password;
+    const char *           domain_suffix_match;
+    const char *           domain_match;
+    char                   setting_buf[128];
+
+    /* TODO: should check that all certificates and the key are RSA */
+    /* Note: up to IWD 1.9 only the PEM encoding was supported for certificates
+     * and only PKCS#8 PEM for keys but we don't know the IWD version here.
+     * From IWD 1.10 raw (DER) X.509 certificates and PKCS#12 are also supported
+     * for certificates but a certificate list or chain still has to be PEM
+     * (i.e. if it contains more than one certificate.)  Raw PKCS#12 and
+     * old-style OpenSSL PEM formats are also supported for keys.  Hopefully
+     * this is in practice the same set of file:// formats as supported by
+     * nm_crypto_* / wpa_supplicant so we need no conversions here.
+     */
+
+    if (nm_setting_802_1x_get_system_ca_certs(s_8021x)) {
+        /* Either overrides or is added to the certificates in (phase2-)ca-cert
+         * and ca-path depending on whether it points to a file or a directory.
+         * We can't ignore this property so it's an error if it is set.
+         * Fortunately not used by nm-connection-editor.
+         */
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "The system-ca-certs property is not supported");
+        return FALSE;
+    }
+
+    if (ca_path) {
+        /* To support this (and this could be applied to system-ca-certs as
+         * well) we'd have to scan the directory, parse the certificates and
+         * write a new certificate-list file to point to in the IWD config.
+         * This is going to create issues of where to store these files, for
+         * how long and with what permission bits.  Fortunately this doesn't
+         * seem to be used by nm-connection-editor either.
+         *
+         * That file would also have to contain whatever the (phase2-)ca-cert
+         * propterty points to because IWD has only one CACert setting per
+         * phase.
+         */
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "The (phase2-)ca-path property is not supported");
+        return FALSE;
+    }
+
+    if (ca_cert_scheme != NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) {
+        if (ca_cert_scheme != NM_SETTING_802_1X_CK_SCHEME_PATH) {
+            /* To support the blob scheme we'd have to either convert the
+             * certificate data into a PEM payload and embed the PEM file in
+             * the IWD config file, which is not supported by GKeyFile, or write
+             * it into a new file to point to in the IWD config.  This is again
+             * is going to create issues of where to store these files, for how
+             * long and with what permission bits.  Fortunately this scheme isn't
+             * used in nm-connection-editor either.
+             *
+             * PKCS#11 is not supported by IWD in any way so we don't need to
+             * support the PKCS#11 URI scheme.
+             *
+             * If scheme is unknown, assume no value is set.
+             */
+            g_set_error_literal(
+                error,
+                NM_CONNECTION_ERROR,
+                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                "(phase2-)ca-cert property schemes other than file:// not supported");
+            return FALSE;
+        }
+
+        cert_path = phase2 ? nm_setting_802_1x_get_phase2_ca_cert_path(s_8021x)
+                           : nm_setting_802_1x_get_ca_cert_path(s_8021x);
+        if (cert_path)
+            g_key_file_set_string(file,
+                                  "Security",
+                                  nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "CACert"),
+                                  cert_path);
+    }
+
+    if (client_cert_scheme == NM_SETTING_802_1X_CK_SCHEME_UNKNOWN)
+        goto private_key_done;
+
+    if (client_cert_scheme != NM_SETTING_802_1X_CK_SCHEME_PATH) {
+        g_set_error_literal(
+            error,
+            NM_CONNECTION_ERROR,
+            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+            "(phase2-)client-cert property schemes other than file:// not supported");
+        return FALSE;
+    }
+
+    cert_path = phase2 ? nm_setting_802_1x_get_phase2_client_cert_path(s_8021x)
+                       : nm_setting_802_1x_get_client_cert_path(s_8021x);
+    if (!cert_path)
+        goto private_key_done;
+    g_key_file_set_string(file,
+                          "Security",
+                          nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "ClientCert"),
+                          cert_path);
+
+    key_scheme = phase2 ? nm_setting_802_1x_get_phase2_private_key_scheme(s_8021x)
+                        : nm_setting_802_1x_get_private_key_scheme(s_8021x);
+    if (key_scheme == NM_SETTING_802_1X_CK_SCHEME_PATH)
+        key_path = phase2 ? nm_setting_802_1x_get_phase2_private_key_path(s_8021x)
+                          : nm_setting_802_1x_get_private_key_path(s_8021x);
+    if (key_scheme != NM_SETTING_802_1X_CK_SCHEME_PATH || !key_path) {
+        /* The same comments apply to writing the key into a temporary file
+         * as for the certificates (above), except this is even more
+         * sensitive.
+         */
+        g_set_error_literal(
+            error,
+            NM_CONNECTION_ERROR,
+            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+            "(phase2-)private-key property schemes other than file:// not supported");
+        return FALSE;
+    }
+    g_key_file_set_string(file,
+                          "Security",
+                          nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "ClientKey"),
+                          key_path);
+
+    key_password       = phase2 ? nm_setting_802_1x_get_phase2_private_key_password(s_8021x)
+                                : nm_setting_802_1x_get_private_key_password(s_8021x);
+    key_password_flags = phase2 ? nm_setting_802_1x_get_phase2_private_key_password_flags(s_8021x)
+                                : nm_setting_802_1x_get_private_key_password_flags(s_8021x);
+    if (!key_password || NM_FLAGS_ANY(key_password_flags, SECRETS_DONT_STORE_FLAGS)) {
+        g_key_file_set_comment(
+            file,
+            "Security",
+            setting_buf,
+            "ClientKeyPassphrase not to be saved, will be queried through the agent if needed",
+            NULL);
+        goto private_key_done;
+    }
+    g_key_file_set_string(file,
+                          "Security",
+                          nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "ClientKeyPassphrase"),
+                          key_password);
+
+private_key_done:
+    if (phase2 ? nm_setting_802_1x_get_phase2_subject_match(s_8021x)
+               : nm_setting_802_1x_get_subject_match(s_8021x)) {
+        g_set_error_literal(
+            error,
+            NM_CONNECTION_ERROR,
+            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+            "(phase2-)subject-match not supported, use domain-match or domain-suffix-match");
+        return FALSE;
+    }
+
+    if (phase2 ? nm_setting_802_1x_get_num_phase2_altsubject_matches(s_8021x)
+               : nm_setting_802_1x_get_num_altsubject_matches(s_8021x)) {
+        /* We could convert the "DNS:" entries into a ServerDomainMask but we'd
+         * have to leave out the "EMAIL:" and "URI:" types or report error.
+         * The interpretation still wouldn't be exactly the same as in
+         * wpa_supplicant.
+         */
+        g_set_error_literal(
+            error,
+            NM_CONNECTION_ERROR,
+            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+            "(phase2-)altsubject-matches not supported, use domain-match or domain-suffix-match");
+        return FALSE;
+    }
+
+    domain_suffix_match = phase2 ? nm_setting_802_1x_get_phase2_domain_suffix_match(s_8021x)
+                                 : nm_setting_802_1x_get_domain_suffix_match(s_8021x);
+    domain_match        = phase2 ? nm_setting_802_1x_get_phase2_domain_match(s_8021x)
+                                 : nm_setting_802_1x_get_domain_match(s_8021x);
+
+    if (domain_suffix_match || domain_match) {
+        GString *   s = g_string_sized_new(128);
+        const char *ptr;
+        const char *end;
+
+        for (ptr = domain_suffix_match; ptr; ptr = *end == ';' ? end + 1 : NULL) {
+            if (s->len)
+                g_string_append_c(s, ';');
+            end = strchrnul(ptr, ';');
+            /* Use *.<suffix> to get the suffix match effect */
+            g_string_append(s, "*.");
+            g_string_append_len(s, ptr, end - ptr);
+        }
+
+        /* domain-match can be appended as-is */
+        if (domain_match) {
+            if (s->len)
+                g_string_append_c(s, ';');
+            g_string_append(s, domain_match);
+        }
+
+        g_key_file_set_string(file,
+                              "Security",
+                              nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "ServerDomainMask"),
+                              s->str);
+        g_string_free(s, TRUE);
+    }
+
+    return TRUE;
+}
+
+static void
+eap_method_name_to_iwd_config(GKeyFile *file, const char *iwd_prefix, const char *method)
+{
+    char setting_buf[128];
+
+    g_key_file_set_string(file,
+                          "Security",
+                          nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "Method"),
+                          method);
+}
+
+static void
+eap_optional_identity_to_iwd_config(GKeyFile *file, const char *iwd_prefix, const char *identity)
+{
+    char setting_buf[128];
+
+    /* The identity is optional for some methods where an authenticator may
+     * in theory not ask for it.  For our usage here we treat it as always
+     * optional because it can be omitted in the config file if the user
+     * wants IWD to query for it on every connection.
+     */
+    if (identity) {
+        g_key_file_set_string(file,
+                              "Security",
+                              nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "Identity"),
+                              identity);
+    } else {
+        g_key_file_set_comment(
+            file,
+            "Security",
+            nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "Method"),
+            "Identity not to be saved, will be queried through the agent if needed",
+            NULL);
+    }
+}
+
+static gboolean
+eap_optional_password_to_iwd_config(GKeyFile *      file,
+                                    const char *    iwd_prefix,
+                                    NMSetting8021x *s_8021x,
+                                    GError **       error)
+{
+    char                 setting_buf[128];
+    const char *         password = nm_setting_802_1x_get_password(s_8021x);
+    NMSettingSecretFlags flags    = nm_setting_802_1x_get_password_flags(s_8021x);
+
+    if (!password && nm_setting_802_1x_get_password_raw(s_8021x)) {
+        /* IWD doesn't support passwords that can't be encoded in the config
+         * file, i.e. containing NUL characters.  Those that don't have NULs
+         * could in theory be written to the config file but GKeyFile may not
+         * like that if they're no UTF-8, and the password-raw property is
+         * not written by nm-connection-editor anyway.
+         */
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "Non-UTF-8 passwords are not supported, if the password is UTF-8 set "
+                            "the \"password\" property");
+        return FALSE;
+    }
+    if (!password || NM_FLAGS_ANY(flags, SECRETS_DONT_STORE_FLAGS)) {
+        return g_key_file_set_comment(file,
+                                      "Security",
+                                      nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "Method"),
+                                      "Password not to be saved, will be queried through the agent",
+                                      error);
+    } else {
+        g_key_file_set_string(file,
+                              "Security",
+                              nm_sprintf_buf(setting_buf, "%s%s", iwd_prefix, "Password"),
+                              password);
+        return TRUE;
+    }
+}
+
+static void
+eap_phase1_identity_to_iwd_config(GKeyFile *file, const char *iwd_prefix, NMSetting8021x *s_8021x)
+{
+    const char *phase1_identity = nm_setting_802_1x_get_anonymous_identity(s_8021x);
+
+    if (!phase1_identity) {
+        phase1_identity = nm_setting_802_1x_get_identity(s_8021x);
+
+        if (phase1_identity) {
+            nm_log_info(LOGD_WIFI,
+                        "IWD network config will send the same EAP Identity string in "
+                        "plaintext in phase 1 as in phase 2 (encrypted) to mimic legacy "
+                        "behavior, set [%s].%s=anonymous to prevent exposing the value",
+                        NM_SETTING_802_1X_SETTING_NAME,
+                        NM_SETTING_802_1X_ANONYMOUS_IDENTITY);
+        }
+    }
+
+    eap_optional_identity_to_iwd_config(file, iwd_prefix, phase1_identity);
+}
+
+static gboolean
+eap_method_config_to_iwd_config(GKeyFile *      file,
+                                NMSetting8021x *s_8021x,
+                                gboolean        phase2,
+                                const char *    method,
+                                const char *    iwd_prefix,
+                                GError **       error)
+{
+    char prefix_buf[128];
+
+    if (nm_streq0(method, "tls")) {
+        eap_method_name_to_iwd_config(file, iwd_prefix, "TLS");
+        eap_optional_identity_to_iwd_config(file,
+                                            iwd_prefix,
+                                            nm_setting_802_1x_get_identity(s_8021x));
+
+        return eap_certs_to_iwd_config(file,
+                                       s_8021x,
+                                       phase2,
+                                       nm_sprintf_buf(prefix_buf, "%s%s", iwd_prefix, "TLS-"),
+                                       error);
+    } else if (nm_streq0(method, "ttls") && !phase2) {
+        const char *noneap_method = nm_setting_802_1x_get_phase2_auth(s_8021x);
+
+        eap_method_name_to_iwd_config(file, iwd_prefix, "TTLS");
+        eap_phase1_identity_to_iwd_config(file, iwd_prefix, s_8021x);
+
+        if (!eap_certs_to_iwd_config(file,
+                                     s_8021x,
+                                     phase2,
+                                     nm_sprintf_buf(prefix_buf, "%s%s", iwd_prefix, "TTLS-"),
+                                     error))
+            return FALSE;
+
+        nm_sprintf_buf(prefix_buf, "%s%s", iwd_prefix, "TTLS-Phase2-");
+
+        if (nm_setting_802_1x_get_phase2_autheap(s_8021x)) {
+            if (noneap_method) {
+                g_set_error_literal(error,
+                                    NM_CONNECTION_ERROR,
+                                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                    "Only one TTLS phase 2 method can be set");
+                return FALSE;
+            }
+            return eap_method_config_to_iwd_config(file,
+                                                   s_8021x,
+                                                   TRUE,
+                                                   nm_setting_802_1x_get_phase2_autheap(s_8021x),
+                                                   prefix_buf,
+                                                   error);
+        }
+
+        if (NM_IN_STRSET(noneap_method, "chap", "mschap", "mschapv2", "pap")) {
+            const char *iwd_method;
+
+            if (nm_streq0(noneap_method, "chap")) {
+                iwd_method = "Tunneled-CHAP";
+            } else if (nm_streq0(noneap_method, "mschap")) {
+                iwd_method = "Tunneled-MSCHAP";
+            } else if (nm_streq0(noneap_method, "mschapv2")) {
+                iwd_method = "Tunneled-MSCHAPv2";
+            } else {
+                iwd_method = "Tunneled-PAP";
+            }
+
+            eap_method_name_to_iwd_config(file, prefix_buf, iwd_method);
+            eap_optional_identity_to_iwd_config(file,
+                                                prefix_buf,
+                                                nm_setting_802_1x_get_identity(s_8021x));
+            return eap_optional_password_to_iwd_config(file, prefix_buf, s_8021x, error);
+        }
+
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "Unsupported TTLS non-EAP inner method");
+        return FALSE;
+    } else if (nm_streq0(method, "peap") && !phase2) {
+        eap_method_name_to_iwd_config(file, iwd_prefix, "PEAP");
+        eap_phase1_identity_to_iwd_config(file, iwd_prefix, s_8021x);
+
+        if (!eap_certs_to_iwd_config(file,
+                                     s_8021x,
+                                     phase2,
+                                     nm_sprintf_buf(prefix_buf, "%s%s", iwd_prefix, "PEAP-"),
+                                     error))
+            return FALSE;
+
+        if (nm_setting_802_1x_get_phase1_peapver(s_8021x)
+            || nm_setting_802_1x_get_phase1_peaplabel(s_8021x))
+            nm_log_info(LOGD_WIFI,
+                        "IWD network config will not honour the PEAP version and label properties "
+                        "in the 802.1x setting (unsupported)");
+
+        if (!nm_setting_802_1x_get_phase2_auth(s_8021x)) {
+            /* Apparently PEAP can be used without a phase 2 but this is not
+             * supported by either NM or IWD.
+             */
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                "PEAP without an inner method is unsupported");
+            return FALSE;
+        }
+
+        return eap_method_config_to_iwd_config(
+            file,
+            s_8021x,
+            TRUE,
+            nm_setting_802_1x_get_phase2_auth(s_8021x),
+            nm_sprintf_buf(prefix_buf, "%s%s", iwd_prefix, "PEAP-Phase2-"),
+            error);
+    } else if (nm_streq0(method, "md5") && phase2) {
+        eap_method_name_to_iwd_config(file, iwd_prefix, "MD5");
+        eap_optional_identity_to_iwd_config(file,
+                                            iwd_prefix,
+                                            nm_setting_802_1x_get_identity(s_8021x));
+        return eap_optional_password_to_iwd_config(file, iwd_prefix, s_8021x, error);
+    } else if (nm_streq0(method, "gtc") && phase2) {
+        eap_method_name_to_iwd_config(file, iwd_prefix, "GTC");
+        eap_optional_identity_to_iwd_config(file,
+                                            iwd_prefix,
+                                            nm_setting_802_1x_get_identity(s_8021x));
+        return eap_optional_password_to_iwd_config(file, iwd_prefix, s_8021x, error);
+    } else if (nm_streq0(method, "pwd")) {
+        eap_method_name_to_iwd_config(file, iwd_prefix, "PWD");
+        eap_optional_identity_to_iwd_config(file,
+                                            iwd_prefix,
+                                            nm_setting_802_1x_get_identity(s_8021x));
+        return eap_optional_password_to_iwd_config(file, iwd_prefix, s_8021x, error);
+    } else if (nm_streq0(method, "mschapv2")) {
+        eap_method_name_to_iwd_config(file, iwd_prefix, "MSCHAPV2");
+        eap_optional_identity_to_iwd_config(file,
+                                            iwd_prefix,
+                                            nm_setting_802_1x_get_identity(s_8021x));
+        /* In this case we can support password-raw but would have to
+         * MD4-hash it and set as <iwd_prefix>Password-Hash
+         */
+        return eap_optional_password_to_iwd_config(file, iwd_prefix, s_8021x, error);
+    } else if (nm_streq0(method, "external")) {
+        /* This may be a connection created by NMIwdManager in whch case there
+         * may be no need to be convert it back to the IWD format.  Ideally we
+         * would still rewrite the other sections/groups in the IWD settings
+         * file and preserve the [Security] group -- TODO.  Possibly this should
+         * also not be reported as an error.
+         */
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "Connection contains no EAP method configuration");
+        return FALSE;
+    } else {
+        /* Some methods are only allowed in phase 1 or only phase 2.
+         * OTP, LEAP and FAST are not supported by IWD at all.
+         */
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            phase2 ? "Unsupported phase 2 EAP method"
+                                   : "Unsupported phase 1 EAP method");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
+eap_setting_to_iwd_config(GKeyFile *file, NMSetting8021x *s_8021x, GError **error)
+{
+    const char *method;
+
+    if (!s_8021x || nm_setting_802_1x_get_num_eap_methods(s_8021x) == 0) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "The 802.1x setting is missing or no EAP method set");
+        return FALSE;
+    }
+
+    if (!nm_setting_verify(NM_SETTING(s_8021x), NULL, error))
+        return FALSE;
+
+    method = nm_setting_802_1x_get_eap_method(s_8021x, 0);
+
+    if (nm_setting_802_1x_get_num_eap_methods(s_8021x) > 1)
+        nm_log_info(LOGD_WIFI,
+                    "IWD network config will only contain the first EAP method: %s",
+                    method);
+
+    if (nm_setting_802_1x_get_phase1_auth_flags(s_8021x))
+        nm_log_info(LOGD_WIFI,
+                    "IWD network config will not honour the TLSv1.x-disable flags in the 802.1x "
+                    "setting (unsupported)");
+
+    if (nm_setting_802_1x_get_auth_timeout(s_8021x))
+        nm_log_info(LOGD_WIFI,
+                    "IWD network config will not honour the auth-timeout property in the 802.1x "
+                    "setting (unsupported)");
+
+    return eap_method_config_to_iwd_config(file, s_8021x, FALSE, method, "EAP-", error);
+}
+
+static gboolean
+ip4_config_to_iwd_config(GKeyFile *file, NMSettingIPConfig *s_ip, GError **error)
+{
+    guint          num;
+    struct in_addr ip;
+
+    /* These settings are not acutally used unless global
+     * [General].EnableNetworkConfiguration is true, which we don't support.
+     * We add them for sake of completness, although many NMSettingIPConfig
+     * configurations can't be mapped to IWD configs and we simply ignore
+     * them.  If they were to be used we'd need to add a few warnings.
+     */
+
+    if (!s_ip)
+        return TRUE;
+
+    num = nm_setting_ip_config_get_num_dns(s_ip);
+    if (num) {
+        nm_auto_free_gstring GString *s = g_string_sized_new(128);
+        guint                         i;
+
+        for (i = 0; i < num; i++) {
+            if (s->len)
+                g_string_append_c(s, ' ');
+            g_string_append(s, nm_setting_ip_config_get_dns(s_ip, i));
+        }
+        /* It doesn't matter whether we add the DNS under [IPv4] or [IPv6]
+         * except that with method=auto the list will override the
+         * DNS addresses received over the DHCP version corresponing to
+         * v4 or v6.
+         * Note ignore-auto-dns=false isn't supported, this list always
+         * overrides the DHCP DNSes.
+         */
+        g_key_file_set_string(file, "IPv4", "DNS", s->str);
+    }
+
+    if (!nm_streq0(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+        return TRUE;
+
+    num = nm_setting_ip_config_get_num_addresses(s_ip);
+    if (num) {
+        NMIPAddress *addr    = nm_setting_ip_config_get_address(s_ip, 0);
+        guint        prefix  = nm_ip_address_get_prefix(addr);
+        in_addr_t    netmask = _nm_utils_ip4_prefix_to_netmask(prefix);
+        char         buf[INET_ADDRSTRLEN];
+
+        nm_ip_address_get_address_binary(addr, &ip);
+        g_key_file_set_string(file, "IPv4", "Address", nm_ip_address_get_address(addr));
+        g_key_file_set_string(file, "IPv4", "Netmask", _nm_utils_inet4_ntop(netmask, buf));
+    } else {
+        inet_pton(AF_INET, "10.42.0.100", &ip);
+        g_key_file_set_string(file, "IPv4", "Address", "10.42.0.100");
+    }
+
+    if (nm_setting_ip_config_get_gateway(s_ip)) {
+        g_key_file_set_string(file, "IPv4", "Gateway", nm_setting_ip_config_get_gateway(s_ip));
+    } else {
+        uint32_t val;
+        char     buf[INET_ADDRSTRLEN];
+
+        /* IWD won't enable static IP unless both Address and Gateway are
+         * set so generate a gateway address if not known.
+         */
+        val = (ntohl(ip.s_addr) & 0xfffffff0) + 1;
+        if (val == ntohl(ip.s_addr))
+            val += 1;
+        g_key_file_set_string(file, "IPv4", "Gateway", _nm_utils_inet4_ntop(htonl(val), buf));
+    }
+
+    return TRUE;
+}
+
+static gboolean
+ip6_config_to_iwd_config(GKeyFile *file, NMSettingIPConfig *s_ip, GError **error)
+{
+    guint        num;
+    NMIPAddress *addr;
+    char         buf[INET6_ADDRSTRLEN + 10];
+
+    if (!s_ip)
+        return TRUE;
+
+    num = nm_setting_ip_config_get_num_dns(s_ip);
+    if (num) {
+        nm_auto_free_gstring GString *s = g_string_sized_new(128);
+        guint                         i;
+
+        for (i = 0; i < num; i++) {
+            if (s->len)
+                g_string_append_c(s, ' ');
+            g_string_append(s, nm_setting_ip_config_get_dns(s_ip, i));
+        }
+        g_key_file_set_string(file, "IPv6", "DNS", s->str);
+    }
+
+    if (!NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip),
+                      NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+                      NM_SETTING_IP6_CONFIG_METHOD_DHCP,
+                      NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
+        return TRUE;
+
+    g_key_file_set_boolean(file, "IPv6", "Enabled", TRUE);
+
+    if (!nm_streq0(nm_setting_ip_config_get_method(s_ip), NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
+        return TRUE;
+
+    if (!nm_setting_ip_config_get_num_addresses(s_ip)) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "IP address required for IPv6 manual config");
+        return FALSE;
+    }
+
+    addr = nm_setting_ip_config_get_address(s_ip, 0);
+    g_key_file_set_string(file,
+                          "IPv6",
+                          "Address",
+                          nm_sprintf_buf(buf,
+                                         "%s/%u",
+                                         nm_ip_address_get_address(addr),
+                                         nm_ip_address_get_prefix(addr)));
+    if (nm_setting_ip_config_get_gateway(s_ip))
+        g_key_file_set_string(file, "IPv6", "Gateway", nm_setting_ip_config_get_gateway(s_ip));
+    return TRUE;
+}
+
+GKeyFile *
+nm_wifi_utils_connection_to_iwd_config(NMConnection *connection,
+                                       char **       out_filename,
+                                       GError **     error)
+{
+    NMSettingConnection * s_conn = nm_connection_get_setting_connection(connection);
+    NMSettingWireless *   s_wifi = nm_connection_get_setting_wireless(connection);
+    GBytes *              ssid;
+    const guint8 *        ssid_data;
+    gsize                 ssid_len;
+    NMIwdNetworkSecurity  security;
+    const char *          cloned_mac_addr;
+    gs_free char *        comment        = NULL;
+    nm_auto_unref_keyfile GKeyFile *file = NULL;
+
+    if (!s_conn || !s_wifi
+        || !nm_streq(nm_setting_connection_get_connection_type(s_conn),
+                     NM_SETTING_WIRELESS_SETTING_NAME)) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "Connection and/or wireless settings are missing");
+        return NULL;
+    }
+
+    if (!NM_IN_STRSET(nm_setting_wireless_get_mode(s_wifi), NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
+        g_set_error_literal(
+            error,
+            NM_CONNECTION_ERROR,
+            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+            "Non-infrastructure-mode connections don't have IWD profiles (or aren't supported)");
+        return NULL;
+    }
+
+    ssid      = nm_setting_wireless_get_ssid(s_wifi);
+    ssid_data = ssid ? g_bytes_get_data(ssid, &ssid_len) : NULL;
+    if (!ssid_data || ssid_len <= 0 || ssid_len > NM_IW_ESSID_MAX_SIZE
+        || !g_utf8_validate((const char *) ssid_data, ssid_len, NULL)) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "Empty or non-UTF-8 SSIDs not supported by IWD");
+        return NULL;
+    }
+
+    if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security)) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "Connection's security type unrecognised");
+        return NULL;
+    }
+
+    file = g_key_file_new();
+
+    comment = g_strdup_printf(" Auto-generated from NetworkManager connection \"%s\"\n"
+                              " Changes to that connection overwrite this file when "
+                              "enabled by NM's [%s].%s value",
+                              nm_setting_connection_get_id(s_conn),
+                              NM_CONFIG_KEYFILE_GROUP_MAIN,
+                              NM_CONFIG_KEYFILE_KEY_MAIN_IWD_CONFIG_PATH);
+    g_key_file_set_comment(file, NULL, NULL, comment, NULL);
+
+    if (!nm_setting_connection_get_autoconnect(s_conn))
+        g_key_file_set_boolean(file, "Settings", "AutoConnect", FALSE);
+
+    if (nm_setting_wireless_get_hidden(s_wifi))
+        g_key_file_set_boolean(file, "Settings", "Hidden", TRUE);
+
+    /* Only effective if IWD's global [General].AddressRandomization is set
+     * to "network".  "random" maps to [Settings].AlwaysRandomizeAddress=true,
+     * "stable" is the default, specific address maps to
+     * [Settings].AddressOverride set to that address.  "permanent" is not
+     * supported and "preserve" can only be achieved using the global
+     * [General].AddressRandomization=disabled setting.  We don't print
+     * warnings when we can't map the value here because we don't know what
+     * IWD's [General].AddressRandomization is set to.
+     */
+    cloned_mac_addr = nm_setting_wireless_get_cloned_mac_address(s_wifi);
+    if (nm_streq0(cloned_mac_addr, NM_CLONED_MAC_RANDOM))
+        g_key_file_set_boolean(file, "Settings", "AlwaysRandomizeAddress", TRUE);
+    else if (cloned_mac_addr && nm_utils_hwaddr_valid(cloned_mac_addr, ETH_ALEN))
+        g_key_file_set_string(file, "Settings", "AddressOverride", cloned_mac_addr);
+
+    if (!ip4_config_to_iwd_config(
+            file,
+            NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip4_config(connection)),
+            error))
+        return NULL;
+
+    if (!ip6_config_to_iwd_config(
+            file,
+            NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip6_config(connection)),
+            error))
+        return NULL;
+
+    switch (security) {
+    case NM_IWD_NETWORK_SECURITY_OPEN:
+        break;
+    case NM_IWD_NETWORK_SECURITY_PSK:
+        if (!psk_setting_to_iwd_config(file,
+                                       nm_connection_get_setting_wireless_security(connection),
+                                       error))
+            return NULL;
+
+        break;
+    case NM_IWD_NETWORK_SECURITY_8021X:
+        if (!eap_setting_to_iwd_config(file, nm_connection_get_setting_802_1x(connection), error))
+            return NULL;
+
+        break;
+    default:
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            "Connection security type is not supported");
+        return NULL;
+    }
+
+    if (out_filename)
+        *out_filename =
+            nm_wifi_utils_get_iwd_config_filename((const char *) ssid_data, ssid_len, security);
+
+    return g_steal_pointer(&file);
+}
diff --git a/src/core/devices/wifi/nm-wifi-utils.h b/src/core/devices/wifi/nm-wifi-utils.h
index 474bea41..2664c03e 100644
--- a/src/core/devices/wifi/nm-wifi-utils.h
+++ b/src/core/devices/wifi/nm-wifi-utils.h
@@ -11,6 +11,7 @@
 #include "nm-setting-wireless.h"
 #include "nm-setting-wireless-security.h"
 #include "nm-setting-8021x.h"
+#include "libnm-base/nm-base.h"
 
 typedef enum {
     NM_IWD_NETWORK_SECURITY_OPEN,
@@ -21,7 +22,7 @@ typedef enum {
 
 gboolean nm_wifi_utils_complete_connection(GBytes *      ssid,
                                            const char *  bssid,
-                                           NM80211Mode   mode,
+                                           _NM80211Mode  mode,
                                            guint32       ap_freq,
                                            guint32       flags,
                                            guint32       wpa_flags,
@@ -35,5 +36,11 @@ gboolean nm_wifi_utils_is_manf_default_ssid(GBytes *ssid);
 gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *        connection,
                                                       char **               ssid,
                                                       NMIwdNetworkSecurity *security);
+char *   nm_wifi_utils_get_iwd_config_filename(const char *         ssid,
+                                               gssize               ssid_len,
+                                               NMIwdNetworkSecurity security);
+
+GKeyFile *
+nm_wifi_utils_connection_to_iwd_config(NMConnection *conn, char **out_filename, GError **error);
 
 #endif /* __NM_WIFI_UTILS_H__ */
diff --git a/src/core/devices/wifi/tests/test-devices-wifi.c b/src/core/devices/wifi/tests/test-devices-wifi.c
index bc0ba126..dc278c94 100644
--- a/src/core/devices/wifi/tests/test-devices-wifi.c
+++ b/src/core/devices/wifi/tests/test-devices-wifi.c
@@ -7,7 +7,7 @@
 
 #include "devices/wifi/nm-wifi-utils.h"
 #include "devices/wifi/nm-device-wifi.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 
 #include "nm-test-utils-core.h"
 
@@ -49,7 +49,7 @@
 static gboolean
 complete_connection(const char *  ssid,
                     const char *  bssid,
-                    NM80211Mode   mode,
+                    _NM80211Mode  mode,
                     guint32       flags,
                     guint32       wpa_flags,
                     guint32       rsn_flags,
@@ -197,7 +197,7 @@ fill_8021x(NMConnection *connection, const KeyData items[])
 }
 
 static NMConnection *
-create_basic(const char *ssid, const char *bssid, NM80211Mode mode)
+create_basic(const char *ssid, const char *bssid, _NM80211Mode mode)
 {
     NMConnection *     connection;
     NMSettingWireless *s_wifi = NULL;
@@ -217,9 +217,9 @@ create_basic(const char *ssid, const char *bssid, NM80211Mode mode)
     if (bssid)
         g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_BSSID, bssid, NULL);
 
-    if (mode == NM_802_11_MODE_INFRA)
+    if (mode == _NM_802_11_MODE_INFRA)
         g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_MODE, "infrastructure", NULL);
-    else if (mode == NM_802_11_MODE_ADHOC)
+    else if (mode == _NM_802_11_MODE_ADHOC)
         g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_MODE, "adhoc", NULL);
     else
         g_assert_not_reached();
@@ -241,14 +241,14 @@ test_lock_bssid(void)
     src      = nm_simple_connection_new();
     success  = complete_connection(ssid,
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_NONE,
                                   NM_802_11_AP_SEC_NONE,
                                   NM_802_11_AP_SEC_NONE,
                                   TRUE,
                                   src,
                                   &error);
-    expected = create_basic(ssid, bssid, NM_802_11_MODE_INFRA);
+    expected = create_basic(ssid, bssid, _NM_802_11_MODE_INFRA);
     COMPARE(src, expected, success, error, 0, 0);
 
     g_object_unref(src);
@@ -273,14 +273,14 @@ test_open_ap_empty_connection(void)
     src      = nm_simple_connection_new();
     success  = complete_connection(ssid,
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_NONE,
                                   NM_802_11_AP_SEC_NONE,
                                   NM_802_11_AP_SEC_NONE,
                                   FALSE,
                                   src,
                                   &error);
-    expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA);
+    expected = create_basic(ssid, NULL, _NM_802_11_MODE_INFRA);
     COMPARE(src, expected, success, error, 0, 0);
 
     g_object_unref(src);
@@ -311,7 +311,7 @@ test_open_ap_leap_connection_1(gconstpointer add_wifi)
 
     success = complete_connection("blahblah",
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_NONE,
                                   NM_802_11_AP_SEC_NONE,
                                   NM_802_11_AP_SEC_NONE,
@@ -345,7 +345,7 @@ test_open_ap_leap_connection_2(void)
 
     success = complete_connection("blahblah",
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_NONE,
                                   NM_802_11_AP_SEC_NONE,
                                   NM_802_11_AP_SEC_NONE,
@@ -383,7 +383,7 @@ test_open_ap_wep_connection(gconstpointer add_wifi)
     fill_wsec(src, src_wsec);
     success = complete_connection("blahblah",
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_NONE,
                                   NM_802_11_AP_SEC_NONE,
                                   NM_802_11_AP_SEC_NONE,
@@ -427,7 +427,7 @@ test_ap_wpa_psk_connection_base(const char *  key_mgmt,
     fill_wsec(src, both_wsec);
     success = complete_connection(ssid,
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   flags,
                                   wpa_flags,
                                   rsn_flags,
@@ -553,7 +553,7 @@ test_ap_wpa_eap_connection_base(const char *key_mgmt,
     fill_8021x(src, src_empty);
     success = complete_connection("blahblah",
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   flags,
                                   wpa_flags,
                                   rsn_flags,
@@ -751,7 +751,7 @@ test_priv_ap_empty_connection(void)
     src     = nm_simple_connection_new();
     success = complete_connection(ssid,
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_PRIVACY,
                                   NM_802_11_AP_SEC_NONE,
                                   NM_802_11_AP_SEC_NONE,
@@ -760,7 +760,7 @@ test_priv_ap_empty_connection(void)
                                   &error);
 
     /* Static WEP connection expected */
-    expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA);
+    expected = create_basic(ssid, NULL, _NM_802_11_MODE_INFRA);
     fill_wsec(expected, exp_wsec);
     COMPARE(src, expected, success, error, 0, 0);
 
@@ -798,7 +798,7 @@ test_priv_ap_leap_connection_1(gconstpointer add_wifi)
     fill_wsec(src, src_wsec);
     success = complete_connection(ssid,
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_PRIVACY,
                                   NM_802_11_AP_SEC_NONE,
                                   NM_802_11_AP_SEC_NONE,
@@ -809,7 +809,7 @@ test_priv_ap_leap_connection_1(gconstpointer add_wifi)
      * there's no way to determine from the AP's beacon whether it's static WEP,
      * dynamic WEP, or LEAP.
      */
-    expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA);
+    expected = create_basic(ssid, NULL, _NM_802_11_MODE_INFRA);
     fill_wsec(expected, exp_wsec);
     COMPARE(src, expected, success, error, 0, 0);
 
@@ -840,7 +840,7 @@ test_priv_ap_leap_connection_2(void)
     fill_wsec(src, src_wsec);
     success = complete_connection("blahblah",
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_PRIVACY,
                                   NM_802_11_AP_SEC_NONE,
                                   NM_802_11_AP_SEC_NONE,
@@ -885,7 +885,7 @@ test_priv_ap_dynamic_wep_1(void)
     fill_8021x(src, both_8021x);
     success = complete_connection(ssid,
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_PRIVACY,
                                   NM_802_11_AP_SEC_NONE,
                                   NM_802_11_AP_SEC_NONE,
@@ -894,7 +894,7 @@ test_priv_ap_dynamic_wep_1(void)
                                   &error);
 
     /* We expect a completed Dynamic WEP connection */
-    expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA);
+    expected = create_basic(ssid, NULL, _NM_802_11_MODE_INFRA);
     fill_wsec(expected, exp_wsec);
     fill_8021x(expected, both_8021x);
     COMPARE(src, expected, success, error, 0, 0);
@@ -933,7 +933,7 @@ test_priv_ap_dynamic_wep_2(void)
     fill_8021x(src, both_8021x);
     success = complete_connection(ssid,
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_PRIVACY,
                                   NM_802_11_AP_SEC_NONE,
                                   NM_802_11_AP_SEC_NONE,
@@ -942,7 +942,7 @@ test_priv_ap_dynamic_wep_2(void)
                                   &error);
 
     /* We expect a completed Dynamic WEP connection */
-    expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA);
+    expected = create_basic(ssid, NULL, _NM_802_11_MODE_INFRA);
     fill_wsec(expected, exp_wsec);
     fill_8021x(expected, both_8021x);
     COMPARE(src, expected, success, error, 0, 0);
@@ -976,7 +976,7 @@ test_priv_ap_dynamic_wep_3(void)
     fill_8021x(src, src_8021x);
     success = complete_connection("blahblah",
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_PRIVACY,
                                   NM_802_11_AP_SEC_NONE,
                                   NM_802_11_AP_SEC_NONE,
@@ -1102,7 +1102,7 @@ test_wpa_ap_empty_connection(gconstpointer data)
     src     = nm_simple_connection_new();
     success = complete_connection(ssid,
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_PRIVACY,
                                   wpa_flags_for_idx(idx),
                                   rsn_flags_for_idx(idx),
@@ -1111,7 +1111,7 @@ test_wpa_ap_empty_connection(gconstpointer data)
                                   &error);
 
     /* WPA connection expected */
-    expected = create_basic(ssid, NULL, NM_802_11_MODE_INFRA);
+    expected = create_basic(ssid, NULL, _NM_802_11_MODE_INFRA);
     fill_wsec(expected, exp_wsec);
     COMPARE(src, expected, success, error, 0, 0);
 
@@ -1144,7 +1144,7 @@ test_wpa_ap_leap_connection_1(gconstpointer data)
     fill_wsec(src, src_wsec);
     success = complete_connection(ssid,
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_PRIVACY,
                                   wpa_flags_for_idx(idx),
                                   rsn_flags_for_idx(idx),
@@ -1180,7 +1180,7 @@ test_wpa_ap_leap_connection_2(gconstpointer data)
     fill_wsec(src, src_wsec);
     success = complete_connection("blahblah",
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_PRIVACY,
                                   wpa_flags_for_idx(idx),
                                   rsn_flags_for_idx(idx),
@@ -1214,7 +1214,7 @@ test_wpa_ap_dynamic_wep_connection(gconstpointer data)
     fill_wsec(src, src_wsec);
     success = complete_connection("blahblah",
                                   bssid,
-                                  NM_802_11_MODE_INFRA,
+                                  _NM_802_11_MODE_INFRA,
                                   NM_802_11_AP_FLAGS_PRIVACY,
                                   wpa_flags_for_idx(idx),
                                   rsn_flags_for_idx(idx),
diff --git a/src/core/devices/wwan/meson.build b/src/core/devices/wwan/meson.build
index 87af0429..37ef738c 100644
--- a/src/core/devices/wwan/meson.build
+++ b/src/core/devices/wwan/meson.build
@@ -17,7 +17,6 @@ libnm_wwan = shared_module(
     libsystemd_dep,
     mm_glib_dep,
   ],
-  c_args: daemon_c_flags,
   link_args: '-Wl,--version-script,@0@'.format(linker_script),
   link_depends: linker_script,
   install: true,
@@ -51,7 +50,6 @@ libnm_device_plugin_wwan = shared_module(
     libsystemd_dep,
     mm_glib_dep,
   ],
-  c_args: daemon_c_flags,
   link_with: libnm_wwan,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
diff --git a/src/core/devices/wwan/nm-device-modem.c b/src/core/devices/wwan/nm-device-modem.c
index 3ea89d2c..4d892e6f 100644
--- a/src/core/devices/wwan/nm-device-modem.c
+++ b/src/core/devices/wwan/nm-device-modem.c
@@ -14,7 +14,7 @@
 #include "settings/nm-settings-connection.h"
 #include "nm-modem-broadband.h"
 #include "NetworkManagerUtils.h"
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceModem
 #include "devices/nm-device-logging.h"
@@ -193,15 +193,20 @@ modem_ip4_config_result(NMModem *modem, NMIP4Config *config, GError *error, gpoi
     NMDeviceModem *self   = NM_DEVICE_MODEM(user_data);
     NMDevice *     device = NM_DEVICE(self);
 
-    g_return_if_fail(nm_device_activate_ip4_state_in_conf(device) == TRUE);
+    if (!nm_device_activate_ip4_state_in_conf(device)) {
+        _LOGD(LOGD_MB | LOGD_IP4,
+              "retrieving IPv4 configuration while no longer in state IPv4 conf");
+        return;
+    }
 
     if (error) {
         _LOGW(LOGD_MB | LOGD_IP4, "retrieving IPv4 configuration failed: %s", error->message);
         nm_device_ip_method_failed(device, AF_INET, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-    } else {
-        nm_device_set_dev2_ip_config(device, AF_INET, NM_IP_CONFIG_CAST(config));
-        nm_device_activate_schedule_ip_config_result(device, AF_INET, NULL);
+        return;
     }
+
+    nm_device_set_dev2_ip_config(device, AF_INET, NM_IP_CONFIG_CAST(config));
+    nm_device_activate_schedule_ip_config_result(device, AF_INET, NULL);
 }
 
 static void
@@ -218,7 +223,11 @@ modem_ip6_config_result(NMModem *    modem,
     gs_unref_object NMIP6Config *ignored    = NULL;
     gboolean                     got_config = !!config;
 
-    g_return_if_fail(nm_device_activate_ip6_state_in_conf(device) == TRUE);
+    if (!nm_device_activate_ip6_state_in_conf(device)) {
+        _LOGD(LOGD_MB | LOGD_IP6,
+              "retrieving IPv6 configuration while no longer in state IPv6 conf");
+        return;
+    }
 
     if (error) {
         _LOGW(LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: %s", error->message);
@@ -357,7 +366,6 @@ modem_state_cb(NMModem *modem, int new_state_i, int old_state_i, gpointer user_d
         nm_device_state_changed(device,
                                 NM_DEVICE_STATE_FAILED,
                                 NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
-        return;
     }
 
     if (new_state > NM_MODEM_STATE_LOCKED && old_state == NM_MODEM_STATE_LOCKED) {
@@ -856,14 +864,13 @@ dispose(GObject *object)
 static const NMDBusInterfaceInfoExtended interface_info_device_modem = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_MODEM,
-        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
         .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ModemCapabilities",
-                                                             "u",
-                                                             NM_DEVICE_MODEM_CAPABILITIES),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("CurrentCapabilities",
-                                                             "u",
-                                                             NM_DEVICE_MODEM_CURRENT_CAPABILITIES),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ModemCapabilities",
+                                                           "u",
+                                                           NM_DEVICE_MODEM_CAPABILITIES),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("CurrentCapabilities",
+                                                           "u",
+                                                           NM_DEVICE_MODEM_CURRENT_CAPABILITIES),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("DeviceId",
                                                            "s",
                                                            NM_DEVICE_MODEM_DEVICE_ID),
@@ -871,7 +878,6 @@ static const NMDBusInterfaceInfoExtended interface_info_device_modem = {
                                                            "s",
                                                            NM_DEVICE_MODEM_OPERATOR_CODE),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Apn", "s", NM_DEVICE_MODEM_APN), ), ),
-    .legacy_property_changed = TRUE,
 };
 
 static void
diff --git a/src/core/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c
index 1297d964..0872a8a1 100644
--- a/src/core/devices/wwan/nm-modem-broadband.c
+++ b/src/core/devices/wwan/nm-modem-broadband.c
@@ -11,10 +11,10 @@
 #include <arpa/inet.h>
 #include <libmm-glib.h>
 
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
 
diff --git a/src/core/devices/wwan/nm-modem-manager.c b/src/core/devices/wwan/nm-modem-manager.c
index 598c6898..4fb9a378 100644
--- a/src/core/devices/wwan/nm-modem-manager.c
+++ b/src/core/devices/wwan/nm-modem-manager.c
@@ -17,7 +17,7 @@
     #define sd_booted() FALSE
 #endif
 
-#include "nm-std-aux/nm-dbus-compat.h"
+#include "libnm-std-aux/nm-dbus-compat.h"
 #include "nm-modem.h"
 #include "nm-modem-broadband.h"
 
diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c
index 21734cee..78ad7b68 100644
--- a/src/core/devices/wwan/nm-modem-ofono.c
+++ b/src/core/devices/wwan/nm-modem-ofono.c
@@ -7,10 +7,10 @@
 
 #include "nm-modem-ofono.h"
 
-#include "nm-core-internal.h"
+#include "libnm-core-intern/nm-core-internal.h"
 #include "devices/nm-device-private.h"
 #include "nm-modem.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-ip4-config.h"
 
 #define VARIANT_IS_OF_TYPE_BOOLEAN(v) \
@@ -316,10 +316,9 @@ handle_sim_property(GDBusProxy *proxy, const char *property, GVariant *v, gpoint
 static void
 sim_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
 {
-    GVariant *v_child = g_variant_get_child_value(v, 0);
+    gs_unref_variant GVariant *v_child = g_variant_get_child_value(v, 0);
 
     handle_sim_property(proxy, property, v_child, user_data);
-    g_variant_unref(v_child);
 }
 
 static void
@@ -330,7 +329,7 @@ sim_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_dat
     gs_free_error GError *error             = NULL;
     gs_unref_variant GVariant *v_properties = NULL;
     gs_unref_variant GVariant *v_dict       = NULL;
-    GVariant *                 v;
+    gs_unref_variant GVariant *v            = NULL;
     GVariantIter               i;
     const char *               property;
 
@@ -370,9 +369,8 @@ sim_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_dat
      */
 
     g_variant_iter_init(&i, v_dict);
-    while (g_variant_iter_next(&i, "{&sv}", &property, &v)) {
+    while (g_variant_iter_loop(&i, "{&sv}", &property, &v)) {
         handle_sim_property(NULL, property, v, self);
-        g_variant_unref(v);
     }
 }
 
@@ -477,10 +475,9 @@ handle_connman_property(GDBusProxy *proxy, const char *property, GVariant *v, gp
 static void
 connman_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
 {
-    GVariant *v_child = g_variant_get_child_value(v, 0);
+    gs_unref_variant GVariant *v_child = g_variant_get_child_value(v, 0);
 
     handle_connman_property(proxy, property, v_child, user_data);
-    g_variant_unref(v_child);
 }
 
 static void
@@ -491,7 +488,7 @@ connman_get_properties_done(GObject *source, GAsyncResult *result, gpointer user
     gs_free_error GError *error             = NULL;
     gs_unref_variant GVariant *v_properties = NULL;
     gs_unref_variant GVariant *v_dict       = NULL;
-    GVariant *                 v;
+    gs_unref_variant GVariant *v            = NULL;
     GVariantIter               i;
     const char *               property;
 
@@ -523,9 +520,8 @@ connman_get_properties_done(GObject *source, GAsyncResult *result, gpointer user
      */
 
     g_variant_iter_init(&i, v_dict);
-    while (g_variant_iter_next(&i, "{&sv}", &property, &v)) {
+    while (g_variant_iter_loop(&i, "{&sv}", &property, &v)) {
         handle_connman_property(NULL, property, v, self);
-        g_variant_unref(v);
     }
 }
 
@@ -700,9 +696,8 @@ modem_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_d
      */
 
     g_variant_iter_init(&i, v_dict);
-    while (g_variant_iter_next(&i, "{&sv}", &property, &v)) {
+    while (g_variant_iter_loop(&i, "{&sv}", &property, &v)) {
         handle_modem_property(NULL, property, v, self);
-        g_variant_unref(v);
     }
 }
 
@@ -726,35 +721,30 @@ stage1_prepare_done(GObject *source, GAsyncResult *result, gpointer user_data)
     nm_clear_pointer(&priv->connect_properties, g_hash_table_destroy);
 
     if (error) {
-        _LOGW("connection failed: %s", error->message);
-
-        nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY);
-        /*
-         * FIXME: add code to check for InProgress so that the
-         * connection doesn't continue to try and activate,
-         * leading to the connection being disabled, and a 5m
-         * timeout...
-         */
+        if (!g_strstr_len(error->message,
+                          NM_STRLEN(OFONO_ERROR_IN_PROGRESS),
+                          OFONO_ERROR_IN_PROGRESS)) {
+            nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY);
+        }
     }
 }
 
 static void
-context_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
+handle_settings(GVariant *v_dict, gpointer user_data)
 {
     NMModemOfono *       self = NM_MODEM_OFONO(user_data);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
     NMPlatformIP4Address addr;
-    gboolean             ret          = FALSE;
-    gs_unref_variant GVariant *v_dict = NULL;
-    const char *               interface;
-    const char *               s;
-    const char **              array, **iter;
-    guint32                    address_network, gateway_network;
-    guint32                    ip4_route_table, ip4_route_metric;
-    int                        ifindex;
-    GError *                   error = NULL;
+    gboolean             ret = FALSE;
+    const char *         interface;
+    const char *         s;
+    const char **        array, **iter;
+    guint32              address_network, gateway_network;
+    guint32              ip4_route_table, ip4_route_metric;
+    int                  ifindex;
+    GError *             error = NULL;
 
-    _LOGD("PropertyChanged: %s", property);
+    //_LOGD("PropertyChanged: %s", property);
 
     /*
      * TODO: might be a good idea and re-factor this to mimic bluez-device,
@@ -762,15 +752,6 @@ context_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, g
      * handle the action.
      */
 
-    if (g_strcmp0(property, "Settings") != 0)
-        return;
-
-    v_dict = g_variant_get_child_value(v, 0);
-    if (!v_dict) {
-        _LOGW("error getting IPv4 Settings: no v_dict");
-        goto out;
-    }
-
     _LOGI("IPv4 static Settings:");
 
     if (!g_variant_lookup(v_dict, "Interface", "&s", &interface)) {
@@ -909,6 +890,28 @@ out:
     }
 }
 
+static void
+context_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
+{
+    NMModemOfono *   self             = NM_MODEM_OFONO(user_data);
+    gs_unref_variant GVariant *v_dict = NULL;
+
+    _LOGD("PropertyChanged: %s", property);
+
+    if (g_strcmp0(property, "Settings") != 0)
+        return;
+
+    v_dict = g_variant_get_child_value(v, 0);
+    if (!v_dict) {
+        _LOGW("ofono: (%s): error getting IPv4 Settings", nm_modem_get_uid(NM_MODEM(self)));
+        return;
+    }
+
+    g_assert(g_variant_is_of_type(v_dict, G_VARIANT_TYPE_VARDICT));
+
+    handle_settings(v_dict, user_data);
+}
+
 static NMActStageReturn
 static_stage3_ip4_config_start(NMModem *            modem,
                                NMActRequest *       req,
@@ -936,6 +939,72 @@ static_stage3_ip4_config_start(NMModem *            modem,
 }
 
 static void
+context_properties_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+    NMModemOfono *       self;
+    NMModemOfonoPrivate *priv;
+    gs_free_error GError *error           = NULL;
+    gs_unref_variant GVariant *properties = NULL;
+    gs_unref_variant GVariant *settings   = NULL;
+    gs_unref_variant GVariant *v_dict     = NULL;
+    gboolean                   active;
+
+    self = NM_MODEM_OFONO(user_data);
+    priv = NM_MODEM_OFONO_GET_PRIVATE(self);
+
+    properties = g_dbus_proxy_call_finish(proxy, result, &error);
+
+    if (!properties) {
+        _LOGW("ofono: connection failed: no context properties returned %s", error->message);
+        g_clear_error(&error);
+        goto error;
+    }
+
+    v_dict = g_variant_get_child_value(properties, 0);
+    if (!v_dict || !g_variant_is_of_type(v_dict, G_VARIANT_TYPE_VARDICT)) {
+        _LOGW("ofono: connection failed; could not read connection properties");
+        goto error;
+    }
+
+    if (!g_variant_lookup(v_dict, "Active", "b", &active)) {
+        _LOGW("ofono: connection failed; can not read 'Active' property");
+        goto error;
+    }
+
+    /* Watch for custom ofono PropertyChanged signals */
+    _nm_dbus_signal_connect(priv->context_proxy,
+                            "PropertyChanged",
+                            G_VARIANT_TYPE("(sv)"),
+                            G_CALLBACK(context_property_changed),
+                            self);
+
+    if (active) {
+        _LOGD("ofono: connection is already Active");
+
+        settings = g_variant_lookup_value(v_dict, "Settings", G_VARIANT_TYPE_VARDICT);
+        if (settings == NULL) {
+            _LOGW("ofono: connection failed; can not read 'Settings' property");
+            goto error;
+        }
+
+        handle_settings(settings, user_data);
+    } else {
+        g_dbus_proxy_call(priv->context_proxy,
+                          "SetProperty",
+                          g_variant_new("(sv)", "Active", g_variant_new("b", TRUE)),
+                          G_DBUS_CALL_FLAGS_NONE,
+                          20000,
+                          NULL,
+                          (GAsyncReadyCallback) stage1_prepare_done,
+                          self);
+    }
+    return;
+
+error:
+    nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY);
+}
+
+static void
 context_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
     NMModemOfono *       self;
@@ -973,19 +1042,15 @@ context_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
      */
     g_clear_object(&priv->ip4_config);
 
-    _nm_dbus_signal_connect(priv->context_proxy,
-                            "PropertyChanged",
-                            G_VARIANT_TYPE("(sv)"),
-                            G_CALLBACK(context_property_changed),
-                            self);
-
+    /* We need to directly query ConnectionContextinteface to get the current
+     * property values */
     g_dbus_proxy_call(priv->context_proxy,
-                      "SetProperty",
-                      g_variant_new("(sv)", "Active", g_variant_new("b", TRUE)),
+                      "GetProperties",
+                      NULL,
                       G_DBUS_CALL_FLAGS_NONE,
                       20000,
-                      priv->context_proxy_cancellable,
-                      stage1_prepare_done,
+                      NULL,
+                      (GAsyncReadyCallback) context_properties_cb,
                       self);
 }
 
@@ -1069,6 +1134,7 @@ modem_act_stage1_prepare(NMModem *            modem,
 
     _LOGI("activating context %s", priv->context_path);
 
+    update_modem_state(self);
     if (nm_modem_get_state(modem) == NM_MODEM_STATE_REGISTERED) {
         do_context_activate(self);
     } else {
diff --git a/src/core/devices/wwan/nm-modem-ofono.h b/src/core/devices/wwan/nm-modem-ofono.h
index 260e3954..ebe21023 100644
--- a/src/core/devices/wwan/nm-modem-ofono.h
+++ b/src/core/devices/wwan/nm-modem-ofono.h
@@ -25,6 +25,8 @@
 #define OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT "org.ofono.ConnectionContext"
 #define OFONO_DBUS_INTERFACE_SIM_MANAGER        "org.ofono.SimManager"
 
+#define OFONO_ERROR_IN_PROGRESS "org.ofono.Error.InProgress"
+
 typedef struct _NMModemOfono      NMModemOfono;
 typedef struct _NMModemOfonoClass NMModemOfonoClass;
 
diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c
index 0d334fa4..15baa677 100644
--- a/src/core/devices/wwan/nm-modem.c
+++ b/src/core/devices/wwan/nm-modem.c
@@ -10,10 +10,11 @@
 
 #include <fcntl.h>
 #include <termios.h>
+#include <linux/if.h>
 #include <linux/rtnetlink.h>
 
-#include "nm-core-internal.h"
-#include "platform/nm-platform.h"
+#include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-setting-connection.h"
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
@@ -799,8 +800,10 @@ nm_modem_ip4_pre_commit(NMModem *modem, NMDevice *device, NMIP4Config *config)
 
         g_assert(address);
         if (address->plen == 32)
-            nm_platform_link_set_noarp(nm_device_get_platform(device),
-                                       nm_device_get_ip_ifindex(device));
+            nm_platform_link_change_flags(nm_device_get_platform(device),
+                                          nm_device_get_ip_ifindex(device),
+                                          IFF_NOARP,
+                                          TRUE);
     }
 }
 
@@ -1199,7 +1202,7 @@ deactivate_cleanup(NMModem *self, NMDevice *device, gboolean stop_ppp_manager)
 
                 nm_platform_ip_route_flush(platform, AF_UNSPEC, ifindex);
                 nm_platform_ip_address_flush(platform, AF_UNSPEC, ifindex);
-                nm_platform_link_set_down(platform, ifindex);
+                nm_platform_link_change_flags(platform, ifindex, IFF_UP, FALSE);
             }
         }
     }
diff --git a/src/core/devices/wwan/nm-wwan-factory.c b/src/core/devices/wwan/nm-wwan-factory.c
index 5d2ce2b3..0d802ae1 100644
--- a/src/core/devices/wwan/nm-wwan-factory.c
+++ b/src/core/devices/wwan/nm-wwan-factory.c
@@ -12,7 +12,7 @@
 #include "nm-setting-cdma.h"
 #include "nm-modem-manager.h"
 #include "nm-device-modem.h"
-#include "platform/nm-platform.h"
+#include "libnm-platform/nm-platform.h"
 
 /*****************************************************************************/