summary refs log tree commit diff
path: root/src/core/settings/plugins/keyfile
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/settings/plugins/keyfile')
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-plugin.c1348
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-plugin.h67
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-reader.c237
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-reader.h31
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-storage.c272
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-storage.h255
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-utils.c389
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-utils.h71
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-writer.c489
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-writer.h42
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT24
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain24
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component15
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main18
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property8
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection41
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection13
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_211
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format13
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format10
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID9
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags15
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name13
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names16
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config11
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection72
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP620
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case32
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob22
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New22
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old22
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing22
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection22
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection33
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_12
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_21
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_14
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_27
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_34
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_44
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem27
-rw-r--r--src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem118
-rw-r--r--src/core/settings/plugins/keyfile/tests/meson.build15
-rw-r--r--src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c2923
49 files changed, 6869 insertions, 0 deletions
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
new file mode 100644
index 00000000..789bfc1a
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -0,0 +1,1348 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2018 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-keyfile-plugin.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include "nm-std-aux/c-list-util.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-glib-aux/nm-io-utils.h"
+
+#include "nm-connection.h"
+#include "nm-setting.h"
+#include "nm-setting-connection.h"
+#include "nm-utils.h"
+#include "nm-config.h"
+#include "nm-core-internal.h"
+#include "nm-keyfile-internal.h"
+
+#include "systemd/nm-sd-utils-shared.h"
+
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
+#include "settings/nm-settings-utils.h"
+
+#include "nms-keyfile-storage.h"
+#include "nms-keyfile-writer.h"
+#include "nms-keyfile-reader.h"
+#include "nms-keyfile-utils.h"
+
+/*****************************************************************************/
+
+typedef struct {
+    NMConfig *config;
+
+    /* there can/could be multiple read-only directories. For example, one
+     * could set dirname_libs to
+     *   - /usr/lib/NetworkManager/profiles/
+     *   - /etc/NetworkManager/system-connections
+     * and leave dirname_etc unset. In this case, there would be multiple
+     * read-only directories.
+     *
+     * Directories that come later have higher priority and shadow profiles
+     * from earlier directories.
+     *
+     * Currently, this is only an array with zero or one elements. It could be
+     * easily extended to support multiple read-only directories.
+     */
+    char *dirname_libs[2];
+    char *dirname_etc;
+    char *dirname_run;
+
+    NMSettUtilStorages storages;
+
+} NMSKeyfilePluginPrivate;
+
+struct _NMSKeyfilePlugin {
+    NMSettingsPlugin        parent;
+    NMSKeyfilePluginPrivate _priv;
+};
+
+struct _NMSKeyfilePluginClass {
+    NMSettingsPluginClass parent;
+};
+
+G_DEFINE_TYPE(NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN)
+
+#define NMS_KEYFILE_PLUGIN_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMSKeyfilePlugin, NMS_IS_KEYFILE_PLUGIN, NMSettingsPlugin)
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME "keyfile"
+#define _NMLOG_DOMAIN      LOGD_SETTINGS
+#define _NMLOG(level, ...)                          \
+    nm_log((level),                                 \
+           _NMLOG_DOMAIN,                           \
+           NULL,                                    \
+           NULL,                                    \
+           "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+           _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__))
+
+/*****************************************************************************/
+
+static const char *
+_extra_flags_to_string(char *   str,
+                       gsize    str_len,
+                       gboolean is_nm_generated,
+                       gboolean is_volatile,
+                       gboolean is_external)
+{
+    const char *str0 = str;
+
+    if (!is_nm_generated && !is_volatile && !is_external)
+        nm_utils_strbuf_append_str(&str, &str_len, "");
+    else {
+        char ch = '(';
+
+        nm_utils_strbuf_append_c(&str, &str_len, ' ');
+        if (is_nm_generated) {
+            nm_utils_strbuf_append_c(&str, &str_len, ch);
+            nm_utils_strbuf_append_str(&str, &str_len, "nm-generated");
+            ch = ',';
+        }
+        if (is_volatile) {
+            nm_utils_strbuf_append_c(&str, &str_len, ch);
+            nm_utils_strbuf_append_str(&str, &str_len, "volatile");
+            ch = ',';
+        }
+        if (is_external) {
+            nm_utils_strbuf_append_c(&str, &str_len, ch);
+            nm_utils_strbuf_append_str(&str, &str_len, "external");
+            ch = ',';
+        }
+        nm_utils_strbuf_append_c(&str, &str_len, ')');
+    }
+
+    return str0;
+}
+
+static gboolean
+_ignore_filename(NMSKeyfileStorageType storage_type, const char *filename)
+{
+    /* for backward-compatibility, we don't require an extension for
+     * files under "/etc/...". */
+    return nm_keyfile_utils_ignore_filename(filename,
+                                            (storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC));
+}
+
+static const char *
+_get_plugin_dir(NMSKeyfilePluginPrivate *priv)
+{
+    /* the plugin dir is only needed to generate connection.uuid value via
+     * nm_keyfile_read_ensure_uuid(). This is either the configured /etc
+     * directory, of the compile-time default (in case the /etc directory
+     * is disabled). */
+    return priv->dirname_etc ?: NM_KEYFILE_PATH_NAME_ETC_DEFAULT;
+}
+
+static gboolean
+_path_detect_storage_type(const char *           full_filename,
+                          const char *const *    dirname_libs,
+                          const char *           dirname_etc,
+                          const char *           dirname_run,
+                          NMSKeyfileStorageType *out_storage_type,
+                          const char **          out_dirname,
+                          const char **          out_filename,
+                          gboolean *             out_is_nmmeta_file,
+                          gboolean *             out_failed_due_to_invalid_filename)
+{
+    NMSKeyfileStorageType storage_type;
+    const char *          filename = NULL;
+    const char *          dirname  = NULL;
+    guint                 i;
+    gboolean              is_nmmeta_file = FALSE;
+
+    NM_SET_OUT(out_failed_due_to_invalid_filename, FALSE);
+
+    if (full_filename[0] != '/')
+        return FALSE;
+
+    if (dirname_run && (filename = nm_utils_file_is_in_path(full_filename, dirname_run))) {
+        storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN;
+        dirname      = dirname_run;
+    } else if (dirname_etc && (filename = nm_utils_file_is_in_path(full_filename, dirname_etc))) {
+        storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC;
+        dirname      = dirname_etc;
+    } else {
+        for (i = 0; dirname_libs && dirname_libs[i]; i++) {
+            if ((filename = nm_utils_file_is_in_path(full_filename, dirname_libs[i]))) {
+                storage_type = NMS_KEYFILE_STORAGE_TYPE_LIB(i);
+                dirname      = dirname_libs[i];
+                break;
+            }
+        }
+        if (!dirname)
+            return FALSE;
+    }
+
+    if (_ignore_filename(storage_type, filename)) {
+        /* we accept nmmeta files, but only in /etc and /run directories. */
+
+        if (!NM_IN_SET(storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN, NMS_KEYFILE_STORAGE_TYPE_ETC)
+            || !nms_keyfile_nmmeta_check_filename(filename, NULL)) {
+            NM_SET_OUT(out_failed_due_to_invalid_filename, TRUE);
+            return FALSE;
+        }
+
+        is_nmmeta_file = TRUE;
+    }
+
+    NM_SET_OUT(out_storage_type, storage_type);
+    NM_SET_OUT(out_dirname, dirname);
+    NM_SET_OUT(out_filename, filename);
+    NM_SET_OUT(out_is_nmmeta_file, is_nmmeta_file);
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static NMConnection *
+_read_from_file(const char * full_filename,
+                const char * plugin_dir,
+                struct stat *out_stat,
+                NMTernary *  out_is_nm_generated,
+                NMTernary *  out_is_volatile,
+                NMTernary *  out_is_external,
+                char **      out_shadowed_storage,
+                NMTernary *  out_shadowed_owned,
+                GError **    error)
+{
+    NMConnection *connection;
+
+    nm_assert(full_filename && full_filename[0] == '/');
+
+    connection = nms_keyfile_reader_from_file(full_filename,
+                                              plugin_dir,
+                                              out_stat,
+                                              out_is_nm_generated,
+                                              out_is_volatile,
+                                              out_is_external,
+                                              out_shadowed_storage,
+                                              out_shadowed_owned,
+                                              error);
+
+    nm_assert(!connection
+              || (_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS));
+    nm_assert(!connection || nm_utils_is_uuid(nm_connection_get_uuid(connection)));
+
+    return connection;
+}
+
+/*****************************************************************************/
+
+static void
+_nm_assert_storage(gpointer plugin /* NMSKeyfilePlugin  */,
+                   gpointer storage /* NMSKeyfileStorage */,
+                   gboolean tracked)
+{
+#if NM_MORE_ASSERTS
+    NMSettUtilStorageByUuidHead *sbuh;
+    const char *                 uuid;
+
+    nm_assert(!plugin || NMS_IS_KEYFILE_PLUGIN(plugin));
+    nm_assert(NMS_IS_KEYFILE_STORAGE(storage));
+    nm_assert(!plugin || plugin == nm_settings_storage_get_plugin(storage));
+
+    nm_assert(({
+        const char *f = nms_keyfile_storage_get_filename(storage);
+        f &&        f[0] == '/';
+    }));
+
+    uuid = nms_keyfile_storage_get_uuid(storage);
+
+    nm_assert(nm_utils_is_uuid(uuid));
+
+    nm_assert(((NMSKeyfileStorage *) storage)->is_meta_data
+              || !(((NMSKeyfileStorage *) storage)->u.conn_data.connection)
+              || (NM_IS_CONNECTION((((NMSKeyfileStorage *) storage)->u.conn_data.connection))
+                  && nm_streq0(uuid,
+                               nm_connection_get_uuid(
+                                   (((NMSKeyfileStorage *) storage)->u.conn_data.connection)))));
+
+    nm_assert(
+        !tracked || !plugin
+        || c_list_contains(&NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin)->storages._storage_lst_head,
+                           &NMS_KEYFILE_STORAGE(storage)->parent._storage_lst));
+
+    nm_assert(!tracked || !plugin
+              || storage
+                     == g_hash_table_lookup(
+                         NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin)->storages.idx_by_filename,
+                         nms_keyfile_storage_get_filename(storage)));
+
+    if (tracked && plugin) {
+        sbuh = g_hash_table_lookup(NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin)->storages.idx_by_uuid,
+                                   &uuid);
+        nm_assert(sbuh);
+        nm_assert(c_list_contains(&sbuh->_storage_by_uuid_lst_head,
+                                  &((NMSKeyfileStorage *) storage)->parent._storage_by_uuid_lst));
+    }
+#endif
+}
+
+/*****************************************************************************/
+
+static NMSKeyfileStorage *
+_load_file(NMSKeyfilePlugin *    self,
+           const char *          dirname,
+           const char *          filename,
+           NMSKeyfileStorageType storage_type,
+           GError **             error)
+{
+    NMSKeyfilePluginPrivate *priv;
+    gs_unref_object NMConnection *connection = NULL;
+    NMTernary                     is_nm_generated_opt;
+    NMTernary                     is_volatile_opt;
+    NMTernary                     is_external_opt;
+    NMTernary                     shadowed_owned_opt;
+    gs_free char *                shadowed_storage = NULL;
+    gs_free_error GError *local                    = NULL;
+    gs_free char *        full_filename            = NULL;
+    struct stat           st;
+
+    if (_ignore_filename(storage_type, filename)) {
+        gs_free char *nmmeta                    = NULL;
+        gs_free char *loaded_path               = NULL;
+        gs_free char *shadowed_storage_filename = NULL;
+
+        if (!nms_keyfile_nmmeta_check_filename(filename, NULL)) {
+            if (error)
+                nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "skip due to invalid filename");
+            else
+                _LOGT("load: \"%s/%s\": skip file due to invalid filename", dirname, filename);
+            return NULL;
+        }
+        if (!nms_keyfile_nmmeta_read(dirname,
+                                     filename,
+                                     &full_filename,
+                                     &nmmeta,
+                                     &loaded_path,
+                                     &shadowed_storage_filename,
+                                     NULL)) {
+            if (error)
+                nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "skip unreadable nmmeta file");
+            else
+                _LOGT("load: \"%s/%s\": skip unreadable nmmeta file", dirname, filename);
+            return NULL;
+        }
+        nm_assert(loaded_path);
+        if (!NM_IN_SET(storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN, NMS_KEYFILE_STORAGE_TYPE_ETC)) {
+            if (error)
+                nm_utils_error_set(error,
+                                   NM_UTILS_ERROR_UNKNOWN,
+                                   "skip nmmeta file from read-only directory");
+            else
+                _LOGT("load: \"%s/%s\": skip nmmeta file from read-only directory",
+                      dirname,
+                      filename);
+            return NULL;
+        }
+        if (!nm_streq(loaded_path, NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL)) {
+            if (error)
+                nm_utils_error_set(error,
+                                   NM_UTILS_ERROR_UNKNOWN,
+                                   "skip nmmeta file not symlinking %s",
+                                   NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL);
+            else
+                _LOGT("load: \"%s/%s\": skip nmmeta file not symlinking to %s",
+                      dirname,
+                      filename,
+                      NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL);
+            return NULL;
+        }
+
+        return nms_keyfile_storage_new_tombstone(self,
+                                                 nmmeta,
+                                                 full_filename,
+                                                 storage_type,
+                                                 shadowed_storage_filename);
+    }
+
+    full_filename = g_build_filename(dirname, filename, NULL);
+
+    priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+
+    connection = _read_from_file(full_filename,
+                                 _get_plugin_dir(priv),
+                                 &st,
+                                 &is_nm_generated_opt,
+                                 &is_volatile_opt,
+                                 &is_external_opt,
+                                 &shadowed_storage,
+                                 &shadowed_owned_opt,
+                                 &local);
+    if (!connection) {
+        if (error)
+            g_propagate_error(error, g_steal_pointer(&local));
+        else
+            _LOGW("load: \"%s\": failed to load connection: %s", full_filename, local->message);
+        return NULL;
+    }
+
+    return nms_keyfile_storage_new_connection(self,
+                                              g_steal_pointer(&connection),
+                                              full_filename,
+                                              storage_type,
+                                              is_nm_generated_opt,
+                                              is_volatile_opt,
+                                              is_external_opt,
+                                              shadowed_storage,
+                                              shadowed_owned_opt,
+                                              &st.st_mtim);
+}
+
+static NMSKeyfileStorage *
+_load_file_from_path(NMSKeyfilePlugin *    self,
+                     const char *          full_filename,
+                     NMSKeyfileStorageType storage_type,
+                     GError **             error)
+{
+    gs_free char *f_dirname_free = NULL;
+    const char *  f_filename;
+    const char *  f_dirname;
+
+    nm_assert(full_filename && full_filename[0] == '/');
+
+    f_filename = strrchr(full_filename, '/');
+    f_dirname  = nm_strndup_a(300, full_filename, f_filename - full_filename, &f_dirname_free);
+    f_filename++;
+    return _load_file(self, f_dirname, f_filename, storage_type, error);
+}
+
+static void
+_load_dir(NMSKeyfilePlugin *    self,
+          NMSKeyfileStorageType storage_type,
+          const char *          dirname,
+          NMSettUtilStorages *  storages)
+{
+    const char *       filename;
+    GDir *             dir;
+    gs_unref_hashtable GHashTable *dupl_filenames = NULL;
+
+    dir = g_dir_open(dirname, 0, NULL);
+    if (!dir)
+        return;
+
+    dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free);
+
+    while ((filename = g_dir_read_name(dir))) {
+        gs_unref_object NMSKeyfileStorage *storage = NULL;
+
+        filename = g_strdup(filename);
+        if (!g_hash_table_add(dupl_filenames, (char *) filename))
+            continue;
+
+        storage = _load_file(self, dirname, filename, storage_type, NULL);
+        if (!storage)
+            continue;
+
+        nm_sett_util_storages_add_take(storages, g_steal_pointer(&storage));
+    }
+
+    g_dir_close(dir);
+
+#if NM_MORE_ASSERTS
+    {
+        NMSKeyfileStorage *storage;
+
+        c_list_for_each_entry (storage, &storages->_storage_lst_head, parent._storage_lst)
+            nm_assert(NMS_IS_KEYFILE_STORAGE(storage));
+    }
+#endif
+}
+
+/*****************************************************************************/
+
+static void
+_storages_consolidate(NMSKeyfilePlugin *                     self,
+                      NMSettUtilStorages *                   storages_new,
+                      gboolean                               replace_all,
+                      GHashTable *                           storages_replaced,
+                      NMSettingsPluginConnectionLoadCallback callback,
+                      gpointer                               user_data)
+{
+    NMSKeyfilePluginPrivate *priv                  = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    CList                    lst_conn_info_deleted = C_LIST_INIT(lst_conn_info_deleted);
+    gs_unref_ptrarray GPtrArray *storages_modified = NULL;
+    CList                        storages_deleted;
+    NMSKeyfileStorage *          storage_safe;
+    NMSKeyfileStorage *          storage_new;
+    NMSKeyfileStorage *          storage_old;
+    NMSKeyfileStorage *          storage;
+    guint                        i;
+
+    storages_modified = g_ptr_array_new_with_free_func(g_object_unref);
+    c_list_init(&storages_deleted);
+
+    c_list_for_each_entry (storage_old, &priv->storages._storage_lst_head, parent._storage_lst)
+        storage_old->is_dirty = TRUE;
+
+    c_list_for_each_entry_safe (storage_new,
+                                storage_safe,
+                                &storages_new->_storage_lst_head,
+                                parent._storage_lst) {
+        storage_old =
+            nm_sett_util_storages_lookup_by_filename(&priv->storages,
+                                                     nms_keyfile_storage_get_filename(storage_new));
+
+        nm_sett_util_storages_steal(storages_new, storage_new);
+
+        if (!storage_old
+            || !nm_streq(nms_keyfile_storage_get_uuid(storage_new),
+                         nms_keyfile_storage_get_uuid(storage_old))) {
+            if (storage_old) {
+                nm_sett_util_storages_steal(&priv->storages, storage_old);
+                c_list_link_tail(&storages_deleted, &storage_old->parent._storage_by_uuid_lst);
+            }
+            storage_new->is_dirty = FALSE;
+            nm_sett_util_storages_add_take(&priv->storages, storage_new);
+            g_ptr_array_add(storages_modified, g_object_ref(storage_new));
+            continue;
+        }
+
+        storage_old->is_dirty = FALSE;
+        nms_keyfile_storage_copy_content(storage_old, storage_new);
+        nms_keyfile_storage_destroy(storage_new);
+        g_ptr_array_add(storages_modified, g_object_ref(storage_old));
+    }
+
+    c_list_for_each_entry_safe (storage_old,
+                                storage_safe,
+                                &priv->storages._storage_lst_head,
+                                parent._storage_lst) {
+        if (!storage_old->is_dirty)
+            continue;
+        if (replace_all
+            || (storages_replaced && g_hash_table_contains(storages_replaced, storage_old))) {
+            nm_sett_util_storages_steal(&priv->storages, storage_old);
+            c_list_link_tail(&storages_deleted, &storage_old->parent._storage_by_uuid_lst);
+        }
+    }
+
+    /* raise events. */
+
+    for (i = 0; i < storages_modified->len; i++) {
+        storage           = storages_modified->pdata[i];
+        storage->is_dirty = TRUE;
+    }
+
+    for (i = 0; i < storages_modified->len; i++) {
+        gs_unref_object NMConnection *connection = NULL;
+
+        storage = storages_modified->pdata[i];
+
+        if (!storage->is_dirty) {
+            /* the entry is no longer is_dirty. In the meantime we already emitted
+             * another signal for it. */
+            continue;
+        }
+        storage->is_dirty = FALSE;
+
+        if (c_list_is_empty(&storage->parent._storage_lst)) {
+            /* hm? The profile was deleted in the meantime? That is only possible
+             * if the signal handler called again into the plugin. In any case, the event
+             * was already emitted. Skip. */
+            continue;
+        }
+
+        nm_assert(
+            storage
+            == nm_sett_util_storages_lookup_by_filename(&priv->storages,
+                                                        nms_keyfile_storage_get_filename(storage)));
+
+        connection = nms_keyfile_storage_steal_connection(storage);
+
+        callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), connection, user_data);
+    }
+
+    while ((storage = c_list_first_entry(&storages_deleted,
+                                         NMSKeyfileStorage,
+                                         parent._storage_by_uuid_lst))) {
+        c_list_unlink(&storage->parent._storage_by_uuid_lst);
+        callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), NULL, user_data);
+        nms_keyfile_storage_destroy(storage);
+    }
+}
+
+static void
+reload_connections(NMSettingsPlugin *                     plugin,
+                   NMSettingsPluginConnectionLoadCallback callback,
+                   gpointer                               user_data)
+{
+    NMSKeyfilePlugin *                                  self = NMS_KEYFILE_PLUGIN(plugin);
+    NMSKeyfilePluginPrivate *                           priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new =
+        NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_keyfile_storage_destroy);
+    int i;
+
+    _load_dir(self, NMS_KEYFILE_STORAGE_TYPE_RUN, priv->dirname_run, &storages_new);
+    if (priv->dirname_etc)
+        _load_dir(self, NMS_KEYFILE_STORAGE_TYPE_ETC, priv->dirname_etc, &storages_new);
+    for (i = 0; priv->dirname_libs[i]; i++)
+        _load_dir(self, NMS_KEYFILE_STORAGE_TYPE_LIB(i), priv->dirname_libs[i], &storages_new);
+
+    _storages_consolidate(self, &storages_new, TRUE, NULL, callback, user_data);
+}
+
+static void
+load_connections(NMSettingsPlugin *                     plugin,
+                 NMSettingsPluginConnectionLoadEntry *  entries,
+                 gsize                                  n_entries,
+                 NMSettingsPluginConnectionLoadCallback callback,
+                 gpointer                               user_data)
+{
+    NMSKeyfilePlugin *                                  self = NMS_KEYFILE_PLUGIN(plugin);
+    NMSKeyfilePluginPrivate *                           priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new =
+        NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_keyfile_storage_destroy);
+    gs_unref_hashtable GHashTable *dupl_filenames    = NULL;
+    gs_unref_hashtable GHashTable *storages_replaced = NULL;
+    gs_unref_hashtable GHashTable *loaded_uuids      = NULL;
+    const char *                   loaded_uuid;
+    GHashTableIter                 h_iter;
+    gsize                          i;
+
+    if (n_entries == 0)
+        return;
+
+    dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL);
+
+    loaded_uuids = g_hash_table_new(nm_str_hash, g_str_equal);
+
+    storages_replaced = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL);
+
+    for (i = 0; i < n_entries; i++) {
+        NMSettingsPluginConnectionLoadEntry *const entry = &entries[i];
+        NMSKeyfileStorageType                      storage_type;
+        gs_free_error GError *               local = NULL;
+        const char *                         f_filename;
+        const char *                         f_dirname;
+        const char *                         full_filename;
+        gs_free char *                       full_filename_keep = NULL;
+        gboolean                             is_nmmeta_file;
+        NMSettingsPluginConnectionLoadEntry *dupl_content_entry;
+        gboolean                             failed_due_to_invalid_filename;
+        gs_unref_object NMSKeyfileStorage *storage = NULL;
+
+        if (entry->handled)
+            continue;
+
+        if (!_path_detect_storage_type(entry->filename,
+                                       (const char *const *) priv->dirname_libs,
+                                       priv->dirname_etc,
+                                       priv->dirname_run,
+                                       &storage_type,
+                                       &f_dirname,
+                                       &f_filename,
+                                       &is_nmmeta_file,
+                                       &failed_due_to_invalid_filename)) {
+            if (failed_due_to_invalid_filename) {
+                entry->handled = TRUE;
+                nm_utils_error_set(&entry->error,
+                                   NM_UTILS_ERROR_UNKNOWN,
+                                   "filename is not valid for a keyfile");
+            }
+            continue;
+        }
+
+        full_filename_keep = g_build_filename(f_dirname, f_filename, NULL);
+
+        if ((dupl_content_entry = g_hash_table_lookup(dupl_filenames, full_filename_keep))) {
+            /* we already visited this file. */
+            entry->handled = dupl_content_entry->handled;
+            if (dupl_content_entry->error) {
+                g_set_error_literal(&entry->error,
+                                    dupl_content_entry->error->domain,
+                                    dupl_content_entry->error->code,
+                                    dupl_content_entry->error->message);
+            }
+            continue;
+        }
+
+        entry->handled = TRUE;
+
+        full_filename = full_filename_keep;
+        if (!g_hash_table_insert(dupl_filenames, g_steal_pointer(&full_filename_keep), entry))
+            nm_assert_not_reached();
+
+        storage = _load_file(self, f_dirname, f_filename, storage_type, &local);
+        if (!storage) {
+            if (nm_utils_file_stat(full_filename, NULL) == -ENOENT) {
+                NMSKeyfileStorage *storage2;
+
+                /* the file does not exist. We take that as indication to unload the file
+                 * that was previously loaded... */
+                storage2 = nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename);
+                if (storage2)
+                    g_hash_table_add(storages_replaced, g_object_ref(storage2));
+                continue;
+            }
+            g_propagate_error(&entry->error, g_steal_pointer(&local));
+            continue;
+        }
+
+        g_hash_table_add(loaded_uuids, (char *) nms_keyfile_storage_get_uuid(storage));
+
+        nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage));
+    }
+
+    /* now we visit all UUIDs that are about to change... */
+    g_hash_table_iter_init(&h_iter, loaded_uuids);
+    while (g_hash_table_iter_next(&h_iter, (gpointer *) &loaded_uuid, NULL)) {
+        NMSKeyfileStorage *          storage;
+        NMSettUtilStorageByUuidHead *sbuh;
+
+        sbuh = nm_sett_util_storages_lookup_by_uuid(&priv->storages, loaded_uuid);
+        if (!sbuh)
+            continue;
+
+        c_list_for_each_entry (storage,
+                               &sbuh->_storage_by_uuid_lst_head,
+                               parent._storage_by_uuid_lst) {
+            const char *    full_filename = nms_keyfile_storage_get_filename(storage);
+            gs_unref_object NMSKeyfileStorage *storage_new = NULL;
+            gs_free_error GError *local                    = NULL;
+
+            if (g_hash_table_contains(dupl_filenames, full_filename)) {
+                /* already re-loaded. */
+                continue;
+            }
+
+            /* @storage has a UUID that was just loaded from disk, but we have an entry in cache.
+             * Reload that file too despite not being told to do so. The reason is to get
+             * the latest file timestamp so that we get the priorities right. */
+
+            storage_new = _load_file_from_path(self, full_filename, storage->storage_type, &local);
+            if (storage_new && !nm_streq(loaded_uuid, nms_keyfile_storage_get_uuid(storage_new))) {
+                /* the file now references a different UUID. We are not told to reload
+                 * that file, so this means the existing storage (with the previous
+                 * filename and UUID tuple) is no longer valid. */
+                g_clear_object(&storage_new);
+            }
+
+            g_hash_table_add(storages_replaced, g_object_ref(storage));
+            if (storage_new)
+                nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage_new));
+        }
+    }
+
+    nm_clear_pointer(&loaded_uuids, g_hash_table_destroy);
+    nm_clear_pointer(&dupl_filenames, g_hash_table_destroy);
+
+    _storages_consolidate(self, &storages_new, FALSE, storages_replaced, callback, user_data);
+}
+
+gboolean
+nms_keyfile_plugin_add_connection(NMSKeyfilePlugin *  self,
+                                  NMConnection *      connection,
+                                  gboolean            in_memory,
+                                  gboolean            is_nm_generated,
+                                  gboolean            is_volatile,
+                                  gboolean            is_external,
+                                  const char *        shadowed_storage,
+                                  gboolean            shadowed_owned,
+                                  NMSettingsStorage **out_storage,
+                                  NMConnection **     out_connection,
+                                  GError **           error)
+{
+    NMSKeyfilePluginPrivate *priv               = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    gs_unref_object NMConnection *reread        = NULL;
+    gs_free char *                full_filename = NULL;
+    NMSKeyfileStorageType         storage_type;
+    gs_unref_object NMSKeyfileStorage *storage = NULL;
+    GError *                           local   = NULL;
+    const char *                       uuid;
+    gboolean                           reread_same;
+    struct timespec                    mtime;
+    char                               strbuf[100];
+
+    nm_assert(NM_IS_CONNECTION(connection));
+    nm_assert(out_storage && !*out_storage);
+    nm_assert(out_connection && !*out_connection);
+
+    nm_assert(in_memory
+              || (!is_nm_generated && !is_volatile && !is_external && !shadowed_storage
+                  && !shadowed_owned));
+
+    uuid = nm_connection_get_uuid(connection);
+
+    /* Note that even if the caller requests persistent storage, we may switch to in-memory, if
+     * no /etc directory is configured. */
+    storage_type = !in_memory && priv->dirname_etc ? NMS_KEYFILE_STORAGE_TYPE_ETC
+                                                   : NMS_KEYFILE_STORAGE_TYPE_RUN;
+
+    if (!nms_keyfile_writer_connection(
+            connection,
+            is_nm_generated,
+            is_volatile,
+            is_external,
+            shadowed_storage,
+            shadowed_owned,
+            storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC ? priv->dirname_etc : priv->dirname_run,
+            _get_plugin_dir(priv),
+            NULL,
+            FALSE,
+            FALSE,
+            nm_sett_util_allow_filename_cb,
+            NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, NULL),
+            &full_filename,
+            &reread,
+            &reread_same,
+            &local)) {
+        _LOGT("commit: %s (%s) failed to add: %s",
+              nm_connection_get_uuid(connection),
+              nm_connection_get_id(connection),
+              local->message);
+        g_propagate_error(error, local);
+        return FALSE;
+    }
+
+    if (!reread || reread_same)
+        nm_g_object_ref_set(&reread, connection);
+
+    nm_assert(_nm_connection_verify(reread, NULL) == NM_SETTING_VERIFY_SUCCESS);
+    nm_assert(nm_streq0(nm_connection_get_uuid(connection), nm_connection_get_uuid(reread)));
+
+    nm_assert(full_filename && full_filename[0] == '/');
+    nm_assert(!nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename));
+
+    _LOGT("commit: %s (%s) added as \"%s\"%s%s%s%s",
+          uuid,
+          nm_connection_get_id(connection),
+          full_filename,
+          _extra_flags_to_string(strbuf, sizeof(strbuf), is_nm_generated, is_volatile, is_external),
+          NM_PRINT_FMT_QUOTED(shadowed_storage,
+                              " (shadows \"",
+                              shadowed_storage,
+                              shadowed_owned ? "\", owned)" : "\")",
+                              ""));
+
+    storage =
+        nms_keyfile_storage_new_connection(self,
+                                           g_steal_pointer(&reread),
+                                           full_filename,
+                                           storage_type,
+                                           is_nm_generated ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+                                           is_volatile ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+                                           is_external ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+                                           shadowed_storage,
+                                           shadowed_owned ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+                                           nm_sett_util_stat_mtime(full_filename, FALSE, &mtime));
+
+    nm_sett_util_storages_add_take(&priv->storages, g_object_ref(storage));
+
+    *out_connection = nms_keyfile_storage_steal_connection(storage);
+    *out_storage    = NM_SETTINGS_STORAGE(g_steal_pointer(&storage));
+
+    return TRUE;
+}
+
+static gboolean
+add_connection(NMSettingsPlugin *  plugin,
+               NMConnection *      connection,
+               NMSettingsStorage **out_storage,
+               NMConnection **     out_connection,
+               GError **           error)
+{
+    return nms_keyfile_plugin_add_connection(NMS_KEYFILE_PLUGIN(plugin),
+                                             connection,
+                                             FALSE,
+                                             FALSE,
+                                             FALSE,
+                                             FALSE,
+                                             NULL,
+                                             FALSE,
+                                             out_storage,
+                                             out_connection,
+                                             error);
+}
+
+gboolean
+nms_keyfile_plugin_update_connection(NMSKeyfilePlugin *  self,
+                                     NMSettingsStorage * storage_x,
+                                     NMConnection *      connection,
+                                     gboolean            is_nm_generated,
+                                     gboolean            is_volatile,
+                                     gboolean            is_external,
+                                     const char *        shadowed_storage,
+                                     gboolean            shadowed_owned,
+                                     gboolean            force_rename,
+                                     NMSettingsStorage **out_storage,
+                                     NMConnection **     out_connection,
+                                     GError **           error)
+{
+    NMSKeyfilePluginPrivate *priv                  = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    NMSKeyfileStorage *      storage               = NMS_KEYFILE_STORAGE(storage_x);
+    gs_unref_object NMConnection *connection_clone = NULL;
+    gs_unref_object NMConnection *reread           = NULL;
+    gs_free char *                full_filename    = NULL;
+    gs_free_error GError *local                    = NULL;
+    struct timespec       mtime;
+    const char *          previous_filename;
+    gboolean              reread_same;
+    const char *          uuid;
+    char                  strbuf[100];
+
+    _nm_assert_storage(self, storage, TRUE);
+    nm_assert(NM_IS_CONNECTION(connection));
+    nm_assert(_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+    nm_assert(nm_streq(nms_keyfile_storage_get_uuid(storage), nm_connection_get_uuid(connection)));
+    nm_assert(!error || !*error);
+    nm_assert(NM_IN_SET(storage->storage_type,
+                        NMS_KEYFILE_STORAGE_TYPE_ETC,
+                        NMS_KEYFILE_STORAGE_TYPE_RUN));
+    nm_assert(!storage->is_meta_data);
+    nm_assert(storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN
+              || (!is_nm_generated && !is_volatile && !is_external && !shadowed_storage
+                  && !shadowed_owned));
+    nm_assert(!shadowed_owned || shadowed_storage);
+    nm_assert(priv->dirname_etc || storage->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC);
+
+    previous_filename = nms_keyfile_storage_get_filename(storage);
+    uuid              = nms_keyfile_storage_get_uuid(storage);
+
+    if (!nms_keyfile_writer_connection(
+            connection,
+            is_nm_generated,
+            is_volatile,
+            is_external,
+            shadowed_storage,
+            shadowed_owned,
+            storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC ? priv->dirname_etc
+                                                                  : priv->dirname_run,
+            _get_plugin_dir(priv),
+            previous_filename,
+            FALSE,
+            FALSE,
+            nm_sett_util_allow_filename_cb,
+            NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, previous_filename),
+            &full_filename,
+            &reread,
+            &reread_same,
+            &local)) {
+        _LOGW("commit: failure to write %s (%s) to \"%s\": %s",
+              uuid,
+              nm_connection_get_id(connection_clone),
+              previous_filename,
+              local->message);
+        g_propagate_error(error, g_steal_pointer(&local));
+        return FALSE;
+    }
+
+    nm_assert(full_filename && nm_streq(full_filename, previous_filename));
+
+    if (!reread || reread_same)
+        nm_g_object_ref_set(&reread, connection);
+
+    nm_assert(_nm_connection_verify(reread, NULL) == NM_SETTING_VERIFY_SUCCESS);
+    nm_assert(nm_streq(nm_connection_get_uuid(reread), uuid));
+
+    _LOGT("commit: \"%s\": profile %s (%s) written%s%s%s%s",
+          full_filename,
+          uuid,
+          nm_connection_get_id(connection),
+          _extra_flags_to_string(strbuf, sizeof(strbuf), is_nm_generated, is_volatile, is_external),
+          NM_PRINT_FMT_QUOTED(shadowed_storage,
+                              shadowed_owned ? " (owns \"" : " (shadows \"",
+                              shadowed_storage,
+                              "\")",
+                              ""));
+
+    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;
+
+    *out_storage    = g_object_ref(NM_SETTINGS_STORAGE(storage));
+    *out_connection = g_steal_pointer(&reread);
+    return TRUE;
+}
+
+static gboolean
+update_connection(NMSettingsPlugin *  plugin,
+                  NMSettingsStorage * storage,
+                  NMConnection *      connection,
+                  NMSettingsStorage **out_storage,
+                  NMConnection **     out_connection,
+                  GError **           error)
+{
+    return nms_keyfile_plugin_update_connection(NMS_KEYFILE_PLUGIN(plugin),
+                                                storage,
+                                                connection,
+                                                FALSE,
+                                                FALSE,
+                                                FALSE,
+                                                NULL,
+                                                FALSE,
+                                                FALSE,
+                                                out_storage,
+                                                out_connection,
+                                                error);
+}
+
+static gboolean
+delete_connection(NMSettingsPlugin *plugin, NMSettingsStorage *storage_x, GError **error)
+{
+    NMSKeyfilePlugin *       self              = NMS_KEYFILE_PLUGIN(plugin);
+    NMSKeyfilePluginPrivate *priv              = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+    gs_unref_object NMSKeyfileStorage *storage = g_object_ref(NMS_KEYFILE_STORAGE(storage_x));
+    const char *                       remove_from_disk_errmsg = NULL;
+    const char *                       operation_message;
+    const char *                       previous_filename;
+    const char *                       uuid;
+    gboolean                           success = TRUE;
+
+    _nm_assert_storage(self, storage, TRUE);
+    nm_assert(!error || !*error);
+
+    previous_filename = nms_keyfile_storage_get_filename(storage);
+    uuid              = nms_keyfile_storage_get_uuid(storage);
+
+    if (!NM_IN_SET(storage->storage_type,
+                   NMS_KEYFILE_STORAGE_TYPE_ETC,
+                   NMS_KEYFILE_STORAGE_TYPE_RUN)) {
+        nm_utils_error_set(error,
+                           NM_UTILS_ERROR_UNKNOWN,
+                           "profile in read-only storage cannot be deleted");
+        success           = FALSE;
+        operation_message = "dropped readonly file from memory";
+    } else if (unlink(previous_filename) != 0) {
+        int errsv;
+
+        errsv = errno;
+        if (errsv != ENOENT) {
+            remove_from_disk_errmsg = nm_strerror_native(errsv);
+            operation_message       = "failed to delete from disk";
+            success                 = FALSE;
+            nm_utils_error_set_errno(error,
+                                     errsv,
+                                     "failure to delete \"%s\": %s",
+                                     previous_filename);
+        } else
+            operation_message = "does not exist on disk";
+    } else
+        operation_message = "deleted from disk";
+
+    _LOGT("commit: deleted \"%s\", %s %s (%s%s%s%s)",
+          previous_filename,
+          storage->is_meta_data ? "meta-data" : "profile",
+          uuid,
+          operation_message,
+          NM_PRINT_FMT_QUOTED(remove_from_disk_errmsg, ": ", remove_from_disk_errmsg, "", ""));
+
+    if (success) {
+        nm_sett_util_storages_steal(&priv->storages, storage);
+        nms_keyfile_storage_destroy(storage);
+    }
+
+    return success;
+}
+
+/**
+ * nms_keyfile_plugin_set_nmmeta_tombstone:
+ * @self: the #NMSKeyfilePlugin instance
+ * @simulate: if %TRUE, don't do anything on the filename but just pretend
+ *   that the loaded UUID file gets tracked/untracked. In this mode, the function
+ *   cannot fail (except on hard-failure, see below).
+ *   The idea is that you first try without simulate to write to disk.
+ *   If that fails, you might still want to forcefully pretend (in-memory
+ *   only) that this uuid is marked as tombstone (or not), as desired.
+ *   So you repeate the call with @simulate %TRUE.
+ * @uuid: the UUID for which to write/delete the nmmeta file
+ * @in_memory: the storage type, either /etc or /run. Note that if @self
+ *   has no /etc directory configured, this results in a hard failure.
+ * @set: if %TRUE, write the symlink to point to /dev/null. If %FALSE,
+ *   delete the nmmeta file (if it exists).
+ * @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
+ *   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.
+ *
+ * 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).
+ *
+ * A hard failure can only happen if @self has no /etc directory configured
+ * and @in_memory is FALSE. In such case even @simulate call fails (which
+ * otherwise would always succeed).
+ * Also, if you get a hard-failure (with @simulate %FALSE) there is no point
+ * in retrying with @simulate %TRUE (contrary to all other cases!).
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+nms_keyfile_plugin_set_nmmeta_tombstone(NMSKeyfilePlugin *  self,
+                                        gboolean            simulate,
+                                        const char *        uuid,
+                                        gboolean            in_memory,
+                                        gboolean            set,
+                                        const char *        shadowed_storage,
+                                        NMSettingsStorage **out_storage,
+                                        gboolean *          out_hard_failure)
+{
+    NMSKeyfilePluginPrivate *priv;
+    gboolean                 hard_failure = FALSE;
+    NMSKeyfileStorage *      storage;
+    gs_unref_object NMSKeyfileStorage *storage_result = NULL;
+    gboolean                           nmmeta_errno;
+    gs_free char *                     nmmeta_filename = NULL;
+    NMSKeyfileStorageType              storage_type;
+    const char *                       loaded_path;
+    const char *                       dirname;
+
+    nm_assert(NMS_IS_KEYFILE_PLUGIN(self));
+    nm_assert(nm_utils_is_uuid(uuid));
+    nm_assert(!out_storage || !*out_storage);
+    nm_assert(!shadowed_storage || (set && in_memory));
+
+    priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+
+    loaded_path = set ? NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL : NULL;
+
+    if (in_memory) {
+        storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN;
+        dirname      = priv->dirname_run;
+    } else {
+        if (!priv->dirname_etc) {
+            _LOGT("commit: cannot %s%s nmmeta file for %s as there is no /etc directory",
+                  simulate ? "simulate " : "",
+                  loaded_path ? "write" : "delete",
+                  uuid);
+            nmmeta_errno = 0;
+            hard_failure = TRUE;
+            goto out;
+        }
+        storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC;
+        dirname      = priv->dirname_etc;
+    }
+
+    if (simulate) {
+        nmmeta_errno    = 0;
+        nmmeta_filename = nms_keyfile_nmmeta_filename(dirname, uuid, FALSE);
+    } else {
+        nmmeta_errno = nms_keyfile_nmmeta_write(dirname,
+                                                uuid,
+                                                loaded_path,
+                                                FALSE,
+                                                shadowed_storage,
+                                                &nmmeta_filename);
+    }
+
+    _LOGT("commit: %s nmmeta file \"%s\"%s%s%s%s%s%s %s%s%s%s",
+          loaded_path ? "writing" : "deleting",
+          nmmeta_filename,
+          NM_PRINT_FMT_QUOTED(loaded_path, " (pointing to \"", loaded_path, "\")", ""),
+          NM_PRINT_FMT_QUOTED(shadowed_storage, " (shadows \"", shadowed_storage, "\")", ""),
+          simulate ? "simulated" : (nmmeta_errno < 0 ? "failed" : "succeeded"),
+          NM_PRINT_FMT_QUOTED(nmmeta_errno < 0,
+                              " (",
+                              nm_strerror_native(nm_errno_native(nmmeta_errno)),
+                              ")",
+                              ""));
+
+    if (nmmeta_errno < 0)
+        goto out;
+
+    storage = nm_sett_util_storages_lookup_by_filename(&priv->storages, nmmeta_filename);
+
+    nm_assert(!storage
+              || (storage->is_meta_data && storage->storage_type == storage_type
+                  && nm_streq(nms_keyfile_storage_get_uuid(storage), uuid)));
+
+    if (loaded_path) {
+        if (!storage) {
+            storage = nms_keyfile_storage_new_tombstone(self,
+                                                        uuid,
+                                                        nmmeta_filename,
+                                                        storage_type,
+                                                        shadowed_storage);
+            nm_sett_util_storages_add_take(&priv->storages, storage);
+        } else {
+            g_free(storage->u.meta_data.shadowed_storage);
+            storage->u.meta_data.shadowed_storage = g_strdup(shadowed_storage);
+        }
+
+        storage_result = g_object_ref(storage);
+    } else {
+        if (storage)
+            storage_result = nm_sett_util_storages_steal(&priv->storages, storage);
+    }
+
+out:
+    nm_assert(nmmeta_errno <= 0);
+    nm_assert(nmmeta_errno < 0 || !hard_failure);
+    nm_assert(nmmeta_errno == 0 || !storage_result);
+
+    NM_SET_OUT(out_hard_failure, hard_failure);
+    NM_SET_OUT(out_storage, (NMSettingsStorage *) g_steal_pointer(&storage_result));
+    return nmmeta_errno >= 0;
+}
+
+/*****************************************************************************/
+
+static void
+config_changed_cb(NMConfig *          config,
+                  NMConfigData *      config_data,
+                  NMConfigChangeFlags changes,
+                  NMConfigData *      old_data,
+                  NMSKeyfilePlugin *  self)
+{
+    gs_free char *old_value = NULL;
+    gs_free char *new_value = NULL;
+
+    old_value = nm_config_data_get_value(old_data,
+                                         NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                         NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES,
+                                         NM_CONFIG_GET_VALUE_TYPE_SPEC);
+    new_value = nm_config_data_get_value(config_data,
+                                         NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                         NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES,
+                                         NM_CONFIG_GET_VALUE_TYPE_SPEC);
+
+    if (!nm_streq0(old_value, new_value))
+        _nm_settings_plugin_emit_signal_unmanaged_specs_changed(NM_SETTINGS_PLUGIN(self));
+}
+
+static GSList *
+get_unmanaged_specs(NMSettingsPlugin *config)
+{
+    NMSKeyfilePluginPrivate *priv  = NMS_KEYFILE_PLUGIN_GET_PRIVATE(config);
+    gs_free char *           value = NULL;
+
+    value = nm_config_data_get_value(nm_config_get_data(priv->config),
+                                     NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                     NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES,
+                                     NM_CONFIG_GET_VALUE_TYPE_SPEC);
+    return nm_match_spec_split(value);
+}
+
+/*****************************************************************************/
+
+static void
+nms_keyfile_plugin_init(NMSKeyfilePlugin *plugin)
+{
+    NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(plugin);
+
+    priv->config = g_object_ref(nm_config_get());
+
+    priv->storages = (NMSettUtilStorages) NM_SETT_UTIL_STORAGES_INIT(priv->storages,
+                                                                     nms_keyfile_storage_destroy);
+
+    /* dirname_libs are a set of read-only directories with lower priority than /etc or /run.
+     * There is nothing complicated about having multiple of such directories, so dirname_libs
+     * is a list (which currently only has at most one directory). */
+    priv->dirname_libs[0] = nm_sd_utils_path_simplify(g_strdup(NM_KEYFILE_PATH_NAME_LIB), FALSE);
+    priv->dirname_libs[1] = NULL;
+    priv->dirname_run     = nm_sd_utils_path_simplify(g_strdup(NM_KEYFILE_PATH_NAME_RUN), FALSE);
+    priv->dirname_etc     = nm_config_data_get_value(NM_CONFIG_GET_DATA_ORIG,
+                                                 NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                                 NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
+                                                 NM_CONFIG_GET_VALUE_STRIP);
+    if (priv->dirname_etc && priv->dirname_etc[0] == '\0') {
+        /* special case: configure an empty keyfile path so that NM has no writable keyfile
+         * directory. In this case, NM will only honor dirname_libs and dirname_run, meaning
+         * it cannot persist profile to non-volatile memory. */
+        nm_clear_g_free(&priv->dirname_etc);
+    } else if (!priv->dirname_etc || priv->dirname_etc[0] != '/') {
+        /* either invalid path or unspecified. Use the default. */
+        g_free(priv->dirname_etc);
+        priv->dirname_etc =
+            nm_sd_utils_path_simplify(g_strdup(NM_KEYFILE_PATH_NAME_ETC_DEFAULT), FALSE);
+    } else
+        nm_sd_utils_path_simplify(priv->dirname_etc, FALSE);
+
+    /* no duplicates */
+    if (NM_IN_STRSET(priv->dirname_libs[0], priv->dirname_etc, priv->dirname_run))
+        nm_clear_g_free(&priv->dirname_libs[0]);
+    if (NM_IN_STRSET(priv->dirname_etc, priv->dirname_run))
+        nm_clear_g_free(&priv->dirname_etc);
+
+    nm_assert(!priv->dirname_libs[0] || priv->dirname_libs[0][0] == '/');
+    nm_assert(!priv->dirname_etc || priv->dirname_etc[0] == '/');
+    nm_assert(priv->dirname_run && priv->dirname_run[0] == '/');
+}
+
+static void
+constructed(GObject *object)
+{
+    NMSKeyfilePlugin *       self = NMS_KEYFILE_PLUGIN(object);
+    NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+
+    G_OBJECT_CLASS(nms_keyfile_plugin_parent_class)->constructed(object);
+
+    if (nm_config_data_has_value(nm_config_get_data_orig(priv->config),
+                                 NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                 NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME,
+                                 NM_CONFIG_GET_VALUE_RAW))
+        _LOGW("'hostname' option is deprecated and has no effect");
+
+    if (nm_config_data_has_value(nm_config_get_data_orig(priv->config),
+                                 NM_CONFIG_KEYFILE_GROUP_MAIN,
+                                 NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES,
+                                 NM_CONFIG_GET_VALUE_RAW))
+        _LOGW("'monitor-connection-files' option is deprecated and has no effect");
+
+    g_signal_connect(G_OBJECT(priv->config),
+                     NM_CONFIG_SIGNAL_CONFIG_CHANGED,
+                     G_CALLBACK(config_changed_cb),
+                     self);
+}
+
+NMSKeyfilePlugin *
+nms_keyfile_plugin_new(void)
+{
+    return g_object_new(NMS_TYPE_KEYFILE_PLUGIN, NULL);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMSKeyfilePlugin *       self = NMS_KEYFILE_PLUGIN(object);
+    NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE(self);
+
+    if (priv->config)
+        g_signal_handlers_disconnect_by_func(priv->config, config_changed_cb, object);
+
+    nm_sett_util_storages_clear(&priv->storages);
+
+    nm_clear_g_free(&priv->dirname_libs[0]);
+    nm_clear_g_free(&priv->dirname_etc);
+    nm_clear_g_free(&priv->dirname_run);
+
+    g_clear_object(&priv->config);
+
+    G_OBJECT_CLASS(nms_keyfile_plugin_parent_class)->dispose(object);
+}
+
+static void
+nms_keyfile_plugin_class_init(NMSKeyfilePluginClass *klass)
+{
+    GObjectClass *         object_class = G_OBJECT_CLASS(klass);
+    NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS(klass);
+
+    object_class->constructed = constructed;
+    object_class->dispose     = dispose;
+
+    plugin_class->plugin_name         = "keyfile";
+    plugin_class->get_unmanaged_specs = get_unmanaged_specs;
+    plugin_class->reload_connections  = reload_connections;
+    plugin_class->load_connections    = load_connections;
+    plugin_class->add_connection      = add_connection;
+    plugin_class->update_connection   = update_connection;
+    plugin_class->delete_connection   = delete_connection;
+}
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.h b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.h
new file mode 100644
index 00000000..5d9f2da3
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2011 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_PLUGIN_H__
+#define __NMS_KEYFILE_PLUGIN_H__
+
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
+
+#include "nms-keyfile-utils.h"
+
+#define NMS_TYPE_KEYFILE_PLUGIN (nms_keyfile_plugin_get_type())
+#define NMS_KEYFILE_PLUGIN(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePlugin))
+#define NMS_KEYFILE_PLUGIN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePluginClass))
+#define NMS_IS_KEYFILE_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_KEYFILE_PLUGIN))
+#define NMS_IS_KEYFILE_PLUGIN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_KEYFILE_PLUGIN))
+#define NMS_KEYFILE_PLUGIN_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePluginClass))
+
+typedef struct _NMSKeyfilePlugin      NMSKeyfilePlugin;
+typedef struct _NMSKeyfilePluginClass NMSKeyfilePluginClass;
+
+GType nms_keyfile_plugin_get_type(void);
+
+NMSKeyfilePlugin *nms_keyfile_plugin_new(void);
+
+gboolean nms_keyfile_plugin_add_connection(NMSKeyfilePlugin *  self,
+                                           NMConnection *      connection,
+                                           gboolean            in_memory,
+                                           gboolean            is_nm_generated,
+                                           gboolean            is_volatile,
+                                           gboolean            is_external,
+                                           const char *        shadowed_storage,
+                                           gboolean            shadowed_owned,
+                                           NMSettingsStorage **out_storage,
+                                           NMConnection **     out_connection,
+                                           GError **           error);
+
+gboolean nms_keyfile_plugin_update_connection(NMSKeyfilePlugin *  self,
+                                              NMSettingsStorage * storage,
+                                              NMConnection *      connection,
+                                              gboolean            is_nm_generated,
+                                              gboolean            is_volatile,
+                                              gboolean            is_external,
+                                              const char *        shadowed_storage,
+                                              gboolean            shadowed_owned,
+                                              gboolean            force_rename,
+                                              NMSettingsStorage **out_storage,
+                                              NMConnection **     out_connection,
+                                              GError **           error);
+
+gboolean nms_keyfile_plugin_set_nmmeta_tombstone(NMSKeyfilePlugin *  self,
+                                                 gboolean            simulate,
+                                                 const char *        uuid,
+                                                 gboolean            in_memory,
+                                                 gboolean            set,
+                                                 const char *        shadowed_storage,
+                                                 NMSettingsStorage **out_storage,
+                                                 gboolean *          out_hard_failure);
+
+#endif /* __NMS_KEYFILE_PLUGIN_H__ */
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-reader.c b/src/core/settings/plugins/keyfile/nms-keyfile-reader.c
new file mode 100644
index 00000000..70b047f3
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-keyfile-reader.h"
+
+#include <sys/stat.h>
+
+#include "nm-keyfile-internal.h"
+
+#include "NetworkManagerUtils.h"
+#include "nms-keyfile-utils.h"
+
+/*****************************************************************************/
+
+static const char *
+_fmt_warn(const NMKeyfileHandlerData *handler_data, char **out_message)
+{
+    const char *group   = handler_data->kf_group_name;
+    const char *message = _nm_keyfile_handler_data_warn_get_message(handler_data);
+
+    if (group) {
+        NMSetting * setting       = handler_data->cur_setting;
+        const char *property_name = handler_data->cur_property;
+        const char *setting_name  = setting ? nm_setting_get_name(setting) : NULL;
+        char *      res;
+
+        if (setting_name) {
+            if (property_name && !strcmp(group, setting_name))
+                res = g_strdup_printf("%s.%s: %s", group, property_name, message);
+            else if (property_name)
+                res = g_strdup_printf("%s/%s.%s: %s", group, setting_name, property_name, message);
+            else if (!strcmp(group, setting_name))
+                res = g_strdup_printf("%s: %s", group, message);
+            else
+                res = g_strdup_printf("%s/%s: %s", group, setting_name, message);
+        } else
+            res = g_strdup_printf("%s: %s", group, message);
+        *out_message = res;
+        return res;
+    }
+
+    return message;
+}
+
+typedef struct {
+    bool verbose;
+} ReadInfo;
+
+static gboolean
+_handler_read(GKeyFile *            keyfile,
+              NMConnection *        connection,
+              NMKeyfileHandlerType  handler_type,
+              NMKeyfileHandlerData *handler_data,
+              void *                user_data)
+{
+    const ReadInfo *read_info = user_data;
+
+    if (handler_type == NM_KEYFILE_HANDLER_TYPE_WARN) {
+        const NMKeyfileHandlerDataWarn *warn_data = &handler_data->warn;
+        NMLogLevel                      level;
+        char *                          message_free = NULL;
+
+        if (!read_info->verbose)
+            return TRUE;
+
+        if (warn_data->severity > NM_KEYFILE_WARN_SEVERITY_WARN)
+            level = LOGL_ERR;
+        else if (warn_data->severity >= NM_KEYFILE_WARN_SEVERITY_WARN)
+            level = LOGL_WARN;
+        else if (warn_data->severity == NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE)
+            level = LOGL_WARN;
+        else
+            level = LOGL_INFO;
+
+        nm_log(level,
+               LOGD_SETTINGS,
+               NULL,
+               nm_connection_get_uuid(connection),
+               "keyfile: %s",
+               _fmt_warn(handler_data, &message_free));
+        g_free(message_free);
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+NMConnection *
+nms_keyfile_reader_from_keyfile(GKeyFile *  key_file,
+                                const char *filename,
+                                const char *base_dir,
+                                const char *profile_dir,
+                                gboolean    verbose,
+                                GError **   error)
+{
+    NMConnection *connection;
+    ReadInfo      read_info = {
+        .verbose = verbose,
+    };
+    gs_free char *base_dir_free         = NULL;
+    gs_free char *profile_filename_free = NULL;
+    gs_free char *filename_id           = NULL;
+    const char *  profile_filename      = NULL;
+
+    nm_assert(filename && filename[0]);
+    nm_assert(!base_dir || base_dir[0] == '/');
+    nm_assert(!profile_dir || profile_dir[0] == '/');
+
+    if (base_dir)
+        nm_assert(!strchr(filename, '/'));
+    else {
+        const char *s;
+
+        nm_assert(filename[0] == '/');
+
+        /* @base_dir may be NULL, in which case @filename must be an absolute path,
+         * and the directory is taken as the @base_dir. */
+        s        = strrchr(filename, '/');
+        base_dir = nm_strndup_a(255, filename, s - filename, &base_dir_free);
+        if (!profile_dir || nm_streq(base_dir, profile_dir))
+            profile_filename = filename;
+        filename = &s[1];
+    }
+
+    connection = nm_keyfile_read(key_file,
+                                 base_dir,
+                                 NM_KEYFILE_HANDLER_FLAGS_NONE,
+                                 _handler_read,
+                                 &read_info,
+                                 error);
+    if (!connection)
+        return NULL;
+
+    if (g_str_has_suffix(filename, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) {
+        gsize l = strlen(filename);
+
+        if (l > NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMCONNECTION))
+            filename_id = g_strndup(filename, l - NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMCONNECTION));
+    }
+
+    nm_keyfile_read_ensure_id(connection, filename_id ?: filename);
+
+    if (!profile_filename) {
+        profile_filename_free = g_build_filename(profile_dir ?: base_dir, filename, NULL);
+        profile_filename      = profile_filename_free;
+    }
+    nm_keyfile_read_ensure_uuid(connection, profile_filename);
+
+    return connection;
+}
+
+NMConnection *
+nms_keyfile_reader_from_file(const char * full_filename,
+                             const char * profile_dir,
+                             struct stat *out_stat,
+                             NMTernary *  out_is_nm_generated,
+                             NMTernary *  out_is_volatile,
+                             NMTernary *  out_is_external,
+                             char **      out_shadowed_storage,
+                             NMTernary *  out_shadowed_owned,
+                             GError **    error)
+{
+    nm_auto_unref_keyfile GKeyFile *key_file     = NULL;
+    NMConnection *                  connection   = NULL;
+    GError *                        verify_error = NULL;
+
+    nm_assert(full_filename && full_filename[0] == '/');
+    nm_assert(!profile_dir || profile_dir[0] == '/');
+
+    NM_SET_OUT(out_is_nm_generated, NM_TERNARY_DEFAULT);
+    NM_SET_OUT(out_is_volatile, NM_TERNARY_DEFAULT);
+    NM_SET_OUT(out_is_external, NM_TERNARY_DEFAULT);
+    NM_SET_OUT(out_shadowed_owned, NM_TERNARY_DEFAULT);
+
+    if (!nms_keyfile_utils_check_file_permissions(NMS_KEYFILE_FILETYPE_KEYFILE,
+                                                  full_filename,
+                                                  out_stat,
+                                                  error))
+        return NULL;
+
+    key_file = g_key_file_new();
+    if (!g_key_file_load_from_file(key_file, full_filename, G_KEY_FILE_NONE, error))
+        return NULL;
+
+    connection =
+        nms_keyfile_reader_from_keyfile(key_file, full_filename, NULL, profile_dir, TRUE, error);
+    if (!connection)
+        return NULL;
+
+    /* Normalize and verify the connection */
+    if (!nm_connection_normalize(connection, NULL, NULL, &verify_error)) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                    "invalid connection: %s",
+                    verify_error->message);
+        g_clear_error(&verify_error);
+        g_object_unref(connection);
+        connection = NULL;
+    }
+
+    NM_SET_OUT(out_is_nm_generated,
+               nm_key_file_get_boolean(key_file,
+                                       NM_KEYFILE_GROUP_NMMETA,
+                                       NM_KEYFILE_KEY_NMMETA_NM_GENERATED,
+                                       NM_TERNARY_DEFAULT));
+
+    NM_SET_OUT(out_is_volatile,
+               nm_key_file_get_boolean(key_file,
+                                       NM_KEYFILE_GROUP_NMMETA,
+                                       NM_KEYFILE_KEY_NMMETA_VOLATILE,
+                                       NM_TERNARY_DEFAULT));
+
+    NM_SET_OUT(out_is_external,
+               nm_key_file_get_boolean(key_file,
+                                       NM_KEYFILE_GROUP_NMMETA,
+                                       NM_KEYFILE_KEY_NMMETA_EXTERNAL,
+                                       NM_TERNARY_DEFAULT));
+
+    NM_SET_OUT(out_shadowed_storage,
+               g_key_file_get_string(key_file,
+                                     NM_KEYFILE_GROUP_NMMETA,
+                                     NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE,
+                                     NULL));
+
+    NM_SET_OUT(out_shadowed_owned,
+               nm_key_file_get_boolean(key_file,
+                                       NM_KEYFILE_GROUP_NMMETA,
+                                       NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED,
+                                       NM_TERNARY_DEFAULT));
+
+    return connection;
+}
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-reader.h b/src/core/settings/plugins/keyfile/nms-keyfile-reader.h
new file mode 100644
index 00000000..247457d8
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-reader.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_READER_H__
+#define __NMS_KEYFILE_READER_H__
+
+#include "nm-connection.h"
+
+NMConnection *nms_keyfile_reader_from_keyfile(GKeyFile *  key_file,
+                                              const char *filename,
+                                              const char *base_dir,
+                                              const char *profile_dir,
+                                              gboolean    verbose,
+                                              GError **   error);
+
+struct stat;
+
+NMConnection *nms_keyfile_reader_from_file(const char * full_filename,
+                                           const char * profile_dir,
+                                           struct stat *out_stat,
+                                           NMTernary *  out_is_nm_generated,
+                                           NMTernary *  out_is_volatile,
+                                           NMTernary *  out_is_external,
+                                           char **      out_shadowed_storage,
+                                           NMTernary *  out_shadowed_owned,
+                                           GError **    error);
+
+#endif /* __NMS_KEYFILE_READER_H__ */
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-storage.c b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
new file mode 100644
index 00000000..89be817a
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
@@ -0,0 +1,272 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-keyfile-storage.h"
+
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nms-keyfile-plugin.h"
+
+/*****************************************************************************/
+
+struct _NMSKeyfileStorageClass {
+    NMSettingsStorageClass parent;
+};
+
+G_DEFINE_TYPE(NMSKeyfileStorage, nms_keyfile_storage, NM_TYPE_SETTINGS_STORAGE)
+
+/*****************************************************************************/
+
+void
+nms_keyfile_storage_copy_content(NMSKeyfileStorage *dst, const NMSKeyfileStorage *src)
+{
+    nm_assert(src != dst);
+    nm_assert(nm_streq(nms_keyfile_storage_get_uuid(dst), nms_keyfile_storage_get_uuid(src)));
+    nm_assert(
+        nms_keyfile_storage_get_filename(dst)
+        && nm_streq(nms_keyfile_storage_get_filename(dst), nms_keyfile_storage_get_filename(src)));
+    nm_assert(dst->storage_type == src->storage_type);
+    nm_assert(dst->is_meta_data == src->is_meta_data);
+
+    if (dst->is_meta_data) {
+        gs_free char *shadowed_storage_to_free = NULL;
+
+        shadowed_storage_to_free          = g_steal_pointer(&dst->u.meta_data.shadowed_storage);
+        dst->u.meta_data                  = src->u.meta_data;
+        dst->u.meta_data.shadowed_storage = g_strdup(dst->u.meta_data.shadowed_storage);
+    } else {
+        gs_unref_object NMConnection *connection_to_free       = NULL;
+        gs_free char *                shadowed_storage_to_free = NULL;
+
+        connection_to_free       = g_steal_pointer(&dst->u.conn_data.connection);
+        shadowed_storage_to_free = g_steal_pointer(&dst->u.conn_data.shadowed_storage);
+        dst->u.conn_data         = src->u.conn_data;
+        nm_g_object_ref(dst->u.conn_data.connection);
+        dst->u.conn_data.shadowed_storage = g_strdup(dst->u.conn_data.shadowed_storage);
+    }
+}
+
+NMConnection *
+nms_keyfile_storage_steal_connection(NMSKeyfileStorage *self)
+{
+    nm_assert(NMS_IS_KEYFILE_STORAGE(self));
+    nm_assert(self->is_meta_data || NM_IS_CONNECTION(self->u.conn_data.connection));
+
+    return self->is_meta_data ? NULL : g_steal_pointer(&self->u.conn_data.connection);
+}
+
+/*****************************************************************************/
+
+static int
+cmp_fcn(const NMSKeyfileStorage *a, const NMSKeyfileStorage *b)
+{
+    nm_assert(NMS_IS_KEYFILE_STORAGE(a));
+    nm_assert(NMS_IS_KEYFILE_STORAGE(b));
+    nm_assert(a != b);
+
+    /* sort by storage-type, which also has a numeric value according to their
+     * (inverse) priority. */
+    NM_CMP_FIELD_UNSAFE(b, a, storage_type);
+
+    /* meta-data is more important. */
+    NM_CMP_FIELD_UNSAFE(a, b, is_meta_data);
+
+    if (a->is_meta_data) {
+        nm_assert(
+            nm_streq(nms_keyfile_storage_get_filename(a), nms_keyfile_storage_get_filename(b)));
+        NM_CMP_FIELD_UNSAFE(a, b, u.meta_data.is_tombstone);
+    } else {
+        /* newer files are more important. */
+        NM_CMP_FIELD(a, b, u.conn_data.stat_mtime.tv_sec);
+        NM_CMP_FIELD(a, b, u.conn_data.stat_mtime.tv_nsec);
+
+        NM_CMP_DIRECT_STRCMP(nms_keyfile_storage_get_filename(a),
+                             nms_keyfile_storage_get_filename(b));
+    }
+
+    return 0;
+}
+
+/*****************************************************************************/
+
+static void
+nms_keyfile_storage_init(NMSKeyfileStorage *self)
+{}
+
+static NMSKeyfileStorage *
+_storage_new(NMSKeyfilePlugin *    plugin,
+             const char *          uuid,
+             const char *          filename,
+             gboolean              is_meta_data,
+             NMSKeyfileStorageType storage_type)
+
+{
+    NMSKeyfileStorage *self;
+
+    nm_assert(NMS_IS_KEYFILE_PLUGIN(plugin));
+    nm_assert(nm_utils_is_uuid(uuid));
+    nm_assert(filename && filename[0] == '/');
+
+    self = g_object_new(NMS_TYPE_KEYFILE_STORAGE,
+                        NM_SETTINGS_STORAGE_PLUGIN,
+                        plugin,
+                        NM_SETTINGS_STORAGE_UUID,
+                        uuid,
+                        NM_SETTINGS_STORAGE_FILENAME,
+                        filename,
+                        NULL);
+
+    *((bool *) &self->is_meta_data)                  = is_meta_data;
+    *((NMSKeyfileStorageType *) &self->storage_type) = storage_type;
+
+    return self;
+}
+
+NMSKeyfileStorage *
+nms_keyfile_storage_new_tombstone(NMSKeyfilePlugin *    plugin,
+                                  const char *          uuid,
+                                  const char *          filename,
+                                  NMSKeyfileStorageType storage_type,
+                                  const char *          shadowed_storage)
+{
+    NMSKeyfileStorage *self;
+
+    nm_assert(nm_utils_is_uuid(uuid));
+    nm_assert(filename && filename[0] == '/');
+    nm_assert(nms_keyfile_nmmeta_check_filename(filename, NULL));
+    nm_assert(NM_IN_SET(storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC, NMS_KEYFILE_STORAGE_TYPE_RUN));
+
+    self                           = _storage_new(plugin, uuid, filename, TRUE, storage_type);
+    self->u.meta_data.is_tombstone = TRUE;
+    if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN)
+        self->u.meta_data.shadowed_storage = g_strdup(shadowed_storage);
+    return self;
+}
+
+NMSKeyfileStorage *
+nms_keyfile_storage_new_connection(NMSKeyfilePlugin *     plugin,
+                                   NMConnection *         connection_take /* pass reference */,
+                                   const char *           filename,
+                                   NMSKeyfileStorageType  storage_type,
+                                   NMTernary              is_nm_generated_opt,
+                                   NMTernary              is_volatile_opt,
+                                   NMTernary              is_external_opt,
+                                   const char *           shadowed_storage,
+                                   NMTernary              shadowed_owned_opt,
+                                   const struct timespec *stat_mtime)
+{
+    NMSKeyfileStorage *self;
+
+    nm_assert(NMS_IS_KEYFILE_PLUGIN(plugin));
+    nm_assert(NM_IS_CONNECTION(connection_take));
+    nm_assert(_nm_connection_verify(connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS);
+    nm_assert(filename && filename[0] == '/');
+    nm_assert(storage_type >= NMS_KEYFILE_STORAGE_TYPE_RUN
+              && storage_type <= _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST);
+    nmtst_connection_assert_unchanging(connection_take);
+
+    self = _storage_new(plugin,
+                        nm_connection_get_uuid(connection_take),
+                        filename,
+                        FALSE,
+                        storage_type);
+
+    self->u.conn_data.connection = connection_take; /* take reference. */
+
+    self->u.conn_data.shadowed_storage = g_strdup(shadowed_storage);
+
+    if (stat_mtime)
+        self->u.conn_data.stat_mtime = *stat_mtime;
+
+    if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) {
+        self->u.conn_data.is_nm_generated = (is_nm_generated_opt == NM_TERNARY_TRUE);
+        self->u.conn_data.is_volatile     = (is_volatile_opt == NM_TERNARY_TRUE);
+        self->u.conn_data.is_external     = (is_external_opt == NM_TERNARY_TRUE);
+        self->u.conn_data.shadowed_owned =
+            shadowed_storage && (shadowed_owned_opt == NM_TERNARY_TRUE);
+    }
+
+    return self;
+}
+
+static void
+_storage_clear(NMSKeyfileStorage *self)
+{
+    c_list_unlink(&self->parent._storage_lst);
+    c_list_unlink(&self->parent._storage_by_uuid_lst);
+    if (self->is_meta_data)
+        nm_clear_g_free(&self->u.meta_data.shadowed_storage);
+    else {
+        g_clear_object(&self->u.conn_data.connection);
+        nm_clear_g_free(&self->u.conn_data.shadowed_storage);
+        self->u.conn_data.shadowed_owned = FALSE;
+    }
+}
+
+static void
+dispose(GObject *object)
+{
+    NMSKeyfileStorage *self = NMS_KEYFILE_STORAGE(object);
+
+    _storage_clear(self);
+
+    G_OBJECT_CLASS(nms_keyfile_storage_parent_class)->dispose(object);
+}
+
+void
+nms_keyfile_storage_destroy(NMSKeyfileStorage *self)
+{
+    _storage_clear(self);
+    g_object_unref(self);
+}
+
+static void
+nms_keyfile_storage_class_init(NMSKeyfileStorageClass *klass)
+{
+    GObjectClass *          object_class  = G_OBJECT_CLASS(klass);
+    NMSettingsStorageClass *storage_class = NM_SETTINGS_STORAGE_CLASS(klass);
+
+    object_class->dispose = dispose;
+
+    storage_class->cmp_fcn = (int (*)(NMSettingsStorage *, NMSettingsStorage *)) cmp_fcn;
+}
+
+/*****************************************************************************/
+
+#include "settings/nm-settings-connection.h"
+
+void
+nm_settings_storage_load_sett_flags(NMSettingsStorage *           self,
+                                    NMSettingsConnectionIntFlags *sett_flags,
+                                    NMSettingsConnectionIntFlags *sett_mask)
+{
+    NMSKeyfileStorage *s;
+
+    *sett_flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
+    *sett_mask  = NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+                 | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+                 | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL;
+
+    if (!NMS_IS_KEYFILE_STORAGE(self))
+        return;
+
+    s = NMS_KEYFILE_STORAGE(self);
+
+    if (s->is_meta_data)
+        return;
+    if (s->storage_type != NMS_KEYFILE_STORAGE_TYPE_RUN)
+        return;
+
+    if (s->u.conn_data.is_nm_generated)
+        *sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED;
+
+    if (s->u.conn_data.is_volatile)
+        *sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+
+    if (s->u.conn_data.is_external)
+        *sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL;
+}
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-storage.h b/src/core/settings/plugins/keyfile/nms-keyfile-storage.h
new file mode 100644
index 00000000..008070df
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-storage.h
@@ -0,0 +1,255 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_STORAGE_H__
+#define __NMS_KEYFILE_STORAGE_H__
+
+#include "c-list/src/c-list.h"
+#include "settings/nm-settings-storage.h"
+#include "nms-keyfile-utils.h"
+
+/*****************************************************************************/
+
+#define NMS_TYPE_KEYFILE_STORAGE (nms_keyfile_storage_get_type())
+#define NMS_KEYFILE_STORAGE(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorage))
+#define NMS_KEYFILE_STORAGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass))
+#define NMS_IS_KEYFILE_STORAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_KEYFILE_STORAGE))
+#define NMS_IS_KEYFILE_STORAGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_KEYFILE_STORAGE))
+#define NMS_KEYFILE_STORAGE_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass))
+
+typedef struct {
+    /* whether this is a tombstone to hide a UUID (via symlink to /dev/null). */
+    char *shadowed_storage;
+    bool  is_tombstone : 1;
+} NMSettingsMetaData;
+
+typedef struct {
+    NMSettingsStorage parent;
+
+    /* The connection. Note that there are tombstones (loaded-uuid files to /dev/null)
+     * that don't have a connection.
+     *
+     * Also, we don't actually remember the loaded connection after returning it
+     * to NMSettings. So, also for regular storages (non-tombstones) this field
+     * is often cleared. */
+    union {
+        struct {
+            NMConnection *connection;
+
+            /* when we move a profile from permanent storage to unsaved (/run), then
+             * we may leave the profile on disk (depending on options for Update2()).
+             *
+             * Later, when we save the profile again to disk, we want to re-use that filename.
+             * Likewise, we delete the (now in-memory) profile, we may want to also delete
+             * the original filename.
+             *
+             * This is the original filename, and we store it inside [.nmmeta] in the
+             * keyfile in /run. Note that we don't store this in the .nmmeta file, because
+             * the information is tied to the particular keyfile in /run, not to all UUIDs
+             * in general. */
+            char *shadowed_storage;
+
+            /* the timestamp (stat's mtime) of the keyfile. For meta-data this
+             * is irrelevant. The purpose is that if the same storage type (directory) has
+             * multiple files with the same UUID, then the newer file gets preferred. */
+            struct timespec stat_mtime;
+
+            /* these flags are only relevant for storages with %NMS_KEYFILE_STORAGE_TYPE_RUN
+             * (and non-metadata). This is to persist and reload these settings flags to
+             * /run.
+             *
+             * Note that these flags are not stored in as meta-data. The reason is that meta-data
+             * is per UUID. But these flags are only relevant for a particular keyfile on disk.
+             * That is, it must be tied to the actual keyfile, and not to the UUID. */
+            bool is_nm_generated : 1;
+            bool is_volatile : 1;
+            bool is_external : 1;
+
+            /* if shadowed_storage is set, then this flag indicates whether the file
+             * is owned. The difference comes into play when deleting the in-memory,
+             * shadowing profile: a owned profile will also be deleted. */
+            bool shadowed_owned : 1;
+
+        } conn_data;
+
+        /* the content from the .nmmeta file. Note that the nmmeta file has the UUID
+         * in the filename, that means there can be only two variants of this file:
+         * in /etc and in /run. As such, this is really meta-data about the entire profile
+         * (the UUID), and not about the individual keyfile. */
+        NMSettingsMetaData meta_data;
+
+    } u;
+
+    /* The storage type. This is directly related to the filename. Since
+     * the filename cannot change, this value is unchanging. */
+    const NMSKeyfileStorageType storage_type;
+
+    /* whether union "u" has meta_data or conn_data. Since the type of the storage
+     * depends on the (immutable) filename, this is also const. */
+    const bool is_meta_data;
+
+    /* this flag is only used during reload to mark and prune old entries. */
+    bool is_dirty : 1;
+
+} NMSKeyfileStorage;
+
+typedef struct _NMSKeyfileStorageClass NMSKeyfileStorageClass;
+
+GType nms_keyfile_storage_get_type(void);
+
+struct _NMSKeyfilePlugin;
+
+NMSKeyfileStorage *nms_keyfile_storage_new_tombstone(struct _NMSKeyfilePlugin *self,
+                                                     const char *              uuid,
+                                                     const char *              filename,
+                                                     NMSKeyfileStorageType     storage_type,
+                                                     const char *              shadowed_storage);
+
+NMSKeyfileStorage *
+nms_keyfile_storage_new_connection(struct _NMSKeyfilePlugin *self,
+                                   NMConnection *            connection_take /* pass reference */,
+                                   const char *              filename,
+                                   NMSKeyfileStorageType     storage_type,
+                                   NMTernary                 is_nm_generated_opt,
+                                   NMTernary                 is_volatile_opt,
+                                   NMTernary                 is_external_opt,
+                                   const char *              shadowed_storage,
+                                   NMTernary                 shadowed_owned_opt,
+                                   const struct timespec *   stat_mtime);
+
+void nms_keyfile_storage_destroy(NMSKeyfileStorage *storage);
+
+/*****************************************************************************/
+
+void nms_keyfile_storage_copy_content(NMSKeyfileStorage *dst, const NMSKeyfileStorage *src);
+
+NMConnection *nms_keyfile_storage_steal_connection(NMSKeyfileStorage *storage);
+
+/*****************************************************************************/
+
+static inline const char *
+nms_keyfile_storage_get_uuid(const NMSKeyfileStorage *self)
+{
+    return nm_settings_storage_get_uuid((const NMSettingsStorage *) self);
+}
+
+static inline const char *
+nms_keyfile_storage_get_filename(const NMSKeyfileStorage *self)
+{
+    return nm_settings_storage_get_filename((const NMSettingsStorage *) self);
+}
+
+/*****************************************************************************/
+
+static inline gboolean
+nm_settings_storage_is_keyfile_run(const NMSettingsStorage *self)
+{
+    return NMS_IS_KEYFILE_STORAGE(self)
+           && (((NMSKeyfileStorage *) self)->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN);
+}
+
+static inline gboolean
+nm_settings_storage_is_keyfile_lib(const NMSettingsStorage *self)
+{
+    return NMS_IS_KEYFILE_STORAGE(self)
+           && (((NMSKeyfileStorage *) self)->storage_type >= NMS_KEYFILE_STORAGE_TYPE_LIB_BASE);
+}
+
+static inline const NMSettingsMetaData *
+nm_settings_storage_is_meta_data(const NMSettingsStorage *storage)
+{
+    const NMSKeyfileStorage *self;
+
+    if (!NMS_IS_KEYFILE_STORAGE(storage))
+        return NULL;
+
+    self = (NMSKeyfileStorage *) storage;
+
+    if (!self->is_meta_data)
+        return NULL;
+
+    return &self->u.meta_data;
+}
+
+static inline const NMSettingsMetaData *
+nm_settings_storage_is_meta_data_alive(const NMSettingsStorage *storage)
+{
+    const NMSettingsMetaData *meta_data;
+
+    meta_data = nm_settings_storage_is_meta_data(storage);
+
+    if (!meta_data)
+        return NULL;
+
+    /* Regular (all other) storages are alive as long as they report a NMConnection, and
+     * they will be dropped, once they have no more connection.
+     *
+     * Meta-data storages are special: they never report a NMConnection.
+     * So, a meta-data storage is alive as long as it is tracked by the
+     * settings plugin.
+     *
+     * This function is used to ckeck for that. */
+
+    if (c_list_is_empty(&storage->_storage_lst))
+        return NULL;
+
+    return meta_data;
+}
+
+static inline const char *
+nm_settings_storage_get_shadowed_storage(const NMSettingsStorage *storage,
+                                         gboolean *               out_shadowed_owned)
+{
+    if (NMS_IS_KEYFILE_STORAGE(storage)) {
+        const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage;
+
+        if (self->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) {
+            if (!self->is_meta_data) {
+                if (self->u.conn_data.shadowed_storage) {
+                    NM_SET_OUT(out_shadowed_owned, self->u.conn_data.shadowed_owned);
+                    return self->u.conn_data.shadowed_storage;
+                }
+            } else {
+                NM_SET_OUT(out_shadowed_owned, FALSE);
+                return self->u.meta_data.shadowed_storage;
+            }
+        }
+    }
+
+    NM_SET_OUT(out_shadowed_owned, FALSE);
+    return NULL;
+}
+
+static inline const char *
+nm_settings_storage_get_filename_for_shadowed_storage(const NMSettingsStorage *storage)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_STORAGE(storage), NULL);
+
+    if (!storage->_filename)
+        return NULL;
+
+    if (NMS_IS_KEYFILE_STORAGE(storage)) {
+        const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage;
+
+        if (self->is_meta_data || self->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC)
+            return NULL;
+    }
+
+    return storage->_filename;
+}
+
+/*****************************************************************************/
+
+enum _NMSettingsConnectionIntFlags;
+
+void nm_settings_storage_load_sett_flags(NMSettingsStorage *                 self,
+                                         enum _NMSettingsConnectionIntFlags *sett_flags,
+                                         enum _NMSettingsConnectionIntFlags *sett_mask);
+
+#endif /* __NMS_KEYFILE_STORAGE_H__ */
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-utils.c b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c
new file mode 100644
index 00000000..f3dffdae
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -0,0 +1,389 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010 - 2018 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-keyfile-utils.h"
+
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "nm-glib-aux/nm-io-utils.h"
+#include "nm-keyfile-internal.h"
+#include "nm-utils.h"
+#include "nm-setting-wired.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-wireless-security.h"
+#include "nm-config.h"
+
+/*****************************************************************************/
+
+#define NMMETA_KF_GROUP_NAME_NMMETA                "nmmeta"
+#define NMMETA_KF_KEY_NAME_NMMETA_UUID             "uuid"
+#define NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH      "loaded-path"
+#define NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE "shadowed-storage"
+
+/*****************************************************************************/
+
+const char *
+nms_keyfile_nmmeta_check_filename(const char *filename, guint *out_uuid_len)
+{
+    const char *uuid;
+    const char *s;
+    gsize       len;
+
+    s = strrchr(filename, '/');
+    if (s)
+        filename = &s[1];
+
+    len = strlen(filename);
+    if (len <= NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA)
+        || memcmp(&filename[len - NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA)],
+                  NM_KEYFILE_PATH_SUFFIX_NMMETA,
+                  NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA))
+               != 0) {
+        /* the filename does not have the right suffix. */
+        return NULL;
+    }
+
+    len -= NM_STRLEN(NM_KEYFILE_PATH_SUFFIX_NMMETA);
+
+    if (!NM_IN_SET(len, 36, 40)) {
+        /* the remaining part of the filename has not the right length to
+         * contain a UUID (according to nm_utils_is_uuid()). */
+        return NULL;
+    }
+
+    uuid = nm_strndup_a(100, filename, len, NULL);
+    if (!nm_utils_is_uuid(uuid))
+        return NULL;
+
+    NM_SET_OUT(out_uuid_len, len);
+    return filename;
+}
+
+char *
+nms_keyfile_nmmeta_filename(const char *dirname, const char *uuid, gboolean temporary)
+{
+    char  filename[250];
+    char *s;
+
+    nm_assert(dirname && dirname[0] == '/');
+    nm_assert(nm_utils_is_uuid(uuid) && !strchr(uuid, '/'));
+
+    if (g_snprintf(filename,
+                   sizeof(filename),
+                   "%s%s%s",
+                   uuid,
+                   NM_KEYFILE_PATH_SUFFIX_NMMETA,
+                   temporary ? "~" : "")
+        >= sizeof(filename)) {
+        /* valid uuids are limited in length (nm_utils_is_uuid). The buffer should always
+         * be large enough. */
+        nm_assert_not_reached();
+    }
+
+    s = g_build_filename(dirname, filename, NULL);
+
+    nm_assert(nm_keyfile_utils_ignore_filename(s, FALSE));
+
+    return s;
+}
+
+gboolean
+nms_keyfile_nmmeta_read(const char * dirname,
+                        const char * filename,
+                        char **      out_full_filename,
+                        char **      out_uuid,
+                        char **      out_loaded_path,
+                        char **      out_shadowed_storage,
+                        struct stat *out_st)
+{
+    const char *  uuid;
+    guint         uuid_len;
+    gs_free char *full_filename    = NULL;
+    gs_free char *loaded_path      = NULL;
+    gs_free char *shadowed_storage = NULL;
+    struct stat   st_stack;
+    struct stat * st = out_st ?: &st_stack;
+
+    nm_assert(dirname && dirname[0] == '/');
+    nm_assert(filename && filename[0] && !strchr(filename, '/'));
+
+    uuid = nms_keyfile_nmmeta_check_filename(filename, &uuid_len);
+    if (!uuid)
+        return FALSE;
+
+    full_filename = g_build_filename(dirname, filename, NULL);
+
+    if (!nms_keyfile_utils_check_file_permissions(NMS_KEYFILE_FILETYPE_NMMETA,
+                                                  full_filename,
+                                                  st,
+                                                  NULL))
+        return FALSE;
+
+    if (S_ISREG(st->st_mode)) {
+        nm_auto_unref_keyfile GKeyFile *kf     = NULL;
+        gs_free char *                  v_uuid = NULL;
+
+        kf = g_key_file_new();
+
+        if (!g_key_file_load_from_file(kf, full_filename, G_KEY_FILE_NONE, NULL))
+            return FALSE;
+
+        v_uuid = g_key_file_get_string(kf,
+                                       NMMETA_KF_GROUP_NAME_NMMETA,
+                                       NMMETA_KF_KEY_NAME_NMMETA_UUID,
+                                       NULL);
+        if (!nm_streq0(v_uuid, uuid))
+            return FALSE;
+
+        loaded_path      = g_key_file_get_string(kf,
+                                            NMMETA_KF_GROUP_NAME_NMMETA,
+                                            NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH,
+                                            NULL);
+        shadowed_storage = g_key_file_get_string(kf,
+                                                 NMMETA_KF_GROUP_NAME_NMMETA,
+                                                 NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE,
+                                                 NULL);
+
+        if (!loaded_path && !shadowed_storage) {
+            /* if there is no useful information in the file, it is the same as if
+             * the file is not present. Signal failure. */
+            return FALSE;
+        }
+
+    } else {
+        loaded_path = nm_utils_read_link_absolute(full_filename, NULL);
+        if (!loaded_path)
+            return FALSE;
+    }
+
+    NM_SET_OUT(out_uuid, g_strndup(uuid, uuid_len));
+    NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename));
+    NM_SET_OUT(out_loaded_path, g_steal_pointer(&loaded_path));
+    NM_SET_OUT(out_shadowed_storage, g_steal_pointer(&shadowed_storage));
+    return TRUE;
+}
+
+gboolean
+nms_keyfile_nmmeta_read_from_file(const char *full_filename,
+                                  char **     out_dirname,
+                                  char **     out_filename,
+                                  char **     out_uuid,
+                                  char **     out_loaded_path,
+                                  char **     out_shadowed_storage)
+{
+    gs_free char *dirname  = NULL;
+    gs_free char *filename = NULL;
+
+    nm_assert(full_filename && full_filename[0] == '/');
+
+    filename = g_path_get_basename(full_filename);
+    dirname  = g_path_get_dirname(full_filename);
+
+    if (!nms_keyfile_nmmeta_read(dirname,
+                                 filename,
+                                 NULL,
+                                 out_uuid,
+                                 out_loaded_path,
+                                 out_shadowed_storage,
+                                 NULL))
+        return FALSE;
+
+    NM_SET_OUT(out_dirname, g_steal_pointer(&dirname));
+    NM_SET_OUT(out_filename, g_steal_pointer(&filename));
+    return TRUE;
+}
+
+int
+nms_keyfile_nmmeta_write(const char *dirname,
+                         const char *uuid,
+                         const char *loaded_path,
+                         gboolean    loaded_path_allow_relative,
+                         const char *shadowed_storage,
+                         char **     out_full_filename)
+{
+    gs_free char *full_filename_tmp = NULL;
+    gs_free char *full_filename     = NULL;
+    int           errsv;
+
+    nm_assert(dirname && dirname[0] == '/');
+    nm_assert(nm_utils_is_uuid(uuid) && !strchr(uuid, '/'));
+    nm_assert(!loaded_path || loaded_path[0] == '/');
+    nm_assert(!shadowed_storage || loaded_path);
+
+    full_filename_tmp = nms_keyfile_nmmeta_filename(dirname, uuid, TRUE);
+
+    nm_assert(g_str_has_suffix(full_filename_tmp, "~"));
+    nm_assert(nm_utils_file_is_in_path(full_filename_tmp, dirname));
+
+    (void) unlink(full_filename_tmp);
+
+    if (!loaded_path) {
+        full_filename_tmp[strlen(full_filename_tmp) - 1] = '\0';
+        errsv                                            = 0;
+        if (unlink(full_filename_tmp) != 0) {
+            errsv = -NM_ERRNO_NATIVE(errno);
+            if (errsv == -ENOENT)
+                errsv = 0;
+        }
+        NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename_tmp));
+        return errsv;
+    }
+
+    if (loaded_path_allow_relative) {
+        const char *f;
+
+        f = nm_utils_file_is_in_path(loaded_path, dirname);
+        if (f) {
+            /* @loaded_path points to a file directly in @dirname.
+             * Don't use absolute paths. */
+            loaded_path = f;
+        }
+    }
+
+    full_filename = g_strndup(full_filename_tmp, strlen(full_filename_tmp) - 1);
+
+    if (shadowed_storage) {
+        nm_auto_unref_keyfile GKeyFile *kf       = NULL;
+        gs_free char *                  contents = NULL;
+        gsize                           length;
+
+        kf = g_key_file_new();
+
+        g_key_file_set_string(kf,
+                              NMMETA_KF_GROUP_NAME_NMMETA,
+                              NMMETA_KF_KEY_NAME_NMMETA_UUID,
+                              uuid);
+        g_key_file_set_string(kf,
+                              NMMETA_KF_GROUP_NAME_NMMETA,
+                              NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH,
+                              loaded_path);
+        g_key_file_set_string(kf,
+                              NMMETA_KF_GROUP_NAME_NMMETA,
+                              NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE,
+                              shadowed_storage);
+
+        contents = g_key_file_to_data(kf, &length, NULL);
+
+        if (!nm_utils_file_set_contents(full_filename, contents, length, 0600, &errsv, NULL)) {
+            NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename_tmp));
+            return -NM_ERRNO_NATIVE(errsv);
+        }
+    } else {
+        /* we only have the "loaded_path" to store. That is commonly used for the tombstones to
+         * link to /dev/null. A symlink is sufficient to store that amount of information.
+         * No need to bother with a keyfile. */
+        if (symlink(loaded_path, full_filename_tmp) != 0) {
+            errsv                                            = -NM_ERRNO_NATIVE(errno);
+            full_filename_tmp[strlen(full_filename_tmp) - 1] = '\0';
+            NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename_tmp));
+            return errsv;
+        }
+
+        if (rename(full_filename_tmp, full_filename) != 0) {
+            errsv = -NM_ERRNO_NATIVE(errno);
+            (void) unlink(full_filename_tmp);
+            NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename));
+            return errsv;
+        }
+    }
+
+    NM_SET_OUT(out_full_filename, g_steal_pointer(&full_filename));
+    return 0;
+}
+
+/*****************************************************************************/
+
+gboolean
+nms_keyfile_utils_check_file_permissions_stat(NMSKeyfileFiletype filetype,
+                                              const struct stat *st,
+                                              GError **          error)
+{
+    g_return_val_if_fail(st, FALSE);
+
+    if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) {
+        if (!S_ISREG(st->st_mode)) {
+            g_set_error_literal(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                "file is not a regular file");
+            return FALSE;
+        }
+    } else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) {
+        if (!S_ISLNK(st->st_mode) && !S_ISREG(st->st_mode)) {
+            g_set_error_literal(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                "file is neither a symlink nor a regular file");
+            return FALSE;
+        }
+    } else
+        g_return_val_if_reached(FALSE);
+
+    if (!NM_FLAGS_HAS(nm_utils_get_testing(), NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK)) {
+        if (st->st_uid != 0) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "File owner (%lld) is insecure",
+                        (long long) st->st_uid);
+            return FALSE;
+        }
+
+        if (S_ISREG(st->st_mode) && (st->st_mode & 0077)) {
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "File permissions (%03o) are insecure",
+                        st->st_mode);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+gboolean
+nms_keyfile_utils_check_file_permissions(NMSKeyfileFiletype filetype,
+                                         const char *       filename,
+                                         struct stat *      out_st,
+                                         GError **          error)
+{
+    struct stat st;
+    int         errsv;
+
+    g_return_val_if_fail(filename && filename[0] == '/', FALSE);
+
+    if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) {
+        if (stat(filename, &st) != 0) {
+            errsv = errno;
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "cannot access file: %s",
+                        nm_strerror_native(errsv));
+            return FALSE;
+        }
+    } else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) {
+        if (lstat(filename, &st) != 0) {
+            errsv = errno;
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                        "cannot access file: %s",
+                        nm_strerror_native(errsv));
+            return FALSE;
+        }
+    } else
+        g_return_val_if_reached(FALSE);
+
+    if (!nms_keyfile_utils_check_file_permissions_stat(filetype, &st, error))
+        return FALSE;
+
+    NM_SET_OUT(out_st, st);
+    return TRUE;
+}
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-utils.h b/src/core/settings/plugins/keyfile/nms-keyfile-utils.h
new file mode 100644
index 00000000..419a17ed
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-utils.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2010 - 2018 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_UTILS_H__
+#define __NMS_KEYFILE_UTILS_H__
+
+#include "NetworkManagerUtils.h"
+
+typedef enum {
+    NMS_KEYFILE_FILETYPE_KEYFILE,
+    NMS_KEYFILE_FILETYPE_NMMETA,
+} NMSKeyfileFiletype;
+
+typedef enum {
+    NMS_KEYFILE_STORAGE_TYPE_RUN      = 1, /* read-write, runtime only, e.g. /run */
+    NMS_KEYFILE_STORAGE_TYPE_ETC      = 2, /* read-write, persistent,   e.g. /etc     */
+    NMS_KEYFILE_STORAGE_TYPE_LIB_BASE = 3, /* read-only,                e.g. /usr/lib */
+
+    _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST = 1000,
+} NMSKeyfileStorageType;
+
+static inline NMSKeyfileStorageType
+NMS_KEYFILE_STORAGE_TYPE_LIB(guint run_idx)
+{
+    nm_assert(run_idx <= (_NMS_KEYFILE_STORAGE_TYPE_LIB_LAST - NMS_KEYFILE_STORAGE_TYPE_LIB_BASE));
+    return NMS_KEYFILE_STORAGE_TYPE_LIB_BASE + run_idx;
+}
+
+/*****************************************************************************/
+
+const char *nms_keyfile_nmmeta_check_filename(const char *filename, guint *out_uuid_len);
+
+char *nms_keyfile_nmmeta_filename(const char *dirname, const char *uuid, gboolean temporary);
+
+gboolean nms_keyfile_nmmeta_read(const char * dirname,
+                                 const char * filename,
+                                 char **      out_full_filename,
+                                 char **      out_uuid,
+                                 char **      out_loaded_path,
+                                 char **      out_shadowed_storage,
+                                 struct stat *out_st);
+
+gboolean nms_keyfile_nmmeta_read_from_file(const char *full_filename,
+                                           char **     out_dirname,
+                                           char **     out_filename,
+                                           char **     out_uuid,
+                                           char **     out_loaded_path,
+                                           char **     out_shadowed_storage);
+
+int nms_keyfile_nmmeta_write(const char *dirname,
+                             const char *uuid,
+                             const char *loaded_path,
+                             gboolean    loaded_path_allow_relative,
+                             const char *shadowed_storage,
+                             char **     out_full_filename);
+
+/*****************************************************************************/
+
+struct stat;
+gboolean nms_keyfile_utils_check_file_permissions_stat(NMSKeyfileFiletype filetype,
+                                                       const struct stat *st,
+                                                       GError **          error);
+
+gboolean nms_keyfile_utils_check_file_permissions(NMSKeyfileFiletype filetype,
+                                                  const char *       filename,
+                                                  struct stat *      out_st,
+                                                  GError **          error);
+
+#endif /* __NMS_KEYFILE_UTILS_H__ */
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
new file mode 100644
index 00000000..0e2c94b5
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -0,0 +1,489 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2015 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nms-keyfile-writer.h"
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "nm-keyfile-internal.h"
+
+#include "nms-keyfile-utils.h"
+#include "nms-keyfile-reader.h"
+
+#include "nm-glib-aux/nm-io-utils.h"
+
+/*****************************************************************************/
+
+typedef struct {
+    const char *keyfile_dir;
+} WriteInfo;
+
+static void
+cert_writer(NMConnection *                    connection,
+            GKeyFile *                        file,
+            NMSetting8021x *                  setting,
+            const NMSetting8021xSchemeVtable *vtable,
+            WriteInfo *                       info,
+            GError **                         error)
+{
+    const char *           setting_name = nm_setting_get_name(NM_SETTING(setting));
+    NMSetting8021xCKScheme scheme;
+    NMSetting8021xCKFormat format;
+    const char *           path = NULL, *ext = "pem";
+
+    scheme = vtable->scheme_func(setting);
+    if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+        char *      tmp           = NULL;
+        const char *accepted_path = NULL;
+
+        path = vtable->path_func(setting);
+        g_assert(path);
+
+        if (g_str_has_prefix(path, info->keyfile_dir)) {
+            const char *p = path + strlen(info->keyfile_dir);
+
+            /* If the path is rooted in the keyfile directory, just use a
+             * relative path instead of an absolute one.
+             */
+            if (*p == '/') {
+                while (*p == '/')
+                    p++;
+                if (p[0]) {
+                    /* If @p looks like an integer list, the following detection will fail too and
+                     * we will file:// qualify the path below. We thus avoid writing a path string
+                     * that would be interpreted as legacy binary format by reader. */
+                    tmp = nm_keyfile_detect_unqualified_path_scheme(info->keyfile_dir,
+                                                                    p,
+                                                                    -1,
+                                                                    FALSE,
+                                                                    NULL);
+                    if (tmp) {
+                        nm_clear_g_free(&tmp);
+                        accepted_path = p;
+                    }
+                }
+            }
+        }
+        if (!accepted_path) {
+            /* What we are about to write, must also be understood by the reader.
+             * Otherwise, add a file:// prefix */
+            tmp =
+                nm_keyfile_detect_unqualified_path_scheme(info->keyfile_dir, path, -1, FALSE, NULL);
+            if (tmp) {
+                nm_clear_g_free(&tmp);
+                accepted_path = path;
+            }
+        }
+
+        if (!accepted_path)
+            accepted_path = tmp = g_strconcat(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL);
+        nm_keyfile_plugin_kf_set_string(file, setting_name, vtable->setting_key, accepted_path);
+        g_free(tmp);
+    } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
+        nm_keyfile_plugin_kf_set_string(file,
+                                        setting_name,
+                                        vtable->setting_key,
+                                        vtable->uri_func(setting));
+    } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+        GBytes *      blob;
+        const guint8 *blob_data;
+        gsize         blob_len;
+        gboolean      success;
+        GError *      local = NULL;
+        char *        new_path;
+
+        blob = vtable->blob_func(setting);
+        g_assert(blob);
+        blob_data = g_bytes_get_data(blob, &blob_len);
+
+        if (vtable->format_func) {
+            /* Get the extension for a private key */
+            format = vtable->format_func(setting);
+            if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12)
+                ext = "p12";
+        } else {
+            /* DER or PEM format certificate? */
+            if (blob_len > 2 && blob_data[0] == 0x30 && blob_data[1] == 0x82)
+                ext = "der";
+        }
+
+        /* Write the raw data out to the standard file so that we can use paths
+         * from now on instead of pushing around the certificate data.
+         */
+        new_path = g_strdup_printf("%s/%s-%s.%s",
+                                   info->keyfile_dir,
+                                   nm_connection_get_uuid(connection),
+                                   vtable->file_suffix,
+                                   ext);
+
+        /* FIXME(keyfile-parse-in-memory): writer must not access/write to the file system before
+         * being sure that the entire profile can be written and all circumstances are good to
+         * proceed. That means, while writing we must only collect the blogs in-memory, and write
+         * them all in the end together (or not at all). */
+        success = nm_utils_file_set_contents(new_path,
+                                             (const char *) blob_data,
+                                             blob_len,
+                                             0600,
+                                             NULL,
+                                             &local);
+        if (success) {
+            /* Write the path value to the keyfile.
+             * We know, that basename(new_path) starts with a UUID, hence no conflict with "data:;base64,"  */
+            nm_keyfile_plugin_kf_set_string(file,
+                                            setting_name,
+                                            vtable->setting_key,
+                                            strrchr(new_path, '/') + 1);
+        } else {
+            nm_log_warn(LOGD_SETTINGS,
+                        "keyfile: %s.%s: failed to write certificate to file %s: %s",
+                        setting_name,
+                        vtable->setting_key,
+                        new_path,
+                        local->message);
+            g_error_free(local);
+        }
+        g_free(new_path);
+    } else {
+        /* scheme_func() returns UNKNOWN in all other cases. The only valid case
+         * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this
+         * case, we don't expect the writer to be called, because the default value
+         * will not be serialized.
+         * The only other reason for the scheme to be UNKNOWN is an invalid cert.
+         * But our connection verifies, so that cannot happen either. */
+        g_return_if_reached();
+    }
+}
+
+static gboolean
+_handler_write(NMConnection *        connection,
+               GKeyFile *            keyfile,
+               NMKeyfileHandlerType  type,
+               NMKeyfileHandlerData *type_data,
+               void *                user_data)
+{
+    if (type == NM_KEYFILE_HANDLER_TYPE_WRITE_CERT) {
+        cert_writer(connection,
+                    keyfile,
+                    NM_SETTING_802_1X(type_data->cur_setting),
+                    type_data->write_cert.vtable,
+                    user_data,
+                    type_data->p_error);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+static gboolean
+_internal_write_connection(NMConnection *                  connection,
+                           gboolean                        is_nm_generated,
+                           gboolean                        is_volatile,
+                           gboolean                        is_external,
+                           const char *                    shadowed_storage,
+                           gboolean                        shadowed_owned,
+                           const char *                    keyfile_dir,
+                           const char *                    profile_dir,
+                           gboolean                        with_extension,
+                           uid_t                           owner_uid,
+                           pid_t                           owner_grp,
+                           const char *                    existing_path,
+                           gboolean                        existing_path_read_only,
+                           gboolean                        force_rename,
+                           NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                           gpointer                        allow_filename_user_data,
+                           char **                         out_path,
+                           NMConnection **                 out_reread,
+                           gboolean *                      out_reread_same,
+                           GError **                       error)
+{
+    nm_auto_unref_keyfile GKeyFile *kf_file        = NULL;
+    gs_free char *                  kf_content_buf = NULL;
+    gsize                           kf_content_len;
+    gs_free char *                  path = NULL;
+    const char *                    id;
+    WriteInfo                       info = {0};
+    gs_free_error GError *local_err      = NULL;
+    int                   errsv;
+    gboolean              rename;
+    int                   i_path;
+    gs_unref_object NMConnection *reread      = NULL;
+    gboolean                      reread_same = FALSE;
+
+    g_return_val_if_fail(!out_path || !*out_path, FALSE);
+    g_return_val_if_fail(keyfile_dir && keyfile_dir[0] == '/', FALSE);
+
+    nm_assert(_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+
+    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));
+
+    id = nm_connection_get_id(connection);
+    nm_assert(id && *id);
+
+    info.keyfile_dir = keyfile_dir;
+
+    kf_file =
+        nm_keyfile_write(connection, NM_KEYFILE_HANDLER_FLAGS_NONE, _handler_write, &info, error);
+    if (!kf_file)
+        return FALSE;
+
+    if (is_nm_generated) {
+        g_key_file_set_boolean(kf_file,
+                               NM_KEYFILE_GROUP_NMMETA,
+                               NM_KEYFILE_KEY_NMMETA_NM_GENERATED,
+                               TRUE);
+    }
+
+    if (is_volatile) {
+        g_key_file_set_boolean(kf_file,
+                               NM_KEYFILE_GROUP_NMMETA,
+                               NM_KEYFILE_KEY_NMMETA_VOLATILE,
+                               TRUE);
+    }
+
+    if (is_external) {
+        g_key_file_set_boolean(kf_file,
+                               NM_KEYFILE_GROUP_NMMETA,
+                               NM_KEYFILE_KEY_NMMETA_EXTERNAL,
+                               TRUE);
+    }
+
+    if (shadowed_storage) {
+        g_key_file_set_string(kf_file,
+                              NM_KEYFILE_GROUP_NMMETA,
+                              NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE,
+                              shadowed_storage);
+    }
+
+    if (shadowed_owned) {
+        g_key_file_set_boolean(kf_file,
+                               NM_KEYFILE_GROUP_NMMETA,
+                               NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED,
+                               TRUE);
+    }
+
+    kf_content_buf = g_key_file_to_data(kf_file, &kf_content_len, error);
+    if (!kf_content_buf)
+        return FALSE;
+
+    if (!g_file_test(keyfile_dir, G_FILE_TEST_IS_DIR))
+        (void) g_mkdir_with_parents(keyfile_dir, 0755);
+
+    for (i_path = -2; i_path < 10000; i_path++) {
+        gs_free char *path_candidate = NULL;
+        gboolean      is_existing_path;
+
+        if (i_path == -2) {
+            if (!existing_path || rename)
+                continue;
+            path_candidate = g_strdup(existing_path);
+        } else if (i_path == -1) {
+            gs_free char *filename_escaped = NULL;
+
+            filename_escaped = nm_keyfile_utils_create_filename(id, with_extension);
+            path_candidate   = g_build_filename(keyfile_dir, filename_escaped, NULL);
+        } else {
+            gs_free char *filename_escaped = NULL;
+            gs_free char *filename         = NULL;
+
+            if (i_path == 0)
+                filename = g_strdup_printf("%s-%s", id, nm_connection_get_uuid(connection));
+            else
+                filename =
+                    g_strdup_printf("%s-%s-%d", id, nm_connection_get_uuid(connection), i_path);
+
+            filename_escaped = nm_keyfile_utils_create_filename(filename, with_extension);
+
+            path_candidate = g_strdup_printf("%s/%s", keyfile_dir, filename_escaped);
+        }
+
+        is_existing_path = existing_path && nm_streq(existing_path, path_candidate);
+
+        if (is_existing_path && rename)
+            continue;
+
+        if (allow_filename_cb && !allow_filename_cb(path_candidate, allow_filename_user_data))
+            continue;
+
+        if (!is_existing_path) {
+            if (g_file_test(path_candidate, G_FILE_TEST_EXISTS))
+                continue;
+        }
+
+        path = g_steal_pointer(&path_candidate);
+        break;
+    }
+
+    if (!path) {
+        gs_free char *ss = NULL;
+
+        /* this really should not happen, we tried hard to find an unused name... bail out. */
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "could not find suitable keyfile file name (%s already used)",
+                    ss = ({
+                        gs_free char *filename_escaped = NULL;
+
+                        filename_escaped = nm_keyfile_utils_create_filename(id, with_extension);
+                        g_build_filename(keyfile_dir, filename_escaped, NULL);
+                    }));
+
+        return FALSE;
+    }
+
+    if (out_reread || out_reread_same) {
+        gs_free_error GError *reread_error = NULL;
+
+        reread =
+            nms_keyfile_reader_from_keyfile(kf_file, path, NULL, profile_dir, FALSE, &reread_error);
+
+        if (!reread || !nm_connection_normalize(reread, NULL, NULL, &reread_error)) {
+            nm_log_err(
+                LOGD_SETTINGS,
+                "BUG: the profile cannot be stored in keyfile format without becoming unusable: %s",
+                reread_error->message);
+            g_set_error(error,
+                        NM_SETTINGS_ERROR,
+                        NM_SETTINGS_ERROR_FAILED,
+                        "keyfile writer produces an invalid connection: %s",
+                        reread_error->message);
+            nm_assert_not_reached();
+            return FALSE;
+        }
+
+        if (out_reread_same) {
+            reread_same =
+                !!nm_connection_compare(reread, connection, NM_SETTING_COMPARE_FLAG_EXACT);
+
+            nm_assert(reread_same
+                      == nm_connection_compare(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
+            nm_assert(reread_same == ({
+                          gs_unref_hashtable GHashTable *_settings = NULL;
+
+                          (nm_connection_diff(reread,
+                                              connection,
+                                              NM_SETTING_COMPARE_FLAG_EXACT,
+                                              &_settings)
+                           && !_settings);
+                      }));
+        }
+    }
+
+    nm_utils_file_set_contents(path, kf_content_buf, kf_content_len, 0600, NULL, &local_err);
+    if (local_err) {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "error writing to file '%s': %s",
+                    path,
+                    local_err->message);
+        return FALSE;
+    }
+
+    if (chown(path, owner_uid, owner_grp) < 0) {
+        errsv = errno;
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "error chowning '%s': %s (%d)",
+                    path,
+                    nm_strerror_native(errsv),
+                    errsv);
+        unlink(path);
+        return FALSE;
+    }
+
+    /* In case of updating the connection and changing the file path,
+     * we need to remove the old one, not to end up with two connections.
+     */
+    if (existing_path && !existing_path_read_only && !nm_streq(path, existing_path))
+        unlink(existing_path);
+
+    NM_SET_OUT(out_reread, g_steal_pointer(&reread));
+    NM_SET_OUT(out_reread_same, reread_same);
+    NM_SET_OUT(out_path, g_steal_pointer(&path));
+
+    return TRUE;
+}
+
+gboolean
+nms_keyfile_writer_connection(NMConnection *                  connection,
+                              gboolean                        is_nm_generated,
+                              gboolean                        is_volatile,
+                              gboolean                        is_external,
+                              const char *                    shadowed_storage,
+                              gboolean                        shadowed_owned,
+                              const char *                    keyfile_dir,
+                              const char *                    profile_dir,
+                              const char *                    existing_path,
+                              gboolean                        existing_path_read_only,
+                              gboolean                        force_rename,
+                              NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                              gpointer                        allow_filename_user_data,
+                              char **                         out_path,
+                              NMConnection **                 out_reread,
+                              gboolean *                      out_reread_same,
+                              GError **                       error)
+{
+    return _internal_write_connection(connection,
+                                      is_nm_generated,
+                                      is_volatile,
+                                      is_external,
+                                      shadowed_storage,
+                                      shadowed_owned,
+                                      keyfile_dir,
+                                      profile_dir,
+                                      TRUE,
+                                      0,
+                                      0,
+                                      existing_path,
+                                      existing_path_read_only,
+                                      force_rename,
+                                      allow_filename_cb,
+                                      allow_filename_user_data,
+                                      out_path,
+                                      out_reread,
+                                      out_reread_same,
+                                      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)
+{
+    return _internal_write_connection(connection,
+                                      FALSE,
+                                      FALSE,
+                                      FALSE,
+                                      NULL,
+                                      FALSE,
+                                      keyfile_dir,
+                                      keyfile_dir,
+                                      FALSE,
+                                      owner_uid,
+                                      owner_grp,
+                                      NULL,
+                                      FALSE,
+                                      FALSE,
+                                      NULL,
+                                      NULL,
+                                      out_path,
+                                      out_reread,
+                                      out_reread_same,
+                                      error);
+}
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.h b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
new file mode 100644
index 00000000..dffd3b04
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2008 - 2011 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_WRITER_H__
+#define __NMS_KEYFILE_WRITER_H__
+
+#include "nm-connection.h"
+
+typedef gboolean (*NMSKeyfileWriterAllowFilenameCb)(const char *check_filename,
+                                                    gpointer    allow_filename_user_data);
+
+gboolean nms_keyfile_writer_connection(NMConnection *                  connection,
+                                       gboolean                        is_nm_generated,
+                                       gboolean                        is_volatile,
+                                       gboolean                        is_external,
+                                       const char *                    shadowed_storage,
+                                       gboolean                        shadowed_owned,
+                                       const char *                    keyfile_dir,
+                                       const char *                    profile_dir,
+                                       const char *                    existing_path,
+                                       gboolean                        existing_path_read_only,
+                                       gboolean                        force_rename,
+                                       NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                                       gpointer                        allow_filename_user_data,
+                                       char **                         out_path,
+                                       NMConnection **                 out_reread,
+                                       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);
+
+#endif /* __NMS_KEYFILE_WRITER_H__ */
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT
new file mode 100644
index 00000000..162bf72e
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_BT
@@ -0,0 +1,24 @@
+
+[connection]
+id=AT&T Data Connect BT
+uuid=089130ab-ce28-46e4-ad77-d44869b03d19
+type=bluetooth
+autoconnect=false
+
+[ipv4]
+method=auto
+
+[gsm]
+number=*99#
+username=ISP@CINGULARGPRS.COM
+password=CINGULAR1
+apn=ISP.CINGULAR
+
+[serial]
+baud=115200
+parity=o
+
+[bluetooth]
+bdaddr=00:11:22:33:44:55
+type=dun
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain
new file mode 100644
index 00000000..11e1ec6f
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/ATT_Data_Connect_Plain
@@ -0,0 +1,24 @@
+
+[connection]
+id=AT&T Data Connect
+uuid=15d742f1-2b5a-421e-9f27-fcb1fc26d72c
+type=gsm
+autoconnect=false
+
+[ipv4]
+method=auto
+
+[gsm]
+number=*99#
+username=ISP@CINGULARGPRS.COM
+password=CINGULAR1
+apn=ISP.CINGULAR
+network-id=24005
+pin=2345
+device-id=da812de91eec16620b06cd0ca5cbc7ea25245222
+sim-id=89148000000060671234
+sim-operator-id=310260
+
+[serial]
+baud=115200
+parity=111
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component
new file mode 100644
index 00000000..f5463ed0
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Component
@@ -0,0 +1,15 @@
+[connection]
+id=Test Bridge Component
+uuid=d7b4f96c-c45e-4298-bef8-f48574f8c1c0
+type=802-3-ethernet
+master=br0
+slave-type=bridge
+
+[802-3-ethernet]
+mac-address=00:22:15:59:62:97
+
+[bridge-port]
+priority=28
+hairpin-mode=true
+path-cost=100
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main
new file mode 100644
index 00000000..fc9fd8f8
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Bridge_Main
@@ -0,0 +1,18 @@
+[connection]
+id=Test Bridge Main
+uuid=8f061643-fe41-4d4c-a8d9-097d26e2ad3a
+type=bridge
+
+[bridge]
+interface-name=br0
+stp=true
+forward-delay=2
+priority=32744
+hello-time=7
+max-age=39
+ageing-time=235352
+multicast-snooping=false
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property
new file mode 100644
index 00000000..52b395b3
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Enum_Property
@@ -0,0 +1,8 @@
+[connection]
+id=Test Wired Connection IP6
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=802-3-ethernet
+
+[ipv6]
+method=auto
+ip6-privacy=2
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property
new file mode 100644
index 00000000..3a46611d
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Flags_Property
@@ -0,0 +1,11 @@
+[connection]
+id=Test Flags Property
+uuid=05a5ec81-fa72-4b7c-9f85-4a0dfd36c84f
+type=gsm
+
+[gsm]
+number=*99#
+username=username
+password-flags=5
+apn=my.apn
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection
new file mode 100644
index 00000000..3d58fee0
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_GSM_Connection
@@ -0,0 +1,41 @@
+
+[serial]
+baud=115200
+bits=8
+parity=110
+stopbits=1
+send-delay=0
+
+[connection]
+id=Test GSM Connection
+uuid=05a5ec81-fa72-4b7c-9f85-4a0dfd36c84f
+type=gsm
+autoconnect=false
+timestamp=0
+
+[gsm]
+number=*99#
+username=username
+apn=my.apn
+network-type=0
+band=0
+
+[ppp]
+noauth=false
+refuse-eap=false
+refuse-pap=false
+refuse-chap=false
+refuse-mschap=false
+refuse-mschapv2=false
+nobsdcomp=false
+nodeflate=false
+no-vj-comp=false
+require-mppe=false
+require-mppe-128=false
+mppe-stateful=false
+crtscts=false
+baud=0
+mru=0
+mtu=0
+lcp-echo-failure=5
+lcp-echo-interval=30
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection
new file mode 100644
index 00000000..3a984b77
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_InfiniBand_Connection
@@ -0,0 +1,13 @@
+[connection]
+id=Test InfiniBand Connection
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=infiniband
+
+[infiniband]
+mac-address=00:11:22:33:44:55:66:77:88:99:01:12:23:34:45:56:67:78:89:90
+transport-mode=datagram
+mtu=1400
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID
new file mode 100644
index 00000000..2bacb725
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID
@@ -0,0 +1,11 @@
+[connection]
+id=Test 
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=802-11-wireless
+
+[802-11-wireless]
+ssid=101
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2
new file mode 100644
index 00000000..20240251
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlike_SSID_2
@@ -0,0 +1,11 @@
+[connection]
+id=Test SSID - escaping semicolon in string
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=802-11-wireless
+
+[802-11-wireless]
+ssid=11\\;12\\;13\\;
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID
new file mode 100644
index 00000000..6d2bc0fa
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Intlist_SSID
@@ -0,0 +1,11 @@
+[connection]
+id=Test 
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=802-11-wireless
+
+[802-11-wireless]
+ssid=98;108;97;104;49;50;51;52;
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format
new file mode 100644
index 00000000..b2bf9155
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_IB_Old_Format
@@ -0,0 +1,13 @@
+[connection]
+id=Test InfiniBand Connection
+uuid=5680a56d-c99f-45ad-a6dd-b44d5c398c12
+type=infiniband
+
+[infiniband]
+mac-address=0;17;34;51;68;85;102;119;136;153;1;18;35;52;69;86;103;120;137;144;
+transport-mode=datagram
+mtu=1400
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format
new file mode 100644
index 00000000..9427b16c
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_MAC_Old_Format
@@ -0,0 +1,10 @@
+[connection]
+id=Test MAC Old Format
+uuid=8980a26d-c99f-4aad-a6bd-b439bc348ca4
+type=802-3-ethernet
+
+[802-3-ethernet]
+mac-address=00:11:aa:BB:CC:55
+cloned-mac-address=00;22;170;187;204;254;
+mtu=1400
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID
new file mode 100644
index 00000000..138380d0
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_ID_UUID
@@ -0,0 +1,9 @@
+# id and uuid keys are missing
+# They will be auto-genetrated by NetworkManager
+
+[connection]
+type=ethernet
+autoconnect=true
+
+[802-3-ethernet]
+mac-address=00:11:22:33:44:55
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags
new file mode 100644
index 00000000..330adda0
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Flags
@@ -0,0 +1,15 @@
+# VLAN setting with missing 'flags' key
+# vlan.flags will be set to 0 (even if the default 'flags' property value is 1)
+
+[connection]
+id=Test Missing Vlan Flags
+uuid=803ebe47-8c31-401d-b47b-03fc0d34eb11
+type=vlan
+autoconnect=true
+
+[802-3-ethernet]
+mac-address=00:11:22:33:44:55
+
+[vlan]
+id=444
+parent=em1
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting
new file mode 100644
index 00000000..95d7b9a9
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Missing_Vlan_Setting
@@ -0,0 +1,11 @@
+# Settings with all default values are not written, including
+# VLAN settings with a VLAN ID of 0, which is the default value.
+
+[connection]
+id=Test Missing Vlan Setting
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=vlan
+autoconnect=true
+
+[802-3-ethernet]
+mac-address=00:11:22:33:44:55
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name
new file mode 100644
index 00000000..f27cd4a3
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wired_Group_Name
@@ -0,0 +1,13 @@
+
+[connection]
+id=Test Wired Connection
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=ethernet
+
+[ethernet]
+mac-address=00:11:22:33:44:55
+mtu=1400
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names
new file mode 100644
index 00000000..d9ac5864
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_New_Wireless_Group_Names
@@ -0,0 +1,16 @@
+[connection]
+id=Test New Wireless Group Names
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=wifi
+
+[wifi]
+ssid=foobar
+mode=infrastructure
+
+[wifi-security]
+key-mgmt=wpa-psk
+psk=s3cu4e passphrase
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID
new file mode 100644
index 00000000..4a3b56d2
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_String_SSID
@@ -0,0 +1,11 @@
+[connection]
+id=Test 
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=802-11-wireless
+
+[802-11-wireless]
+ssid=blah blah ssid 1234
+
+[ipv4]
+method=auto
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config
new file mode 100644
index 00000000..14280eef
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_TC_Config
@@ -0,0 +1,11 @@
+
+[connection]
+id=Test TC Config
+uuid=32a51666-95ea-1337-b371-dcf5e54b1001
+type=802-3-ethernet
+
+[tc]
+qdisc.root=handle 1234 fq_codel
+qdisc.ffff:fff1=ingress
+tfilter.1234:=matchall action drop
+tfilter.ffff:=matchall action simple sdata Hello
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
new file mode 100644
index 00000000..f9ccc003
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection
@@ -0,0 +1,72 @@
+
+[connection]
+id=Test Wired Connection
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=802-3-ethernet
+autoconnect=true
+timestamp=6654332
+
+[802-3-ethernet]
+mac-address=00:11:22:33:44:55
+speed=0
+duplex=full
+auto-negotiate=true
+mtu=1400
+
+[ipv4]
+method=manual
+dns=4.2.2.1;bogus;4.2.2.2;
+addresses1=192.168.0.5;24;192.168.0.1;
+addresses2=1.2.3.4;16;1.2.1.1;
+address=2.3.4.5/24,2.3.4.6
+address3=3.4.5.6/16
+address4=4.5.6.7/1.2.3.4
+address5=5.6.7.8
+routes1=1.2.3.0/24,2.3.4.8,99
+route=5.6.7.8/32
+routes2=1.1.1.2/12,
+routes3=1.1.1.3/13,,
+routes7=1.1.1.7/17,0.0.0.0
+routes4=1.1.1.4/14,2.2.2.4
+address30=1.2.3.130/24
+routes5=1.1.1.5/15,2.2.2.5,
+routes6=1.1.1.6/16,2.2.2.6,0
+routes8=1.1.1.8/18,0.0.0.0,
+routes9=1.1.1.9/19,0.0.0.0,0
+route10=1.1.1.10/21,,0
+routes10=1.1.1.10/20,,0
+routes11=1.1.1.11/21,,21
+routes11_options=cwnd=10,lock-cwnd=true,mtu=1430,src=7.7.7.7,type=unicast
+routes12=1.2.3.4/32
+routes12_options=type=local
+address30=1.2.3.30/24
+addresses30=1.2.3.30/25
+addresses31=1.2.3.31/25
+address31=1.2.3.31/24
+ignore-auto-routes=false
+ignore-auto-dns=false
+
+[ipv6]
+method=manual
+dns=1111:dddd::aaaa;1::cafe;
+dns-search=super-domain.com;redhat.com;gnu.org;
+addresses1=abcd:1234:ffff::cdde/64
+addresses2=1:2:3:4:5:6:7:8/96
+address=2:3:4:5:6:7:8:9/64,2:3:4:5:1:2:3:4
+address3=3:4:5:6:7:8:9:0/128
+address4=3:4:5:6:7:8:9:14
+address5=3:4:5:6:7:8:9:15,
+address6=3:4:5:6:7:8:9:16,66
+address7=3:4:5:6:7:8:9:17,67,
+address8=3:4:5:6:7:8:9:18,68,::
+address9=3:4:5:6:7:8:9:19,69,1::09
+routes1=a:b:c:d::/64;f:e:d:c:1:2:3:4;99;
+route=d:e:f:0:1:2:3:4/64,f:e:d:c:1:2:3:4
+route2=8:7:6:5:4:3:2:1/128
+route3=6:7:8:9:0:1:2:3/126,,1
+route4=7:8:9:0:1:2:3:4/125/::,5
+route5=8:9:0:1:2:3:4:5/124,6
+route6=8:9:0:1:2:3:4:6/123,,
+route6_options=from=abce::/63
+ignore-auto-routes=false
+ignore-auto-dns=false
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6
new file mode 100644
index 00000000..a42dd5d2
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_IP6
@@ -0,0 +1,20 @@
+
+[connection]
+id=Test Wired Connection IP6
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=802-3-ethernet
+autoconnect=true
+timestamp=6654332
+
+[802-3-ethernet]
+auto-negotiate=true
+mtu=1400
+
+[ipv4]
+method=disabled
+
+[ipv6]
+method=manual
+addresses1=abcd:1234:ffff::cdde/64,abcd:1234:ffff::cdd1
+dns=1111:dddd::aaaa;1::cafe;
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case
new file mode 100644
index 00000000..29aef494
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_Connection_MAC_Case
@@ -0,0 +1,32 @@
+
+[connection]
+id=Test Wired Connection MAC Case
+uuid=4e80a56d-c99f-4aad-a6dd-b449bc398c57
+type=802-3-ethernet
+autoconnect=true
+timestamp=6654332
+
+[802-3-ethernet]
+mac-address=00:11:aa:BB:CC:55
+speed=0
+duplex=full
+auto-negotiate=true
+mtu=1400
+
+[ipv4]
+method=manual
+dns=4.2.2.1;4.2.2.2;
+addresses1=192.168.0.5;24;192.168.0.1;
+addresses2=1.2.3.4;16;1.2.1.1;
+ignore-auto-routes=false
+ignore-auto-dns=false
+
+[ipv6]
+method=manual
+dns=1111:dddd::aaaa;1::cafe;
+dns-search=super-domain.com;redhat.com;gnu.org;
+addresses1=abcd:1234:ffff::cdde/64
+addresses2=1:2:3:4:5:6:7:8/96
+routes1=a:b:c:d::/64;f:e:d:c:1:2:3:4;99;
+ignore-auto-routes=false
+ignore-auto-dns=false
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob
new file mode 100644
index 00000000..62e6ae31
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Blob
@@ -0,0 +1,22 @@
+
+[connection]
+id=Wired TLS
+uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488
+type=802-3-ethernet
+
+[802-1x]
+eap=tls;
+identity=Bill Smith
+ca-cert=48;130;2;52;48;130;1;161;2;16;2;173;102;126;78;69;254;94;87;111;60;152;25;94;221;192;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;30;23;13;57;52;49;49;48;57;48;48;48;48;48;48;90;23;13;49;48;48;49;48;55;50;51;53;57;53;57;90;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;129;155;48;13;6;9;42;134;72;134;247;13;1;1;1;5;0;3;129;137;0;48;129;133;2;126;0;146;206;122;193;174;131;62;90;170;137;131;87;172;37;1;118;12;173;174;142;44;55;206;235;53;120;100;84;3;229;132;64;81;201;191;143;8;226;138;130;8;210;22;134;55;85;233;177;33;2;173;118;104;129;154;5;162;75;201;75;37;102;34;86;108;136;7;143;247;129;89;109;132;7;101;112;19;113;118;62;155;119;76;227;80;137;86;152;72;185;29;167;41;26;19;46;74;17;89;156;30;21;213;73;84;44;115;58;105;130;177;151;57;156;109;112;103;72;229;221;45;214;200;30;123;2;3;1;0;1;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;3;126;0;101;221;126;225;178;236;176;226;58;224;236;113;70;154;25;17;184;211;199;160;180;3;64;38;2;62;9;156;225;18;179;209;90;246;55;165;183;97;3;182;91;22;105;59;198;68;8;12;136;83;12;107;151;73;199;62;53;220;108;185;187;170;223;92;187;58;47;147;96;182;169;75;77;242;32;247;205;95;127;100;123;142;220;0;92;215;250;119;202;57;22;89;111;14;234;211;181;131;127;77;77;66;86;118;180;201;95;4;248;56;248;235;210;95;117;95;205;123;252;229;142;128;124;252;80;
+client-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;
+private-key=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;
+private-key-password=12345testing
+
+[ipv4]
+method=auto
+
+[802-3-ethernet]
+duplex=full
+
+[ipv6]
+method=ignore
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New
new file mode 100644
index 00000000..4cd86686
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_New
@@ -0,0 +1,22 @@
+
+[connection]
+id=Wired TLS
+uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488
+type=802-3-ethernet
+
+[802-1x]
+eap=tls;
+identity=Bill Smith
+ca-cert=test-ca-cert.pem
+client-cert=test-key-and-cert.pem
+private-key=test-key-and-cert.pem
+private-key-password=12345testing
+
+[ipv4]
+method=auto
+
+[802-3-ethernet]
+duplex=full
+
+[ipv6]
+method=ignore
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old
new file mode 100644
index 00000000..d3da598c
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Old
@@ -0,0 +1,22 @@
+
+[connection]
+id=Wired TLS
+uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488
+type=802-3-ethernet
+
+[802-1x]
+eap=tls;
+identity=Bill Smith
+ca-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;67;65;47;101;97;112;116;101;115;116;95;99;97;95;99;101;114;116;46;112;101;109;0;
+client-cert=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;
+private-key=102;105;108;101;58;47;47;47;67;65;83;65;47;100;99;98;119;47;68;101;115;107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;109;0;
+private-key-password=12345testing
+
+[ipv4]
+method=auto
+
+[802-3-ethernet]
+duplex=full
+
+[ipv6]
+method=ignore
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing
new file mode 100644
index 00000000..2b39538e
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wired_TLS_Path_Missing
@@ -0,0 +1,22 @@
+
+[connection]
+id=Wired TLS
+uuid=5ee46013-9469-4c6a-a60a-0c7a1e1c7488
+type=802-3-ethernet
+
+[802-1x]
+eap=tls;
+identity=Bill Smith
+ca-cert=/some/random/cert/path.pem
+client-cert=test-key-and-cert.pem
+private-key=test-key-and-cert.pem
+private-key-password=12345testing
+
+[ipv4]
+method=auto
+
+[802-3-ethernet]
+duplex=full
+
+[ipv6]
+method=ignore
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection
new file mode 100644
index 00000000..b1949d0d
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_Wireless_Connection
@@ -0,0 +1,22 @@
+[connection]
+id=Test Wireless Connection
+uuid=2f962388-e5f3-45af-a62c-ac220b8f7baa
+type=802-11-wireless
+autoconnect=false
+timestamp=1226604314
+
+[802-11-wireless]
+ssid=110;109;45;116;101;115;116;45;97;112;
+mode=infrastructure
+channel=0
+rate=0
+tx-power=0
+mtu=0
+bssid=00:1a:33:44:99:82
+
+[ipv4]
+method=auto
+ignore-auto-routes=false
+ignore-auto-dns=false
+never-default=false
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection
new file mode 100644
index 00000000..16d4b45e
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_dcb_connection
@@ -0,0 +1,33 @@
+[connection]
+id=dcb connection 1
+uuid=ac3c251e-260f-49b6-8ceb-12d37ea00751
+type=ethernet
+autoconnect=false
+permissions=
+secondaries=
+
+[ethernet]
+mac-address-blacklist=
+
+[dcb]
+app-fcoe-flags=1
+app-fip-flags=1
+app-iscsi-flags=1
+priority-bandwidth=0;0;0;0;0;0;0;0;
+priority-flow-control=0;0;0;0;0;0;0;0;
+priority-flow-control-flags=1
+priority-group-bandwidth=100;0;0;0;0;0;0;0;
+priority-group-flags=1
+priority-group-id=0;0;0;0;0;0;0;0;
+priority-strict-bandwidth=0;0;0;0;0;0;0;0;
+priority-traffic-class=0;0;0;0;0;0;0;0;
+
+[ipv4]
+dns-search=
+method=auto
+
+[ipv6]
+addr-gen-mode=stable-privacy
+dns-search=
+ip6-privacy=0
+method=auto
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1
new file mode 100644
index 00000000..cac135ad
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_1
@@ -0,0 +1,2 @@
+[connection]
+type=802-3-ethernet
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2
new file mode 100644
index 00000000..bbf2d8d6
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_2
@@ -0,0 +1 @@
+[802-3-ethernet]
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1
new file mode 100644
index 00000000..d3122d53
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_1
@@ -0,0 +1,4 @@
+[connection]
+type=802-3-ethernet
+master=br0
+slave-type=bridge
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2
new file mode 100644
index 00000000..eb1cdace
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_2
@@ -0,0 +1,7 @@
+[connection]
+master=br0
+
+[802-3-ethernet]
+
+[bridge-port]
+
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3
new file mode 100644
index 00000000..7419e97d
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_3
@@ -0,0 +1,4 @@
+[connection]
+master=br0
+slave-type=bridge
+[802-3-ethernet]
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4 b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4
new file mode 100644
index 00000000..626b6f68
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/Test_minimal_slave_4
@@ -0,0 +1,4 @@
+[connection]
+type=802-3-ethernet
+master=br0
+[bridge-port]
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem b/src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem
new file mode 100644
index 00000000..ef1be20d
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/test-ca-cert.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEjzCCA3egAwIBAgIJAOvnZPt59yIZMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD
+VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw
+FQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UE
+AxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAzMTAx
+NTEyMTRaFw0xOTAzMDgxNTEyMTRaMIGLMQswCQYDVQQGEwJVUzESMBAGA1UECBMJ
+QmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55
+IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3
+DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAKot9j+/+CX1/gZLgJHIXCRgCItKLGnf7qGbgqB9T2ACBqR0jllKWwDKrcWU
+xjXNIc+GF9Wnv+lX6G0Okn4Zt3/uRNobL+2b/yOF7M3Td3/9W873zdkQQX930YZc
+Rr8uxdRPP5bxiCgtcw632y21sSEbG9mjccAUnV/0jdvfmMNj0i8gN6E0fMBiJ9S3
+FkxX/KFvt9JWE9CtoyL7ki7UIDq+6vj7Gd5N0B3dOa1y+rRHZzKlJPcSXQSEYUS4
+HmKDwiKSVahft8c4tDn7KPi0vex91hlgZVd3usL2E/Vq7o5D9FAZ5kZY0AdFXwdm
+J4lO4Mj7ac7GE4vNERNcXVIX59sCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUuDU3Mr7P
+T3n1e3Sy8hBauoDFahAwgcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDF
+ahChgZGkgY4wgYsxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO
+BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQL
+EwdUZXN0aW5nMQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRl
+c3QuY29tggkA6+dk+3n3IhkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC
+AQEAVRG4aALIvCXCiKfe7K+iJxjBVRDFPEf7JWA9LGgbFOn6pNvbxonrR+0BETdc
+JV1ET4ct2xsE7QNFIkp9GKRC+6J32zCo8qtLCD5+v436r8TUG2/t2JRMkb9I2XVT
+p7RJoot6M0Ltf8KNQUPYh756xmKZ4USfQUwc58MOSDGY8VWEXJOYij9Pf0e0c52t
+qiCEjXH7uXiS8Pgq9TYm7AkWSOrglYhSa83x0f8mtT8Q15nBESIHZ6o8FAS2bBgn
+B0BkrKRjtBUkuJG3vTox+bYINh2Gxi1JZHWSV1tN5z3hd4VFcKqanW5OgQwToBqp
+3nniskIjbH0xjgZf/nVMyLnjxg==
+-----END CERTIFICATE-----
diff --git a/src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem b/src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem
new file mode 100644
index 00000000..dec9aa1b
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/keyfiles/test-key-and-cert.pem
@@ -0,0 +1,118 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,4DE0615F23D82107
+
+QPNCO5Dobvz9dDhN32KkZRoEifW+HDm2PCbRQhKDiscGwB6LgypvVjHNsZiFKwzz
+L4R51UqgQeJx7GSGJqE626e9z9J+UNBhop02aOO2X0eSPdvBzr/uJ6Umiyr1xqD7
+zWf7u9l5kXElDJRhK+87GMBewp4Ie9NeXDjhF8hzC5Kiulen4AH3AYnfH3S7DimU
+h8GFMg8inrudrTbcjBhCdPeHG2jCygOxw3InRFz7uaN6LIhOaPQvmvpP4Cc1WRnW
+ZPq9o+eU3fPWPD5t+Op/VzYLvKwgBy/yK1rQXUm6ZMO7MhhRJ94ZCsJv+nVWpJlv
+QyBlxDKxwfkfYbDELdnnDQdHdMbKatLqa0KhSkgpp8LywBtanPz731tyT0r7b3na
+eLdra59lRU7ZQLPEdS3lPZd2O/KQvWf8wbg7MjXS9LxQ7R5HOPu6DNJlwXVZBmmo
+cAfu2q8ubU2IePvWLD1GOrBi6hE9TiGvFJkw+wBK+t72sz3njv9Xm/zlxruaEk5m
+RW/kybU3FP4PtjriBbskz3/VZaaxuRN7OoOYTkmyHmG1ADgcRUV6fea19qqsBlN8
+xb+SRtoH28oT/JVWU5neE2dbNzk5LeVO+w70NNdR5s5xqkBhbGGaJxvXwNP4ltFr
+T06SMh8znOLKwWB00aRtwfU7jOwR3mOleQO4ugIHmau3zp1TqzAHW8XtpuV7qVeI
+ESZOZuf0vW43BtNzgLXt1+r+bmsMsRwhnyomL9M0TUyyBdVYY9GkzTG9pOESheRo
+RSvAZ8qKGUliTpgBcbt2v1+NqkszcHa6FxuvS8YU4uo5/GqsgTxHTNIB232hIrrZ
+EIm6QL9TC5oFXMjy6UNqoCm5Nb8DBJ6aErt7pt7aoktqUW3O3QIzQT3IbZ4nAcTt
+lVF4d7j29I9t7bcC8GOVU1neilguZUss4ghJg9x4zI5UZdR7hZ8fbFT47TyxB+j5
+r0YdmjbjVTaSyaN2JGh1wvb4TzawGNVx/U2EJE16HigOtPfsfQRJ3x+FROKBdVa4
+aIFYXkRBeIPxX6n9pcw0lBCsnXo6/5iTjQSk2VqO3rHO/wyWiEjNczhL33dY2A8W
+GG5ECMO5SqXZHQQzpABqK94dxe3UC8aEESO5NhEqDuV7qQGol0qPKrUA3wb0jb2e
+DrejJ9HS2m1SUDmjpvvmEGy6GN7CRibbKt5rNZdJNNvWArOF5d0F6wkixQLl73oE
+lq5gLQQk9n7ClleKLhlQpBCorxilBbzmSUekkJLi0eaZiBBFWBX9udqnUZloXTgO
+8qwuO8K/GPR9Jy1/UH2Vh1H+wivaqKTVgEb0NotzgzECgTEFKJafl7rUNs1OZRZ3
+VBjevi6+iDpxVFgF71kXfdUC4ph0E1XDl0ja2rrKQGivMkUhWJ57+4EV5+hBkAnt
+G0RV45NwHXLrK2bd8F9PlRk2XHW6mIcFRXsW1DjeBhk/sQjvlO9R01GRSgcXtekJ
+tmX17FWrMrzXHpvy1IC3fk4RVnSjpzQ8O+17YE8/la9wVaeZZzHyYFmMT7VXjIhW
+QozJQ0vJ2jxJRh5GYn3tpJzdaeRfvTBik0pChNdUTnWP+BJ35xoCTs8iwJbmgVZ1
+-----END RSA PRIVATE KEY-----
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=Berkshire, L=Newbury, O=My Company Ltd, OU=Testing, CN=test/emailAddress=test@test.com
+        Validity
+            Not Before: Mar 10 15:13:16 2009 GMT
+            Not After : Mar  8 15:13:16 2019 GMT
+        Subject: C=US, ST=Berkshire, O=My Company Ltd, OU=Testing, CN=test1/emailAddress=test@test.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:cd:34:b1:2e:b0:04:c6:f4:2b:a2:c0:a0:39:7a:
+                    82:ed:96:c4:f7:19:83:91:5c:b4:e7:9c:de:ec:48:
+                    ec:2d:e4:51:08:26:42:ac:d3:98:26:7a:72:f7:49:
+                    c2:9e:66:05:c6:47:29:fe:3b:ac:6b:af:6f:5e:a8:
+                    03:5a:73:33:ba:19:03:00:35:f5:00:bc:a8:be:14:
+                    ce:46:69:e3:6d:ed:34:37:85:55:87:62:b3:b7:c9:
+                    c0:cc:9a:aa:61:05:5b:cd:a2:17:42:d3:e5:6f:1c:
+                    60:8d:c2:15:41:46:f8:12:54:d0:38:57:e1:fd:8d:
+                    44:c8:fb:56:b3:b9:6c:e9:f8:9e:21:11:57:1b:8b:
+                    f9:cf:e3:17:e7:d8:fd:ac:d1:01:c6:92:30:f3:2d:
+                    c9:d6:c1:f0:3d:fd:ca:30:dd:75:74:e7:d1:6b:75:
+                    d8:c5:4d:43:61:fe:f6:ad:7e:4c:63:7c:03:17:a2:
+                    06:8f:d0:8b:69:d3:7a:07:0f:0b:a2:cf:0c:70:38:
+                    ba:cc:55:35:60:84:58:d8:d2:be:1f:ef:76:a9:ba:
+                    ae:6a:dc:08:97:80:de:42:00:b7:d4:ce:9a:b0:36:
+                    2a:c7:6f:45:04:7c:ea:41:19:d8:b9:19:04:1f:11:
+                    a9:22:80:bd:69:08:15:0d:3c:de:cd:7e:88:6c:0f:
+                    a3:43
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                CE:03:7E:EF:E7:DE:C9:87:BF:DE:56:F4:C8:A3:40:F6:C8:6F:05:8C
+            X509v3 Authority Key Identifier: 
+                keyid:B8:35:37:32:BE:CF:4F:79:F5:7B:74:B2:F2:10:5A:BA:80:C5:6A:10
+                DirName:/C=US/ST=Berkshire/L=Newbury/O=My Company Ltd/OU=Testing/CN=test/emailAddress=test@test.com
+                serial:EB:E7:64:FB:79:F7:22:19
+
+    Signature Algorithm: md5WithRSAEncryption
+        7a:20:93:63:40:73:7d:33:01:2e:c0:13:52:a4:a7:e1:4d:82:
+        f4:fb:b2:7b:d0:2b:5a:3f:0e:3c:28:61:71:ab:01:4d:fe:89:
+        b5:cd:2f:97:59:93:53:9d:51:86:48:dd:b9:e4:73:5e:22:0b:
+        12:0d:25:39:76:16:44:06:0c:40:45:21:6b:a6:b1:e0:bf:76:
+        1b:36:f3:1e:41:82:57:d9:59:b7:60:40:43:1c:1d:79:f6:48:
+        32:5c:4e:e2:06:89:96:41:d2:54:1f:4a:6f:f6:78:a5:3c:02:
+        85:21:e2:65:e1:8a:6d:24:19:95:f8:c0:35:ab:bd:ff:3d:f1:
+        fb:50:2d:30:1e:67:a6:7c:50:f9:d5:77:66:77:5a:14:0f:5c:
+        cd:21:09:9b:a3:92:57:19:dd:01:a4:18:c5:f9:70:e4:17:43:
+        8d:b1:e6:61:e9:50:89:83:4f:ce:a4:57:68:58:40:70:ae:71:
+        1c:47:66:d2:30:54:50:ea:3a:87:32:64:3b:18:42:fe:5a:19:
+        07:64:f7:f1:b1:10:07:fd:a7:d2:a7:a8:05:79:5b:25:ba:69:
+        7b:1a:3e:b1:3e:e4:17:17:01:ba:eb:54:ae:83:00:ed:66:62:
+        8d:c0:3e:8a:b4:27:5f:e9:01:ce:20:c3:34:a9:28:c0:6f:c7:
+        3b:65:fe:f9
+-----BEGIN CERTIFICATE-----
+MIIEojCCA4qgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCVVMx
+EjAQBgNVBAgTCUJlcmtzaGlyZTEQMA4GA1UEBxMHTmV3YnVyeTEXMBUGA1UEChMO
+TXkgQ29tcGFueSBMdGQxEDAOBgNVBAsTB1Rlc3RpbmcxDTALBgNVBAMTBHRlc3Qx
+HDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wHhcNMDkwMzEwMTUxMzE2WhcN
+MTkwMzA4MTUxMzE2WjB6MQswCQYDVQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJl
+MRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzEOMAwG
+A1UEAxMFdGVzdDExHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNNLEusATG9CuiwKA5eoLtlsT3GYOR
+XLTnnN7sSOwt5FEIJkKs05gmenL3ScKeZgXGRyn+O6xrr29eqANaczO6GQMANfUA
+vKi+FM5GaeNt7TQ3hVWHYrO3ycDMmqphBVvNohdC0+VvHGCNwhVBRvgSVNA4V+H9
+jUTI+1azuWzp+J4hEVcbi/nP4xfn2P2s0QHGkjDzLcnWwfA9/cow3XV059FrddjF
+TUNh/vatfkxjfAMXogaP0Itp03oHDwuizwxwOLrMVTVghFjY0r4f73apuq5q3AiX
+gN5CALfUzpqwNirHb0UEfOpBGdi5GQQfEakigL1pCBUNPN7NfohsD6NDAgMBAAGj
+ggEfMIIBGzAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVy
+YXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUzgN+7+feyYe/3lb0yKNA9shvBYww
+gcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDFahChgZGkgY4wgYsxCzAJ
+BgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAOBgNVBAcTB05ld2J1cnkx
+FzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQLEwdUZXN0aW5nMQ0wCwYD
+VQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tggkA6+dk+3n3
+IhkwDQYJKoZIhvcNAQEEBQADggEBAHogk2NAc30zAS7AE1Kkp+FNgvT7snvQK1o/
+DjwoYXGrAU3+ibXNL5dZk1OdUYZI3bnkc14iCxINJTl2FkQGDEBFIWumseC/dhs2
+8x5BglfZWbdgQEMcHXn2SDJcTuIGiZZB0lQfSm/2eKU8AoUh4mXhim0kGZX4wDWr
+vf898ftQLTAeZ6Z8UPnVd2Z3WhQPXM0hCZujklcZ3QGkGMX5cOQXQ42x5mHpUImD
+T86kV2hYQHCucRxHZtIwVFDqOocyZDsYQv5aGQdk9/GxEAf9p9KnqAV5WyW6aXsa
+PrE+5BcXAbrrVK6DAO1mYo3APoq0J1/pAc4gwzSpKMBvxztl/vk=
+-----END CERTIFICATE-----
diff --git a/src/core/settings/plugins/keyfile/tests/meson.build b/src/core/settings/plugins/keyfile/tests/meson.build
new file mode 100644
index 00000000..3c89bcf9
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+exe = executable(
+  'test-keyfile-settings',
+  'test-keyfile-settings.c',
+  dependencies: libNetworkManagerTest_dep,
+  c_args: test_c_flags,
+)
+
+test(
+  'test-keyfile-settings',
+  test_script,
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
+)
diff --git a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
new file mode 100644
index 00000000..348b0ed6
--- /dev/null
+++ b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -0,0 +1,2923 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2008 - 2017 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <linux/pkt_sched.h>
+#include <linux/if_ether.h>
+#include <linux/if_infiniband.h>
+
+#include "nm-core-internal.h"
+
+#include "settings/plugins/keyfile/nms-keyfile-reader.h"
+#include "settings/plugins/keyfile/nms-keyfile-writer.h"
+#include "settings/plugins/keyfile/nms-keyfile-utils.h"
+
+#include "nm-test-utils-core.h"
+
+#define TEST_KEYFILES_DIR NM_BUILD_SRCDIR "/src/core/settings/plugins/keyfile/tests/keyfiles"
+#define TEST_SCRATCH_DIR  NM_BUILD_BUILDDIR "/src/core/settings/plugins/keyfile/tests/keyfiles"
+
+/*****************************************************************************/
+
+static void
+check_ip_address(NMSettingIPConfig *config, int idx, const char *address, int plen)
+{
+    NMIPAddress *ip4 = nm_setting_ip_config_get_address(config, idx);
+
+    g_assert(ip4);
+    g_assert_cmpstr(nm_ip_address_get_address(ip4), ==, address);
+    g_assert_cmpint(nm_ip_address_get_prefix(ip4), ==, plen);
+}
+
+static void
+check_ip_route(NMSettingIPConfig *config,
+               int                idx,
+               const char *       destination,
+               int                plen,
+               const char *       next_hop,
+               gint64             metric)
+{
+    NMIPRoute *route = nm_setting_ip_config_get_route(config, idx);
+
+    g_assert(route);
+    g_assert_cmpstr(nm_ip_route_get_dest(route), ==, destination);
+    g_assert_cmpint(nm_ip_route_get_prefix(route), ==, plen);
+    g_assert_cmpstr(nm_ip_route_get_next_hop(route), ==, next_hop);
+    g_assert_cmpint(nm_ip_route_get_metric(route), ==, metric);
+}
+
+#define keyfile_read_connection_from_file(full_filename)                                  \
+    ({                                                                                    \
+        gs_free_error GError *_error = NULL;                                              \
+        NMConnection *        _connection;                                                \
+                                                                                          \
+        g_assert(full_filename &&full_filename[0] == '/');                                \
+                                                                                          \
+        _connection =                                                                     \
+            nms_keyfile_reader_from_file(full_filename,                                   \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         NULL,                                            \
+                                         (nmtst_get_rand_uint32() % 2) ? &_error : NULL); \
+        nmtst_assert_success(_connection, _error);                                        \
+        nmtst_assert_connection_verifies_without_normalization(_connection);              \
+                                                                                          \
+        _connection;                                                                      \
+    })
+
+static void
+assert_reread(NMConnection *connection, gboolean normalize_connection, const char *testfile)
+{
+    gs_unref_object NMConnection *reread           = NULL;
+    gs_unref_object NMConnection *connection_clone = NULL;
+    NMSettingConnection *         s_con;
+
+    g_assert(NM_IS_CONNECTION(connection));
+
+    reread = keyfile_read_connection_from_file(testfile);
+
+    if (!normalize_connection && (s_con = nm_connection_get_setting_connection(connection))
+        && !nm_setting_connection_get_master(s_con)
+        && !nm_connection_get_setting_proxy(connection)) {
+        connection_clone = nmtst_clone_connection(connection);
+        connection       = connection_clone;
+        nm_connection_add_setting(connection, nm_setting_proxy_new());
+    }
+
+    nmtst_assert_connection_equals(connection, normalize_connection, reread, FALSE);
+}
+
+static void
+assert_reread_and_unlink(NMConnection *connection,
+                         gboolean      normalize_connection,
+                         const char *  testfile)
+{
+    assert_reread(connection, normalize_connection, testfile);
+    unlink(testfile);
+}
+
+static void
+assert_reread_same(NMConnection *connection, NMConnection *reread)
+{
+    nmtst_assert_connection_verifies_without_normalization(reread);
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
+write_test_connection_reread(NMConnection * connection,
+                             char **        testfile,
+                             NMConnection **out_reread,
+                             gboolean *     out_reread_same)
+{
+    uid_t           owner_uid;
+    gid_t           owner_grp;
+    gboolean        success;
+    GError *        error   = NULL;
+    GError **       p_error = (nmtst_get_rand_uint32() % 2) ? &error : NULL;
+    gs_unref_object NMConnection *connection_normalized = NULL;
+
+    g_assert(NM_IS_CONNECTION(connection));
+    g_assert(testfile && !*testfile);
+
+    owner_uid = geteuid();
+    owner_grp = getegid();
+
+    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);
+    g_assert_no_error(error);
+    g_assert(success);
+    g_assert(*testfile && (*testfile)[0]);
+}
+
+static void
+write_test_connection(NMConnection *connection, char **testfile)
+{
+    gs_unref_object NMConnection *reread      = NULL;
+    gboolean                      reread_same = FALSE;
+
+    write_test_connection_reread(connection, testfile, &reread, &reread_same);
+    assert_reread_same(connection, reread);
+    g_assert(reread_same);
+}
+
+static void
+write_test_connection_and_reread(NMConnection *connection, gboolean normalize_connection)
+{
+    gs_free char *testfile = NULL;
+
+    g_assert(NM_IS_CONNECTION(connection));
+
+    write_test_connection(connection, &testfile);
+    assert_reread_and_unlink(connection, normalize_connection, testfile);
+}
+
+static GKeyFile *
+keyfile_load_from_file(const char *testfile)
+{
+    GKeyFile *keyfile;
+    GError *  error = NULL;
+    gboolean  success;
+
+    g_assert(testfile && *testfile);
+
+    keyfile = g_key_file_new();
+    success = g_key_file_load_from_file(keyfile, testfile, G_KEY_FILE_NONE, &error);
+    g_assert_no_error(error);
+    g_assert(success);
+
+    return keyfile;
+}
+
+static void
+_setting_copy_property_gbytes(NMConnection *src,
+                              NMConnection *dst,
+                              const char *  setting_name,
+                              const char *  property_name)
+{
+    gs_unref_bytes GBytes *blob = NULL;
+    NMSetting *            s_src;
+    NMSetting *            s_dst;
+
+    g_assert(NM_IS_CONNECTION(src));
+    g_assert(NM_IS_CONNECTION(dst));
+    g_assert(setting_name);
+    g_assert(property_name);
+
+    s_src = nm_connection_get_setting_by_name(src, setting_name);
+    g_assert(NM_IS_SETTING(s_src));
+    s_dst = nm_connection_get_setting_by_name(dst, setting_name);
+    g_assert(NM_IS_SETTING(s_dst));
+
+    g_object_get(s_src, property_name, &blob, NULL);
+    g_object_set(s_dst, property_name, blob, NULL);
+}
+
+/*****************************************************************************/
+
+static void
+test_read_valid_wired_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMIPRoute *                   route;
+    const char *                  mac;
+    char expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
+
+    NMTST_EXPECT_NM_INFO("*ipv4.addresses:*semicolon at the end*addresses1*");
+    NMTST_EXPECT_NM_INFO("*ipv4.addresses:*semicolon at the end*addresses2*");
+    NMTST_EXPECT_NM_WARN("*missing prefix length*address4*");
+    NMTST_EXPECT_NM_WARN("*missing prefix length*address5*");
+    NMTST_EXPECT_NM_WARN("*ipv4.dns: ignoring invalid DNS server IPv4 address 'bogus'*");
+    NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes2*");
+    NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes3*");
+    NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes5*");
+    NMTST_EXPECT_NM_INFO("*ipv4.routes*semicolon at the end*routes8*");
+    NMTST_EXPECT_NM_WARN("*missing prefix length*address4*");
+    NMTST_EXPECT_NM_INFO("*ipv6.address*semicolon at the end*address5*");
+    NMTST_EXPECT_NM_WARN("*missing prefix length*address5*");
+    NMTST_EXPECT_NM_INFO("*ipv6.address*semicolon at the end*address7*");
+    NMTST_EXPECT_NM_INFO("*ipv6.routes*semicolon at the end*routes1*");
+    NMTST_EXPECT_NM_INFO("*ipv6.route*semicolon at the end*route6*");
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_Connection");
+    g_test_assert_expected_messages();
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wired Connection");
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con),
+                    ==,
+                    "4e80a56d-c99f-4aad-a6dd-b449bc398c57");
+    g_assert_cmpuint(nm_setting_connection_get_timestamp(s_con), ==, 6654332);
+    g_assert(nm_setting_connection_get_autoconnect(s_con));
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
+    g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1400);
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                    ==,
+                    NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2");
+
+    /* IPv4 addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 10);
+    check_ip_address(s_ip4, 0, "2.3.4.5", 24);
+    check_ip_address(s_ip4, 1, "192.168.0.5", 24);
+    check_ip_address(s_ip4, 2, "1.2.3.4", 16);
+    check_ip_address(s_ip4, 3, "3.4.5.6", 16);
+    check_ip_address(s_ip4, 4, "4.5.6.7", 24);
+    check_ip_address(s_ip4, 5, "5.6.7.8", 24);
+    check_ip_address(s_ip4, 6, "1.2.3.30", 24);
+    check_ip_address(s_ip4, 7, "1.2.3.30", 25);
+    check_ip_address(s_ip4, 8, "1.2.3.31", 24);
+    check_ip_address(s_ip4, 9, "1.2.3.31", 25);
+
+    /* IPv4 gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "2.3.4.6");
+
+    /* IPv4 routes */
+    g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 14);
+    check_ip_route(s_ip4, 0, "5.6.7.8", 32, NULL, -1);
+    check_ip_route(s_ip4, 1, "1.2.3.0", 24, "2.3.4.8", 99);
+    check_ip_route(s_ip4, 2, "1.1.1.2", 12, NULL, -1);
+    check_ip_route(s_ip4, 3, "1.1.1.3", 13, NULL, -1);
+    check_ip_route(s_ip4, 4, "1.1.1.4", 14, "2.2.2.4", -1);
+    check_ip_route(s_ip4, 5, "1.1.1.5", 15, "2.2.2.5", -1);
+    check_ip_route(s_ip4, 6, "1.1.1.6", 16, "2.2.2.6", 0);
+    check_ip_route(s_ip4, 7, "1.1.1.7", 17, NULL, -1);
+    check_ip_route(s_ip4, 8, "1.1.1.8", 18, NULL, -1);
+    check_ip_route(s_ip4, 9, "1.1.1.9", 19, NULL, 0);
+    check_ip_route(s_ip4, 10, "1.1.1.10", 21, NULL, 0);
+    check_ip_route(s_ip4, 11, "1.1.1.10", 20, NULL, 0);
+    check_ip_route(s_ip4, 12, "1.1.1.11", 21, NULL, 21);
+    check_ip_route(s_ip4, 13, "1.2.3.4", 32, NULL, -1);
+
+    /* Route attributes */
+    route = nm_setting_ip_config_get_route(s_ip4, 12);
+    g_assert(route);
+
+    nmtst_assert_route_attribute_uint32(route, NM_IP_ROUTE_ATTRIBUTE_CWND, 10);
+    nmtst_assert_route_attribute_uint32(route, NM_IP_ROUTE_ATTRIBUTE_MTU, 1430);
+    nmtst_assert_route_attribute_boolean(route, NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, TRUE);
+    nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_SRC, "7.7.7.7");
+    nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "unicast");
+
+    route = nm_setting_ip_config_get_route(s_ip4, 13);
+    g_assert(route);
+
+    nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local");
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                    ==,
+                    NM_SETTING_IP6_CONFIG_METHOD_MANUAL);
+
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 2);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "1111:dddd::aaaa");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 1), ==, "1::cafe");
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip6), ==, 3);
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 0), ==, "super-domain.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 1), ==, "redhat.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 2), ==, "gnu.org");
+
+    /* IPv6 addresses */
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 10);
+    check_ip_address(s_ip6, 0, "2:3:4:5:6:7:8:9", 64);
+    check_ip_address(s_ip6, 1, "abcd:1234:ffff::cdde", 64);
+    check_ip_address(s_ip6, 2, "1:2:3:4:5:6:7:8", 96);
+    check_ip_address(s_ip6, 3, "3:4:5:6:7:8:9:0", 128);
+    check_ip_address(s_ip6, 4, "3:4:5:6:7:8:9:14", 64);
+    check_ip_address(s_ip6, 5, "3:4:5:6:7:8:9:15", 64);
+    check_ip_address(s_ip6, 6, "3:4:5:6:7:8:9:16", 66);
+    check_ip_address(s_ip6, 7, "3:4:5:6:7:8:9:17", 67);
+    check_ip_address(s_ip6, 8, "3:4:5:6:7:8:9:18", 68);
+    check_ip_address(s_ip6, 9, "3:4:5:6:7:8:9:19", 69);
+
+    /* IPv6 gateway */
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "2:3:4:5:1:2:3:4");
+
+    /* Routes */
+    g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 7);
+    check_ip_route(s_ip6, 0, "d:e:f:0:1:2:3:4", 64, "f:e:d:c:1:2:3:4", -1);
+    check_ip_route(s_ip6, 1, "a:b:c:d::", 64, "f:e:d:c:1:2:3:4", 99);
+    check_ip_route(s_ip6, 2, "8:7:6:5:4:3:2:1", 128, NULL, -1);
+    check_ip_route(s_ip6, 3, "6:7:8:9:0:1:2:3", 126, NULL, 1);
+    check_ip_route(s_ip6, 4, "7:8:9:0:1:2:3:4", 125, NULL, 5);
+    check_ip_route(s_ip6, 5, "8:9:0:1:2:3:4:5", 124, NULL, 6);
+    check_ip_route(s_ip6, 6, "8:9:0:1:2:3:4:6", 123, NULL, -1);
+
+    /* Route attributes */
+    route = nm_setting_ip_config_get_route(s_ip6, 6);
+    g_assert(route);
+    nmtst_assert_route_attribute_string(route, NM_IP_ROUTE_ATTRIBUTE_FROM, "abce::/63");
+}
+
+static void
+add_one_ip_address(NMSettingIPConfig *s_ip, const char *addr, guint32 prefix)
+{
+    NMIPAddress * ip_addr;
+    gs_free_error GError *error = NULL;
+
+    ip_addr = nm_ip_address_new(NM_IS_SETTING_IP4_CONFIG(s_ip) ? AF_INET : AF_INET6,
+                                addr,
+                                prefix,
+                                &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_address(s_ip, ip_addr);
+    nm_ip_address_unref(ip_addr);
+}
+
+static void
+add_one_ip_route(NMSettingIPConfig *s_ip,
+                 const char *       dest,
+                 const char *       nh,
+                 guint32            prefix,
+                 gint64             metric)
+{
+    NMIPRoute *   route;
+    gs_free_error GError *error = NULL;
+
+    g_assert(prefix > 0);
+    route = nm_ip_route_new(NM_IS_SETTING_IP4_CONFIG(s_ip) ? AF_INET : AF_INET6,
+                            dest,
+                            prefix,
+                            nh,
+                            metric,
+                            &error);
+    g_assert_no_error(error);
+    nm_setting_ip_config_add_route(s_ip, route);
+    nm_ip_route_unref(route);
+}
+
+static void
+test_write_wired_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMIPRoute *                   rt;
+    const char *                  mac         = "99:88:77:66:55:44";
+    const char *                  dns1        = "4.2.2.1";
+    const char *                  dns2        = "4.2.2.2";
+    const char *                  address1    = "192.168.0.5";
+    const char *                  address2    = "1.2.3.4";
+    const char *                  gw          = "192.168.0.1";
+    const char *                  route1      = "10.10.10.2";
+    const char *                  route1_nh   = "10.10.10.1";
+    const char *                  route2      = "1.1.1.1";
+    const char *                  route2_nh   = "1.2.1.1";
+    const char *                  route3      = "2.2.2.2";
+    const char *                  route3_nh   = NULL;
+    const char *                  route4      = "3.3.3.3";
+    const char *                  route4_nh   = NULL;
+    const char *                  dns6_1      = "1::cafe";
+    const char *                  dns6_2      = "2::cafe";
+    const char *                  address6_1  = "abcd::beef";
+    const char *                  address6_2  = "dcba::beef";
+    const char *                  route6_1    = "1:2:3:4:5:6:7:8";
+    const char *                  route6_1_nh = "8:7:6:5:4:3:2:1";
+    const char *                  route6_2    = "2001::1000";
+    const char *                  route6_2_nh = "2001::1111";
+    const char *                  route6_3    = "4:5:6:7:8:9:0:1";
+    const char *                  route6_3_nh = "::";
+    const char *                  route6_4    = "5:6:7:8:9:0:1:2";
+    const char *                  route6_4_nh = "::";
+    guint64                       timestamp   = 0x12345678L;
+    GError *                      error       = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Work Wired",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NM_SETTING_CONNECTION_TIMESTAMP,
+                 timestamp,
+                 NULL);
+
+    /* Wired setting */
+
+    s_wired = NM_SETTING_WIRED(nm_setting_wired_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, 900, NULL);
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 gw,
+                 NULL);
+
+    /* Addresses */
+    add_one_ip_address(s_ip4, address1, 24);
+    add_one_ip_address(s_ip4, address2, 8);
+
+    /* Routes */
+    add_one_ip_route(s_ip4, route1, route1_nh, 24, 3);
+    add_one_ip_route(s_ip4, route2, route2_nh, 8, 1);
+    add_one_ip_route(s_ip4, route3, route3_nh, 7, -1);
+
+    rt = nm_ip_route_new(AF_INET, route4, 6, route4_nh, 4, &error);
+    g_assert_no_error(error);
+    nm_ip_route_set_attribute(rt, NM_IP_ROUTE_ATTRIBUTE_CWND, g_variant_new_uint32(10));
+    nm_ip_route_set_attribute(rt, NM_IP_ROUTE_ATTRIBUTE_MTU, g_variant_new_uint32(1492));
+    nm_ip_route_set_attribute(rt, NM_IP_ROUTE_ATTRIBUTE_SRC, g_variant_new_string("1.2.3.4"));
+    g_assert(nm_setting_ip_config_add_route(s_ip4, rt));
+    nm_ip_route_unref(rt);
+
+    /* DNS servers */
+    nm_setting_ip_config_add_dns(s_ip4, dns1);
+    nm_setting_ip_config_add_dns(s_ip4, dns2);
+
+    /* IP6 setting */
+
+    s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL);
+
+    /* Addresses */
+    add_one_ip_address(s_ip6, address6_1, 64);
+    add_one_ip_address(s_ip6, address6_2, 56);
+
+    /* Routes */
+    add_one_ip_route(s_ip6, route6_1, route6_1_nh, 64, 3);
+    add_one_ip_route(s_ip6, route6_2, route6_2_nh, 56, 1);
+    add_one_ip_route(s_ip6, route6_3, route6_3_nh, 63, 5);
+    add_one_ip_route(s_ip6, route6_4, route6_4_nh, 62, -1);
+
+    /* DNS servers */
+    nm_setting_ip_config_add_dns(s_ip6, dns6_1);
+    nm_setting_ip_config_add_dns(s_ip6, dns6_2);
+
+    /* DNS searches */
+    nm_setting_ip_config_add_dns_search(s_ip6, "wallaceandgromit.com");
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_ip6_wired_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_Connection_IP6");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wired Connection IP6");
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con),
+                    ==,
+                    "4e80a56d-c99f-4aad-a6dd-b449bc398c57");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
+                    ==,
+                    NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 0);
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
+                    ==,
+                    NM_SETTING_IP6_CONFIG_METHOD_MANUAL);
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1);
+    check_ip_address(s_ip6, 0, "abcd:1234:ffff::cdde", 64);
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "abcd:1234:ffff::cdd1");
+}
+
+static void
+test_write_ip6_wired_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    const char *                  dns     = "1::cafe";
+    const char *                  address = "abcd::beef";
+    const char *                  gw      = "dcba::beef";
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Work Wired IP6",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+
+    s_wired = NM_SETTING_WIRED(nm_setting_wired_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL);
+
+    /* IP6 setting */
+
+    s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 gw,
+                 NULL);
+
+    /* Addresses */
+    add_one_ip_address(s_ip6, address, 64);
+
+    /* DNS servers */
+    nm_setting_ip_config_add_dns(s_ip6, dns);
+
+    /* DNS searches */
+    nm_setting_ip_config_add_dns_search(s_ip6, "wallaceandgromit.com");
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_wired_mac_case(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    const char *                  mac;
+    char expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0xaa, 0xbb, 0xcc, 0x55};
+
+    NMTST_EXPECT_NM_INFO("*ipv4.addresses*semicolon at the end*addresses1*");
+    NMTST_EXPECT_NM_INFO("*ipv4.addresses*semicolon at the end*addresses2*");
+    NMTST_EXPECT_NM_INFO("*ipv6.routes*semicolon at the end*routes1*");
+    connection =
+        keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_Connection_MAC_Case");
+    g_test_assert_expected_messages();
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wired Connection MAC Case");
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con),
+                    ==,
+                    "4e80a56d-c99f-4aad-a6dd-b449bc398c57");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
+}
+
+static void
+test_read_mac_old_format(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    const char *                  mac;
+    char                          expected_mac[ETH_ALEN] = {0x00, 0x11, 0xaa, 0xbb, 0xcc, 0x55};
+    char expected_cloned_mac[ETH_ALEN]                   = {0x00, 0x16, 0xaa, 0xbb, 0xcc, 0xfe};
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_MAC_Old_Format");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, ETH_ALEN));
+
+    mac = nm_setting_wired_get_cloned_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_cloned_mac, ETH_ALEN));
+}
+
+static void
+test_read_mac_ib_old_format(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingInfiniband *         s_ib;
+    const char *                  mac;
+    guint8 expected_mac[INFINIBAND_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                                            0x77, 0x88, 0x99, 0x01, 0x12, 0x23, 0x34,
+                                            0x45, 0x56, 0x67, 0x78, 0x89, 0x90};
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_MAC_IB_Old_Format");
+
+    s_ib = nm_connection_get_setting_infiniband(connection);
+    g_assert(s_ib);
+
+    /* MAC address */
+    mac = nm_setting_infiniband_get_mac_address(s_ib);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac)));
+}
+
+static void
+test_read_valid_wireless_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wireless;
+    NMSettingIPConfig *           s_ip4;
+    const char *                  bssid;
+    const guint8                  expected_bssid[ETH_ALEN] = {0x00, 0x1a, 0x33, 0x44, 0x99, 0x82};
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wireless_Connection");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "Test Wireless Connection");
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con),
+                    ==,
+                    "2f962388-e5f3-45af-a62c-ac220b8f7baa");
+    g_assert_cmpuint(nm_setting_connection_get_timestamp(s_con), ==, 1226604314);
+    g_assert(nm_setting_connection_get_autoconnect(s_con) == FALSE);
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+    bssid = nm_setting_wireless_get_bssid(s_wireless);
+    g_assert(bssid);
+    g_assert(nm_utils_hwaddr_matches(bssid, -1, expected_bssid, sizeof(expected_bssid)));
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+}
+
+static void
+test_write_wireless_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wireless;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    const char *                  bssid = "aa:b9:a1:74:55:44";
+    GBytes *                      ssid;
+    unsigned char                 tmpssid[] = {0x31, 0x33, 0x33, 0x37};
+    guint64                       timestamp = 0x12344433L;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Work Wireless",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NM_SETTING_CONNECTION_TIMESTAMP,
+                 timestamp,
+                 NULL);
+
+    /* Wireless setting */
+
+    s_wireless = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_wireless));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+
+    g_object_set(s_wireless,
+                 NM_SETTING_WIRELESS_BSSID,
+                 bssid,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRED_MTU,
+                 1000,
+                 NULL);
+
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+
+    s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_string_ssid(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWireless *           s_wireless;
+    GBytes *                      ssid;
+    const guint8 *                ssid_data;
+    gsize                         ssid_len;
+    const char *                  expected_ssid = "blah blah ssid 1234";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_String_SSID");
+
+    s_wireless = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wireless);
+
+    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    g_assert(ssid);
+
+    ssid_data = g_bytes_get_data(ssid, &ssid_len);
+    g_assert_cmpmem(ssid_data, ssid_len, expected_ssid, strlen(expected_ssid));
+}
+
+static void
+test_write_string_ssid(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wireless;
+    NMSettingIPConfig *           s_ip4;
+    char *                        tmp;
+    gs_free char *                testfile = NULL;
+    GBytes *                      ssid;
+    unsigned char                 tmpssid[] = {65, 49, 50, 51, 32, 46, 92, 46, 36, 37, 126, 93};
+    nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "String SSID Test",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wireless setting */
+
+    s_wireless = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_wireless));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+    g_object_set(s_wireless, NM_SETTING_WIRELESS_SSID, ssid, NULL);
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection(connection, &testfile);
+
+    /* Ensure the SSID was written out as a string */
+    keyfile = keyfile_load_from_file(testfile);
+    tmp     = g_key_file_get_string(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, NULL);
+    g_assert(tmp);
+    g_assert_cmpmem(tmp, strlen(tmp), tmpssid, sizeof(tmpssid));
+    g_free(tmp);
+
+    assert_reread_and_unlink(connection, TRUE, testfile);
+}
+
+static void
+test_read_intlist_ssid(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWireless *           s_wifi;
+    GBytes *                      ssid;
+    const guint8 *                ssid_data;
+    gsize                         ssid_len;
+    const char *                  expected_ssid = "blah1234";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Intlist_SSID");
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
+    g_assert(ssid != NULL);
+    ssid_data = g_bytes_get_data(ssid, &ssid_len);
+    g_assert_cmpmem(ssid_data, ssid_len, expected_ssid, strlen(expected_ssid));
+}
+
+static void
+test_write_intlist_ssid(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingIPConfig *           s_ip4;
+    gs_free char *                testfile = NULL;
+    GBytes *                      ssid;
+    unsigned char                 tmpssid[] = {65, 49, 50, 51, 0, 50, 50};
+    gs_free_error GError *error             = NULL;
+    nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
+    int *                           intlist;
+    gsize                           len = 0, i;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Intlist SSID Test",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wireless setting */
+    s_wifi = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+    g_assert(s_wifi);
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+    g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NULL);
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    g_assert(s_ip4);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection(connection, &testfile);
+
+    /* Ensure the SSID was written out as an int list */
+    keyfile = keyfile_load_from_file(testfile);
+
+    intlist = g_key_file_get_integer_list(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, &len, &error);
+    g_assert_no_error(error);
+    g_assert(intlist);
+    g_assert_cmpint(len, ==, sizeof(tmpssid));
+
+    for (i = 0; i < len; i++)
+        g_assert_cmpint(intlist[i], ==, tmpssid[i]);
+    g_free(intlist);
+
+    assert_reread_and_unlink(connection, TRUE, testfile);
+}
+
+static void
+test_read_intlike_ssid(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWireless *           s_wifi;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "101";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Intlike_SSID");
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
+    g_assert(ssid);
+    g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
+}
+
+static void
+test_read_intlike_ssid_2(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWireless *           s_wifi;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "11;12;13;";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Intlike_SSID_2");
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
+    g_assert(ssid);
+    g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
+}
+
+static void
+test_write_intlike_ssid(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingIPConfig *           s_ip4;
+    gs_free char *                testfile = NULL;
+    GBytes *                      ssid;
+    unsigned char                 tmpssid[] = {49, 48, 49};
+    gs_free_error GError *error             = NULL;
+    nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
+    char *                          tmp;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Intlike SSID Test",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wireless setting */
+    s_wifi = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+    g_assert(s_wifi);
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+    g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NULL);
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    g_assert(s_ip4);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection(connection, &testfile);
+
+    /* Ensure the SSID was written out as a plain "101" */
+    keyfile = keyfile_load_from_file(testfile);
+
+    tmp = g_key_file_get_string(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, &error);
+    g_assert_no_error(error);
+    g_assert(tmp);
+    g_assert_cmpstr(tmp, ==, "101");
+    g_free(tmp);
+
+    assert_reread_and_unlink(connection, TRUE, testfile);
+}
+
+static void
+test_write_intlike_ssid_2(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingIPConfig *           s_ip4;
+    gs_free char *                testfile = NULL;
+    GBytes *                      ssid;
+    unsigned char                 tmpssid[] = {49, 49, 59, 49, 50, 59, 49, 51, 59};
+    gs_free_error GError *error             = NULL;
+    nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
+    char *                          tmp;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Intlike SSID Test 2",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wireless setting */
+    s_wifi = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+    g_assert(s_wifi);
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+    g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NULL);
+    g_bytes_unref(ssid);
+
+    /* IP4 setting */
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    g_assert(s_ip4);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection(connection, &testfile);
+
+    /* Ensure the SSID was written out as a plain "11;12;13;" */
+    keyfile = keyfile_load_from_file(testfile);
+
+    tmp = g_key_file_get_string(keyfile, "wifi", NM_SETTING_WIRELESS_SSID, &error);
+    g_assert_no_error(error);
+    g_assert(tmp);
+    g_assert_cmpstr(tmp, ==, "11\\;12\\;13\\;");
+    g_free(tmp);
+
+    assert_reread_and_unlink(connection, TRUE, testfile);
+}
+
+static void
+test_read_bt_dun_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBluetooth *          s_bluetooth;
+    NMSettingSerial *             s_serial;
+    NMSettingGsm *                s_gsm;
+    const char *                  bdaddr;
+    const guint8                  expected_bdaddr[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/ATT_Data_Connect_BT");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "AT&T Data Connect BT");
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con),
+                    ==,
+                    "089130ab-ce28-46e4-ad77-d44869b03d19");
+
+    s_bluetooth = nm_connection_get_setting_bluetooth(connection);
+    g_assert(s_bluetooth);
+    bdaddr = nm_setting_bluetooth_get_bdaddr(s_bluetooth);
+    g_assert(bdaddr);
+    g_assert(nm_utils_hwaddr_matches(bdaddr, -1, expected_bdaddr, sizeof(expected_bdaddr)));
+    g_assert_cmpstr(nm_setting_bluetooth_get_connection_type(s_bluetooth),
+                    ==,
+                    NM_SETTING_BLUETOOTH_TYPE_DUN);
+
+    s_gsm = nm_connection_get_setting_gsm(connection);
+    g_assert(s_gsm);
+    g_assert_cmpstr(nm_setting_gsm_get_apn(s_gsm), ==, "ISP.CINGULAR");
+    g_assert_cmpstr(nm_setting_gsm_get_username(s_gsm), ==, "ISP@CINGULARGPRS.COM");
+    g_assert_cmpstr(nm_setting_gsm_get_password(s_gsm), ==, "CINGULAR1");
+
+    s_serial = nm_connection_get_setting_serial(connection);
+    g_assert(s_serial);
+    g_assert(nm_setting_serial_get_parity(s_serial) == NM_SETTING_SERIAL_PARITY_ODD);
+}
+
+static void
+test_write_bt_dun_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBluetooth *          s_bt;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingGsm *                s_gsm;
+    const char *                  bdaddr    = "aa:b9:a1:74:55:44";
+    guint64                       timestamp = 0x12344433L;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "T-Mobile Funkadelic",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_BLUETOOTH_SETTING_NAME,
+                 NM_SETTING_CONNECTION_TIMESTAMP,
+                 timestamp,
+                 NULL);
+
+    /* Bluetooth setting */
+
+    s_bt = NM_SETTING_BLUETOOTH(nm_setting_bluetooth_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_bt));
+
+    g_object_set(s_bt,
+                 NM_SETTING_BLUETOOTH_BDADDR,
+                 bdaddr,
+                 NM_SETTING_BLUETOOTH_TYPE,
+                 NM_SETTING_BLUETOOTH_TYPE_DUN,
+                 NULL);
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* GSM setting */
+    s_gsm = NM_SETTING_GSM(nm_setting_gsm_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_gsm));
+
+    g_object_set(s_gsm,
+                 NM_SETTING_GSM_APN,
+                 "internet2.voicestream.com",
+                 NM_SETTING_GSM_USERNAME,
+                 "george.clinton",
+                 NM_SETTING_GSM_PASSWORD,
+                 "parliament",
+                 NULL);
+
+    write_test_connection_and_reread(connection, TRUE);
+}
+
+static void
+test_read_gsm_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingSerial *             s_serial;
+    NMSettingGsm *                s_gsm;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/ATT_Data_Connect_Plain");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "AT&T Data Connect");
+    g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
+                    ==,
+                    NM_SETTING_GSM_SETTING_NAME);
+
+    /* Plain GSM, so no BT setting expected */
+    g_assert(nm_connection_get_setting_bluetooth(connection) == NULL);
+
+    s_gsm = nm_connection_get_setting_gsm(connection);
+    g_assert(s_gsm);
+    g_assert_cmpstr(nm_setting_gsm_get_apn(s_gsm), ==, "ISP.CINGULAR");
+    g_assert_cmpstr(nm_setting_gsm_get_username(s_gsm), ==, "ISP@CINGULARGPRS.COM");
+    g_assert_cmpstr(nm_setting_gsm_get_password(s_gsm), ==, "CINGULAR1");
+    g_assert_cmpstr(nm_setting_gsm_get_network_id(s_gsm), ==, "24005");
+    g_assert_cmpstr(nm_setting_gsm_get_pin(s_gsm), ==, "2345");
+    g_assert_cmpstr(nm_setting_gsm_get_device_id(s_gsm),
+                    ==,
+                    "da812de91eec16620b06cd0ca5cbc7ea25245222");
+    g_assert_cmpstr(nm_setting_gsm_get_sim_id(s_gsm), ==, "89148000000060671234");
+    g_assert_cmpstr(nm_setting_gsm_get_sim_operator_id(s_gsm), ==, "310260");
+
+    s_serial = nm_connection_get_setting_serial(connection);
+    g_assert(s_serial);
+    g_assert_cmpint(nm_setting_serial_get_parity(s_serial), ==, NM_SETTING_SERIAL_PARITY_ODD);
+}
+
+static void
+test_write_gsm_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingGsm *                s_gsm;
+    guint64                       timestamp = 0x12344433L;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "T-Mobile Funkadelic 2",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_GSM_SETTING_NAME,
+                 NM_SETTING_CONNECTION_TIMESTAMP,
+                 timestamp,
+                 NULL);
+
+    /* IP4 setting */
+
+    s_ip4 = NM_SETTING_IP_CONFIG(nm_setting_ip4_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* GSM setting */
+    s_gsm = NM_SETTING_GSM(nm_setting_gsm_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_gsm));
+
+    g_object_set(s_gsm,
+                 NM_SETTING_GSM_APN,
+                 "internet2.voicestream.com",
+                 NM_SETTING_GSM_USERNAME,
+                 "george.clinton.again",
+                 NM_SETTING_GSM_PASSWORD,
+                 "parliament2",
+                 NM_SETTING_GSM_PIN,
+                 "123456",
+                 NM_SETTING_GSM_NETWORK_ID,
+                 "254098",
+                 NM_SETTING_GSM_HOME_ONLY,
+                 TRUE,
+                 NM_SETTING_GSM_DEVICE_ID,
+                 "da812de91eec16620b06cd0ca5cbc7ea25245222",
+                 NM_SETTING_GSM_SIM_ID,
+                 "89148000000060671234",
+                 NM_SETTING_GSM_SIM_OPERATOR_ID,
+                 "310260",
+                 NULL);
+
+    write_test_connection_and_reread(connection, TRUE);
+}
+
+static void
+test_read_wired_8021x_tls_blob_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSetting8021x *              s_8021x;
+    const char *                  tmp;
+    GBytes *                      blob;
+
+    NMTST_EXPECT_NM_WARN("keyfile: 802-1x.client-cert: certificate or key file "
+                         "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+    NMTST_EXPECT_NM_WARN("keyfile: 802-1x.private-key: certificate or key file "
+                         "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_Blob");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired != NULL);
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x != NULL);
+
+    g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1);
+    tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0);
+    g_assert(g_strcmp0(tmp, "tls") == 0);
+
+    tmp = nm_setting_802_1x_get_identity(s_8021x);
+    g_assert(g_strcmp0(tmp, "Bill Smith") == 0);
+
+    tmp = nm_setting_802_1x_get_private_key_password(s_8021x);
+    g_assert(g_strcmp0(tmp, "12345testing") == 0);
+
+    g_assert_cmpint(nm_setting_802_1x_get_ca_cert_scheme(s_8021x),
+                    ==,
+                    NM_SETTING_802_1X_CK_SCHEME_BLOB);
+
+    /* Make sure it's not a path, since it's a blob */
+    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(scheme == NM_SETTING_802_1X_CK_SCHEME_PATH));
+    tmp = nm_setting_802_1x_get_ca_cert_path(s_8021x);
+    g_test_assert_expected_messages();
+    g_assert(tmp == NULL);
+
+    /* Validate the path */
+    blob = nm_setting_802_1x_get_ca_cert_blob(s_8021x);
+    g_assert(blob != NULL);
+    g_assert_cmpint(g_bytes_get_size(blob), ==, 568);
+
+    tmp = nm_setting_802_1x_get_client_cert_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, "/CASA/dcbw/Desktop/certinfra/client.pem");
+
+    tmp = nm_setting_802_1x_get_private_key_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, "/CASA/dcbw/Desktop/certinfra/client.pem");
+}
+
+static void
+test_read_wired_8021x_tls_bad_path_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSetting8021x *              s_8021x;
+    const char *                  tmp;
+    char *                        tmp2;
+
+    NMTST_EXPECT_NM_WARN("*does not exist*");
+    connection =
+        keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_Path_Missing");
+    g_test_assert_expected_messages();
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired != NULL);
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x != NULL);
+
+    g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1);
+    tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0);
+    g_assert(g_strcmp0(tmp, "tls") == 0);
+
+    tmp = nm_setting_802_1x_get_identity(s_8021x);
+    g_assert(g_strcmp0(tmp, "Bill Smith") == 0);
+
+    tmp = nm_setting_802_1x_get_private_key_password(s_8021x);
+    g_assert(g_strcmp0(tmp, "12345testing") == 0);
+
+    g_assert_cmpint(nm_setting_802_1x_get_ca_cert_scheme(s_8021x),
+                    ==,
+                    NM_SETTING_802_1X_CK_SCHEME_PATH);
+
+    tmp = nm_setting_802_1x_get_ca_cert_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, "/some/random/cert/path.pem");
+
+    tmp2 = g_strdup_printf(TEST_KEYFILES_DIR "/test-key-and-cert.pem");
+
+    tmp = nm_setting_802_1x_get_client_cert_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, tmp2);
+
+    tmp = nm_setting_802_1x_get_private_key_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, tmp2);
+
+    g_free(tmp2);
+}
+
+static void
+test_read_wired_8021x_tls_old_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSetting8021x *              s_8021x;
+    const char *                  tmp;
+
+    NMTST_EXPECT_NM_WARN("keyfile: 802-1x.ca-cert: certificate or key file "
+                         "'/CASA/dcbw/Desktop/certinfra/CA/eaptest_ca_cert.pem' does not exist*");
+    NMTST_EXPECT_NM_WARN("keyfile: 802-1x.client-cert: certificate or key file "
+                         "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+    NMTST_EXPECT_NM_WARN("keyfile: 802-1x.private-key: certificate or key file "
+                         "'/CASA/dcbw/Desktop/certinfra/client.pem' does not exist*");
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_Old");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired != NULL);
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x != NULL);
+
+    g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1);
+    tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0);
+    g_assert(g_strcmp0(tmp, "tls") == 0);
+
+    tmp = nm_setting_802_1x_get_identity(s_8021x);
+    g_assert(g_strcmp0(tmp, "Bill Smith") == 0);
+
+    tmp = nm_setting_802_1x_get_private_key_password(s_8021x);
+    g_assert(g_strcmp0(tmp, "12345testing") == 0);
+
+    tmp = nm_setting_802_1x_get_ca_cert_path(s_8021x);
+    g_assert(g_strcmp0(tmp, "/CASA/dcbw/Desktop/certinfra/CA/eaptest_ca_cert.pem") == 0);
+
+    tmp = nm_setting_802_1x_get_client_cert_path(s_8021x);
+    g_assert(g_strcmp0(tmp, "/CASA/dcbw/Desktop/certinfra/client.pem") == 0);
+
+    tmp = nm_setting_802_1x_get_private_key_path(s_8021x);
+    g_assert(g_strcmp0(tmp, "/CASA/dcbw/Desktop/certinfra/client.pem") == 0);
+}
+
+static void
+test_read_wired_8021x_tls_new_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    NMSetting8021x *              s_8021x;
+    const char *                  tmp;
+    char *                        tmp2;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Wired_TLS_New");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired != NULL);
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x != NULL);
+
+    g_assert(nm_setting_802_1x_get_num_eap_methods(s_8021x) == 1);
+    tmp = nm_setting_802_1x_get_eap_method(s_8021x, 0);
+    g_assert(g_strcmp0(tmp, "tls") == 0);
+
+    tmp = nm_setting_802_1x_get_identity(s_8021x);
+    g_assert(g_strcmp0(tmp, "Bill Smith") == 0);
+
+    tmp = nm_setting_802_1x_get_private_key_password(s_8021x);
+    g_assert(g_strcmp0(tmp, "12345testing") == 0);
+
+    tmp2 = g_strdup_printf(TEST_KEYFILES_DIR "/test-ca-cert.pem");
+    tmp  = nm_setting_802_1x_get_ca_cert_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, tmp2);
+    g_free(tmp2);
+
+    tmp2 = g_strdup_printf(TEST_KEYFILES_DIR "/test-key-and-cert.pem");
+
+    tmp = nm_setting_802_1x_get_client_cert_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, tmp2);
+
+    tmp = nm_setting_802_1x_get_private_key_path(s_8021x);
+    g_assert_cmpstr(tmp, ==, tmp2);
+
+    g_free(tmp2);
+}
+
+#define TEST_WIRED_TLS_CA_CERT     TEST_KEYFILES_DIR "/test-ca-cert.pem"
+#define TEST_WIRED_TLS_CLIENT_CERT TEST_KEYFILES_DIR "/test-key-and-cert.pem"
+#define TEST_WIRED_TLS_PRIVKEY     TEST_KEYFILES_DIR "/test-key-and-cert.pem"
+
+static NMConnection *
+create_wired_tls_connection(NMSetting8021xCKScheme scheme)
+{
+    NMTST_UUID_INIT(uuid);
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSettingIPConfig *  s_ip4;
+    NMSetting *          s_wired;
+    NMSetting8021x *     s_8021x;
+    gboolean             success;
+    gs_free_error GError *error = NULL;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection != NULL);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Wired Really Secure TLS",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    g_assert(s_ip4);
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+
+    /* Wired setting */
+    s_wired = nm_setting_wired_new();
+    g_assert(s_wired);
+    nm_connection_add_setting(connection, s_wired);
+
+    /* 802.1x setting */
+    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
+    g_assert(s_8021x);
+    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
+
+    nm_setting_802_1x_add_eap_method(s_8021x, "tls");
+    g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL);
+
+    success = nm_setting_802_1x_set_ca_cert(s_8021x, TEST_WIRED_TLS_CA_CERT, scheme, NULL, &error);
+    g_assert_no_error(error);
+    g_assert(success);
+
+    success = nm_setting_802_1x_set_client_cert(s_8021x,
+                                                TEST_WIRED_TLS_CLIENT_CERT,
+                                                scheme,
+                                                NULL,
+                                                &error);
+    g_assert_no_error(error);
+    g_assert(success);
+
+    success = nm_setting_802_1x_set_private_key(s_8021x,
+                                                TEST_WIRED_TLS_PRIVKEY,
+                                                "test1",
+                                                scheme,
+                                                NULL,
+                                                &error);
+    g_assert_no_error(error);
+    g_assert(success);
+
+    return connection;
+}
+
+static char *
+get_path(const char *file, gboolean relative)
+{
+    return relative ? g_path_get_basename(file) : g_strdup(file);
+}
+
+static void
+test_write_wired_8021x_tls_connection_path(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    char *                        tmp, *tmp2;
+    gboolean                      success;
+    gs_free char *                testfile      = NULL;
+    nm_auto_unref_keyfile GKeyFile *keyfile     = NULL;
+    gboolean                        relative    = FALSE;
+    gboolean                        reread_same = FALSE;
+
+    connection = create_wired_tls_connection(NM_SETTING_802_1X_CK_SCHEME_PATH);
+    g_assert(connection != NULL);
+
+    write_test_connection_reread(connection, &testfile, &reread, &reread_same);
+    nmtst_assert_connection_verifies_without_normalization(reread);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_CA_CERT);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_CLIENT_CERT);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_PRIVATE_KEY);
+    assert_reread_same(connection, reread);
+    g_clear_object(&reread);
+
+    /* Read the connection back in and compare it to the one we just wrote out */
+    reread = keyfile_read_connection_from_file(testfile);
+
+    success = nm_connection_compare(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT);
+    if (!reread) {
+        g_warning("Written and re-read connection weren't the same");
+        g_assert(success);
+    }
+
+    /* Ensure the cert and key values are properly written out */
+    keyfile = keyfile_load_from_file(testfile);
+
+    /* Depending on whether this test is being run from 'make check' or
+     * 'make distcheck' we might be using relative paths (check) or
+     * absolute ones (distcheck).
+     */
+    tmp2 = g_path_get_dirname(testfile);
+    if (g_strcmp0(tmp2, TEST_KEYFILES_DIR) == 0)
+        relative = TRUE;
+    g_free(tmp2);
+
+    /* CA cert */
+    tmp  = g_key_file_get_string(keyfile,
+                                NM_SETTING_802_1X_SETTING_NAME,
+                                NM_SETTING_802_1X_CA_CERT,
+                                NULL);
+    tmp2 = get_path(TEST_WIRED_TLS_CA_CERT, relative);
+    g_assert_cmpstr(tmp, ==, tmp2);
+    g_free(tmp2);
+    g_free(tmp);
+
+    /* Client cert */
+    tmp  = g_key_file_get_string(keyfile,
+                                NM_SETTING_802_1X_SETTING_NAME,
+                                NM_SETTING_802_1X_CLIENT_CERT,
+                                NULL);
+    tmp2 = get_path(TEST_WIRED_TLS_CLIENT_CERT, relative);
+    g_assert_cmpstr(tmp, ==, tmp2);
+    g_free(tmp2);
+    g_free(tmp);
+
+    /* Private key */
+    tmp  = g_key_file_get_string(keyfile,
+                                NM_SETTING_802_1X_SETTING_NAME,
+                                NM_SETTING_802_1X_PRIVATE_KEY,
+                                NULL);
+    tmp2 = get_path(TEST_WIRED_TLS_PRIVKEY, relative);
+    g_assert_cmpstr(tmp, ==, tmp2);
+    g_free(tmp2);
+    g_free(tmp);
+
+    unlink(testfile);
+}
+
+static void
+test_write_wired_8021x_tls_connection_blob(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSetting8021x *              s_8021x;
+    gs_free char *                testfile = NULL;
+    char *                        new_ca_cert;
+    char *                        new_client_cert;
+    char *                        new_priv_key;
+    const char *                  uuid;
+    gboolean                      reread_same = FALSE;
+    GBytes *                      password_raw;
+
+#define PASSWORD_RAW "password-raw\0test"
+
+    connection = create_wired_tls_connection(NM_SETTING_802_1X_CK_SCHEME_BLOB);
+    g_assert(connection != NULL);
+
+    s_8021x = nm_connection_get_setting_802_1x(connection);
+    g_assert(s_8021x);
+
+    password_raw = g_bytes_new(PASSWORD_RAW, NM_STRLEN(PASSWORD_RAW));
+    g_object_set(s_8021x, NM_SETTING_802_1X_PASSWORD_RAW, password_raw, NULL);
+    g_bytes_unref(password_raw);
+
+    write_test_connection_reread(connection, &testfile, &reread, &reread_same);
+    nmtst_assert_connection_verifies_without_normalization(reread);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_CA_CERT);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_CLIENT_CERT);
+    _setting_copy_property_gbytes(connection,
+                                  reread,
+                                  NM_SETTING_802_1X_SETTING_NAME,
+                                  NM_SETTING_802_1X_PRIVATE_KEY);
+    assert_reread_same(connection, reread);
+    g_clear_object(&reread);
+
+    /* Check that the new certs got written out */
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    uuid = nm_setting_connection_get_uuid(s_con);
+    g_assert(uuid);
+
+    new_ca_cert = g_strdup_printf("%s/%s-ca-cert.pem", TEST_SCRATCH_DIR, uuid);
+    g_assert(new_ca_cert);
+    g_assert(g_file_test(new_ca_cert, G_FILE_TEST_EXISTS));
+
+    new_client_cert = g_strdup_printf("%s/%s-client-cert.pem", TEST_SCRATCH_DIR, uuid);
+    g_assert(new_client_cert);
+    g_assert(g_file_test(new_client_cert, G_FILE_TEST_EXISTS));
+
+    new_priv_key = g_strdup_printf("%s/%s-private-key.pem", TEST_SCRATCH_DIR, uuid);
+    g_assert(new_priv_key);
+    g_assert(g_file_test(new_priv_key, G_FILE_TEST_EXISTS));
+
+    /* Read the connection back in and compare it to the one we just wrote out */
+    reread = keyfile_read_connection_from_file(testfile);
+
+    /* Ensure the re-read connection's certificates use the path scheme */
+    s_8021x = nm_connection_get_setting_802_1x(reread);
+    g_assert(s_8021x);
+    g_assert(nm_setting_802_1x_get_ca_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+    g_assert(nm_setting_802_1x_get_client_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+    g_assert(nm_setting_802_1x_get_private_key_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH);
+
+    password_raw = nm_setting_802_1x_get_password_raw(s_8021x);
+    g_assert(password_raw);
+    g_assert(nm_utils_gbytes_equal_mem(password_raw, PASSWORD_RAW, NM_STRLEN(PASSWORD_RAW)));
+
+    unlink(testfile);
+
+    /* Clean up written certs */
+    unlink(new_ca_cert);
+    g_free(new_ca_cert);
+
+    unlink(new_client_cert);
+    g_free(new_client_cert);
+
+    unlink(new_priv_key);
+    g_free(new_priv_key);
+}
+
+static void
+test_read_dcb_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_dcb_connection");
+}
+
+static void
+test_read_infiniband_connection(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingInfiniband *         s_ib;
+    const char *                  mac;
+    guint8      expected_mac[INFINIBAND_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                                            0x77, 0x88, 0x99, 0x01, 0x12, 0x23, 0x34,
+                                            0x45, 0x56, 0x67, 0x78, 0x89, 0x90};
+    const char *expected_id                   = "Test InfiniBand Connection";
+    const char *expected_uuid                 = "4e80a56d-c99f-4aad-a6dd-b449bc398c57";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_InfiniBand_Connection");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, expected_uuid);
+
+    s_ib = nm_connection_get_setting_infiniband(connection);
+    g_assert(s_ib);
+
+    mac = nm_setting_infiniband_get_mac_address(s_ib);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac)));
+}
+
+static void
+test_write_infiniband_connection(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingInfiniband *         s_ib;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    const char *mac = "99:88:77:66:55:44:ab:bc:cd:de:ef:f0:0a:1b:2c:3d:4e:5f:6f:ba";
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Work InfiniBand",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 FALSE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_INFINIBAND_SETTING_NAME,
+                 NULL);
+
+    /* InfiniBand setting */
+    s_ib = (NMSettingInfiniband *) nm_setting_infiniband_new();
+    g_assert(s_ib);
+    nm_connection_add_setting(connection, NM_SETTING(s_ib));
+
+    g_object_set(s_ib,
+                 NM_SETTING_INFINIBAND_MAC_ADDRESS,
+                 mac,
+                 NM_SETTING_INFINIBAND_MTU,
+                 900,
+                 NM_SETTING_INFINIBAND_TRANSPORT_MODE,
+                 "datagram",
+                 NULL);
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    g_assert(s_ip4);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    g_assert(s_ip6);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_bridge_main(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingBridge *             s_bridge;
+    const char *                  expected_id   = "Test Bridge Main";
+    const char *                  expected_uuid = "8f061643-fe41-4d4c-a8d9-097d26e2ad3a";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Bridge_Main");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, expected_uuid);
+    g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, "br0");
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+
+    s_bridge = nm_connection_get_setting_bridge(connection);
+    g_assert(s_bridge);
+    g_assert_cmpuint(nm_setting_bridge_get_forward_delay(s_bridge), ==, 2);
+    g_assert_cmpuint(nm_setting_bridge_get_stp(s_bridge), ==, TRUE);
+    g_assert_cmpuint(nm_setting_bridge_get_priority(s_bridge), ==, 32744);
+    g_assert_cmpuint(nm_setting_bridge_get_hello_time(s_bridge), ==, 7);
+    g_assert_cmpuint(nm_setting_bridge_get_max_age(s_bridge), ==, 39);
+    g_assert_cmpuint(nm_setting_bridge_get_ageing_time(s_bridge), ==, 235352);
+    g_assert_cmpuint(nm_setting_bridge_get_multicast_snooping(s_bridge), ==, FALSE);
+}
+
+static void
+test_write_bridge_main(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBridge *             s_bridge;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Bridge Main",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_BRIDGE_SETTING_NAME,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 "br0",
+                 NULL);
+
+    /* Bridge setting */
+    s_bridge = (NMSettingBridge *) nm_setting_bridge_new();
+    g_assert(s_bridge);
+    nm_connection_add_setting(connection, NM_SETTING(s_bridge));
+
+    /* IP4 setting */
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    g_assert(s_ip4);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    g_object_set(s_ip4,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                 NM_SETTING_IP_CONFIG_MAY_FAIL,
+                 TRUE,
+                 NM_SETTING_IP_CONFIG_GATEWAY,
+                 "1.1.1.1",
+                 NULL);
+
+    add_one_ip_address(s_ip4, "1.2.3.4", 24);
+
+    /* IP6 setting */
+    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    g_assert(s_ip6);
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_bridge_component(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBridgePort *         s_port;
+    NMSettingWired *              s_wired;
+    const char *                  mac;
+    guint8                        expected_mac[ETH_ALEN] = {0x00, 0x22, 0x15, 0x59, 0x62, 0x97};
+    const char *                  expected_id            = "Test Bridge Component";
+    const char *                  expected_uuid          = "d7b4f96c-c45e-4298-bef8-f48574f8c1c0";
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Bridge_Component");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
+    g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, expected_uuid);
+    g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "br0");
+    g_assert(nm_setting_connection_is_slave_type(s_con, NM_SETTING_BRIDGE_SETTING_NAME));
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac)));
+
+    s_port = nm_connection_get_setting_bridge_port(connection);
+    g_assert(s_port);
+    g_assert(nm_setting_bridge_port_get_hairpin_mode(s_port));
+    g_assert_cmpuint(nm_setting_bridge_port_get_priority(s_port), ==, 28);
+    g_assert_cmpuint(nm_setting_bridge_port_get_path_cost(s_port), ==, 100);
+}
+
+static void
+test_write_bridge_component(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBridgePort *         s_port;
+    NMSettingWired *              s_wired;
+    const char *                  mac = "99:88:77:66:55:44";
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Bridge Component",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NM_SETTING_CONNECTION_MASTER,
+                 "br0",
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_BRIDGE_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = NM_SETTING_WIRED(nm_setting_wired_new());
+    g_assert(s_wired);
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, 1300, NULL);
+
+    /* BridgePort setting */
+    s_port = (NMSettingBridgePort *) nm_setting_bridge_port_new();
+    g_assert(s_port);
+    nm_connection_add_setting(connection, NM_SETTING(s_port));
+
+    g_object_set(s_port,
+                 NM_SETTING_BRIDGE_PORT_PRIORITY,
+                 3,
+                 NM_SETTING_BRIDGE_PORT_PATH_COST,
+                 99,
+                 NULL);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_new_wired_group_name(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWired *              s_wired;
+    const char *                  mac;
+    guint8                        expected_mac[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_New_Wired_Group_Name");
+
+    s_wired = nm_connection_get_setting_wired(connection);
+    g_assert(s_wired);
+    g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1400);
+
+    mac = nm_setting_wired_get_mac_address(s_wired);
+    g_assert(mac);
+    g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac, sizeof(expected_mac)));
+}
+
+static void
+test_write_new_wired_group_name(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    nm_auto_unref_keyfile GKeyFile *kf       = NULL;
+    NMSettingConnection *           s_con;
+    NMSettingWired *                s_wired;
+    gs_free char *                  testfile = NULL;
+    gs_free_error GError *error              = NULL;
+    char *                s;
+    int                   mtu;
+
+    connection = nm_simple_connection_new();
+    g_assert(connection);
+
+    /* Connection setting */
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    g_assert(s_con);
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Wired New Group Name",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = (NMSettingWired *) nm_setting_wired_new();
+    g_assert(s_wired);
+    g_object_set(s_wired, NM_SETTING_WIRED_MTU, 1400, NULL);
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    write_test_connection(connection, &testfile);
+
+    assert_reread(connection, TRUE, testfile);
+
+    /* Look at the keyfile itself to ensure we wrote out the new group names and type */
+    kf = keyfile_load_from_file(testfile);
+
+    s = g_key_file_get_string(kf,
+                              NM_SETTING_CONNECTION_SETTING_NAME,
+                              NM_SETTING_CONNECTION_TYPE,
+                              &error);
+    g_assert_no_error(error);
+    g_assert_cmpstr(s, ==, "ethernet");
+    g_free(s);
+
+    mtu = g_key_file_get_integer(kf, "ethernet", NM_SETTING_WIRED_MTU, &error);
+    g_assert_no_error(error);
+    g_assert_cmpint(mtu, ==, 1400);
+
+    unlink(testfile);
+}
+
+static void
+test_read_new_wireless_group_names(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "foobar";
+
+    connection =
+        keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_New_Wireless_Group_Names");
+
+    s_wifi = nm_connection_get_setting_wireless(connection);
+    g_assert(s_wifi);
+
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
+    g_assert(ssid);
+    g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
+
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, NM_SETTING_WIRELESS_MODE_INFRA);
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    g_assert(s_wsec);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk");
+    g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), ==, "s3cu4e passphrase");
+}
+
+static void
+test_write_new_wireless_group_names(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    nm_auto_unref_keyfile GKeyFile *kf       = NULL;
+    NMSettingConnection *           s_con;
+    NMSettingWireless *             s_wifi;
+    NMSettingWirelessSecurity *     s_wsec;
+    GBytes *                        ssid;
+    unsigned char                   tmpssid[]    = {0x31, 0x33, 0x33, 0x37};
+    const char *                    expected_psk = "asdfasdfasdfa12315";
+    gs_free char *                  testfile     = NULL;
+    gs_free_error GError *error                  = NULL;
+    char *                s;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = (NMSettingConnection *) nm_setting_connection_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write New Wireless Group Names",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRELESS_SETTING_NAME,
+                 NULL);
+
+    /* Wi-Fi setting */
+    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
+
+    ssid = g_bytes_new(tmpssid, sizeof(tmpssid));
+    g_object_set(s_wifi,
+                 NM_SETTING_WIRELESS_SSID,
+                 ssid,
+                 NM_SETTING_WIRELESS_MODE,
+                 NM_SETTING_WIRELESS_MODE_INFRA,
+                 NULL);
+    g_bytes_unref(ssid);
+
+    /* Wi-Fi security setting */
+    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
+    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+    g_object_set(s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                 "wpa-psk",
+                 NM_SETTING_WIRELESS_SECURITY_PSK,
+                 expected_psk,
+                 NULL);
+
+    write_test_connection(connection, &testfile);
+
+    assert_reread(connection, TRUE, testfile);
+
+    /* Look at the keyfile itself to ensure we wrote out the new group names and type */
+    kf = keyfile_load_from_file(testfile);
+
+    s = g_key_file_get_string(kf,
+                              NM_SETTING_CONNECTION_SETTING_NAME,
+                              NM_SETTING_CONNECTION_TYPE,
+                              &error);
+    g_assert_no_error(error);
+    g_assert_cmpstr(s, ==, "wifi");
+    g_free(s);
+
+    s = g_key_file_get_string(kf, "wifi", NM_SETTING_WIRELESS_MODE, &error);
+    g_assert_no_error(error);
+    g_assert_cmpstr(s, ==, NM_SETTING_WIRELESS_MODE_INFRA);
+    g_free(s);
+
+    s = g_key_file_get_string(kf, "wifi-security", NM_SETTING_WIRELESS_SECURITY_PSK, &error);
+    g_assert_no_error(error);
+    g_assert_cmpstr(s, ==, expected_psk);
+    g_free(s);
+
+    unlink(testfile);
+}
+
+static void
+test_read_missing_vlan_setting(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Missing_Vlan_Setting");
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 0);
+    g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS);
+}
+
+static void
+test_read_missing_vlan_flags(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Missing_Vlan_Flags");
+
+    s_vlan = nm_connection_get_setting_vlan(connection);
+    g_assert(s_vlan);
+
+    g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 444);
+    g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "em1");
+    g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS);
+}
+
+static void
+test_read_missing_id_uuid(void)
+{
+    gs_unref_object NMConnection *connection    = NULL;
+    gs_free char *                expected_uuid = NULL;
+    const char *                  FILENAME      = TEST_KEYFILES_DIR "/Test_Missing_ID_UUID";
+
+    expected_uuid = _nm_utils_uuid_generate_from_strings("keyfile", FILENAME, NULL);
+
+    connection = keyfile_read_connection_from_file(FILENAME);
+
+    g_assert_cmpstr(nm_connection_get_id(connection), ==, "Test_Missing_ID_UUID");
+    g_assert_cmpstr(nm_connection_get_uuid(connection), ==, expected_uuid);
+}
+
+static void
+test_read_minimal(void)
+{
+    gs_unref_object NMConnection *connection    = NULL;
+    gs_unref_object NMConnection *con_archetype = NULL;
+    NMSettingConnection *         s_con;
+
+    con_archetype = nmtst_create_minimal_connection("Test_minimal_x",
+                                                    "a15bd68f-c32b-40b8-8d27-49e472a85919",
+                                                    NM_SETTING_WIRED_SETTING_NAME,
+                                                    &s_con);
+    nmtst_connection_normalize(con_archetype);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_1");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_2");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+}
+
+static void
+test_read_minimal_slave(void)
+{
+    gs_unref_object NMConnection *connection    = NULL;
+    gs_unref_object NMConnection *con_archetype = NULL;
+    NMSettingConnection *         s_con;
+
+    con_archetype = nmtst_create_minimal_connection("Test_minimal_slave_x",
+                                                    "a56b4ca5-7075-43d4-82c7-5d0cb15f7654",
+                                                    NM_SETTING_WIRED_SETTING_NAME,
+                                                    &s_con);
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_MASTER,
+                 "br0",
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 "bridge",
+                 NULL);
+    nmtst_connection_normalize(con_archetype);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_1");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_2");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_3");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_minimal_slave_4");
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 nm_connection_get_id(connection),
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_connection_get_uuid(connection),
+                 NULL);
+    nmtst_assert_connection_equals(con_archetype, FALSE, connection, FALSE);
+    g_clear_object(&connection);
+}
+
+static void
+test_read_enum_property(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Enum_Property");
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+    g_assert_cmpint(nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip6)),
+                    ==,
+                    NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR);
+}
+
+static void
+test_write_enum_property(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip6;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Enum Property",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NULL);
+
+    /* Wired setting */
+    s_wired = NM_SETTING_WIRED(nm_setting_wired_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+
+    /* IP6 setting */
+    s_ip6 = NM_SETTING_IP_CONFIG(nm_setting_ip6_config_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+                 NM_SETTING_IP6_CONFIG_IP6_PRIVACY,
+                 NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
+                 NULL);
+
+    nmtst_connection_normalize(connection);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+static void
+test_read_flags_property(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingGsm *                s_gsm;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_Flags_Property");
+
+    s_gsm = nm_connection_get_setting_gsm(connection);
+    g_assert(s_gsm);
+    g_assert_cmpint(nm_setting_gsm_get_password_flags(s_gsm),
+                    ==,
+                    NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_REQUIRED);
+}
+
+static void
+test_write_flags_property(void)
+{
+    NMTST_UUID_INIT(uuid);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSetting *                   s_gsm;
+
+    connection = nm_simple_connection_new();
+
+    /* Connection setting */
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Flags Property",
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_GSM_SETTING_NAME,
+                 NULL);
+
+    /* GSM setting */
+    s_gsm = nm_setting_gsm_new();
+    nm_connection_add_setting(connection, s_gsm);
+    g_object_set(s_gsm,
+                 NM_SETTING_GSM_APN,
+                 "myapn",
+                 NM_SETTING_GSM_USERNAME,
+                 "adfasdfasdf",
+                 NM_SETTING_GSM_PASSWORD_FLAGS,
+                 NM_SETTING_SECRET_FLAG_NOT_SAVED | NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
+                 NULL);
+
+    nmtst_connection_normalize(connection);
+
+    write_test_connection_and_reread(connection, FALSE);
+}
+
+/*****************************************************************************/
+
+static void
+test_read_tc_config(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingTCConfig *           s_tc;
+    NMTCQdisc *                   qdisc1, *qdisc2;
+    NMTCAction *                  action1, *action2;
+    NMTCTfilter *                 tfilter1, *tfilter2;
+
+    connection = keyfile_read_connection_from_file(TEST_KEYFILES_DIR "/Test_TC_Config");
+
+    s_tc = nm_connection_get_setting_tc_config(connection);
+    g_assert(s_tc);
+
+    g_assert(nm_setting_tc_config_get_num_qdiscs(s_tc) == 2);
+
+    qdisc1 = nm_setting_tc_config_get_qdisc(s_tc, 0);
+    g_assert(qdisc1);
+    g_assert(g_strcmp0(nm_tc_qdisc_get_kind(qdisc1), "fq_codel") == 0);
+    g_assert(nm_tc_qdisc_get_handle(qdisc1) == TC_H_MAKE(0x1234 << 16, 0x0000));
+    g_assert(nm_tc_qdisc_get_parent(qdisc1) == TC_H_ROOT);
+
+    qdisc2 = nm_setting_tc_config_get_qdisc(s_tc, 1);
+    g_assert(qdisc2);
+    g_assert(g_strcmp0(nm_tc_qdisc_get_kind(qdisc2), "ingress") == 0);
+    g_assert(nm_tc_qdisc_get_handle(qdisc2) == TC_H_MAKE(TC_H_INGRESS, 0));
+    g_assert(nm_tc_qdisc_get_parent(qdisc2) == TC_H_INGRESS);
+
+    g_assert(nm_setting_tc_config_get_num_tfilters(s_tc) == 2);
+
+    tfilter1 = nm_setting_tc_config_get_tfilter(s_tc, 0);
+    g_assert(tfilter1);
+    g_assert(g_strcmp0(nm_tc_tfilter_get_kind(tfilter1), "matchall") == 0);
+    g_assert(nm_tc_tfilter_get_handle(tfilter1) == TC_H_UNSPEC);
+    g_assert(nm_tc_tfilter_get_parent(tfilter1) == TC_H_MAKE(0x1234 << 16, 0x0000));
+
+    action1 = nm_tc_tfilter_get_action(tfilter1);
+    g_assert(action1);
+    g_assert(g_strcmp0(nm_tc_action_get_kind(action1), "drop") == 0);
+
+    tfilter2 = nm_setting_tc_config_get_tfilter(s_tc, 1);
+    g_assert(tfilter2);
+    g_assert(g_strcmp0(nm_tc_tfilter_get_kind(tfilter2), "matchall") == 0);
+    g_assert(nm_tc_tfilter_get_handle(tfilter2) == TC_H_UNSPEC);
+    g_assert(nm_tc_tfilter_get_parent(tfilter2) == TC_H_MAKE(TC_H_INGRESS, 0));
+
+    action2 = nm_tc_tfilter_get_action(tfilter2);
+    g_assert(action2);
+    g_assert(g_strcmp0(nm_tc_action_get_kind(action2), "simple") == 0);
+    g_assert(
+        g_strcmp0(g_variant_get_bytestring(nm_tc_action_get_attribute(action2, "sdata")), "Hello")
+        == 0);
+}
+
+static void
+test_write_tc_config(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSetting *                   s_tc;
+    NMTCQdisc *                   qdisc1, *qdisc2;
+    NMTCTfilter *                 tfilter1, *tfilter2;
+    NMTCAction *                  action;
+    GError *                      error = NULL;
+
+    connection =
+        nmtst_create_minimal_connection("Test TC", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+    s_tc = nm_setting_tc_config_new();
+
+    qdisc1 = nm_tc_qdisc_new("fq_codel", TC_H_ROOT, &error);
+    nmtst_assert_success(qdisc1, error);
+    nm_tc_qdisc_set_handle(qdisc1, TC_H_MAKE(0x1234 << 16, 0x0000));
+    nm_setting_tc_config_add_qdisc(NM_SETTING_TC_CONFIG(s_tc), qdisc1);
+
+    qdisc2 = nm_tc_qdisc_new("ingress", TC_H_INGRESS, &error);
+    nmtst_assert_success(qdisc2, error);
+    nm_tc_qdisc_set_handle(qdisc2, TC_H_MAKE(TC_H_INGRESS, 0));
+    nm_setting_tc_config_add_qdisc(NM_SETTING_TC_CONFIG(s_tc), qdisc2);
+
+    tfilter1 = nm_tc_tfilter_new("matchall", TC_H_MAKE(0x1234 << 16, 0x0000), &error);
+    nmtst_assert_success(tfilter1, error);
+    action = nm_tc_action_new("drop", &error);
+    nmtst_assert_success(action, error);
+    nm_tc_tfilter_set_action(tfilter1, action);
+    nm_tc_action_unref(action);
+    nm_setting_tc_config_add_tfilter(NM_SETTING_TC_CONFIG(s_tc), tfilter1);
+    nm_tc_tfilter_unref(tfilter1);
+
+    tfilter2 = nm_tc_tfilter_new("matchall", TC_H_MAKE(TC_H_INGRESS, 0), &error);
+    nmtst_assert_success(tfilter2, error);
+    action = nm_tc_action_new("simple", &error);
+    nmtst_assert_success(action, error);
+    nm_tc_action_set_attribute(action, "sdata", g_variant_new_bytestring("Hello"));
+    nm_tc_tfilter_set_action(tfilter2, action);
+    nm_tc_action_unref(action);
+    nm_setting_tc_config_add_tfilter(NM_SETTING_TC_CONFIG(s_tc), tfilter2);
+    nm_tc_tfilter_unref(tfilter2);
+
+    nm_connection_add_setting(connection, s_tc);
+
+    nmtst_connection_normalize(connection);
+    write_test_connection_and_reread(connection, FALSE);
+
+    nm_tc_qdisc_unref(qdisc1);
+    nm_tc_qdisc_unref(qdisc2);
+}
+
+/*****************************************************************************/
+
+static void
+_escape_filename(gboolean with_extension, const char *filename, gboolean would_be_ignored)
+{
+    gs_free char *esc = NULL;
+
+    g_assert(filename && filename[0]);
+
+    if (!!would_be_ignored != !!nm_keyfile_utils_ignore_filename(filename, with_extension)) {
+        if (would_be_ignored)
+            g_error("We expect filename \"%s\" to be ignored, but it isn't", filename);
+        else
+            g_error("We expect filename \"%s\" not to be ignored, but it is", filename);
+    }
+
+    esc = nm_keyfile_utils_create_filename(filename, with_extension);
+    g_assert(esc && esc[0]);
+    g_assert(!strchr(esc, '/'));
+
+    if (nm_keyfile_utils_ignore_filename(esc, with_extension))
+        g_error("Escaping filename \"%s\" yielded \"%s\", but this is ignored", filename, esc);
+}
+
+static void
+test_nm_keyfile_plugin_utils_escape_filename(void)
+{
+    _escape_filename(FALSE, "ab", FALSE);
+    _escape_filename(FALSE, ".vim-file.swp", TRUE);
+    _escape_filename(FALSE, ".vim-file.Swp", TRUE);
+    _escape_filename(FALSE, ".vim-file.SWP", TRUE);
+    _escape_filename(FALSE, ".vim-file.swpx", TRUE);
+    _escape_filename(FALSE, ".vim-file.Swpx", TRUE);
+    _escape_filename(FALSE, ".vim-file.SWPX", TRUE);
+    _escape_filename(FALSE, ".pem-file.pem", TRUE);
+    _escape_filename(FALSE, ".pem-file.Pem", TRUE);
+    _escape_filename(FALSE, ".pem-file.PEM", TRUE);
+    _escape_filename(FALSE, ".pem-file.der", TRUE);
+    _escape_filename(FALSE, ".pem-file.Der", TRUE);
+    _escape_filename(FALSE, ".mkstemp.ABCEDF", TRUE);
+    _escape_filename(FALSE, ".mkstemp.abcdef", TRUE);
+    _escape_filename(FALSE, ".mkstemp.123456", TRUE);
+    _escape_filename(FALSE, ".mkstemp.A23456", TRUE);
+    _escape_filename(FALSE, ".#emacs-locking", TRUE);
+    _escape_filename(FALSE, "file-with-tilde~", TRUE);
+    _escape_filename(FALSE, ".file-with-dot", TRUE);
+    _escape_filename(FALSE, "/some/path/with/trailing/slash/", TRUE);
+    _escape_filename(FALSE, "/some/path/without/trailing/slash", FALSE);
+
+    _escape_filename(TRUE, "lala", TRUE);
+}
+
+/*****************************************************************************/
+
+static void
+_assert_keyfile_nmmeta(const char *dirname,
+                       const char *uuid,
+                       const char *loaded_path,
+                       gboolean    allow_relative,
+                       const char *exp_full_filename,
+                       const char *exp_uuid,
+                       const char *exp_symlink_target,
+                       const char *exp_loaded_path)
+{
+    gs_free char *full_filename  = NULL;
+    gs_free char *symlink_target = NULL;
+    gs_free char *uuid2          = NULL;
+    gs_free char *loaded_path2   = NULL;
+    gs_free char *dirname3       = NULL;
+    gs_free char *filename3      = NULL;
+    gs_free char *uuid3          = NULL;
+    gs_free char *loaded_path3   = NULL;
+    gboolean      success;
+    gs_free char *filename = NULL;
+
+    g_assert(dirname && dirname[0] == '/');
+    g_assert(exp_full_filename && exp_full_filename[0]);
+    g_assert(!exp_loaded_path || exp_loaded_path[0] == '/');
+
+    filename = g_path_get_basename(exp_full_filename);
+
+    full_filename = nms_keyfile_nmmeta_filename(dirname, uuid, FALSE);
+    g_assert_cmpstr(full_filename, ==, full_filename);
+    nm_clear_g_free(&full_filename);
+
+    g_assert_cmpint(
+        nms_keyfile_nmmeta_write(dirname, uuid, loaded_path, allow_relative, NULL, &full_filename),
+        ==,
+        0);
+    g_assert_cmpstr(full_filename, ==, exp_full_filename);
+    nm_clear_g_free(&full_filename);
+
+    if (exp_symlink_target)
+        g_assert(g_file_test(exp_full_filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_SYMLINK));
+    else
+        g_assert(!g_file_test(exp_full_filename, G_FILE_TEST_EXISTS));
+    symlink_target = g_file_read_link(exp_full_filename, NULL);
+    g_assert_cmpstr(symlink_target, ==, exp_symlink_target);
+
+    success = nms_keyfile_nmmeta_read(dirname,
+                                      filename,
+                                      &full_filename,
+                                      &uuid2,
+                                      &loaded_path2,
+                                      NULL,
+                                      NULL);
+    g_assert_cmpint(!!exp_uuid, ==, success);
+    if (success)
+        g_assert_cmpstr(full_filename, ==, exp_full_filename);
+    else
+        g_assert_cmpstr(full_filename, ==, NULL);
+    nm_clear_g_free(&full_filename);
+    g_assert_cmpstr(uuid2, ==, exp_uuid);
+    g_assert_cmpstr(loaded_path2, ==, exp_loaded_path);
+
+    success = nms_keyfile_nmmeta_read_from_file(exp_full_filename,
+                                                &dirname3,
+                                                &filename3,
+                                                &uuid3,
+                                                &loaded_path3,
+                                                NULL);
+    g_assert_cmpint(!!exp_uuid, ==, success);
+    if (success) {
+        g_assert_cmpstr(dirname3, ==, dirname);
+        g_assert_cmpstr(filename3, ==, filename);
+    } else {
+        g_assert_cmpstr(dirname3, ==, NULL);
+        g_assert_cmpstr(filename3, ==, NULL);
+    }
+    g_assert_cmpstr(uuid3, ==, exp_uuid);
+    g_assert_cmpstr(loaded_path3, ==, exp_loaded_path);
+}
+
+static void
+test_nmmeta(void)
+{
+    const char *  uuid          = "3c03fd17-ddc3-4100-a954-88b6fafff959";
+    gs_free char *filename      = g_strdup_printf("%s%s", uuid, NM_KEYFILE_PATH_SUFFIX_NMMETA);
+    gs_free char *full_filename = g_strdup_printf("%s/%s", TEST_SCRATCH_DIR, filename);
+    const char *  loaded_path0  = NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL;
+    const char *  loaded_path1  = "/some/where/but/not/scratch/dir";
+    const char *  filename2     = "foo1";
+    gs_free char *loaded_path2  = g_strdup_printf("%s/%s", TEST_SCRATCH_DIR, filename2);
+
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, uuid, NULL, FALSE, full_filename, NULL, NULL, NULL);
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR, uuid, NULL, TRUE, full_filename, NULL, NULL, NULL);
+
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path0,
+                           FALSE,
+                           full_filename,
+                           uuid,
+                           loaded_path0,
+                           loaded_path0);
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path0,
+                           TRUE,
+                           full_filename,
+                           uuid,
+                           loaded_path0,
+                           loaded_path0);
+
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path1,
+                           FALSE,
+                           full_filename,
+                           uuid,
+                           loaded_path1,
+                           loaded_path1);
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path1,
+                           TRUE,
+                           full_filename,
+                           uuid,
+                           loaded_path1,
+                           loaded_path1);
+
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path2,
+                           FALSE,
+                           full_filename,
+                           uuid,
+                           loaded_path2,
+                           loaded_path2);
+    _assert_keyfile_nmmeta(TEST_SCRATCH_DIR,
+                           uuid,
+                           loaded_path2,
+                           TRUE,
+                           full_filename,
+                           uuid,
+                           filename2,
+                           loaded_path2);
+
+    (void) unlink(full_filename);
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE();
+
+int
+main(int argc, char **argv)
+{
+    int errsv;
+
+    _nm_utils_set_testing(NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK);
+
+    nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT");
+
+    if (g_mkdir_with_parents(TEST_SCRATCH_DIR, 0755) != 0) {
+        errsv = errno;
+        g_error("failure to create test directory \"%s\": %s",
+                TEST_SCRATCH_DIR,
+                nm_strerror_native(errsv));
+    }
+
+    /* The tests */
+    g_test_add_func("/keyfile/test_read_valid_wired_connection", test_read_valid_wired_connection);
+    g_test_add_func("/keyfile/test_write_wired_connection", test_write_wired_connection);
+
+    g_test_add_func("/keyfile/test_read_ip6_wired_connection", test_read_ip6_wired_connection);
+    g_test_add_func("/keyfile/test_write_ip6_wired_connection", test_write_ip6_wired_connection);
+
+    g_test_add_func("/keyfile/test_read_wired_mac_case", test_read_wired_mac_case);
+    g_test_add_func("/keyfile/test_read_mac_old_format", test_read_mac_old_format);
+    g_test_add_func("/keyfile/test_read_mac_ib_old_format", test_read_mac_ib_old_format);
+
+    g_test_add_func("/keyfile/test_read_valid_wireless_connection",
+                    test_read_valid_wireless_connection);
+    g_test_add_func("/keyfile/test_write_wireless_connection", test_write_wireless_connection);
+
+    g_test_add_func("/keyfile/test_read_string_ssid", test_read_string_ssid);
+    g_test_add_func("/keyfile/test_write_string_ssid", test_write_string_ssid);
+
+    g_test_add_func("/keyfile/test_read_intlist_ssid", test_read_intlist_ssid);
+    g_test_add_func("/keyfile/test_write_intlist_ssid", test_write_intlist_ssid);
+
+    g_test_add_func("/keyfile/test_read_intlike_ssid", test_read_intlike_ssid);
+    g_test_add_func("/keyfile/test_write_intlike_ssid", test_write_intlike_ssid);
+
+    g_test_add_func("/keyfile/test_read_intlike_ssid_2", test_read_intlike_ssid_2);
+    g_test_add_func("/keyfile/test_write_intlike_ssid_2", test_write_intlike_ssid_2);
+
+    g_test_add_func("/keyfile/test_read_bt_dun_connection", test_read_bt_dun_connection);
+    g_test_add_func("/keyfile/test_write_bt_dun_connection", test_write_bt_dun_connection);
+
+    g_test_add_func("/keyfile/test_read_gsm_connection", test_read_gsm_connection);
+    g_test_add_func("/keyfile/test_write_gsm_connection", test_write_gsm_connection);
+
+    g_test_add_func("/keyfile/test_read_wired_8021x_tls_blob_connection",
+                    test_read_wired_8021x_tls_blob_connection);
+    g_test_add_func("/keyfile/test_read_wired_8021x_tls_bad_path_connection",
+                    test_read_wired_8021x_tls_bad_path_connection);
+
+    g_test_add_func("/keyfile/test_read_wired_8021x_tls_old_connection",
+                    test_read_wired_8021x_tls_old_connection);
+    g_test_add_func("/keyfile/test_read_wired_8021x_tls_new_connection",
+                    test_read_wired_8021x_tls_new_connection);
+    g_test_add_func("/keyfile/test_write_wired_8021x_tls_connection_path",
+                    test_write_wired_8021x_tls_connection_path);
+    g_test_add_func("/keyfile/test_write_wired_8021x_tls_connection_blob",
+                    test_write_wired_8021x_tls_connection_blob);
+
+    g_test_add_func("/keyfile/test_read_dcb_connection", test_read_dcb_connection);
+
+    g_test_add_func("/keyfile/test_read_infiniband_connection", test_read_infiniband_connection);
+    g_test_add_func("/keyfile/test_write_infiniband_connection", test_write_infiniband_connection);
+
+    g_test_add_func("/keyfile/test_read_bridge_main", test_read_bridge_main);
+    g_test_add_func("/keyfile/test_write_bridge_main", test_write_bridge_main);
+    g_test_add_func("/keyfile/test_read_bridge_component", test_read_bridge_component);
+    g_test_add_func("/keyfile/test_write_bridge_component", test_write_bridge_component);
+
+    g_test_add_func("/keyfile/test_read_new_wired_group_name", test_read_new_wired_group_name);
+    g_test_add_func("/keyfile/test_write_new_wired_group_name", test_write_new_wired_group_name);
+    g_test_add_func("/keyfile/test_read_new_wireless_group_names",
+                    test_read_new_wireless_group_names);
+    g_test_add_func("/keyfile/test_write_new_wireless_group_names",
+                    test_write_new_wireless_group_names);
+
+    g_test_add_func("/keyfile/test_read_missing_vlan_setting", test_read_missing_vlan_setting);
+    g_test_add_func("/keyfile/test_read_missing_vlan_flags", test_read_missing_vlan_flags);
+    g_test_add_func("/keyfile/test_read_missing_id_uuid", test_read_missing_id_uuid);
+
+    g_test_add_func("/keyfile/test_read_minimal", test_read_minimal);
+    g_test_add_func("/keyfile/test_read_minimal_slave", test_read_minimal_slave);
+
+    g_test_add_func("/keyfile/test_read_enum_property", test_read_enum_property);
+    g_test_add_func("/keyfile/test_write_enum_property", test_write_enum_property);
+    g_test_add_func("/keyfile/test_read_flags_property", test_read_flags_property);
+    g_test_add_func("/keyfile/test_write_flags_property", test_write_flags_property);
+
+    g_test_add_func("/keyfile/test_read_tc_config", test_read_tc_config);
+    g_test_add_func("/keyfile/test_write_tc_config", test_write_tc_config);
+
+    g_test_add_func("/keyfile/test_nm_keyfile_plugin_utils_escape_filename",
+                    test_nm_keyfile_plugin_utils_escape_filename);
+
+    g_test_add_func("/keyfile/test_nmmeta", test_nmmeta);
+
+    return g_test_run();
+}