summary refs log tree commit diff
path: root/src/core/settings
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/settings')
-rw-r--r--src/core/settings/nm-secret-agent.c2
-rw-r--r--src/core/settings/nm-settings-connection.c321
-rw-r--r--src/core/settings/nm-settings-connection.h59
-rw-r--r--src/core/settings/nm-settings.c137
-rw-r--r--src/core/settings/nm-settings.h2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c28
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c26
-rw-r--r--src/core/settings/plugins/ifcfg-rh/shvar.c23
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-plugin.c60
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-writer.c31
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-writer.h18
-rw-r--r--src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c16
15 files changed, 457 insertions, 285 deletions
diff --git a/src/core/settings/nm-secret-agent.c b/src/core/settings/nm-secret-agent.c
index a3df4497..bb300345 100644
--- a/src/core/settings/nm-secret-agent.c
+++ b/src/core/settings/nm-secret-agent.c
@@ -463,7 +463,7 @@ _call_cancel_cb(GObject *source, GAsyncResult *result, gpointer user_data)
  * nm_secret_agent_cancel_call:
  * @self: the #NMSecretAgent instance for the @call_id.
  *   Maybe be %NULL if @call_id is %NULL.
- * @call_id: (allow-none): the call id to cancel. May be %NULL for convenience,
+ * @call_id: (nullable): the call id to cancel. May be %NULL for convenience,
  *   in which case it does nothing.
  *
  * It is an error to pass an invalid @call_id or a @call_id for an operation
diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c
index 024c0009..176cc2c2 100644
--- a/src/core/settings/nm-settings-connection.c
+++ b/src/core/settings/nm-settings-connection.c
@@ -24,13 +24,10 @@
 #include "libnm-core-intern/nm-core-internal.h"
 #include "nm-audit-manager.h"
 #include "nm-settings.h"
+#include "nm-manager.h"
 #include "nm-dbus-manager.h"
 #include "settings/plugins/keyfile/nms-keyfile-storage.h"
 
-#define AUTOCONNECT_RETRIES_UNSET       -2
-#define AUTOCONNECT_RETRIES_FOREVER     -1
-#define AUTOCONNECT_RESET_RETRIES_TIMER 300
-
 #define SEEN_BSSIDS_MAX 30
 
 #define _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES                          \
@@ -112,7 +109,11 @@ _seen_bssids_hash_new(void)
 
 /*****************************************************************************/
 
-NM_GOBJECT_PROPERTIES_DEFINE(NMSettingsConnection, PROP_UNSAVED, PROP_FLAGS, PROP_FILENAME, );
+NM_GOBJECT_PROPERTIES_DEFINE(NMSettingsConnection,
+                             PROP_VERSION_ID,
+                             PROP_UNSAVED,
+                             PROP_FLAGS,
+                             PROP_FILENAME, );
 
 enum { UPDATED_INTERNAL, FLAGS_CHANGED, LAST_SIGNAL };
 
@@ -159,9 +160,7 @@ typedef struct _NMSettingsConnectionPrivate {
 
     guint64 last_secret_agent_version_id;
 
-    int autoconnect_retries;
-
-    gint32 autoconnect_retries_blocked_until;
+    guint64 version_id;
 
     bool timestamp_set : 1;
 
@@ -227,6 +226,22 @@ static guint _get_seen_bssids(NMSettingsConnection *self,
 
 /*****************************************************************************/
 
+NMSettings *
+nm_settings_connection_get_settings(NMSettingsConnection *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL);
+
+    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->settings;
+}
+
+NMManager *
+nm_settings_connection_get_manager(NMSettingsConnection *self)
+{
+    return nm_settings_get_manager(nm_settings_connection_get_settings(self));
+}
+
+/*****************************************************************************/
+
 NMDevice *
 nm_settings_connection_default_wired_get_device(NMSettingsConnection *self)
 {
@@ -361,6 +376,20 @@ nm_settings_connection_get_connection(NMSettingsConnection *self)
     return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->connection;
 }
 
+gpointer
+nm_settings_connection_get_setting(NMSettingsConnection *self, NMMetaSettingType meta_type)
+{
+    NMConnection *connection;
+
+    nm_assert(NM_IS_SETTINGS_CONNECTION(self));
+
+    connection = NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->connection;
+
+    nm_assert(NM_IS_SIMPLE_CONNECTION(connection));
+
+    return _nm_connection_get_setting_by_metatype_unsafe(connection, meta_type);
+}
+
 void
 _nm_settings_connection_set_connection(NMSettingsConnection            *self,
                                        NMConnection                    *new_connection,
@@ -1049,7 +1078,7 @@ get_secrets_idle_cb(NMSettingsConnectionCallId *call_id)
 /**
  * nm_settings_connection_get_secrets:
  * @self: the #NMSettingsConnection
- * @applied_connection: (allow-none): if provided, only request secrets
+ * @applied_connection: (nullable): if provided, only request secrets
  *   if @self equals to @applied_connection. Also, update the secrets
  *   in the @applied_connection.
  * @subject: the #NMAuthSubject originating the request
@@ -1414,6 +1443,7 @@ typedef struct {
     NMSettingsUpdate2Flags flags;
     char                  *audit_args;
     char                  *plugin_name;
+    guint64                version_id;
     bool                   is_update2 : 1;
 } UpdateInfo;
 
@@ -1442,53 +1472,7 @@ update_complete(NMSettingsConnection *self, UpdateInfo *info, GError *error)
     g_clear_object(&info->new_settings);
     g_free(info->audit_args);
     g_free(info->plugin_name);
-    g_slice_free(UpdateInfo, info);
-}
-
-static int
-_autoconnect_retries_initial(NMSettingsConnection *self)
-{
-    NMSettingConnection *s_con;
-    int                  retries = -1;
-
-    s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(self));
-    if (s_con)
-        retries = nm_setting_connection_get_autoconnect_retries(s_con);
-
-    /* -1 means 'default' */
-    if (retries == -1)
-        retries = nm_config_data_get_autoconnect_retries_default(NM_CONFIG_GET_DATA);
-
-    /* 0 means 'forever', which is translated to a retry count of -1 */
-    if (retries == 0)
-        retries = AUTOCONNECT_RETRIES_FOREVER;
-
-    nm_assert(retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0);
-    return retries;
-}
-
-static void
-_autoconnect_retries_set(NMSettingsConnection *self, int retries, gboolean is_reset)
-{
-    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
-
-    g_return_if_fail(retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0);
-
-    if (priv->autoconnect_retries != retries) {
-        _LOGT("autoconnect: retries set %d%s", retries, is_reset ? " (reset)" : "");
-        priv->autoconnect_retries = retries;
-    }
-
-    if (retries)
-        priv->autoconnect_retries_blocked_until = 0;
-    else {
-        /* NOTE: the blocked time must be identical for all connections, otherwise
-         * the tracking of resetting the retry count in NMPolicy needs adjustment
-         * in _connection_autoconnect_retries_set() (as it would need to re-evaluate
-         * the next-timeout every time a connection gets blocked). */
-        priv->autoconnect_retries_blocked_until =
-            nm_utils_get_monotonic_timestamp_sec() + AUTOCONNECT_RESET_RETRIES_TIMER;
-    }
+    nm_g_slice_free(info);
 }
 
 static void
@@ -1502,14 +1486,23 @@ update_auth_cb(NMSettingsConnection  *self,
     UpdateInfo                     *info  = data;
     gs_free_error GError           *local = NULL;
     NMSettingsConnectionPersistMode persist_mode;
+    gs_unref_object NMConnection   *for_agent = NULL;
 
-    if (error) {
-        update_complete(self, info, error);
-        return;
-    }
+    if (error)
+        goto out;
 
     priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
 
+    if (info->version_id != 0 && info->version_id != priv->version_id) {
+        g_set_error_literal(&local,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_VERSION_ID_MISMATCH,
+                            "Update failed because profile changed in the meantime and the "
+                            "version-id mismatches");
+        error = local;
+        goto out;
+    }
+
     if (info->new_settings) {
         if (!_nm_connection_aggregate(info->new_settings,
                                       NM_CONNECTION_AGGREGATE_ANY_SECRETS,
@@ -1539,10 +1532,13 @@ update_auth_cb(NMSettingsConnection  *self,
             /* New secrets, allow autoconnection again */
             if (nm_settings_connection_autoconnect_blocked_reason_set(
                     self,
-                    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS,
+                    NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS,
                     FALSE)
                 && !nm_settings_connection_autoconnect_blocked_reason_get(self))
-                nm_settings_connection_autoconnect_retries_reset(self);
+                nm_manager_devcon_autoconnect_retries_reset(
+                    nm_settings_connection_get_manager(self),
+                    NULL,
+                    self);
         }
     }
 
@@ -1586,10 +1582,9 @@ update_auth_cb(NMSettingsConnection  *self,
              : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE),
         NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
             | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,
-        NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME
-            | (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY)
-                   ? NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE
-                   : NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL)
+        (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY)
+             ? NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE
+             : NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL)
             | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
             | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS
             | NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET
@@ -1599,25 +1594,29 @@ update_auth_cb(NMSettingsConnection  *self,
         "update-from-dbus",
         &local);
 
-    if (!local) {
-        gs_unref_object NMConnection *for_agent = NULL;
-
-        /* Dupe the connection so we can clear out non-agent-owned secrets,
-         * as agent-owned secrets are the only ones we send back to be saved.
-         * Only send secrets to agents of the same UID that called update too.
-         */
-        for_agent = nm_simple_connection_new_clone(nm_settings_connection_get_connection(self));
-        _nm_connection_clear_secrets_by_secret_flags(for_agent, NM_SETTING_SECRET_FLAG_AGENT_OWNED);
-        nm_agent_manager_save_secrets(info->agent_mgr,
-                                      nm_dbus_object_get_path(NM_DBUS_OBJECT(self)),
-                                      for_agent,
-                                      info->subject);
+    if (local) {
+        error = local;
+        goto out;
     }
 
+    /* Dupe the connection so we can clear out non-agent-owned secrets,
+     * as agent-owned secrets are the only ones we send back to be saved.
+     * Only send secrets to agents of the same UID that called update too.
+     */
+    for_agent = nm_simple_connection_new_clone(nm_settings_connection_get_connection(self));
+    _nm_connection_clear_secrets_by_secret_flags(for_agent, NM_SETTING_SECRET_FLAG_AGENT_OWNED);
+    nm_agent_manager_save_secrets(info->agent_mgr,
+                                  nm_dbus_object_get_path(NM_DBUS_OBJECT(self)),
+                                  for_agent,
+                                  info->subject);
+
     /* Reset auto retries back to default since connection was updated */
-    nm_settings_connection_autoconnect_retries_reset(self);
+    nm_manager_devcon_autoconnect_retries_reset(nm_settings_connection_get_manager(self),
+                                                NULL,
+                                                self);
 
-    update_complete(self, info, local);
+out:
+    update_complete(self, info, error);
 }
 
 static const char *
@@ -1650,6 +1649,7 @@ settings_connection_update(NMSettingsConnection  *self,
                            GDBusMethodInvocation *context,
                            GVariant              *new_settings,
                            const char            *plugin_name,
+                           guint64                version_id,
                            NMSettingsUpdate2Flags flags)
 {
     NMSettingsConnectionPrivate *priv    = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
@@ -1697,14 +1697,17 @@ settings_connection_update(NMSettingsConnection  *self,
                                              &error))
         goto error;
 
-    info               = g_slice_new0(UpdateInfo);
-    info->is_update2   = is_update2;
-    info->context      = context;
-    info->agent_mgr    = g_object_ref(priv->agent_mgr);
-    info->subject      = subject;
-    info->flags        = flags;
-    info->new_settings = tmp;
-    info->plugin_name  = g_strdup(plugin_name);
+    info  = g_slice_new(UpdateInfo);
+    *info = (UpdateInfo){
+        .is_update2   = is_update2,
+        .context      = context,
+        .agent_mgr    = g_object_ref(priv->agent_mgr),
+        .subject      = subject,
+        .flags        = flags,
+        .new_settings = tmp,
+        .plugin_name  = g_strdup(plugin_name),
+        .version_id   = version_id,
+    };
 
     permission = get_update_modify_permission(nm_settings_connection_get_connection(self),
                                               tmp ?: nm_settings_connection_get_connection(self));
@@ -1738,6 +1741,7 @@ impl_settings_connection_update(NMDBusObject                      *obj,
                                invocation,
                                settings,
                                NULL,
+                               0,
                                NM_SETTINGS_UPDATE2_FLAG_TO_DISK);
 }
 
@@ -1759,6 +1763,7 @@ impl_settings_connection_update_unsaved(NMDBusObject                      *obj,
                                invocation,
                                settings,
                                NULL,
+                               0,
                                NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY);
 }
 
@@ -1778,6 +1783,7 @@ impl_settings_connection_save(NMDBusObject                      *obj,
                                invocation,
                                NULL,
                                NULL,
+                               0,
                                NM_SETTINGS_UPDATE2_FLAG_TO_DISK);
 }
 
@@ -1794,6 +1800,7 @@ impl_settings_connection_update2(NMDBusObject                      *obj,
     gs_unref_variant GVariant *settings    = NULL;
     gs_unref_variant GVariant *args        = NULL;
     gs_free char              *plugin_name = NULL;
+    guint64                    version_id  = 0;
     guint32                    flags_u;
     GError                    *error = NULL;
     GVariantIter               iter;
@@ -1840,6 +1847,11 @@ impl_settings_connection_update2(NMDBusObject                      *obj,
             plugin_name = g_variant_dup_string(args_value, NULL);
             continue;
         }
+        if (nm_streq(args_name, "version-id")
+            && g_variant_is_of_type(args_value, G_VARIANT_TYPE_UINT64)) {
+            version_id = g_variant_get_uint64(args_value);
+            continue;
+        }
 
         error = g_error_new(NM_SETTINGS_ERROR,
                             NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
@@ -1849,7 +1861,7 @@ impl_settings_connection_update2(NMDBusObject                      *obj,
         return;
     }
 
-    settings_connection_update(self, TRUE, invocation, settings, plugin_name, flags);
+    settings_connection_update(self, TRUE, invocation, settings, plugin_name, version_id, flags);
 }
 
 static void
@@ -2542,56 +2554,6 @@ nm_settings_connection_get_num_seen_bssids(NMSettingsConnection *self)
 
 /*****************************************************************************/
 
-/**
- * nm_settings_connection_autoconnect_retries_get:
- * @self: the settings connection
- *
- * Returns the number of autoconnect retries left. If the value is
- * not yet set, initialize it with the value from the connection or
- * with the global default.
- */
-int
-nm_settings_connection_autoconnect_retries_get(NMSettingsConnection *self)
-{
-    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
-
-    if (G_UNLIKELY(priv->autoconnect_retries == AUTOCONNECT_RETRIES_UNSET)) {
-        _autoconnect_retries_set(self, _autoconnect_retries_initial(self), TRUE);
-    }
-    return priv->autoconnect_retries;
-}
-
-void
-nm_settings_connection_autoconnect_retries_set(NMSettingsConnection *self, int retries)
-{
-    g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self));
-    g_return_if_fail(retries >= 0);
-
-    _autoconnect_retries_set(self, retries, FALSE);
-}
-
-void
-nm_settings_connection_autoconnect_retries_reset(NMSettingsConnection *self)
-{
-    g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self));
-
-    _autoconnect_retries_set(self, _autoconnect_retries_initial(self), TRUE);
-}
-
-gint32
-nm_settings_connection_autoconnect_retries_blocked_until(NMSettingsConnection *self)
-{
-    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->autoconnect_retries_blocked_until;
-}
-
-static NM_UTILS_FLAGS2STR_DEFINE(
-    _autoconnect_blocked_reason_to_string,
-    NMSettingsAutoconnectBlockedReason,
-    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE, "none"),
-    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, "user-request"),
-    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, "failed"),
-    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, "no-secrets"), );
-
 NMSettingsAutoconnectBlockedReason
 nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self)
 {
@@ -2599,25 +2561,41 @@ nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self
 }
 
 gboolean
-nm_settings_connection_autoconnect_blocked_reason_set_full(NMSettingsConnection              *self,
-                                                           NMSettingsAutoconnectBlockedReason mask,
-                                                           NMSettingsAutoconnectBlockedReason value)
+nm_settings_connection_autoconnect_blocked_reason_set(NMSettingsConnection              *self,
+                                                      NMSettingsAutoconnectBlockedReason reason,
+                                                      gboolean                           set)
 {
     NMSettingsAutoconnectBlockedReason v;
     NMSettingsConnectionPrivate       *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
-    char                               buf[100];
+    char                               buf1[200];
+    char                               buf2[200];
 
-    nm_assert(mask);
-    nm_assert(!NM_FLAGS_ANY(value, ~mask));
+    nm_assert(reason != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE);
+    nm_assert(!NM_FLAGS_ANY(reason,
+                            ~(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST
+                              | NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS)));
 
     v = priv->autoconnect_blocked_reason;
-    v = (v & ~mask) | (value & mask);
+    v = NM_FLAGS_ASSIGN(v, reason, set);
 
     if (priv->autoconnect_blocked_reason == v)
         return FALSE;
 
-    _LOGT("autoconnect: blocked reason: %s",
-          _autoconnect_blocked_reason_to_string(v, buf, sizeof(buf)));
+    if (set) {
+        _LOGT("block-autoconnect: profile: blocked with reason %s (%s %s)",
+              nm_settings_autoconnect_blocked_reason_to_string(v, buf1, sizeof(buf1)),
+              "just blocked",
+              nm_settings_autoconnect_blocked_reason_to_string(reason, buf2, sizeof(buf2)));
+    } else if (v != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE) {
+        _LOGT("block-autoconnect: profile: blocked with reason %s (%s %s)",
+              nm_settings_autoconnect_blocked_reason_to_string(v, buf1, sizeof(buf1)),
+              "just unblocked",
+              nm_settings_autoconnect_blocked_reason_to_string(reason, buf2, sizeof(buf2)));
+    } else {
+        _LOGT("block-autoconnect: profile: not blocked (unblocked %s)",
+              nm_settings_autoconnect_blocked_reason_to_string(reason, buf1, sizeof(buf1)));
+    }
+
     priv->autoconnect_blocked_reason = v;
     return TRUE;
 }
@@ -2632,9 +2610,7 @@ nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self)
 
     priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
 
-    if (priv->autoconnect_blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE)
-        return TRUE;
-    if (priv->autoconnect_retries == 0)
+    if (priv->autoconnect_blocked_reason != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE)
         return TRUE;
 
     flags = priv->flags;
@@ -2693,6 +2669,23 @@ nm_settings_connection_get_uuid(NMSettingsConnection *self)
     return uuid;
 }
 
+guint64
+nm_settings_connection_get_version_id(NMSettingsConnection *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), 0);
+
+    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->version_id;
+}
+
+void
+nm_settings_connection_bump_version_id(NMSettingsConnection *self)
+{
+    g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self));
+
+    NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->version_id++;
+    _notify(self, PROP_VERSION_ID);
+}
+
 const char *
 nm_settings_connection_get_connection_type(NMSettingsConnection *self)
 {
@@ -2716,9 +2709,13 @@ _nm_settings_connection_cleanup_after_remove(NMSettingsConnection *self)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMSettingsConnection *self = NM_SETTINGS_CONNECTION(object);
+    NMSettingsConnection        *self = NM_SETTINGS_CONNECTION(object);
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
 
     switch (prop_id) {
+    case PROP_VERSION_ID:
+        g_value_set_uint64(value, priv->version_id);
+        break;
     case PROP_UNSAVED:
         g_value_set_boolean(value, nm_settings_connection_get_unsaved(self));
         break;
@@ -2748,6 +2745,7 @@ nm_settings_connection_init(NMSettingsConnection *self)
     self->_priv = priv;
 
     c_list_init(&self->_connections_lst);
+    c_list_init(&self->devcon_con_lst_head);
     c_list_init(&priv->seen_bssids_lst_head);
     c_list_init(&priv->call_ids_lst_head);
     c_list_init(&priv->auth_lst_head);
@@ -2755,7 +2753,7 @@ nm_settings_connection_init(NMSettingsConnection *self)
     priv->agent_mgr = g_object_ref(nm_agent_manager_get());
     priv->settings  = g_object_ref(nm_settings_get());
 
-    priv->autoconnect_retries = AUTOCONNECT_RETRIES_UNSET;
+    priv->version_id = 1;
 }
 
 NMSettingsConnection *
@@ -2776,6 +2774,7 @@ dispose(GObject *object)
     nm_assert(!priv->default_wired_device);
 
     nm_assert(c_list_is_empty(&self->_connections_lst));
+    nm_assert(c_list_is_empty(&self->devcon_con_lst_head));
     nm_assert(c_list_is_empty(&priv->auth_lst_head));
 
     /* Cancel in-progress secrets requests */
@@ -2868,7 +2867,10 @@ static const NMDBusInterfaceInfoExtended interface_info_settings_connection = {
                                                            NM_SETTINGS_CONNECTION_FLAGS),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Filename",
                                                            "s",
-                                                           NM_SETTINGS_CONNECTION_FILENAME), ), ),
+                                                           NM_SETTINGS_CONNECTION_FILENAME),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("VersionId",
+                                                           "t",
+                                                           NM_SETTINGS_CONNECTION_VERSION_ID), ), ),
 };
 
 static void
@@ -2886,6 +2888,15 @@ nm_settings_connection_class_init(NMSettingsConnectionClass *klass)
     object_class->dispose      = dispose;
     object_class->get_property = get_property;
 
+    obj_properties[PROP_VERSION_ID] =
+        g_param_spec_uint64(NM_SETTINGS_CONNECTION_VERSION_ID,
+                            "",
+                            "",
+                            0,
+                            G_MAXUINT64,
+                            0,
+                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
     obj_properties[PROP_UNSAVED] = g_param_spec_boolean(NM_SETTINGS_CONNECTION_UNSAVED,
                                                         "",
                                                         "",
diff --git a/src/core/settings/nm-settings-connection.h b/src/core/settings/nm-settings-connection.h
index fce7497c..835a978e 100644
--- a/src/core/settings/nm-settings-connection.h
+++ b/src/core/settings/nm-settings-connection.h
@@ -7,8 +7,11 @@
 #ifndef __NETWORKMANAGER_SETTINGS_CONNECTION_H__
 #define __NETWORKMANAGER_SETTINGS_CONNECTION_H__
 
+#include "libnm-core-intern/nm-meta-setting-base.h"
+
 #include "nm-dbus-object.h"
 #include "nm-connection.h"
+#include "NetworkManagerUtils.h"
 
 #include "nm-settings-storage.h"
 
@@ -138,9 +141,10 @@ typedef enum {
 #define NM_SETTINGS_CONNECTION_FLAGS_CHANGED    "flags-changed"
 
 /* Properties */
-#define NM_SETTINGS_CONNECTION_UNSAVED  "unsaved"
-#define NM_SETTINGS_CONNECTION_FLAGS    "flags"
-#define NM_SETTINGS_CONNECTION_FILENAME "filename"
+#define NM_SETTINGS_CONNECTION_UNSAVED    "unsaved"
+#define NM_SETTINGS_CONNECTION_VERSION_ID "version-id"
+#define NM_SETTINGS_CONNECTION_FLAGS      "flags"
+#define NM_SETTINGS_CONNECTION_FILENAME   "filename"
 
 /**
  * NMSettingsConnectionIntFlags:
@@ -188,19 +192,6 @@ typedef enum _NMSettingsConnectionIntFlags {
     _NM_SETTINGS_CONNECTION_INT_FLAGS_ALL = ((_NM_SETTINGS_CONNECTION_INT_FLAGS_LAST - 1) << 1) - 1,
 } NMSettingsConnectionIntFlags;
 
-typedef enum {
-    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE = 0,
-
-    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST = (1LL << 0),
-    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED       = (1LL << 1),
-    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS   = (1LL << 2),
-
-    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL =
-        (NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST
-         | NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED
-         | NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS),
-} NMSettingsAutoconnectBlockedReason;
-
 typedef struct _NMSettingsConnectionCallId NMSettingsConnectionCallId;
 
 typedef struct _NMSettingsConnectionClass NMSettingsConnectionClass;
@@ -210,6 +201,7 @@ struct _NMSettingsConnectionPrivate;
 struct _NMSettingsConnection {
     NMDBusObject                         parent;
     CList                                _connections_lst;
+    CList                                devcon_con_lst_head;
     struct _NMSettingsConnectionPrivate *_priv;
 };
 
@@ -217,7 +209,13 @@ GType nm_settings_connection_get_type(void);
 
 NMSettingsConnection *nm_settings_connection_new(void);
 
+NMSettings *nm_settings_connection_get_settings(NMSettingsConnection *self);
+
+NMManager *nm_settings_connection_get_manager(NMSettingsConnection *self);
+
 NMConnection *nm_settings_connection_get_connection(NMSettingsConnection *self);
+gpointer      nm_settings_connection_get_setting(NMSettingsConnection *self,
+                                                 NMMetaSettingType     meta_type);
 
 void _nm_settings_connection_set_connection(NMSettingsConnection            *self,
                                             NMConnection                    *new_connection,
@@ -234,6 +232,9 @@ const char *nm_settings_connection_get_filename(NMSettingsConnection *self);
 
 guint64 nm_settings_connection_get_last_secret_agent_version_id(NMSettingsConnection *self);
 
+guint64 nm_settings_connection_get_version_id(NMSettingsConnection *self);
+void    nm_settings_connection_bump_version_id(NMSettingsConnection *self);
+
 gboolean
 nm_settings_connection_has_unmodified_applied_connection(NMSettingsConnection *self,
                                                          NMConnection         *applied_connection,
@@ -347,31 +348,15 @@ void nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const cha
 
 guint nm_settings_connection_get_num_seen_bssids(NMSettingsConnection *self);
 
-int  nm_settings_connection_autoconnect_retries_get(NMSettingsConnection *self);
-void nm_settings_connection_autoconnect_retries_set(NMSettingsConnection *self, int retries);
-void nm_settings_connection_autoconnect_retries_reset(NMSettingsConnection *self);
-
-gint32 nm_settings_connection_autoconnect_retries_blocked_until(NMSettingsConnection *self);
+gboolean nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self);
 
 NMSettingsAutoconnectBlockedReason
-         nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self);
-gboolean nm_settings_connection_autoconnect_blocked_reason_set_full(
-    NMSettingsConnection              *self,
-    NMSettingsAutoconnectBlockedReason mask,
-    NMSettingsAutoconnectBlockedReason value);
+nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self);
 
-static inline gboolean
+gboolean
 nm_settings_connection_autoconnect_blocked_reason_set(NMSettingsConnection              *self,
-                                                      NMSettingsAutoconnectBlockedReason mask,
-                                                      gboolean                           set)
-{
-    return nm_settings_connection_autoconnect_blocked_reason_set_full(
-        self,
-        mask,
-        set ? mask : NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE);
-}
-
-gboolean nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self);
+                                                      NMSettingsAutoconnectBlockedReason reason,
+                                                      gboolean                           set);
 
 const char *nm_settings_connection_get_id(NMSettingsConnection *connection);
 const char *nm_settings_connection_get_uuid(NMSettingsConnection *connection);
diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c
index 9995b490..8796de36 100644
--- a/src/core/settings/nm-settings.c
+++ b/src/core/settings/nm-settings.c
@@ -222,7 +222,7 @@ _sett_conn_entry_get_conn(SettConnEntry *sett_conn_entry)
  * _sett_conn_entry_storage_find_conflicting_storage:
  * @sett_conn_entry: the list of settings-storages for the given UUID.
  * @target_plugin: the settings plugin to check
- * @storage_check_including: (allow-none): optionally compare against this storage.
+ * @storage_check_including: (nullable): optionally compare against this storage.
  * @plugins: the list of plugins sorted in descending priority. This determines
  *   the priority and whether a storage conflicts.
  *
@@ -451,6 +451,16 @@ static void _startup_complete_check(NMSettings *self, gint64 now_msec);
 
 /*****************************************************************************/
 
+NMManager *
+nm_settings_get_manager(NMSettings *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS(self), NULL);
+
+    return NM_SETTINGS_GET_PRIVATE(self)->manager;
+}
+
+/*****************************************************************************/
+
 static void
 _emit_connection_added(NMSettings *self, NMSettingsConnection *sett_conn)
 {
@@ -525,7 +535,7 @@ _startup_complete_check_is_ready(NMSettings           *self,
         /* Check that device is compatible with the device. We are also happy
          * with a device compatible but for which the connection is disallowed
          * by NM configuration. */
-        if (!nm_device_check_connection_compatible(device, conn, &error)
+        if (!nm_device_check_connection_compatible(device, conn, TRUE, &error)
             && !g_error_matches(error,
                                 NM_UTILS_ERROR,
                                 NM_UTILS_ERROR_CONNECTION_AVAILABLE_DISALLOWED))
@@ -1080,11 +1090,13 @@ _connection_changed_update(NMSettings                      *self,
 
     is_new = c_list_is_empty(&sett_conn->_connections_lst);
 
-    _LOGT("update[%s]: %s connection \"%s\" (" NM_SETTINGS_STORAGE_PRINT_FMT ")",
+    _LOGT("update[%s]: %s connection \"%s\" (" NM_SETTINGS_STORAGE_PRINT_FMT "), "
+          "new version-id %" G_GUINT64_FORMAT,
           nm_settings_storage_get_uuid(storage),
           is_new ? "adding" : "updating",
           nm_connection_get_id(connection),
-          NM_SETTINGS_STORAGE_PRINT_ARG(storage));
+          NM_SETTINGS_STORAGE_PRINT_ARG(storage),
+          (nm_settings_connection_get_version_id(sett_conn) + 1u));
 
     _nm_settings_connection_set_storage(sett_conn, storage);
 
@@ -1109,7 +1121,7 @@ _connection_changed_update(NMSettings                      *self,
     if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT)) {
         nm_settings_connection_autoconnect_blocked_reason_set(
             sett_conn,
-            NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
+            NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST,
             TRUE);
     }
 
@@ -1156,6 +1168,8 @@ _connection_changed_update(NMSettings                      *self,
                                      path);
     }
 
+    nm_settings_connection_bump_version_id(sett_conn);
+
     if (is_new) {
         nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self),
                                    &interface_info_settings,
@@ -1237,6 +1251,8 @@ _connection_changed_delete(NMSettings           *self,
                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,
                                      FALSE);
 
+    nm_manager_notify_delete_settings_connections(priv->manager, sett_conn);
+
     _emit_connection_removed(self, sett_conn);
 
     _nm_settings_connection_cleanup_after_remove(sett_conn);
@@ -1453,10 +1469,16 @@ static void
 _plugin_connections_reload(NMSettings *self)
 {
     NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
-    GSList            *iter;
-
-    for (iter = priv->plugins; iter; iter = iter->next) {
-        nm_settings_plugin_reload_connections(iter->data, _plugin_connections_reload_cb, self);
+    GSList            *iter_plugin;
+    GHashTableIter     iter_entry;
+    SettConnEntry     *entry;
+    gboolean           warned = FALSE;
+    gboolean           migrate;
+
+    for (iter_plugin = priv->plugins; iter_plugin; iter_plugin = iter_plugin->next) {
+        nm_settings_plugin_reload_connections(iter_plugin->data,
+                                              _plugin_connections_reload_cb,
+                                              self);
     }
 
     _connection_changed_process_all_dirty(
@@ -1469,8 +1491,53 @@ _plugin_connections_reload(NMSettings *self)
             | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS
             | NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET);
 
-    for (iter = priv->plugins; iter; iter = iter->next)
-        nm_settings_plugin_load_connections_done(iter->data);
+    for (iter_plugin = priv->plugins; iter_plugin; iter_plugin = iter_plugin->next)
+        nm_settings_plugin_load_connections_done(iter_plugin->data);
+
+    migrate = nm_config_data_get_value_boolean(nm_config_get_data(priv->config),
+                                               NM_CONFIG_KEYFILE_GROUP_MAIN,
+                                               NM_CONFIG_KEYFILE_KEY_MAIN_MIGRATE_IFCFG_RH,
+                                               NM_CONFIG_DEFAULT_MAIN_MIGRATE_IFCFG_RH_BOOL);
+
+    g_hash_table_iter_init(&iter_entry, priv->sce_idx);
+    while (g_hash_table_iter_next(&iter_entry, (gpointer *) &entry, NULL)) {
+        const char *plugin;
+
+        plugin = nm_settings_plugin_get_plugin_name(nm_settings_storage_get_plugin(entry->storage));
+
+        if (nm_streq0(plugin, "ifcfg-rh")) {
+            if (!warned) {
+                if (migrate) {
+                    nm_log_warn(
+                        LOGD_SETTINGS,
+                        "Warning: connections were found in ifcfg-rh format and the "
+                        "\"main.migrate-ifcfg-rh\" option is enabled. Those connections will be "
+                        "migrated to keyfile. To convert them back, disable the option and then "
+                        "run \"nmcli connection migrate --plugin ifcfg-rh $UUID\"");
+                } else {
+                    nm_log_info(
+                        LOGD_SETTINGS,
+                        "Warning: the ifcfg-rh plugin is deprecated, please migrate connections "
+                        "to the keyfile format using \"nmcli connection migrate\"");
+                }
+                warned = TRUE;
+            }
+            if (migrate) {
+                _LOGW("migrating connection %s ('%s') from ifcfg-rh to keyfile",
+                      entry->uuid,
+                      nm_settings_connection_get_id(entry->sett_conn));
+                nm_settings_connection_update(entry->sett_conn,
+                                              "keyfile",
+                                              NULL,
+                                              NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+                                              NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+                                              NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+                                              NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE,
+                                              "migrate-ifcfg-rh",
+                                              NULL);
+            }
+        }
+    }
 }
 
 /*****************************************************************************/
@@ -1728,7 +1795,8 @@ _set_nmmeta_tombstone(NMSettings *self,
  * @persist_mode: the persist-mode for this profile.
  * @add_reason: the add-reason flags.
  * @sett_flags: the settings flags to set.
- * @out_sett_conn: (allow-none) (transfer none): the added settings connection on success.
+ * @out_sett_conn: (out) (optional) (nullable) (transfer none): the added
+ *   settings connection on success.
  * @error: on return, a location to store any errors that may occur
  *
  * Creates a new #NMSettingsConnection for the given source @connection.
@@ -2000,9 +2068,10 @@ nm_settings_update_connection(NMSettings                      *self,
     gs_unref_object NMConnection      *new_connection_cloned = NULL;
     gs_unref_object NMConnection      *new_connection        = NULL;
     NMConnection                      *new_connection_real;
-    gs_unref_object NMSettingsStorage *cur_storage  = NULL;
-    gs_unref_object NMSettingsStorage *new_storage  = NULL;
-    NMSettingsStorage                 *drop_storage = NULL;
+    gs_unref_object NMSettingsStorage *cur_storage         = NULL;
+    gs_unref_object NMSettingsStorage *new_storage         = NULL;
+    NMSettingsStorage                 *drop_storage        = NULL;
+    NMSettingsStorage                 *prev_update_storage = NULL;
     SettConnEntry                     *sett_conn_entry;
     gboolean                           cur_in_memory;
     gboolean                           new_in_memory;
@@ -2246,16 +2315,17 @@ nm_settings_update_connection(NMSettings                      *self,
                                                       drop_storage,
                                                       &local);
         } else {
-            success = _update_connection_to_plugin(self,
-                                                   update_storage,
-                                                   connection,
-                                                   new_flags,
-                                                   update_reason,
-                                                   new_shadowed_storage_filename,
-                                                   new_shadowed_owned,
-                                                   &new_storage,
-                                                   &new_connection,
-                                                   &local);
+            success = _update_connection_to_plugin(
+                self,
+                update_storage,
+                connection,
+                new_flags,
+                NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME),
+                new_shadowed_storage_filename,
+                new_shadowed_owned,
+                &new_storage,
+                &new_connection,
+                &local);
         }
         if (!success) {
             gboolean ignore_failure;
@@ -2306,6 +2376,9 @@ nm_settings_update_connection(NMSettings                      *self,
                 nm_assert_not_reached();
                 new_connection_real = new_connection;
             }
+
+            if (update_storage && new_storage != update_storage)
+                prev_update_storage = update_storage;
         }
     }
 
@@ -2314,6 +2387,12 @@ nm_settings_update_connection(NMSettings                      *self,
 
     _connection_changed_track(self, new_storage, new_connection_real, TRUE);
 
+    if (prev_update_storage) {
+        /* The storage was swapped by the update call. The old one needs
+         * to be dropped, which we do by setting the connection to NULL. */
+        _connection_changed_track(self, prev_update_storage, NULL, FALSE);
+    }
+
     if (drop_storage && drop_storage != new_storage) {
         gs_free_error GError *local = NULL;
 
@@ -3102,7 +3181,7 @@ error:
 /**
  * nm_settings_get_connections:
  * @self: the #NMSettings
- * @out_len: (out) (allow-none): returns the number of returned
+ * @out_len: (out) (optional): returns the number of returned
  *   connections.
  *
  * Returns: (transfer none): a list of NMSettingsConnections. The list is
@@ -3205,10 +3284,10 @@ nm_settings_get_connections_sorted_by_autoconnect_priority(NMSettings *self, gui
 /**
  * nm_settings_get_connections_clone:
  * @self: the #NMSetting
- * @out_len: (allow-none): optional output argument
+ * @out_len: (optional): optional output argument
  * @func: caller-supplied function for filtering connections
  * @func_data: caller-supplied data passed to @func
- * @sort_compare_func: (allow-none): optional function pointer for
+ * @sort_compare_func: (nullable): optional function pointer for
  *   sorting the returned list.
  * @sort_data: user data for @sort_compare_func.
  *
@@ -3629,7 +3708,7 @@ have_connection_for_device(NMSettings *self, NMDevice *device)
     c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) {
         NMConnection *connection = nm_settings_connection_get_connection(sett_conn);
 
-        if (!nm_device_check_connection_compatible(device, connection, NULL))
+        if (!nm_device_check_connection_compatible(device, connection, TRUE, NULL))
             continue;
 
         if (nm_settings_connection_default_wired_get_device(sett_conn))
diff --git a/src/core/settings/nm-settings.h b/src/core/settings/nm-settings.h
index aba3c565..020623d0 100644
--- a/src/core/settings/nm-settings.h
+++ b/src/core/settings/nm-settings.h
@@ -58,6 +58,8 @@ NMSettings *nm_settings_get(void);
 
 NMSettings *nm_settings_new(NMManager *manager);
 
+NMManager *nm_settings_get_manager(NMSettings *self);
+
 gboolean nm_settings_start(NMSettings *self, GError **error);
 
 typedef void (*NMSettingsAddCallback)(NMSettings            *settings,
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
index eb0d733d..0a385247 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -52,7 +52,6 @@ typedef struct {
 
     GHashTable *unmanaged_specs;
     GHashTable *unrecognized_specs;
-
 } NMSIfcfgRHPluginPrivate;
 
 struct _NMSIfcfgRHPlugin {
@@ -177,6 +176,7 @@ nm_assert_self(NMSIfcfgRHPlugin *self, gboolean unhandled_specs_consistent)
 static NMSIfcfgRHStorage *
 _load_file(NMSIfcfgRHPlugin *self, const char *filename, GError **error)
 {
+    NMSIfcfgRHStorage            *ret            = NULL;
     gs_unref_object NMConnection *connection     = NULL;
     gs_free_error GError         *load_error     = NULL;
     gs_free char                 *unhandled_spec = NULL;
@@ -224,16 +224,16 @@ _load_file(NMSIfcfgRHPlugin *self, const char *filename, GError **error)
             nm_assert_not_reached();
             return NULL;
         }
-        return nms_ifcfg_rh_storage_new_unhandled(self,
+
+        ret = nms_ifcfg_rh_storage_new_unhandled(self, filename, unmanaged_spec, unrecognized_spec);
+    } else {
+        ret = nms_ifcfg_rh_storage_new_connection(self,
                                                   filename,
-                                                  unmanaged_spec,
-                                                  unrecognized_spec);
+                                                  g_steal_pointer(&connection),
+                                                  &st.st_mtim);
     }
 
-    return nms_ifcfg_rh_storage_new_connection(self,
-                                               filename,
-                                               g_steal_pointer(&connection),
-                                               &st.st_mtim);
+    return ret;
 }
 
 static void
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 6cfb5705..84a9479d 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -917,7 +917,7 @@ enum {
  * @options_route: (in-out): when line is from the OPTIONS setting, this is a pre-created
  *   route object that is completed with the settings from options. Otherwise,
  *   it shall point to %NULL and a new route is created and returned.
- * @out_route: (out) (transfer-full) (allow-none): the parsed %NMIPRoute instance.
+ * @out_route: (out) (transfer full) (optional): the parsed %NMIPRoute instance.
  *   In case a @options_route is passed in, it returns the input route that was modified
  *   in-place. But the caller must unref the returned route in either case.
  * @error: the failure description.
@@ -2481,6 +2481,11 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
         g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_IAID, v, NULL);
 
     nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "DHCPV6_PD_HINT", &value);
+    if (v)
+        g_object_set(s_ip6, NM_SETTING_IP6_CONFIG_DHCP_PD_HINT, v, NULL);
+
+    nm_clear_g_free(&value);
     v = svGetValueStr(ifcfg, "DHCPV6_HOSTNAME", &value);
     /* Use DHCP_HOSTNAME as fallback if it is in FQDN format and ipv6.method is
      * auto or dhcp: this is required to support old ifcfg files
@@ -2591,7 +2596,7 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
                         &local)) {
         PARSE_WARNING("%s", local->message);
         g_clear_error(&local);
-    } else if (errno == ENOENT) {
+    } else if (errno == ENOKEY) {
         /* The key is not specified. If "v" (IPV6_TOKEN) is set,
          * we default to EUI64. Otherwise, the connection would not verify. */
         if (v)
@@ -2683,16 +2688,25 @@ make_hostname_setting(shvarFile *ifcfg)
     NMTernary  from_dns_lookup;
     NMTernary  only_from_default;
     int        priority;
+    gboolean   has_setting = FALSE;
 
     priority = svGetValueInt64(ifcfg, "HOSTNAME_PRIORITY", 10, G_MININT32, G_MAXINT32, 0);
+    if (!has_setting && errno != ENOKEY)
+        has_setting = TRUE;
+
+    from_dhcp = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DHCP");
+    if (!has_setting && errno != ENOKEY)
+        has_setting = TRUE;
+
+    from_dns_lookup = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DNS_LOOKUP");
+    if (!has_setting && errno != ENOKEY)
+        has_setting = TRUE;
 
-    from_dhcp         = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DHCP");
-    from_dns_lookup   = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DNS_LOOKUP");
     only_from_default = svGetValueTernary(ifcfg, "HOSTNAME_ONLY_FROM_DEFAULT");
+    if (!has_setting && errno != ENOKEY)
+        has_setting = TRUE;
 
-    /* Create the setting when at least one key is not default*/
-    if (priority == 0 && from_dhcp == NM_TERNARY_DEFAULT && from_dns_lookup == NM_TERNARY_DEFAULT
-        && only_from_default == NM_TERNARY_DEFAULT)
+    if (!has_setting)
         return NULL;
 
     setting = nm_setting_hostname_new();
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 207b8700..50e352d3 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -881,6 +881,7 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
     _KEY_TYPE("DHCPV6_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DHCPV6_HOSTNAME_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DHCPV6_IAID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DHCPV6_PD_HINT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DHCPV6_SEND_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DHCP_CLIENT_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DHCP_FQDN", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index 51b118e3..eb9e418a 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -33,7 +33,7 @@ typedef struct {
     NMSIfcfgKeyTypeFlags key_flags;
 } NMSIfcfgKeyTypeInfo;
 
-extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[263];
+extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[264];
 
 const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info(const char *key, gssize *out_idx);
 
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index 97637063..08deaf5a 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -62,6 +62,24 @@
 /*****************************************************************************/
 
 static void
+set_error_unsupported(GError      **error,
+                      NMConnection *connection,
+                      const char   *name,
+                      gboolean      is_setting)
+{
+    g_set_error(error,
+                NM_SETTINGS_ERROR,
+                NM_SETTINGS_ERROR_NOT_SUPPORTED_BY_PLUGIN,
+                "The ifcfg-rh plugin doesn't support %s '%s'. If you are modifying an existing "
+                "connection profile saved in ifcfg-rh format, please migrate the connection to "
+                "keyfile using 'nmcli connection migrate %s' or via the Update2() D-Bus API "
+                "and try again.",
+                is_setting ? "setting" : "property",
+                name,
+                nm_connection_get_uuid(connection));
+};
+
+static void
 save_secret_flags(shvarFile *ifcfg, const char *key, NMSettingSecretFlags flags)
 {
     GString *str;
@@ -3100,6 +3118,9 @@ write_ip6_setting(NMConnection *connection, shvarFile *ifcfg, GString **out_rout
                   "DHCPV6_DUID",
                   nm_setting_ip6_config_get_dhcp_duid(NM_SETTING_IP6_CONFIG(s_ip6)));
     svSetValueStr(ifcfg, "DHCPV6_IAID", nm_setting_ip_config_get_dhcp_iaid(s_ip6));
+    svSetValueStr(ifcfg,
+                  "DHCPV6_PD_HINT",
+                  nm_setting_ip6_config_get_dhcp_pd_hint(NM_SETTING_IP6_CONFIG(s_ip6)));
 
     hostname = nm_setting_ip_config_get_dhcp_hostname(s_ip6);
     svSetValueStr(ifcfg, "DHCPV6_HOSTNAME", hostname);
@@ -3485,6 +3506,11 @@ do_write_construct(NMConnection                   *connection,
     write_sriov_setting(connection, ifcfg);
     write_tc_setting(connection, ifcfg);
 
+    if (_nm_connection_get_setting(connection, NM_TYPE_SETTING_LINK)) {
+        set_error_unsupported(error, connection, "link", TRUE);
+        return FALSE;
+    }
+
     route_path_is_svformat = utils_has_route_file_new_syntax(route_path);
 
     has_complex_routes_v4 = utils_has_complex_routes(ifcfg_name, AF_INET);
diff --git a/src/core/settings/plugins/ifcfg-rh/shvar.c b/src/core/settings/plugins/ifcfg-rh/shvar.c
index fe8187c3..1ca2ea60 100644
--- a/src/core/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/core/settings/plugins/ifcfg-rh/shvar.c
@@ -97,23 +97,32 @@ static void _line_link_parse(shvarFile *s, const char *value, gsize len);
  * in case no valid value is found, the fallback value. Valid values
  * are: "yes", "true", "t", "y", "1" and "no", "false", "f", "n", "0".
  *
+ * Always sets errno. Either to zero on success, to ENOKEY for NULL
+ * or to EINVAL otherwise.
+ *
  * Returns: the parsed boolean value or @fallback.
  */
 int
 svParseBoolean(const char *value, int fallback)
 {
-    if (!value)
+    if (!value) {
+        errno = ENOKEY;
         return fallback;
+    }
 
     if (!g_ascii_strcasecmp("yes", value) || !g_ascii_strcasecmp("true", value)
         || !g_ascii_strcasecmp("t", value) || !g_ascii_strcasecmp("y", value)
-        || !g_ascii_strcasecmp("1", value))
+        || !g_ascii_strcasecmp("1", value)) {
+        errno = 0;
         return TRUE;
-    else if (!g_ascii_strcasecmp("no", value) || !g_ascii_strcasecmp("false", value)
-             || !g_ascii_strcasecmp("f", value) || !g_ascii_strcasecmp("n", value)
-             || !g_ascii_strcasecmp("0", value))
+    } else if (!g_ascii_strcasecmp("no", value) || !g_ascii_strcasecmp("false", value)
+               || !g_ascii_strcasecmp("f", value) || !g_ascii_strcasecmp("n", value)
+               || !g_ascii_strcasecmp("0", value)) {
+        errno = 0;
         return FALSE;
+    }
 
+    errno = EINVAL;
     return fallback;
 }
 
@@ -1253,6 +1262,7 @@ svGetValueStr_cp(shvarFile *s, const char *key)
  * @fallback: the fallback value in any error case
  *
  * Reads a value @key and converts it to a boolean using svParseBoolean().
+ * This always sets errno, see svParseBoolean().
  *
  * Returns: the parsed boolean value or @fallback.
  */
@@ -1271,6 +1281,7 @@ svGetValueBoolean(shvarFile *s, const char *key, int fallback)
  * @key: the name of the key to read
  *
  * Reads a value @key and converts it to a NMTernary value.
+ * This always sets errno, see svParseBoolean().
  *
  * Returns: the parsed NMTernary
  */
@@ -1328,7 +1339,7 @@ svGetValueEnum(shvarFile *s, const char *key, GType gtype, int *out_value, GErro
     if (!svalue) {
         /* don't touch out_value. The caller is supposed
          * to initialize it with the default value. */
-        errno = ENOENT;
+        errno = ENOKEY;
         return TRUE;
     }
 
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
index 1d7de8d2..1679cab6 100644
--- a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -891,6 +891,7 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
     gboolean                      reread_same;
     const char                   *uuid;
     char                          strbuf[100];
+    NMTernary                     force_rename2;
 
     _nm_assert_storage(self, storage, TRUE);
     nm_assert(NM_IS_CONNECTION(connection));
@@ -910,6 +911,20 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
     previous_filename = nms_keyfile_storage_get_filename(storage);
     uuid              = nms_keyfile_storage_get_uuid(storage);
 
+    if (force_rename)
+        force_rename2 = NM_TERNARY_TRUE;
+    else {
+        /* If the caller does not force a rename, we honor [keyfile].rename
+         * setting, and (if enabled) we rename by following the preferred name
+         * as necessary.  That's indicated with NM_TERNARY_DEFAULT. */
+        force_rename2 = nm_config_data_get_value_boolean(NM_CONFIG_GET_DATA,
+                                                         NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                                         NM_CONFIG_KEYFILE_KEY_KEYFILE_RENAME,
+                                                         FALSE)
+                            ? NM_TERNARY_DEFAULT
+                            : NM_TERNARY_FALSE;
+    }
+
     if (!nms_keyfile_writer_connection(
             connection,
             is_nm_generated,
@@ -922,7 +937,7 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
             _get_plugin_dir(priv),
             previous_filename,
             FALSE,
-            FALSE,
+            force_rename2,
             nm_sett_util_allow_filename_cb,
             NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, previous_filename),
             &full_filename,
@@ -938,7 +953,8 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
         return FALSE;
     }
 
-    nm_assert(full_filename && nm_streq(full_filename, previous_filename));
+    nm_assert(full_filename);
+    nm_assert(force_rename2 != NM_TERNARY_FALSE || nm_streq(full_filename, previous_filename));
 
     if (!reread || reread_same)
         nm_g_object_ref_set(&reread, connection);
@@ -957,11 +973,33 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
                               "\")",
                               ""));
 
-    storage->u.conn_data.is_nm_generated = is_nm_generated;
-    storage->u.conn_data.is_volatile     = is_volatile;
-    storage->u.conn_data.is_external     = is_external;
-    storage->u.conn_data.stat_mtime      = *nm_sett_util_stat_mtime(full_filename, FALSE, &mtime);
-    storage->u.conn_data.shadowed_owned  = shadowed_owned;
+    nm_sett_util_stat_mtime(full_filename, FALSE, &mtime);
+
+    if (nm_streq(full_filename, previous_filename)) {
+        storage->u.conn_data.is_nm_generated = is_nm_generated;
+        storage->u.conn_data.is_volatile     = is_volatile;
+        storage->u.conn_data.is_external     = is_external;
+        storage->u.conn_data.stat_mtime      = mtime;
+        storage->u.conn_data.shadowed_owned  = shadowed_owned;
+    } else {
+        NMSKeyfileStorage *storage_new;
+
+        /* The filename changed. We cannot modify the filename of an NMSettingsStorage.
+         * We need to create a new one. */
+        storage_new =
+            nms_keyfile_storage_new_connection(NMS_KEYFILE_PLUGIN(storage->parent._plugin),
+                                               g_object_ref(reread),
+                                               full_filename,
+                                               storage->storage_type,
+                                               is_nm_generated,
+                                               is_volatile,
+                                               is_external,
+                                               storage->u.conn_data.shadowed_storage,
+                                               shadowed_owned,
+                                               &mtime);
+        nm_sett_util_storages_add_take(&priv->storages, storage_new);
+        storage = storage_new;
+    }
 
     *out_storage    = g_object_ref(NM_SETTINGS_STORAGE(storage));
     *out_connection = g_steal_pointer(&reread);
@@ -1066,12 +1104,12 @@ delete_connection(NMSettingsPlugin *plugin, NMSettingsStorage *storage_x, GError
  * @shadowed_storage: a tombstone can also shadow an existing storage.
  *   In combination with @set and @in_memory, this is allowed to store
  *   the shadowed storage filename.
- * @out_storage: (transfer full) (allow-none): the storage element that changes, or
- *   NULL if nothing changed. Note that the file on disk is already as
- *   we want to write it, then this still counts as a change. No change only
+ * @out_storage: (transfer full) (optional) (nullable): the storage element that
+ *   changes, or %NULL if nothing changed. Note that the file on disk is already
+ *   as we want to write it, then this still counts as a change. No change only
  *   means if we try to delete a storage (@set %FALSE) that did not
  *   exist previously.
- * @out_hard_failure: (allow-none): on failure, indicate that this is a hard failure.
+ * @out_hard_failure: (optional): on failure, indicate that this is a hard failure.
  *
  * The function writes or deletes nmmeta files to/from filesystem. In this case,
  * the nmmeta files can only be symlinks to /dev/null (to indicate tombstones).
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
index ad6f277c..b1dd2e44 100644
--- a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -195,7 +195,7 @@ _internal_write_connection(NMConnection                   *connection,
                            pid_t                           owner_grp,
                            const char                     *existing_path,
                            gboolean                        existing_path_read_only,
-                           gboolean                        force_rename,
+                           NMTernary                       force_rename,
                            NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
                            gpointer                        allow_filename_user_data,
                            char                          **out_path,
@@ -212,6 +212,7 @@ _internal_write_connection(NMConnection                   *connection,
     gs_free_error GError           *local_err = NULL;
     int                             errsv;
     gboolean                        rename;
+    gboolean                        rename_follow;
     int                             i_path;
     gs_unref_object NMConnection   *reread      = NULL;
     gboolean                        reread_same = FALSE;
@@ -223,8 +224,12 @@ _internal_write_connection(NMConnection                   *connection,
 
     nm_assert(!shadowed_owned || shadowed_storage);
 
-    rename = force_rename || existing_path_read_only
-             || (existing_path && !nm_utils_file_is_in_path(existing_path, keyfile_dir));
+    rename = existing_path_read_only
+             || (existing_path && !nm_utils_file_is_in_path(existing_path, keyfile_dir))
+             || force_rename == NM_TERNARY_TRUE;
+
+    /* Follow the connection.id upon change. */
+    rename_follow = !rename && existing_path && force_rename == NM_TERNARY_DEFAULT;
 
     id = nm_connection_get_id(connection);
     nm_assert(id && *id);
@@ -283,7 +288,7 @@ _internal_write_connection(NMConnection                   *connection,
         gboolean      is_existing_path;
 
         if (i_path == -2) {
-            if (!existing_path || rename)
+            if (!existing_path || rename || rename_follow)
                 continue;
             path_candidate = g_strdup(existing_path);
         } else if (i_path == -1) {
@@ -427,7 +432,7 @@ nms_keyfile_writer_connection(NMConnection                   *connection,
                               const char                     *profile_dir,
                               const char                     *existing_path,
                               gboolean                        existing_path_read_only,
-                              gboolean                        force_rename,
+                              NMTernary                       force_rename,
                               NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
                               gpointer                        allow_filename_user_data,
                               char                          **out_path,
@@ -458,14 +463,14 @@ nms_keyfile_writer_connection(NMConnection                   *connection,
 }
 
 gboolean
-nms_keyfile_writer_test_connection(NMConnection  *connection,
-                                   const char    *keyfile_dir,
-                                   uid_t          owner_uid,
-                                   pid_t          owner_grp,
-                                   char         **out_path,
-                                   NMConnection **out_reread,
-                                   gboolean      *out_reread_same,
-                                   GError       **error)
+nmtst_keyfile_writer_test_connection(NMConnection  *connection,
+                                     const char    *keyfile_dir,
+                                     uid_t          owner_uid,
+                                     pid_t          owner_grp,
+                                     char         **out_path,
+                                     NMConnection **out_reread,
+                                     gboolean      *out_reread_same,
+                                     GError       **error)
 {
     return _internal_write_connection(connection,
                                       FALSE,
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.h b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
index 62aaa19d..850d5522 100644
--- a/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
@@ -22,7 +22,7 @@ gboolean nms_keyfile_writer_connection(NMConnection                   *connectio
                                        const char                     *profile_dir,
                                        const char                     *existing_path,
                                        gboolean                        existing_path_read_only,
-                                       gboolean                        force_rename,
+                                       NMTernary                       force_rename,
                                        NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
                                        gpointer                        allow_filename_user_data,
                                        char                          **out_path,
@@ -30,13 +30,13 @@ gboolean nms_keyfile_writer_connection(NMConnection                   *connectio
                                        gboolean                       *out_reread_same,
                                        GError                        **error);
 
-gboolean nms_keyfile_writer_test_connection(NMConnection  *connection,
-                                            const char    *keyfile_dir,
-                                            uid_t          owner_uid,
-                                            pid_t          owner_grp,
-                                            char         **out_path,
-                                            NMConnection **out_reread,
-                                            gboolean      *out_reread_same,
-                                            GError       **error);
+gboolean nmtst_keyfile_writer_test_connection(NMConnection  *connection,
+                                              const char    *keyfile_dir,
+                                              uid_t          owner_uid,
+                                              pid_t          owner_grp,
+                                              char         **out_path,
+                                              NMConnection **out_reread,
+                                              gboolean      *out_reread_same,
+                                              GError       **error);
 
 #endif /* __NMS_KEYFILE_WRITER_H__ */
diff --git a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
index 83019bab..866b1ffd 100644
--- a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
+++ b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -138,14 +138,14 @@ write_test_connection_reread(NMConnection  *connection,
 
     connection_normalized = nmtst_connection_duplicate_and_normalize(connection);
 
-    success = nms_keyfile_writer_test_connection(connection_normalized,
-                                                 TEST_SCRATCH_DIR,
-                                                 owner_uid,
-                                                 owner_grp,
-                                                 testfile,
-                                                 out_reread,
-                                                 out_reread_same,
-                                                 p_error);
+    success = nmtst_keyfile_writer_test_connection(connection_normalized,
+                                                   TEST_SCRATCH_DIR,
+                                                   owner_uid,
+                                                   owner_grp,
+                                                   testfile,
+                                                   out_reread,
+                                                   out_reread_same,
+                                                   p_error);
     g_assert_no_error(error);
     g_assert(success);
     g_assert(*testfile && (*testfile)[0]);