about summary refs log tree commit diff
path: root/src/core/devices/wifi
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2021-07-05 20:35:03 +0200
committerSebastien Bacher <seb128@ubuntu.com>2021-07-05 20:35:03 +0200
commit35779c6675728fa6f0fd0a21cefb904408509c23 (patch)
tree553e7239e0ba182b4f9b29b1e7a9d66a595d08bc /src/core/devices/wifi
parentd92aa7f298fe84d4cf686c5ad64b73438e00d377 (diff)
New upstream version 1.32.2
Diffstat (limited to 'src/core/devices/wifi')
-rw-r--r--src/core/devices/wifi/meson.build2
-rw-r--r--src/core/devices/wifi/nm-device-iwd.c206
-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.c145
-rw-r--r--src/core/devices/wifi/nm-device-wifi.h2
-rw-r--r--src/core/devices/wifi/nm-iwd-manager.c435
-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
16 files changed, 1660 insertions, 357 deletions
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 95ade44b..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),
@@ -588,10 +588,16 @@ deactivate(NMDevice *device)
             return;
     }
 
-    cleanup_association_attempt(self, TRUE);
+    cleanup_association_attempt(self, FALSE);
     priv->act_mode_switch = FALSE;
 
-    if (!priv->dbus_station_proxy)
+    /* Don't trigger any actions on the IWD side until the device is managed */
+    if (priv->iwd_autoconnect && nm_device_get_state(device) < NM_DEVICE_STATE_DISCONNECTED)
+        return;
+
+    if (priv->dbus_station_proxy)
+        send_disconnect(self);
+    else
         reset_mode(self, NULL, NULL, NULL);
 }
 
@@ -647,6 +653,11 @@ deactivate_async(NMDevice *                 device,
     cleanup_association_attempt(self, FALSE);
     priv->act_mode_switch = FALSE;
 
+    if (priv->iwd_autoconnect && nm_device_get_state(device) < NM_DEVICE_STATE_DISCONNECTED) {
+        nm_utils_invoke_on_idle(cancellable, disconnect_cb_on_idle, user_data);
+        return;
+    }
+
     if (priv->dbus_station_proxy) {
         g_dbus_proxy_call(priv->dbus_station_proxy,
                           "Disconnect",
@@ -801,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");
@@ -819,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");
@@ -1034,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);
@@ -1306,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)
@@ -1320,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;
@@ -1379,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;
@@ -1441,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);
 
@@ -1473,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))
@@ -1501,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;
@@ -1595,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) {
@@ -2610,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));
 
@@ -3034,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;
@@ -3096,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);
 
@@ -3145,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) {
@@ -3250,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)) {
@@ -3263,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);
 
@@ -3452,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,
@@ -3485,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 042d4887..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);
@@ -2565,6 +2566,7 @@ supplicant_iface_notify_current_bss(NMSupplicantInterface *iface,
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
     NMRefString *        current_bss;
     NMWifiAP *           new_ap = NULL;
+    NMActRequest *       req;
 
     current_bss = nm_supplicant_interface_get_current_bss(iface);
     if (current_bss)
@@ -2598,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
@@ -2612,6 +2614,13 @@ supplicant_iface_notify_current_bss(NMSupplicantInterface *iface,
         }
 
         set_current_ap(self, new_ap, TRUE);
+
+        req = nm_device_get_act_request(NM_DEVICE(self));
+        if (req) {
+            nm_active_connection_set_specific_object(
+                NM_ACTIVE_CONNECTION(req),
+                new_ap ? nm_dbus_object_get_path(NM_DBUS_OBJECT(new_ap)) : NULL);
+        }
     }
 }
 
@@ -2782,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;
@@ -2948,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) {
@@ -2982,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;
     }
 
@@ -2993,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
@@ -3022,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 */
@@ -3044,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;
     }
@@ -3146,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;
@@ -3213,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 */
@@ -3333,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)) {
@@ -3686,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
@@ -3698,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 */
@@ -3706,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,
@@ -3814,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,
@@ -3847,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 b4b019d3..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,10 +1398,11 @@ 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,
+        NULL,
     };
     int   rank_a = G_N_ELEMENTS(interface_order);
     int   rank_b = G_N_ELEMENTS(interface_order);
@@ -1145,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;
@@ -1181,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;
 
@@ -1207,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);
     }
 }
 
@@ -1304,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();
 
@@ -1347,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),