summary refs log tree commit diff
path: root/shared
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-04-11 21:28:04 +0200
committerMichael Biebl <biebl@debian.org>2020-04-11 21:28:04 +0200
commit1e5977b62f896e844b548c3007ace9e1dfa7f9ed (patch)
tree7a7416ed410e72b6200f3d860fd315ec11cc106b /shared
parentb012fa6e1d808e0736c009799c62d835cbfcc1dd (diff)
New upstream version 1.23.90 upstream/1.23.90
Diffstat (limited to 'shared')
-rw-r--r--shared/meson.build28
-rw-r--r--shared/n-dhcp4/src/n-dhcp4-client.c56
-rw-r--r--shared/n-dhcp4/src/n-dhcp4-private.h1
-rw-r--r--shared/nm-glib-aux/nm-dbus-aux.c124
-rw-r--r--shared/nm-glib-aux/nm-dbus-aux.h37
-rw-r--r--shared/nm-glib-aux/nm-enum-utils.c23
-rw-r--r--shared/nm-glib-aux/nm-errno.c3
-rw-r--r--shared/nm-glib-aux/nm-glib.h55
-rw-r--r--shared/nm-glib-aux/nm-hash-utils.c112
-rw-r--r--shared/nm-glib-aux/nm-hash-utils.h23
-rw-r--r--shared/nm-glib-aux/nm-io-utils.c76
-rw-r--r--shared/nm-glib-aux/nm-io-utils.h4
-rw-r--r--shared/nm-glib-aux/nm-jansson.h2
-rw-r--r--shared/nm-glib-aux/nm-logging-fwd.h8
-rw-r--r--shared/nm-glib-aux/nm-macros-internal.h204
-rw-r--r--shared/nm-glib-aux/nm-ref-string.h12
-rw-r--r--shared/nm-glib-aux/nm-secret-utils.c28
-rw-r--r--shared/nm-glib-aux/nm-secret-utils.h130
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.c856
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.h495
-rw-r--r--shared/nm-glib-aux/nm-str-buf.h332
-rw-r--r--shared/nm-glib-aux/nm-time-utils.c90
-rw-r--r--shared/nm-glib-aux/nm-time-utils.h36
-rw-r--r--shared/nm-glib-aux/tests/meson.build (renamed from shared/nm-utils/tests/meson.build)4
-rw-r--r--shared/nm-glib-aux/tests/test-shared-general.c (renamed from shared/nm-utils/tests/test-shared-general.c)224
-rw-r--r--shared/nm-keyfile/nm-keyfile-internal.h180
-rw-r--r--shared/nm-keyfile/nm-keyfile-utils.c636
-rw-r--r--shared/nm-keyfile/nm-keyfile-utils.h85
-rw-r--r--shared/nm-keyfile/nm-keyfile.c4089
-rw-r--r--shared/nm-libnm-aux/nm-libnm-aux.c146
-rw-r--r--shared/nm-libnm-aux/nm-libnm-aux.h24
-rw-r--r--shared/nm-libnm-core-intern/nm-auth-subject.c446
-rw-r--r--shared/nm-libnm-core-intern/nm-auth-subject.h56
-rw-r--r--shared/nm-libnm-core-intern/nm-ethtool-utils.h2
-rw-r--r--shared/nm-libnm-core-intern/nm-libnm-core-utils.c119
-rw-r--r--shared/nm-libnm-core-intern/nm-libnm-core-utils.h13
-rw-r--r--shared/nm-meta-setting.c7
-rw-r--r--shared/nm-meta-setting.h1
-rw-r--r--shared/nm-std-aux/nm-dbus-compat.h61
-rw-r--r--shared/nm-test-libnm-utils.h16
-rw-r--r--shared/nm-test-utils-impl.c276
-rw-r--r--shared/nm-udev-aux/nm-udev-utils.c20
-rw-r--r--shared/nm-utils/nm-test-utils.h277
-rw-r--r--shared/nm-utils/nm-vpn-editor-plugin-call.h2
-rw-r--r--shared/nm-version-macros.h7
-rw-r--r--shared/nm-version-macros.h.in3
-rw-r--r--shared/systemd/sd-adapt-shared/arphrd-list.h (renamed from shared/systemd/sd-adapt-shared/missing_syscall.h)0
-rw-r--r--shared/systemd/sd-adapt-shared/missing_keyctl.h3
-rw-r--r--shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h22
-rw-r--r--shared/systemd/src/basic/cgroup-util.h254
-rw-r--r--shared/systemd/src/basic/errno-util.h8
-rw-r--r--shared/systemd/src/basic/escape.c22
-rw-r--r--shared/systemd/src/basic/escape.h15
-rw-r--r--shared/systemd/src/basic/extract-word.c2
-rw-r--r--shared/systemd/src/basic/fileio.c162
-rw-r--r--shared/systemd/src/basic/fileio.h10
-rw-r--r--shared/systemd/src/basic/format-util.h39
-rw-r--r--shared/systemd/src/basic/fs-util.c99
-rw-r--r--shared/systemd/src/basic/fs-util.h1
-rw-r--r--shared/systemd/src/basic/in-addr-util.c48
-rw-r--r--shared/systemd/src/basic/in-addr-util.h2
-rw-r--r--shared/systemd/src/basic/log.h5
-rw-r--r--shared/systemd/src/basic/macro.h28
-rw-r--r--shared/systemd/src/basic/memory-util.h20
-rw-r--r--shared/systemd/src/basic/missing_syscall.h574
-rw-r--r--shared/systemd/src/basic/parse-util.c74
-rw-r--r--shared/systemd/src/basic/parse-util.h13
-rw-r--r--shared/systemd/src/basic/path-util.c28
-rw-r--r--shared/systemd/src/basic/path-util.h15
-rw-r--r--shared/systemd/src/basic/process-util.c133
-rw-r--r--shared/systemd/src/basic/process-util.h32
-rw-r--r--shared/systemd/src/basic/random-util.c14
-rw-r--r--shared/systemd/src/basic/signal-util.c308
-rw-r--r--shared/systemd/src/basic/signal-util.h2
-rw-r--r--shared/systemd/src/basic/socket-util.c271
-rw-r--r--shared/systemd/src/basic/socket-util.h19
-rw-r--r--shared/systemd/src/basic/stat-util.c6
-rw-r--r--shared/systemd/src/basic/string-table.h2
-rw-r--r--shared/systemd/src/basic/string-util.c204
-rw-r--r--shared/systemd/src/basic/string-util.h7
-rw-r--r--shared/systemd/src/basic/strv.c123
-rw-r--r--shared/systemd/src/basic/strv.h48
-rw-r--r--shared/systemd/src/basic/time-util.c27
-rw-r--r--shared/systemd/src/basic/tmpfile-util.c60
-rw-r--r--shared/systemd/src/shared/web-util.c59
-rw-r--r--shared/systemd/src/shared/web-util.h12
86 files changed, 10824 insertions, 1376 deletions
diff --git a/shared/meson.build b/shared/meson.build
index 5f96d49d..9a8768f5 100644
--- a/shared/meson.build
+++ b/shared/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 shared_inc = include_directories('.')
 
 nm_default_dep = declare_dependency(include_directories: [top_inc, shared_inc])
@@ -111,12 +113,21 @@ nm_libnm_core_aux_source = files('nm-libnm-core-aux/nm-libnm-core-aux.c')
 
 nm_libnm_core_utils_source = files('nm-libnm-core-intern/nm-libnm-core-utils.c')
 
+nm_keyfile_source = files(
+  'nm-keyfile/nm-keyfile-utils.c',
+  'nm-keyfile/nm-keyfile.c',
+)
+
+nm_libnm_aux_source = files('nm-libnm-aux/nm-libnm-aux.c')
+
 nm_meta_setting_source = files('nm-meta-setting.c')
 
 nm_test_utils_impl_source = files('nm-test-utils-impl.c')
 
 nm_vpn_plugin_utils_source = files('nm-utils/nm-vpn-plugin-utils.c')
 
+nm_auth_subject_source = files('nm-libnm-core-intern/nm-auth-subject.c')
+
 c_flags = [
   '-DG_LOG_DOMAIN="@0@"'.format(libnm_name),
   '-DNETWORKMANAGER_COMPILATION=0',
@@ -168,15 +179,14 @@ libnm_utils_base_dep = declare_dependency(
   link_with: libnm_utils_base,
 )
 
-deps = [
-  glib_nm_default_dep,
-  libudev_dep,
-]
 
 libnm_udev_aux = static_library(
   'nm-udev-aux',
   sources: 'nm-udev-aux/nm-udev-utils.c',
-  dependencies: deps,
+  dependencies: [
+    glib_nm_default_dep,
+    libudev_dep,
+  ],
   c_args: c_flags,
 )
 
@@ -186,10 +196,11 @@ libnm_udev_aux_dep = declare_dependency(
 )
 
 sources = files(
+  'systemd/nm-sd-utils-shared.c',
   'systemd/src/basic/alloc-util.c',
-  'systemd/src/basic/escape.c',
   'systemd/src/basic/env-file.c',
   'systemd/src/basic/env-util.c',
+  'systemd/src/basic/escape.c',
   'systemd/src/basic/ether-addr-util.c',
   'systemd/src/basic/extract-word.c',
   'systemd/src/basic/fd-util.c',
@@ -209,6 +220,7 @@ sources = files(
   'systemd/src/basic/prioq.c',
   'systemd/src/basic/process-util.c',
   'systemd/src/basic/random-util.c',
+  'systemd/src/basic/signal-util.c',
   'systemd/src/basic/socket-util.c',
   'systemd/src/basic/stat-util.c',
   'systemd/src/basic/string-table.c',
@@ -220,7 +232,7 @@ sources = files(
   'systemd/src/basic/utf8.c',
   'systemd/src/basic/util.c',
   'systemd/src/shared/dns-domain.c',
-  'systemd/nm-sd-utils-shared.c',
+  'systemd/src/shared/web-util.c',
 )
 
 incs = include_directories(
@@ -256,5 +268,5 @@ libnm_systemd_logging_stub = static_library(
 )
 
 if enable_tests
-  subdir('nm-utils/tests')
+  subdir('nm-glib-aux/tests')
 endif
diff --git a/shared/n-dhcp4/src/n-dhcp4-client.c b/shared/n-dhcp4/src/n-dhcp4-client.c
index 4fa3d65d..6b015e81 100644
--- a/shared/n-dhcp4/src/n-dhcp4-client.c
+++ b/shared/n-dhcp4/src/n-dhcp4-client.c
@@ -388,14 +388,10 @@ _c_public_ int n_dhcp4_client_new(NDhcp4Client **clientp, NDhcp4ClientConfig *co
                 return -errno;
 
         client->fd_timer = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK);
-        if (client->fd_timer < 0) {
-                if (errno != EINVAL)
-                        return -errno;
+        if (client->fd_timer < 0 && errno == EINVAL)
                 client->fd_timer = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
-                if (client->fd_timer < 0)
-                        return -errno;
-                client->timerfd_is_monotonic = true;
-        }
+        if (client->fd_timer < 0)
+                return -errno;
 
         ev.data.u32 = N_DHCP4_CLIENT_EPOLL_TIMER;
         r = epoll_ctl(client->fd_epoll, EPOLL_CTL_ADD, client->fd_timer, &ev);
@@ -498,41 +494,45 @@ int n_dhcp4_client_raise(NDhcp4Client *client, NDhcp4CEventNode **nodep, unsigne
  * must be called whenever a timeout on @client might have changed.
  */
 void n_dhcp4_client_arm_timer(NDhcp4Client *client) {
-        uint64_t timeout = 0;
+        uint64_t now, offset, timeout = 0;
         int r;
 
         if (client->current_probe)
                 n_dhcp4_client_probe_get_timeout(client->current_probe, &timeout);
 
         if (timeout != client->scheduled_timeout) {
-                uint64_t scheduled_timeout = timeout;
-                int flags = TFD_TIMER_ABSTIME;
-
-                if (   timeout != 0
-                    && client->timerfd_is_monotonic) {
-                        uint64_t now;
-
-                        /* the timerfd ticks with CLOCK_MONOTONIC. Calculate and set the relative
-                         * timeout. */
-                        now = n_dhcp4_gettime(CLOCK_BOOTTIME);
-                        if (timeout <= now)
-                                timeout = 1;
-                        else
-                                timeout = timeout - now;
-                        flags = 0;
-                }
+                /*
+                 * Across our codebase, timeouts are specified as absolute
+                 * timestamps on CLOCK_BOOTTIME. Unfortunately, there are
+                 * systems with CLOCK_BOOTTIME support, but timerfd lacks it
+                 * (in particular RHEL). Therefore, our timerfd might be on
+                 * CLOCK_MONOTONIC.
+                 * To account for this, we always schedule a relative timeout.
+                 * We fetch the current time and then calculate the offset
+                 * which we then schedule as relative timeout on the timerfd.
+                 * This works regardless which clock the timerfd runs on.
+                 * Once we no longer support CLOCK_MONOTONIC as fallback, we
+                 * can simply switch to TFD_TIMER_ABSTIME here and specify
+                 * `timeout` directly as value.
+                 */
+                now = n_dhcp4_gettime(CLOCK_BOOTTIME);
+                if (now >= timeout)
+                        offset = 1; /* 0 would disarm the timerfd */
+                else
+                        offset = timeout - now;
+
                 r = timerfd_settime(client->fd_timer,
-                                    flags,
+                                    0,
                                     &(struct itimerspec){
                                         .it_value = {
-                                                .tv_sec = timeout / UINT64_C(1000000000),
-                                                .tv_nsec = timeout % UINT64_C(1000000000),
+                                                .tv_sec = offset / UINT64_C(1000000000),
+                                                .tv_nsec = offset % UINT64_C(1000000000),
                                         },
                                     },
                                     NULL);
                 c_assert(r >= 0);
 
-                client->scheduled_timeout = scheduled_timeout;
+                client->scheduled_timeout = timeout;
         }
 }
 
diff --git a/shared/n-dhcp4/src/n-dhcp4-private.h b/shared/n-dhcp4/src/n-dhcp4-private.h
index f647cb5e..e285d95d 100644
--- a/shared/n-dhcp4/src/n-dhcp4-private.h
+++ b/shared/n-dhcp4/src/n-dhcp4-private.h
@@ -332,7 +332,6 @@ struct NDhcp4Client {
         uint64_t scheduled_timeout;
 
         bool preempted : 1;
-        bool timerfd_is_monotonic : 1;
 };
 
 #define N_DHCP4_CLIENT_NULL(_x) {                                               \
diff --git a/shared/nm-glib-aux/nm-dbus-aux.c b/shared/nm-glib-aux/nm-dbus-aux.c
index 86061aef..54e54ca7 100644
--- a/shared/nm-glib-aux/nm-dbus-aux.c
+++ b/shared/nm-glib-aux/nm-dbus-aux.c
@@ -56,7 +56,7 @@ nm_dbus_connection_call_get_name_owner (GDBusConnection *dbus_connection,
 /*****************************************************************************/
 
 static void
-_nm_dbus_connection_call_get_all_cb (GObject *source,
+_nm_dbus_connection_call_default_cb (GObject *source,
                                      GAsyncResult *res,
                                      gpointer user_data)
 {
@@ -96,100 +96,36 @@ nm_dbus_connection_call_get_all (GDBusConnection *dbus_connection,
 	                        G_DBUS_CALL_FLAGS_NONE,
 	                        timeout_msec,
 	                        cancellable,
-	                        _nm_dbus_connection_call_get_all_cb,
+	                        _nm_dbus_connection_call_default_cb,
 	                        nm_utils_user_data_pack (user_data, callback));
 }
 
-/*****************************************************************************/
-
-typedef struct {
-	NMDBusConnectionSignalObjectMangerCb callback;
-	gpointer user_data;
-	GDestroyNotify user_data_free_func;
-} SubscribeObjectManagerData;
-
-static void
-_subscribe_object_manager_cb (GDBusConnection *connection,
-                              const char *sender_name,
-                              const char *arg_object_path,
-                              const char *interface_name,
-                              const char *signal_name,
-                              GVariant *parameters,
-                              gpointer user_data)
-{
-	const SubscribeObjectManagerData *d = user_data;
-
-	nm_assert (nm_streq0 (interface_name, DBUS_INTERFACE_OBJECT_MANAGER));
-
-	if (nm_streq (signal_name, "InterfacesAdded")) {
-		gs_unref_variant GVariant *interfaces_and_properties = NULL;
-		const char *object_path;
-
-		if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oa{sa{sv}})")))
-			return;
-
-		g_variant_get (parameters,
-		               "(&o@a{sa{sv}})",
-		               &object_path,
-		               &interfaces_and_properties);
-
-		d->callback (object_path, interfaces_and_properties, NULL, d->user_data);
-		return;
-	}
-
-	if (nm_streq (signal_name, "InterfacesRemoved")) {
-		gs_free const char **interfaces = NULL;
-		const char *object_path;
-
-		if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oas)")))
-			return;
-
-		g_variant_get (parameters,
-		               "(&o^a&s)",
-		               &object_path,
-		               &interfaces);
-
-		d->callback (object_path, NULL, interfaces, d->user_data);
-		return;
-	}
-}
-
-static void
-_subscribe_object_manager_data_free (gpointer ptr)
-{
-	SubscribeObjectManagerData *d = ptr;
-
-	if (d->user_data_free_func)
-		d->user_data_free_func (d->user_data);
-	nm_g_slice_free (d);
-}
-
-guint
-nm_dbus_connection_signal_subscribe_object_manager (GDBusConnection *dbus_connection,
-                                                    const char *service_name,
-                                                    const char *object_path,
-                                                    NMDBusConnectionSignalObjectMangerCb callback,
-                                                    gpointer user_data,
-                                                    GDestroyNotify user_data_free_func)
+void nm_dbus_connection_call_set (GDBusConnection *dbus_connection,
+                                  const char *bus_name,
+                                  const char *object_path,
+                                  const char *interface_name,
+                                  const char *property_name,
+                                  GVariant *value,
+                                  int timeout_msec,
+                                  GCancellable *cancellable,
+                                  NMDBusConnectionCallDefaultCb callback,
+                                  gpointer user_data)
 {
-	SubscribeObjectManagerData *d;
-
-	g_return_val_if_fail (callback, 0);
-
-	d = g_slice_new (SubscribeObjectManagerData);
-	*d = (SubscribeObjectManagerData) {
-		.callback            = callback,
-		.user_data           = user_data,
-		.user_data_free_func = user_data_free_func,
-	};
-
-	return nm_dbus_connection_signal_subscribe_object_manager_plain (dbus_connection,
-	                                                                 service_name,
-	                                                                 object_path,
-	                                                                 NULL,
-	                                                                 _subscribe_object_manager_cb,
-	                                                                 d,
-	                                                                 _subscribe_object_manager_data_free);
+	g_dbus_connection_call (dbus_connection,
+	                        bus_name,
+	                        object_path,
+	                        DBUS_INTERFACE_PROPERTIES,
+	                        "Set",
+	                        g_variant_new ("(ssv)",
+	                                       interface_name,
+	                                       property_name,
+	                                       value),
+	                        G_VARIANT_TYPE ("()"),
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        timeout_msec,
+	                        cancellable,
+	                        callback ? _nm_dbus_connection_call_default_cb : NULL,
+	                        callback ? nm_utils_user_data_pack (user_data, callback) : NULL);
 }
 
 /*****************************************************************************/
@@ -269,10 +205,9 @@ _call_finish_cb (GObject *source,
 		return;
 	}
 
-	if (!return_void) {
-		nm_assert (!g_variant_is_of_type (ret, G_VARIANT_TYPE ("()")));
+	if (!return_void)
 		g_task_return_pointer (task, g_steal_pointer (&ret), (GDestroyNotify) g_variant_unref);
-	} else {
+	else {
 		nm_assert (g_variant_is_of_type (ret, G_VARIANT_TYPE ("()")));
 		g_task_return_boolean (task, TRUE);
 	}
@@ -317,7 +252,6 @@ nm_dbus_connection_call_finish_void_strip_dbus_error_cb (GObject *source,
  *
  * - user_data must be a GTask, whose reference will be consumed by the
  *   callback.
- * - the return GVariant must not be an empty tuple "()".
  * - the GTask is returned either with error or with a pointer containing the GVariant.
  */
 void
diff --git a/shared/nm-glib-aux/nm-dbus-aux.h b/shared/nm-glib-aux/nm-dbus-aux.h
index 840e23c2..fcf394d1 100644
--- a/shared/nm-glib-aux/nm-dbus-aux.h
+++ b/shared/nm-glib-aux/nm-dbus-aux.h
@@ -128,16 +128,27 @@ void nm_dbus_connection_call_get_all (GDBusConnection *dbus_connection,
                                       NMDBusConnectionCallDefaultCb callback,
                                       gpointer user_data);
 
+void nm_dbus_connection_call_set (GDBusConnection *dbus_connection,
+                                  const char *bus_name,
+                                  const char *object_path,
+                                  const char *interface_name,
+                                  const char *property_name,
+                                  GVariant *value,
+                                  int timeout_msec,
+                                  GCancellable *cancellable,
+                                  NMDBusConnectionCallDefaultCb callback,
+                                  gpointer user_data);
+
 /*****************************************************************************/
 
 static inline guint
-nm_dbus_connection_signal_subscribe_object_manager_plain (GDBusConnection *dbus_connection,
-                                                          const char *service_name,
-                                                          const char *object_path,
-                                                          const char *signal_name,
-                                                          GDBusSignalCallback callback,
-                                                          gpointer user_data,
-                                                          GDestroyNotify user_data_free_func)
+nm_dbus_connection_signal_subscribe_object_manager (GDBusConnection *dbus_connection,
+                                                    const char *service_name,
+                                                    const char *object_path,
+                                                    const char *signal_name,
+                                                    GDBusSignalCallback callback,
+                                                    gpointer user_data,
+                                                    GDestroyNotify user_data_free_func)
 {
 	return g_dbus_connection_signal_subscribe (dbus_connection,
 	                                           service_name,
@@ -151,18 +162,6 @@ nm_dbus_connection_signal_subscribe_object_manager_plain (GDBusConnection *dbus_
 	                                           user_data_free_func);
 }
 
-typedef void (*NMDBusConnectionSignalObjectMangerCb) (const char *object_path,
-                                                      GVariant *added_interfaces_and_properties,
-                                                      const char *const*removed_interfaces,
-                                                      gpointer user_data);
-
-guint nm_dbus_connection_signal_subscribe_object_manager (GDBusConnection *dbus_connection,
-                                                          const char *service_name,
-                                                          const char *object_path,
-                                                          NMDBusConnectionSignalObjectMangerCb callback,
-                                                          gpointer user_data,
-                                                          GDestroyNotify user_data_free_func);
-
 void nm_dbus_connection_call_get_managed_objects (GDBusConnection *dbus_connection,
                                                   const char *bus_name,
                                                   const char *object_path,
diff --git a/shared/nm-glib-aux/nm-enum-utils.c b/shared/nm-glib-aux/nm-enum-utils.c
index e105a4f5..854eda6e 100644
--- a/shared/nm-glib-aux/nm-enum-utils.c
+++ b/shared/nm-glib-aux/nm-enum-utils.c
@@ -6,6 +6,7 @@
 #include "nm-default.h"
 
 #include "nm-enum-utils.h"
+#include "nm-str-buf.h"
 
 /*****************************************************************************/
 
@@ -142,12 +143,14 @@ _nm_utils_enum_to_str_full (GType type,
 		else
 			return g_strdup (enum_value->value_nick);
 	} else if (G_IS_FLAGS_CLASS (klass)) {
-		GFlagsValue *flags_value;
-		GString *str = g_string_new ("");
 		unsigned uvalue = (unsigned) value;
+		GFlagsValue *flags_value;
+		NMStrBuf strbuf;
 
 		flags_separator = flags_separator ?: " ";
 
+		nm_str_buf_init (&strbuf, 16, FALSE);
+
 		for ( ; value_infos && value_infos->nick; value_infos++) {
 
 			nm_assert (_enum_is_valid_flags_nick (value_infos->nick));
@@ -160,9 +163,9 @@ _nm_utils_enum_to_str_full (GType type,
 					continue;
 			}
 
-			if (str->len)
-				g_string_append (str, flags_separator);
-			g_string_append (str, value_infos->nick);
+			if (strbuf.len)
+				nm_str_buf_append (&strbuf, flags_separator);
+			nm_str_buf_append (&strbuf, value_infos->nick);
 			uvalue &= ~((unsigned) value_infos->value);
 			if (uvalue == 0) {
 				/* we printed all flags. Done. */
@@ -172,20 +175,20 @@ _nm_utils_enum_to_str_full (GType type,
 
 		do {
 			flags_value = g_flags_get_first_value (G_FLAGS_CLASS (klass), uvalue);
-			if (str->len)
-				g_string_append (str, flags_separator);
+			if (strbuf.len)
+				nm_str_buf_append (&strbuf, flags_separator);
 			if (   !flags_value
 			    || !_enum_is_valid_flags_nick (flags_value->value_nick)) {
 				if (uvalue)
-					g_string_append_printf (str, "0x%x", uvalue);
+					nm_str_buf_append_printf (&strbuf, "0x%x", uvalue);
 				break;
 			}
-			g_string_append (str, flags_value->value_nick);
+			nm_str_buf_append (&strbuf, flags_value->value_nick);
 			uvalue &= ~flags_value->value;
 		} while (uvalue);
 
 flags_done:
-		return g_string_free (str, FALSE);
+		return nm_str_buf_finalize (&strbuf, NULL);
 	}
 
 	g_return_val_if_reached (NULL);
diff --git a/shared/nm-glib-aux/nm-errno.c b/shared/nm-glib-aux/nm-errno.c
index 9133d87d..e709f9e6 100644
--- a/shared/nm-glib-aux/nm-errno.c
+++ b/shared/nm-glib-aux/nm-errno.c
@@ -11,7 +11,8 @@
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_geterror,
 #if 0
 	enum _NMErrno,
 #else
diff --git a/shared/nm-glib-aux/nm-glib.h b/shared/nm-glib-aux/nm-glib.h
index 26d0eacd..4ecba9ff 100644
--- a/shared/nm-glib-aux/nm-glib.h
+++ b/shared/nm-glib-aux/nm-glib.h
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0+
+// SPDX-License-Identifier: LGPL-2.1+
 /*
  * Copyright (C) 2008 - 2018 Red Hat, Inc.
  */
@@ -193,22 +193,6 @@ __g_test_add_data_func_full (const char     *testpath,
 
 /*****************************************************************************/
 
-#if !GLIB_CHECK_VERSION (2, 34, 0)
-#define G_DEFINE_QUARK(QN, q_n)               \
-GQuark                                        \
-q_n##_quark (void)                            \
-{                                             \
-	static GQuark q;                          \
-                                              \
-	if G_UNLIKELY (q == 0)                    \
-		q = g_quark_from_static_string (#QN); \
-                                              \
-	return q;                                 \
-}
-#endif
-
-/*****************************************************************************/
-
 static inline gboolean
 nm_g_hash_table_replace (GHashTable *hash, gpointer key, gpointer value)
 {
@@ -601,4 +585,41 @@ _g_atomic_pointer_compare_and_exchange (volatile void *atomic,
 
 /*****************************************************************************/
 
+#if !GLIB_CHECK_VERSION (2, 58, 0)
+static inline gboolean
+g_hash_table_steal_extended (GHashTable    *hash_table,
+                             gconstpointer  lookup_key,
+                             gpointer      *stolen_key,
+                             gpointer      *stolen_value)
+{
+	g_assert (stolen_key);
+	g_assert (stolen_value);
+
+	if (g_hash_table_lookup_extended (hash_table, lookup_key, stolen_key, stolen_value)) {
+		g_hash_table_steal (hash_table, lookup_key);
+		return TRUE;
+	}
+	*stolen_key = NULL;
+	*stolen_value = NULL;
+	return FALSE;
+}
+#else
+#define g_hash_table_steal_extended(hash_table, lookup_key, stolen_key, stolen_value) \
+	({ \
+		gpointer *_stolen_key = (stolen_key); \
+		gpointer *_stolen_value = (stolen_value); \
+		\
+		/* we cannot allow NULL arguments, because then we would leak the values in
+		 * the compat implementation. */ \
+		g_assert (_stolen_key); \
+		g_assert (_stolen_value); \
+		\
+		G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
+		g_hash_table_steal_extended (hash_table, lookup_key, _stolen_key, _stolen_value); \
+		G_GNUC_END_IGNORE_DEPRECATIONS \
+	})
+#endif
+
+/*****************************************************************************/
+
 #endif  /* __NM_GLIB_H__ */
diff --git a/shared/nm-glib-aux/nm-hash-utils.c b/shared/nm-glib-aux/nm-hash-utils.c
index cd51bbaf..0a701d06 100644
--- a/shared/nm-glib-aux/nm-hash-utils.c
+++ b/shared/nm-glib-aux/nm-hash-utils.c
@@ -24,7 +24,6 @@ static const guint8 *volatile global_seed = NULL;
 static const guint8 *
 _get_hash_key_init (void)
 {
-	static gsize g_lock;
 	/* the returned hash is aligned to guin64, hence, it is safe
 	 * to use it as guint* or guint64* pointer. */
 	static union {
@@ -34,50 +33,49 @@ _get_hash_key_init (void)
 		guint64 _align_as_uint64;
 	} g_arr;
 	const guint8 *g;
-	union {
-		guint8 v8[HASH_KEY_SIZE];
-		guint vuint;
-	} t_arr;
 
 again:
 	g = g_atomic_pointer_get (&global_seed);
-	if (G_LIKELY (g != NULL)) {
-		nm_assert (g == g_arr.v8);
-		return g;
-	}
-
-	{
-		CSipHash siph_state;
+	if (!G_UNLIKELY (g)) {
+		static gsize g_lock;
 		uint64_t h;
-
-		/* initialize a random key in t_arr. */
+		union {
+			guint vuint;
+			guint8 v8[HASH_KEY_SIZE];
+			guint8 _extra_entropy[3 * HASH_KEY_SIZE];
+		} t_arr;
 
 		nm_utils_random_bytes (&t_arr, sizeof (t_arr));
 
-		/* use siphash() of the key-size, to mangle the first guint. Otherwise,
-		 * the first guint has only the entropy that nm_utils_random_bytes()
-		 * generated for the first 4 bytes and relies on a good random generator.
+		/* We only initialize one random hash key. So we can spend some effort
+		 * of getting this right. For one, we collect more random bytes than
+		 * necessary.
 		 *
-		 * The first int is especially interesting for nm_hash_static() below, and we
-		 * want to have it all the entropy of t_arr. */
-		c_siphash_init (&siph_state, t_arr.v8);
-		c_siphash_append (&siph_state, (const guint8 *) &t_arr, sizeof (t_arr));
-		h = c_siphash_finalize (&siph_state);
-		if (sizeof (guint) < sizeof (h))
-			t_arr.vuint = t_arr.vuint ^ ((guint) (h & 0xFFFFFFFFu)) ^ ((guint) (h >> 32));
+		 * Then, the first guint of the seed should have all the entropy that we could
+		 * obtain in sizeof(t_arr). For that, siphash(t_arr) and xor the first guint
+		 * with hash.
+		 * The first guint is especially interesting for nm_hash_static() below that
+		 * doesn't use siphash itself. */
+		h = c_siphash_hash (t_arr.v8,
+		                    (const guint8 *) &t_arr,
+		                    sizeof (t_arr));
+		if (sizeof (h) > sizeof (guint))
+			t_arr.vuint = t_arr.vuint ^ ((guint) (h & G_MAXUINT)) ^ ((guint) (h >> 32));
 		else
-			t_arr.vuint = t_arr.vuint ^ ((guint) (h & 0xFFFFFFFFu));
-	}
+			t_arr.vuint = t_arr.vuint ^ ((guint) (h & G_MAXUINT));
 
-	if (!g_once_init_enter (&g_lock)) {
-		/* lost a race. The random key is already initialized. */
-		goto again;
+		if (!g_once_init_enter (&g_lock)) {
+			/* lost a race. The random key is already initialized. */
+			goto again;
+		}
+
+		memcpy (g_arr.v8, t_arr.v8, HASH_KEY_SIZE);
+		g = g_arr.v8;
+		g_atomic_pointer_set (&global_seed, g);
+		g_once_init_leave (&g_lock, 1);
 	}
 
-	memcpy (g_arr.v8, t_arr.v8, HASH_KEY_SIZE);
-	g = g_arr.v8;
-	g_atomic_pointer_set (&global_seed, g);
-	g_once_init_leave (&g_lock, 1);
+	nm_assert (g == g_arr.v8);
 	return g;
 }
 
@@ -94,18 +92,24 @@ again:
 guint
 nm_hash_static (guint static_seed)
 {
-	/* note that we only xor the static_seed with the key.
-	 * We don't use siphash, which would mix the bits better.
-	 * Note that this doesn't matter, because static_seed is not
-	 * supposed to be a value that you are hashing (for that, use
-	 * full siphash).
-	 * Instead, different callers may set a different static_seed
-	 * so that nm_hash_str(NULL) != nm_hash_ptr(NULL).
+	/* Note that we only xor the static_seed with the first guint of the key.
+	 *
+	 * We don't use siphash, which would mix the bits better with _get_hash_key().
+	 * Note that nm_hash_static() isn't used to hash the static_seed. Instead, it
+	 * is used to get a unique hash value in a static context. That means, every
+	 * caller is responsible to choose a static_seed that is sufficiently
+	 * distinct from all other callers. In other words, static_seed should be a
+	 * unique constant with good entropy.
+	 *
+	 * Note that _get_hash_key_init() already xored the first guint of the
+	 * key with the siphash of the entire static key. That means, even if
+	 * we got bad randomness for the first guint, the first guint is also
+	 * mixed with the randomness of the entire random key.
 	 *
-	 * Also, ensure that we don't return zero.
+	 * Also, ensure that we don't return zero (like for nm_hash_complete()).
 	 */
-	return ((*((const guint *) _get_hash_key ())) ^ static_seed)
-	       ?: static_seed ?: 3679500967u;
+	return    ((*((const guint *) _get_hash_key ())) ^ static_seed)
+	       ?: 3679500967u;
 }
 
 void
@@ -257,3 +261,25 @@ nm_ppdirect_equal (gconstpointer a, gconstpointer b)
 
 	return **s1 == **s2;
 }
+
+/*****************************************************************************/
+
+guint
+nm_pgbytes_hash (gconstpointer p)
+{
+	GBytes *const*ptr = p;
+	gconstpointer arr;
+	gsize len;
+
+	arr = g_bytes_get_data (*ptr, &len);
+	return nm_hash_mem (1470631313u, arr, len);
+}
+
+gboolean
+nm_pgbytes_equal (gconstpointer a, gconstpointer b)
+{
+	GBytes *const*ptr_a = a;
+	GBytes *const*ptr_b = b;
+
+	return g_bytes_equal (*ptr_a, *ptr_b);
+}
diff --git a/shared/nm-glib-aux/nm-hash-utils.h b/shared/nm-glib-aux/nm-hash-utils.h
index 21c5e584..9f2e9766 100644
--- a/shared/nm-glib-aux/nm-hash-utils.h
+++ b/shared/nm-glib-aux/nm-hash-utils.h
@@ -88,15 +88,15 @@ nm_hash_complete (NMHashState *state)
 	/* we don't ever want to return a zero hash.
 	 *
 	 * NMPObject requires that in _idx_obj_part(), and it's just a good idea. */
-	return (((guint) (h >> 32)) ^ ((guint) h)) ?: 1396707757u;
+	return    (((guint) (h >> 32)) ^ ((guint) h))
+	       ?: 1396707757u;
 }
 
 static inline void
 nm_hash_update (NMHashState *state, const void *ptr, gsize n)
 {
 	nm_assert (state);
-	nm_assert (ptr);
-	nm_assert (n > 0);
+	nm_assert (n == 0 || ptr);
 
 	/* Note: the data passed in here might be sensitive data (secrets),
 	 * that we should nm_explicty_zero() afterwards. However, since
@@ -267,6 +267,18 @@ guint nm_str_hash (gconstpointer str);
 		nm_hash_complete (&_h); \
 	})
 
+static inline guint
+nm_hash_mem (guint static_seed, const void *ptr, gsize n)
+{
+	NMHashState h;
+
+	if (n == 0)
+		return nm_hash_static (static_seed);
+	nm_hash_init (&h, static_seed);
+	nm_hash_update (&h, ptr, n);
+	return nm_hash_complete (&h);
+}
+
 /*****************************************************************************/
 
 /* nm_pstr_*() are for hashing keys that are pointers to strings,
@@ -303,6 +315,11 @@ gboolean nm_ppdirect_equal (gconstpointer a, gconstpointer b);
 
 /*****************************************************************************/
 
+guint nm_pgbytes_hash (gconstpointer p);
+gboolean nm_pgbytes_equal (gconstpointer a, gconstpointer b);
+
+/*****************************************************************************/
+
 #define NM_HASH_OBFUSCATE_PTR_FMT "%016" G_GINT64_MODIFIER "x"
 
 /* sometimes we want to log a pointer directly, for providing context/information about
diff --git a/shared/nm-glib-aux/nm-io-utils.c b/shared/nm-glib-aux/nm-io-utils.c
index ec0cd4c8..776c63e1 100644
--- a/shared/nm-glib-aux/nm-io-utils.c
+++ b/shared/nm-glib-aux/nm-io-utils.c
@@ -11,6 +11,7 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 
+#include "nm-str-buf.h"
 #include "nm-shared-utils.h"
 #include "nm-secret-utils.h"
 #include "nm-errno.h"
@@ -51,32 +52,6 @@ _get_contents_error (GError **error, int errsv, int *out_errsv, const char *form
 		_get_contents_error (error, _errsv, out_errsv, __VA_ARGS__); \
 	})
 
-static char *
-_mem_realloc (char *old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
-{
-	char *new;
-
-	/* re-allocating to zero bytes is an odd case. We don't need it
-	 * and it's not supported. */
-	nm_assert (new_len > 0);
-
-	/* regardless of success/failure, @old will always be freed/consumed. */
-
-	if (do_bzero_mem && cur_len > 0) {
-		new = g_try_malloc (new_len);
-		if (new)
-			memcpy (new, old, NM_MIN (cur_len, new_len));
-		nm_explicit_bzero (old, cur_len);
-		g_free (old);
-	} else {
-		new = g_try_realloc (old, new_len);
-		if (!new)
-			g_free (old);
-	}
-
-	return new;
-}
-
 /**
  * nm_utils_fd_get_contents:
  * @fd: open file descriptor to read. The fd will not be closed,
@@ -161,7 +136,7 @@ nm_utils_fd_get_contents (int fd,
 		str[n_read] = '\0';
 
 		if (n_read < n_stat) {
-			if (!(str = _mem_realloc (str, do_bzero_mem, n_stat + 1, n_read + 1)))
+			if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, n_stat + 1, n_read + 1)))
 				return _get_contents_error (error, ENOMEM, out_errsv, "failure to reallocate buffer with %zu bytes", n_read + 1);
 		}
 		NM_SET_OUT (length, n_read);
@@ -222,7 +197,7 @@ nm_utils_fd_get_contents (int fd,
 					n_alloc = NM_MIN (n_read + 1, sizeof (buf));
 				}
 
-				if (!(str = _mem_realloc (str, do_bzero_mem, old_n_alloc, n_alloc))) {
+				if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, old_n_alloc, n_alloc))) {
 					if (do_bzero_mem)
 						nm_explicit_bzero (buf, sizeof (buf));
 					return _get_contents_error (error, ENOMEM, out_errsv, "failure to allocate buffer of %zu bytes", n_alloc);
@@ -241,7 +216,7 @@ nm_utils_fd_get_contents (int fd,
 		else {
 			str[n_have] = '\0';
 			if (n_have + 1 < n_alloc) {
-				if (!(str = _mem_realloc (str, do_bzero_mem, n_alloc, n_have + 1)))
+				if (!(str = nm_secret_mem_try_realloc_take (str, do_bzero_mem, n_alloc, n_have + 1)))
 					return _get_contents_error (error, ENOMEM, out_errsv, "failure to truncate buffer to %zu bytes", n_have + 1);
 			}
 		}
@@ -438,3 +413,46 @@ nm_utils_file_stat (const char *filename, struct stat *out_st)
 		return -NM_ERRNO_NATIVE (errno);
 	return 0;
 }
+
+/**
+ * nm_utils_fd_read:
+ * @fd: the fd to read from.
+ * @out_string: (out): output string where read bytes will be stored.
+ *
+ * Returns: <0 on failure, which is -(errno).
+ *          0 on EOF.
+ *          >0 on success, which is the number of bytes read.  */
+gssize
+nm_utils_fd_read (int fd, NMStrBuf *out_string)
+{
+	gsize buf_available;
+	gssize n_read;
+	int errsv;
+
+	g_return_val_if_fail (fd >= 0, -1);
+	g_return_val_if_fail (out_string, -1);
+
+	/* If the buffer size is 0, we allocate NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 (1000 bytes)
+	 * the first time. Afterwards, the buffer grows exponentially.
+	 *
+	 * Note that with @buf_available, we always would read as much buffer as we actually
+	 * have reserved. */
+	nm_str_buf_maybe_expand (out_string, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
+
+	buf_available = out_string->allocated - out_string->len;
+
+	n_read = read (fd,
+	               &((nm_str_buf_get_str_unsafe (out_string))[out_string->len]),
+	               buf_available);
+	if (n_read < 0) {
+		errsv = errno;
+		return -NM_ERRNO_NATIVE (errsv);
+	}
+
+	if (n_read > 0) {
+		nm_assert ((gsize) n_read <= buf_available);
+		nm_str_buf_set_size (out_string, out_string->len + (gsize) n_read, TRUE, FALSE);
+	}
+
+	return n_read;
+}
diff --git a/shared/nm-glib-aux/nm-io-utils.h b/shared/nm-glib-aux/nm-io-utils.h
index 18dc5f74..31326fc7 100644
--- a/shared/nm-glib-aux/nm-io-utils.h
+++ b/shared/nm-glib-aux/nm-io-utils.h
@@ -47,6 +47,10 @@ gboolean nm_utils_file_set_contents (const char *filename,
                                      int *out_errsv,
                                      GError **error);
 
+struct _NMStrBuf;
+
+gssize nm_utils_fd_read (int fd, struct _NMStrBuf *out_string);
+
 struct stat;
 
 int nm_utils_file_stat (const char *filename, struct stat *out_st);
diff --git a/shared/nm-glib-aux/nm-jansson.h b/shared/nm-glib-aux/nm-jansson.h
index 0a75cff5..7c034222 100644
--- a/shared/nm-glib-aux/nm-jansson.h
+++ b/shared/nm-glib-aux/nm-jansson.h
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0+
+// SPDX-License-Identifier: LGPL-2.1+
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-logging-fwd.h b/shared/nm-glib-aux/nm-logging-fwd.h
index 47661788..2daa302f 100644
--- a/shared/nm-glib-aux/nm-logging-fwd.h
+++ b/shared/nm-glib-aux/nm-logging-fwd.h
@@ -63,6 +63,14 @@ typedef enum  { /*< skip >*/
 	LOGD_IP         = LOGD_IP4 | LOGD_IP6,
 } NMLogDomain;
 
+static inline NMLogDomain
+LOGD_DHCP_from_addr_family (int addr_family)
+{
+	nm_assert_addr_family (addr_family);
+
+	return addr_family == AF_INET6 ? LOGD_DHCP6 : LOGD_DHCP4;
+}
+
 /* Log levels */
 typedef enum  { /*< skip >*/
 	LOGL_TRACE,
diff --git a/shared/nm-glib-aux/nm-macros-internal.h b/shared/nm-glib-aux/nm-macros-internal.h
index d1ea04c0..d5a8513e 100644
--- a/shared/nm-glib-aux/nm-macros-internal.h
+++ b/shared/nm-glib-aux/nm-macros-internal.h
@@ -337,52 +337,65 @@ _nm_auto_freev (gpointer ptr)
 
 /*****************************************************************************/
 
-/* http://stackoverflow.com/a/11172679 */
-#define  _NM_UTILS_MACRO_FIRST(...)                           __NM_UTILS_MACRO_FIRST_HELPER(__VA_ARGS__, throwaway)
-#define __NM_UTILS_MACRO_FIRST_HELPER(first, ...)             first
-
-#define  _NM_UTILS_MACRO_REST(...)                            __NM_UTILS_MACRO_REST_HELPER(__NM_UTILS_MACRO_REST_NUM(__VA_ARGS__), __VA_ARGS__)
-#define __NM_UTILS_MACRO_REST_HELPER(qty, ...)                __NM_UTILS_MACRO_REST_HELPER2(qty, __VA_ARGS__)
-#define __NM_UTILS_MACRO_REST_HELPER2(qty, ...)               __NM_UTILS_MACRO_REST_HELPER_##qty(__VA_ARGS__)
-#define __NM_UTILS_MACRO_REST_HELPER_ONE(first)
-#define __NM_UTILS_MACRO_REST_HELPER_TWOORMORE(first, ...)    , __VA_ARGS__
-#define __NM_UTILS_MACRO_REST_NUM(...) \
-    __NM_UTILS_MACRO_REST_SELECT_30TH(__VA_ARGS__, \
-                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
-                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
-                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
-                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
-                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
-                TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway)
-#define __NM_UTILS_MACRO_REST_SELECT_30TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, ...) a30
-
-/*****************************************************************************/
-
-/* http://stackoverflow.com/a/2124385/354393
- * https://stackoverflow.com/questions/11317474/macro-to-count-number-of-arguments
- */
-
-#define NM_NARG(...) \
-         _NM_NARG(, ##__VA_ARGS__, _NM_NARG_RSEQ_N())
-#define _NM_NARG(...) \
-         _NM_NARG_ARG_N(__VA_ARGS__)
-#define _NM_NARG_ARG_N( \
-          _0, \
+#define _NM_MACRO_SELECT_ARG_64( \
           _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
          _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
          _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
          _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
          _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
          _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
-         _61,_62,_63,N,...) N
-#define _NM_NARG_RSEQ_N() \
-         63,62,61,60,                   \
-         59,58,57,56,55,54,53,52,51,50, \
-         49,48,47,46,45,44,43,42,41,40, \
-         39,38,37,36,35,34,33,32,31,30, \
-         29,28,27,26,25,24,23,22,21,20, \
-         19,18,17,16,15,14,13,12,11,10, \
-         9,8,7,6,5,4,3,2,1,0
+         _61,_62,_63, N,...) N
+
+/* http://stackoverflow.com/a/2124385/354393
+ * https://stackoverflow.com/questions/11317474/macro-to-count-number-of-arguments
+ */
+
+#define NM_NARG(...) \
+    _NM_MACRO_SELECT_ARG_64(, ##__VA_ARGS__, \
+                                                 62,61,60, \
+                            59,58,57,56,55,54,53,52,51,50, \
+                            49,48,47,46,45,44,43,42,41,40, \
+                            39,38,37,36,35,34,33,32,31,30, \
+                            29,28,27,26,25,24,23,22,21,20, \
+                            19,18,17,16,15,14,13,12,11,10, \
+                             9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+#define NM_NARG_MAX1(...) \
+    _NM_MACRO_SELECT_ARG_64(, ##__VA_ARGS__, \
+                                                  1, 1, 1, \
+                             1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
+                             1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
+                             1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
+                             1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
+                             1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
+                             1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
+#define NM_NARG_MAX2(...) \
+    _NM_MACRO_SELECT_ARG_64(, ##__VA_ARGS__, \
+                                                  2, 2, 2, \
+                             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \
+                             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \
+                             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \
+                             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \
+                             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, \
+                             2, 2, 2, 2, 2, 2, 2, 2, 1, 0)
+
+#define _NM_MACRO_CALL(macro, ...) macro (__VA_ARGS__)
+
+/*****************************************************************************/
+
+#define _NM_MACRO_COMMA_IF_ARGS(...)       _NM_MACRO_CALL (G_PASTE (__NM_MACRO_COMMA_IF_ARGS_, NM_NARG_MAX1(__VA_ARGS__)), __VA_ARGS__)
+#define __NM_MACRO_COMMA_IF_ARGS_0()
+#define __NM_MACRO_COMMA_IF_ARGS_1(...)    ,
+
+/*****************************************************************************/
+
+/* http://stackoverflow.com/a/11172679 */
+#define  _NM_UTILS_MACRO_FIRST(...)                  __NM_UTILS_MACRO_FIRST_HELPER(__VA_ARGS__, throwaway)
+#define __NM_UTILS_MACRO_FIRST_HELPER(first, ...)    first
+
+#define  _NM_UTILS_MACRO_REST(...)                   _NM_MACRO_CALL (G_PASTE (__NM_UTILS_MACRO_REST_, NM_NARG_MAX2(__VA_ARGS__)), __VA_ARGS__)
+#define __NM_UTILS_MACRO_REST_0()
+#define __NM_UTILS_MACRO_REST_1(first)
+#define __NM_UTILS_MACRO_REST_2(first, ...)          , __VA_ARGS__
 
 /*****************************************************************************/
 
@@ -744,6 +757,17 @@ NM_G_ERROR_MSG (GError *error)
 
 /*****************************************************************************/
 
+#define NM_SWAP(a, b) \
+	G_STMT_START { \
+		typeof (a) _tmp; \
+		\
+		_tmp = (a); \
+		(a) = (b); \
+		(b) = _tmp; \
+	} G_STMT_END
+
+/*****************************************************************************/
+
 static inline gboolean
 _NM_IN_STRSET_streq (const char *x, const char *s)
 {
@@ -980,6 +1004,7 @@ nm_str_realloc (char *str)
 	(cond) ? (str) : (str_else), \
 	(cond) ? (suffix) : ""
 #define NM_PRINT_FMT_QUOTE_STRING(arg) NM_PRINT_FMT_QUOTED((arg), "\"", (arg), "\"", "(null)")
+#define NM_PRINT_FMT_QUOTE_REF_STRING(arg) NM_PRINT_FMT_QUOTED((arg), "\"", (arg)->str, "\"", "(null)")
 
 /*****************************************************************************/
 
@@ -1009,19 +1034,37 @@ nm_str_realloc (char *str)
 #define nm_assert_not_reached() G_STMT_START { ; } G_STMT_END
 #endif
 
+/* Usage:
+ *
+ *   if (NM_MORE_ASSERT_ONCE (5)) { extra_check (); }
+ *
+ * This will only run the check once, and only if NM_MORE_ASSERT is >= than
+ * more_assert_level.
+ */
+#define NM_MORE_ASSERT_ONCE(more_assert_level) \
+	(   (NM_MORE_ASSERTS >= (more_assert_level)) \
+	 && ({ \
+	        static volatile int _assert_once = 0; \
+	        \
+	        G_STATIC_ASSERT_EXPR ((more_assert_level) > 0); \
+	        \
+	        G_UNLIKELY (   _assert_once == 0 \
+	                    && g_atomic_int_compare_and_exchange (&_assert_once, 0, 1)); \
+	    }))
+
 /*****************************************************************************/
 
-#define NM_GOBJECT_PROPERTIES_DEFINE_BASE(...) \
+#define NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL(suffix, ...) \
 typedef enum { \
-	PROP_0, \
+	PROP_0##suffix, \
 	__VA_ARGS__ \
-	_PROPERTY_ENUMS_LAST, \
-} _PropertyEnums; \
-static GParamSpec *obj_properties[_PROPERTY_ENUMS_LAST] = { NULL, }
+	_PROPERTY_ENUMS_LAST##suffix, \
+} _PropertyEnums##suffix; \
+static GParamSpec *obj_properties##suffix[_PROPERTY_ENUMS_LAST##suffix] = { NULL, }
 
-#define NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY(obj_type, obj_properties, property_enums_type, prop_0) \
+#define NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY(suffix, obj_type) \
 static inline void \
-_nm_gobject_notify_together_impl (obj_type *obj, guint n, const property_enums_type *props) \
+_nm_gobject_notify_together_impl##suffix (obj_type *obj, guint n, const _PropertyEnums##suffix *props) \
 { \
 	const gboolean freeze_thaw = (n > 1); \
 	\
@@ -1031,12 +1074,12 @@ _nm_gobject_notify_together_impl (obj_type *obj, guint n, const property_enums_t
 	if (freeze_thaw) \
 		g_object_freeze_notify ((GObject *) obj); \
 	while (n-- > 0) { \
-		const property_enums_type prop = *props++; \
+		const _PropertyEnums##suffix prop = *props++; \
 		\
-		if (prop != prop_0) { \
-			nm_assert ((gsize) prop < G_N_ELEMENTS (obj_properties)); \
-			nm_assert (obj_properties[prop]); \
-			g_object_notify_by_pspec ((GObject *) obj, obj_properties[prop]); \
+		if (prop != PROP_0##suffix) { \
+			nm_assert ((gsize) prop < G_N_ELEMENTS (obj_properties##suffix)); \
+			nm_assert (obj_properties##suffix[prop]); \
+			g_object_notify_by_pspec ((GObject *) obj, obj_properties##suffix[prop]); \
 		} \
 	} \
 	if (freeze_thaw) \
@@ -1044,20 +1087,29 @@ _nm_gobject_notify_together_impl (obj_type *obj, guint n, const property_enums_t
 } \
 \
 _nm_unused static inline void \
-_notify (obj_type *obj, property_enums_type prop) \
+_notify##suffix (obj_type *obj, _PropertyEnums##suffix prop) \
 { \
-	_nm_gobject_notify_together_impl (obj, 1, &prop); \
+	_nm_gobject_notify_together_impl##suffix (obj, 1, &prop); \
 } \
 
+#define NM_GOBJECT_PROPERTIES_DEFINE_BASE(...) \
+	NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL (, __VA_ARGS__); \
+
+#define NM_GOBJECT_PROPERTIES_DEFINE_FULL(suffix, obj_type, ...) \
+	NM_GOBJECT_PROPERTIES_DEFINE_BASE_FULL (suffix, __VA_ARGS__); \
+	NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY (suffix, obj_type)
+
 #define NM_GOBJECT_PROPERTIES_DEFINE(obj_type, ...) \
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (__VA_ARGS__); \
-NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY (obj_type, obj_properties, _PropertyEnums, PROP_0)
+	NM_GOBJECT_PROPERTIES_DEFINE_FULL (, obj_type, __VA_ARGS__)
 
 /* invokes _notify() for all arguments (of type _PropertyEnums). Note, that if
  * there are more than one prop arguments, this will involve a freeze/thaw
  * of GObject property notifications. */
+#define nm_gobject_notify_together_full(suffix, obj, ...) \
+	_nm_gobject_notify_together_impl##suffix (obj, NM_NARG (__VA_ARGS__), (const _PropertyEnums##suffix[]) { __VA_ARGS__ })
+
 #define nm_gobject_notify_together(obj, ...) \
-	_nm_gobject_notify_together_impl (obj, NM_NARG (__VA_ARGS__), (const _PropertyEnums[]) { __VA_ARGS__ })
+	nm_gobject_notify_together_full (, obj, __VA_ARGS__)
 
 /*****************************************************************************/
 
@@ -1065,9 +1117,9 @@ NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY (obj_type, obj_properties, _PropertyEnums, P
 #if _NM_CC_SUPPORT_AUTO_TYPE
 #define _NM_GET_PRIVATE_PTR(self, type, is_check, ...) \
 	({ \
-		_nm_auto_type _self = NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__); \
+		_nm_auto_type _self_get_private = NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__); \
 		\
-		NM_PROPAGATE_CONST (_self, _self->_priv); \
+		NM_PROPAGATE_CONST (_self_get_private, _self_get_private->_priv); \
 	})
 #else
 #define _NM_GET_PRIVATE_PTR(self, type, is_check, ...) (NM_GOBJECT_CAST_NON_NULL (type, (self), is_check, ##__VA_ARGS__)->_priv)
@@ -1348,8 +1400,8 @@ nm_g_variant_take_ref (GVariant *v)
 #define NM_UTILS_LOOKUP_ITEM_IGNORE(v)        (void) 0; case v: break; (void) 0
 #define NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER()   (void) 0; default: break; (void) 0
 
-#define _NM_UTILS_LOOKUP_DEFINE(scope, fcn_name, lookup_type, result_type, unknown_val, ...) \
-scope result_type \
+#define NM_UTILS_LOOKUP_DEFINE(fcn_name, lookup_type, result_type, unknown_val, ...) \
+result_type \
 fcn_name (lookup_type val) \
 { \
 	switch (val) { \
@@ -1361,9 +1413,7 @@ fcn_name (lookup_type val) \
 }
 
 #define NM_UTILS_LOOKUP_STR_DEFINE(fcn_name, lookup_type, unknown_val, ...) \
-	_NM_UTILS_LOOKUP_DEFINE (, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__)
-#define NM_UTILS_LOOKUP_STR_DEFINE_STATIC(fcn_name, lookup_type, unknown_val, ...) \
-	_NM_UTILS_LOOKUP_DEFINE (static, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__)
+	NM_UTILS_LOOKUP_DEFINE (fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__)
 
 /* Call the string-lookup-table function @fcn_name. If the function returns
  * %NULL, the numeric index is converted to string using a alloca() buffer.
@@ -1415,6 +1465,14 @@ fcn_name (lookup_type val) \
 			: _flags & (~_val); \
 	})
 
+#define NM_FLAGS_ASSIGN_MASK(flags, mask, val)  ({ \
+		const typeof(flags) _flags = (flags); \
+		const typeof(flags) _mask = (mask); \
+		const typeof(flags) _val = (val); \
+		\
+		((_flags & ~_mask) | (_mask & _val)); \
+	})
+
 /*****************************************************************************/
 
 #define _NM_BACKPORT_SYMBOL_IMPL(version, return_type, orig_func, versioned_func, args_typed, args) \
@@ -1432,7 +1490,12 @@ _NM_BACKPORT_SYMBOL_IMPL(version, return_type, func, _##func##_##version, args_t
 /*****************************************************************************/
 
 /* mirrors g_ascii_isspace() and what we consider spaces in general. */
-#define NM_ASCII_SPACES "\t\n\f\r "
+#define NM_ASCII_SPACES      " \n\t\r\f"
+
+/* Like NM_ASCII_SPACES, but without "\f" (0x0c, Formfeed Page Break).
+ * This is what for example systemd calls WHITESPACE and what it uses to tokenize
+ * the kernel command line. */
+#define NM_ASCII_WHITESPACES " \n\t\r"
 
 #define nm_str_skip_leading_spaces(str) \
 	({ \
@@ -1508,6 +1571,17 @@ nm_strstrip_avoid_copy (const char *str, char **str_free)
 		_str_ssac; \
 	})
 
+static inline gboolean
+nm_str_is_stripped (const char *str)
+{
+	if (str && str[0]) {
+		if (   g_ascii_isspace (str[0])
+		    || g_ascii_isspace (str[strlen (str) - 1]))
+			return FALSE;
+	}
+	return TRUE;
+}
+
 /* g_ptr_array_sort()'s compare function takes pointers to the
  * value. Thus, you cannot use strcmp directly. You can use
  * nm_strcmp_p().
diff --git a/shared/nm-glib-aux/nm-ref-string.h b/shared/nm-glib-aux/nm-ref-string.h
index c80b75ea..60052c45 100644
--- a/shared/nm-glib-aux/nm-ref-string.h
+++ b/shared/nm-glib-aux/nm-ref-string.h
@@ -50,6 +50,18 @@ nm_ref_string_get_len (NMRefString *rstr)
 }
 
 static inline gboolean
+nm_ref_string_equals_str (NMRefString *rstr, const char *s)
+{
+	/* Note that rstr->len might be greater than strlen(rstr->str). This function does
+	 * not cover that and would ignore everything after the first NUL byte. If you need
+	 * that distinction, this function is not for you. */
+
+	return   rstr
+	       ? (s && nm_streq (rstr->str, s))
+	       : (s == NULL);
+}
+
+static inline gboolean
 NM_IS_REF_STRING (const NMRefString *rstr)
 {
 #if NM_MORE_ASSERTS > 10
diff --git a/shared/nm-glib-aux/nm-secret-utils.c b/shared/nm-glib-aux/nm-secret-utils.c
index 5b0afe46..78369b5e 100644
--- a/shared/nm-glib-aux/nm-secret-utils.c
+++ b/shared/nm-glib-aux/nm-secret-utils.c
@@ -8,13 +8,15 @@
 
 #include "nm-secret-utils.h"
 
+#include <malloc.h>
+
 /*****************************************************************************/
 
 void
 nm_explicit_bzero (void *s, gsize n)
 {
 	/* gracefully handle n == 0. This is important, callers rely on it. */
-	if (n == 0)
+	if (G_UNLIKELY (n == 0))
 		return;
 
 	nm_assert (s);
@@ -32,6 +34,30 @@ nm_explicit_bzero (void *s, gsize n)
 #endif
 }
 
+void
+nm_free_secret (char *secret)
+{
+	gsize len;
+
+	if (!secret)
+		return;
+
+#if GLIB_CHECK_VERSION(2,44,0)
+	/* Here we mix malloc() and g_malloc() API. Usually we avoid this,
+	 * however since glib 2.44.0 we are in fact guaranteed that g_malloc()/g_free()
+	 * just wraps malloc()/free(), so this is actually fine.
+	 *
+	 * See https://gitlab.gnome.org/GNOME/glib/commit/3be6ed60aa58095691bd697344765e715a327fc1
+	 */
+	len = malloc_usable_size (secret);
+#else
+	len = strlen (secret);
+#endif
+
+	nm_explicit_bzero (secret, len);
+	g_free (secret);
+}
+
 /*****************************************************************************/
 
 char *
diff --git a/shared/nm-glib-aux/nm-secret-utils.h b/shared/nm-glib-aux/nm-secret-utils.h
index 1b98b7e9..501e7520 100644
--- a/shared/nm-glib-aux/nm-secret-utils.h
+++ b/shared/nm-glib-aux/nm-secret-utils.h
@@ -18,14 +18,7 @@ char *nm_secret_strchomp (char *secret);
 
 /*****************************************************************************/
 
-static inline void
-nm_free_secret (char *secret)
-{
-	if (secret) {
-		nm_explicit_bzero (secret, strlen (secret));
-		g_free (secret);
-	}
-}
+void nm_free_secret (char *secret);
 
 NM_AUTO_DEFINE_FCN0 (char *, _nm_auto_free_secret, nm_free_secret)
 /**
@@ -159,4 +152,125 @@ GBytes *nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len);
 
 gboolean nm_utils_memeqzero_secret (gconstpointer data, gsize length);
 
+/*****************************************************************************/
+
+/**
+ * nm_secret_mem_realloc:
+ * @m_old: the current buffer of length @cur_len.
+ * @do_bzero_mem: if %TRUE, bzero the old buffer
+ * @cur_len: the current buffer length of @m_old. It is necessary for bzero.
+ * @new_len: the desired new length
+ *
+ * If @do_bzero_mem is false, this is like g_realloc().
+ * Otherwise, this will allocate a new buffer of the desired size, copy over the
+ * old data, and bzero the old buffer before freeing it. As such, it also behaves
+ * similar to g_realloc(), with the overhead of nm_explicit_bzero() and using
+ * malloc/free intead of realloc().
+ *
+ * Returns: the new allocated buffer. Think of it behaving like g_realloc().
+ */
+static inline gpointer
+nm_secret_mem_realloc (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
+{
+	gpointer m_new;
+
+	nm_assert (m_old || cur_len == 0);
+
+	if (   do_bzero_mem
+	    && G_LIKELY (cur_len > 0)) {
+		m_new = g_malloc (new_len);
+		if (G_LIKELY (new_len > 0))
+			memcpy (m_new, m_old, NM_MIN (cur_len, new_len));
+		nm_explicit_bzero (m_old, cur_len);
+		g_free (m_old);
+	} else
+		m_new = g_realloc (m_old, new_len);
+
+	return m_new;
+}
+
+/**
+ * nm_secret_mem_try_realloc:
+ * @m_old: the current buffer of length @cur_len.
+ * @do_bzero_mem: if %TRUE, bzero the old buffer
+ * @cur_len: the current buffer length of @m_old. It is necessary for bzero.
+ * @new_len: the desired new length
+ *
+ * If @do_bzero_mem is false, this is like g_try_realloc().
+ * Otherwise, this will try to allocate a new buffer of the desired size, copy over the
+ * old data, and bzero the old buffer before freeing it. As such, it also behaves
+ * similar to g_try_realloc(), with the overhead of nm_explicit_bzero() and using
+ * malloc/free intead of realloc().
+ *
+ * Returns: the new allocated buffer or NULL. Think of it behaving like g_try_realloc().
+ */
+static inline gpointer
+nm_secret_mem_try_realloc (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
+{
+	gpointer m_new;
+
+	nm_assert (m_old || cur_len == 0);
+
+	if (   do_bzero_mem
+	    && G_LIKELY (cur_len > 0)) {
+		if (G_UNLIKELY (new_len == 0))
+			m_new = NULL;
+		else {
+			m_new = g_try_malloc (new_len);
+			if (!m_new)
+				return NULL;
+			memcpy (m_new, m_old, NM_MIN (cur_len, new_len));
+		}
+		nm_explicit_bzero (m_old, cur_len);
+		g_free (m_old);
+		return m_new;
+	}
+
+	return g_try_realloc (m_old, new_len);
+}
+
+/**
+ * nm_secret_mem_try_realloc_take:
+ * @m_old: the current buffer of length @cur_len.
+ * @do_bzero_mem: if %TRUE, bzero the old buffer
+ * @cur_len: the current buffer length of @m_old. It is necessary for bzero.
+ * @new_len: the desired new length
+ *
+ * This works like nm_secret_mem_try_realloc(), which is not unlike g_try_realloc().
+ * The difference is, if we fail to allocate a new buffer, then @m_old will be
+ * freed (and possibly cleared). This differs from plain realloc(), where the
+ * old buffer is unchanged if the operation fails.
+ *
+ * Returns: the new allocated buffer or NULL. Think of it behaving like g_try_realloc()
+ *   but it will always free @m_old.
+ */
+static inline gpointer
+nm_secret_mem_try_realloc_take (gpointer m_old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
+{
+	gpointer m_new;
+
+	nm_assert (m_old || cur_len == 0);
+
+	if (   do_bzero_mem
+	    && G_LIKELY (cur_len > 0)) {
+		if (G_UNLIKELY (new_len == 0))
+			m_new = NULL;
+		else {
+			m_new = g_try_malloc (new_len);
+			if (G_LIKELY (m_new))
+				memcpy (m_new, m_old, NM_MIN (cur_len, new_len));
+		}
+		nm_explicit_bzero (m_old, cur_len);
+		g_free (m_old);
+		return m_new;
+	}
+
+	m_new = g_try_realloc (m_old, new_len);
+	if (G_UNLIKELY (!m_new && new_len > 0))
+		g_free (m_old);
+	return m_new;
+}
+
+/*****************************************************************************/
+
 #endif /* __NM_SECRET_UTILS_H__ */
diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c
index d47c465c..c06399dd 100644
--- a/shared/nm-glib-aux/nm-shared-utils.c
+++ b/shared/nm-glib-aux/nm-shared-utils.c
@@ -15,6 +15,7 @@
 #include <net/if.h>
 
 #include "nm-errno.h"
+#include "nm-str-buf.h"
 
 /*****************************************************************************/
 
@@ -84,6 +85,75 @@ nm_ip_addr_set_from_untrusted (int addr_family,
 
 /*****************************************************************************/
 
+gsize
+nm_utils_get_next_realloc_size (gboolean true_realloc, gsize requested)
+{
+	gsize n, x;
+
+	/* https://doc.qt.io/qt-5/containers.html#growth-strategies */
+
+	if (requested <= 40) {
+		/* small allocations. Increase in small steps of 8 bytes.
+		 *
+		 * We get thus sizes of 8, 16, 32, 40. */
+		if (requested <= 8)
+			return 8;
+		if (requested <= 16)
+			return 16;
+		if (requested <= 32)
+			return 32;
+
+		/* The return values for < 104 are essentially hard-coded, and the choice here is
+		 * made without very strong reasons.
+		 *
+		 * We want to stay 24 bytes below the power-of-two border 64. Hence, return 40 here.
+		 * However, the next step then is already 104 (128 - 24). It's a larger gap than in
+		 * the steps before.
+		 *
+		 * It's not clear whether some of the steps should be adjusted (or how exactly). */
+		return 40;
+	}
+
+	if (   requested <= 0x2000u - 24u
+	    || G_UNLIKELY (!true_realloc)) {
+		/* mid sized allocations. Return next power of two, minus 24 bytes extra space
+		 * at the beginning.
+		 * That means, we double the size as we grow.
+		 *
+		 * With !true_realloc, it means that the caller does not intend to call
+		 * realloc() but instead clone the buffer. This is for example the case, when we
+		 * want to nm_explicit_bzero() the old buffer. In that case we really want to grow
+		 * the buffer exponentially every time and not increment in page sizes of 4K (below).
+		 *
+		 * We get thus sizes of 104, 232, 488, 1000, 2024, 4072, 8168... */
+
+		if (G_UNLIKELY (requested > G_MAXSIZE / 2u - 24u))
+			return G_MAXSIZE;
+
+		x = requested + 24u;
+		n = 128u;
+		while (n < x) {
+			n <<= 1;
+			nm_assert (n > 128u);
+		}
+
+		nm_assert (n > 24u && n - 24u >= requested);
+		return n - 24u;
+	}
+
+	if (G_UNLIKELY (requested > G_MAXSIZE - 0x1000u - 24u))
+		return G_MAXSIZE;
+
+	/* For large allocations (with !true_realloc) we allocate memory in chunks of
+	 * 4K (- 24 bytes extra), assuming that the memory gets mmapped and thus
+	 * realloc() is efficient by just reordering pages. */
+	n = ((requested + (0x0FFFu + 24u)) & ~((gsize) 0x0FFFu)) - 24u;
+	nm_assert (n >= requested);
+	return n;
+}
+
+/*****************************************************************************/
+
 pid_t
 nm_utils_gettid (void)
 {
@@ -374,6 +444,62 @@ nm_utils_gbytes_to_variant_ay (GBytes *bytes)
 
 /*****************************************************************************/
 
+/* Convert a hash table with "char *" keys and values to an "a{ss}" GVariant.
+ * The keys will be sorted asciibetically.
+ * Returns a floating reference.
+ */
+GVariant *
+nm_utils_strdict_to_variant_ass (GHashTable *strdict)
+{
+	GHashTableIter iter;
+	const char *key, *value;
+	GVariantBuilder builder;
+	guint i, len;
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
+
+	if (!strdict)
+		goto out;
+	len = g_hash_table_size (strdict);
+	if (!len)
+		goto out;
+
+	g_hash_table_iter_init (&iter, strdict);
+	if (!g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value))
+		nm_assert_not_reached ();
+
+	if (len == 1)
+		g_variant_builder_add (&builder, "{ss}", key, value);
+	else {
+		gs_free NMUtilsNamedValue *idx_free = NULL;
+		NMUtilsNamedValue *idx;
+
+		if (len > 300 / sizeof (NMUtilsNamedValue)) {
+			idx_free = g_new (NMUtilsNamedValue, len);
+			idx = idx_free;
+		} else
+			idx = g_alloca (sizeof (NMUtilsNamedValue) * len);
+
+		i = 0;
+		do {
+			idx[i].name = key;
+			idx[i].value_str = value;
+			i++;
+		} while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value));
+		nm_assert (i == len);
+
+		nm_utils_named_value_list_sort (idx, len, NULL, NULL);
+
+		for (i = 0; i < len; i++)
+			g_variant_builder_add (&builder, "{ss}", idx[i].name, idx[i].value_str);
+	}
+
+out:
+	return g_variant_builder_end (&builder);
+}
+
+/*****************************************************************************/
+
 /**
  * nm_strquote:
  * @buf: the output buffer of where to write the quoted @str argument.
@@ -685,7 +811,7 @@ nm_utils_ip_is_site_local (int addr_family,
 /*****************************************************************************/
 
 static gboolean
-_parse_legacy_addr4 (const char *text, in_addr_t *out_addr)
+_parse_legacy_addr4 (const char *text, in_addr_t *out_addr, GError **error)
 {
 	gs_free char *s_free = NULL;
 	struct in_addr a1;
@@ -693,8 +819,13 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr)
 	char *s;
 	int i;
 
-	if (inet_aton (text, &a1) != 1)
+	if (inet_aton (text, &a1) != 1) {
+		g_set_error_literal (error,
+		                     NM_UTILS_ERROR,
+		                     NM_UTILS_ERROR_INVALID_ARGUMENT,
+		                     "address invalid according to inet_aton()");
 		return FALSE;
+	}
 
 	/* OK, inet_aton() accepted the format. That's good, because we want
 	 * to accept IPv4 addresses in octal format, like 255.255.000.000.
@@ -711,6 +842,10 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr)
 	if (NM_STRCHAR_ANY (text, ch, (   !(ch >= '0' && ch <= '9')
 	                               && !NM_IN_SET (ch, '.', 'x')))) {
 		/* We only accepts '.', digits, and 'x' for "0x". */
+		g_set_error_literal (error,
+		                     NM_UTILS_ERROR,
+		                     NM_UTILS_ERROR_INVALID_ARGUMENT,
+		                     "contains an invalid character");
 		return FALSE;
 	}
 
@@ -729,13 +864,27 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr)
 		if ((i == G_N_ELEMENTS (bin) - 1) != (s == NULL)) {
 			/* Exactly for the last digit, we expect to have no more following token.
 			 * But this isn't the case. Abort. */
+			g_set_error (error,
+			             NM_UTILS_ERROR,
+			             NM_UTILS_ERROR_INVALID_ARGUMENT,
+			             "wrong number of tokens (index %d, token '%s')",
+			             i, s);
 			return FALSE;
 		}
 
 		v = _nm_utils_ascii_str_to_int64 (current_token, 0, 0, 0xFF, -1);
 		if (v == -1) {
+			int errsv = errno;
+
 			/* we do accept octal and hex (even with leading "0x"). But something
 			 * about this token is wrong. */
+			g_set_error (error,
+			             NM_UTILS_ERROR,
+			             NM_UTILS_ERROR_INVALID_ARGUMENT,
+			             "invalid token '%s': %s (%d)",
+			             current_token,
+			             nm_strerror_native (errsv),
+			             errsv);
 			return FALSE;
 		}
 
@@ -745,6 +894,12 @@ _parse_legacy_addr4 (const char *text, in_addr_t *out_addr)
 	if (memcmp (bin, &a1, sizeof (bin)) != 0) {
 		/* our parsing did not agree with what inet_aton() gave. Something
 		 * is wrong. Abort. */
+		g_set_error (error,
+		             NM_UTILS_ERROR,
+		             NM_UTILS_ERROR_INVALID_ARGUMENT,
+		             "inet_aton() result 0x%08x differs from computed value 0x%02hhx%02hhx%02hhx%02hhx",
+		             a1.s_addr,
+		             bin[0], bin[1], bin[2], bin[3]);
 		return FALSE;
 	}
 
@@ -772,7 +927,7 @@ nm_utils_parse_inaddr_bin_full (int addr_family,
 	if (inet_pton (addr_family, text, &addrbin) != 1) {
 		if (   accept_legacy
 		    && addr_family == AF_INET
-		    && _parse_legacy_addr4 (text, &addrbin.addr4)) {
+		    && _parse_legacy_addr4 (text, &addrbin.addr4, NULL)) {
 			/* The address is in some legacy format which inet_aton() accepts, but not inet_pton().
 			 * Most likely octal digits (leading zeros). We accept the address. */
 		} else
@@ -781,11 +936,17 @@ nm_utils_parse_inaddr_bin_full (int addr_family,
 
 #if NM_MORE_ASSERTS > 10
 	if (addr_family == AF_INET) {
+		gs_free_error GError *error = NULL;
 		in_addr_t a;
 
 		/* The legacy parser should accept everything that inet_pton() accepts too. Meaning,
 		 * it should strictly parse *more* formats. And of course, parse it the same way. */
-		nm_assert (_parse_legacy_addr4 (text, &a));
+		if (!_parse_legacy_addr4 (text, &a, &error)) {
+			char buf[INET_ADDRSTRLEN];
+
+			g_error ("unexpected assertion failure: could parse \"%s\" as %s, but not accepted by legacy parser: %s",
+			         text, _nm_utils_inet4_ntop (addrbin.addr4, buf), error->message);
+		}
 		nm_assert (addrbin.addr4 == a);
 	}
 #endif
@@ -883,6 +1044,195 @@ nm_utils_parse_inaddr_prefix (int addr_family,
 
 /*****************************************************************************/
 
+gboolean
+nm_utils_ipaddr_is_valid (int addr_family,
+                          const char *str_addr)
+{
+	nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	return    str_addr
+	       && nm_utils_parse_inaddr_bin (addr_family,
+	                                     str_addr,
+	                                     NULL,
+	                                     NULL);
+}
+
+gboolean
+nm_utils_ipaddr_is_normalized (int addr_family,
+                               const char *str_addr)
+{
+	NMIPAddr addr;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
+	nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	if (!str_addr)
+		return FALSE;
+
+	if (!nm_utils_parse_inaddr_bin (addr_family,
+	                                str_addr,
+	                                &addr_family,
+	                                &addr))
+		return FALSE;
+
+	nm_utils_inet_ntop (addr_family, &addr, sbuf);
+	return nm_streq (sbuf, str_addr);
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_g_ascii_strtoll()
+ * @nptr: the string to parse
+ * @endptr: the pointer on the first invalid chars
+ * @base: the base.
+ *
+ * This wraps g_ascii_strtoll() and should in almost all cases behave identical
+ * to it.
+ *
+ * However, it seems there are situations where g_ascii_strtoll() might set
+ * errno to some unexpected value EAGAIN. Possibly this is related to creating
+ * the C locale during
+ *
+ *   #ifdef USE_XLOCALE
+ *   return strtoll_l (nptr, endptr, base, get_C_locale ());
+ *
+ * This wrapper tries to workaround that condition.
+ */
+gint64
+nm_g_ascii_strtoll (const char *nptr,
+                    char **endptr,
+                    guint base)
+{
+	int try_count = 2;
+	gint64 v;
+	const int errsv_orig = errno;
+	int errsv;
+
+	nm_assert (nptr);
+	nm_assert (base == 0u || (base >= 2u && base <= 36u));
+
+again:
+	errno = 0;
+	v = g_ascii_strtoll (nptr, endptr, base);
+	errsv = errno;
+
+	if (errsv == 0) {
+		if (errsv_orig != 0)
+			errno = errsv_orig;
+		return v;
+	}
+
+	if (   errsv == ERANGE
+	    && NM_IN_SET (v, G_MININT64, G_MAXINT64))
+		return v;
+
+	if (   errsv == EINVAL
+	    && v == 0
+	    && nptr
+	    && nptr[0] == '\0')
+		return v;
+
+	if (try_count-- > 0)
+		goto again;
+
+#if NM_MORE_ASSERTS
+	g_critical ("g_ascii_strtoll() for \"%s\" failed with errno=%d (%s) and v=%"G_GINT64_FORMAT,
+	            nptr,
+	            errsv,
+	            nm_strerror_native (errsv),
+	            v);
+#endif
+
+	return v;
+}
+
+/* See nm_g_ascii_strtoll() */
+guint64
+nm_g_ascii_strtoull (const char *nptr,
+                     char **endptr,
+                     guint base)
+{
+	int try_count = 2;
+	guint64 v;
+	const int errsv_orig = errno;
+	int errsv;
+
+	nm_assert (nptr);
+	nm_assert (base == 0u || (base >= 2u && base <= 36u));
+
+again:
+	errno = 0;
+	v = g_ascii_strtoull (nptr, endptr, base);
+	errsv = errno;
+
+	if (errsv == 0) {
+		if (errsv_orig != 0)
+			errno = errsv_orig;
+		return v;
+	}
+
+	if (   errsv == ERANGE
+	    && NM_IN_SET (v, G_MAXUINT64))
+		return v;
+
+	if (   errsv == EINVAL
+	    && v == 0
+	    && nptr
+	    && nptr[0] == '\0')
+		return v;
+
+	if (try_count-- > 0)
+		goto again;
+
+#if NM_MORE_ASSERTS
+	g_critical ("g_ascii_strtoull() for \"%s\" failed with errno=%d (%s) and v=%"G_GUINT64_FORMAT,
+	            nptr,
+	            errsv,
+	            nm_strerror_native (errsv),
+	            v);
+#endif
+
+	return v;
+}
+
+/* see nm_g_ascii_strtoll(). */
+double
+nm_g_ascii_strtod (const char *nptr,
+                   char **endptr)
+{
+	int try_count = 2;
+	double v;
+	int errsv;
+
+	nm_assert (nptr);
+
+again:
+	v = g_ascii_strtod (nptr, endptr);
+	errsv = errno;
+
+	if (errsv == 0)
+		return v;
+
+	if (errsv == ERANGE)
+		return v;
+
+	if (try_count-- > 0)
+		goto again;
+
+#if NM_MORE_ASSERTS
+	g_critical ("g_ascii_strtod() for \"%s\" failed with errno=%d (%s) and v=%f",
+	            nptr,
+	            errsv,
+	            nm_strerror_native (errsv),
+	            v);
+#endif
+
+	/* Not really much else to do. Return the parsed value and leave errno set
+	 * to the unexpected value. */
+	return v;
+}
+
 /* _nm_utils_ascii_str_to_int64:
  *
  * A wrapper for g_ascii_strtoll, that checks whether the whole string
@@ -910,7 +1260,7 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma
 	}
 
 	errno = 0;
-	v = g_ascii_strtoll (str, (char **) &s, base);
+	v = nm_g_ascii_strtoll (str, (char **) &s, base);
 
 	if (errno != 0)
 		return fallback;
@@ -946,7 +1296,7 @@ _nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64
 	}
 
 	errno = 0;
-	v = g_ascii_strtoull (str, (char **) &s, base);
+	v = nm_g_ascii_strtoull (str, (char **) &s, base);
 
 	if (errno != 0)
 		return fallback;
@@ -965,8 +1315,8 @@ _nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64
 
 	if (   v != 0
 	    && str[0] == '-') {
-		/* I don't know why, but g_ascii_strtoull() accepts minus signs ("-2" gives 18446744073709551614).
-		 * For "-0" that is OK, but otherwise not. */
+		/* As documented, g_ascii_strtoull() accepts negative values, and returns their
+		 * absolute value. We don't. */
 		errno = ERANGE;
 		return fallback;
 	}
@@ -1147,12 +1497,27 @@ comp_l:
 /*****************************************************************************/
 
 static void
+_char_lookup_table_set_one (guint8 lookup[static 256],
+                             char ch)
+{
+	lookup[(guint8) ch] = 1;
+}
+
+static void
+_char_lookup_table_set_all (guint8 lookup[static 256],
+                            const char *candidates)
+{
+	while (candidates[0] != '\0')
+		_char_lookup_table_set_one (lookup, (candidates++)[0]);
+}
+
+static void
 _char_lookup_table_init (guint8 lookup[static 256],
                          const char *candidates)
 {
 	memset (lookup, 0, 256);
-	while (candidates[0] != '\0')
-		lookup[(guint8) ((candidates++)[0])] = 1;
+	if (candidates)
+		_char_lookup_table_set_all (lookup, candidates);
 }
 
 static gboolean
@@ -1163,6 +1528,19 @@ _char_lookup_has (const guint8 lookup[static 256],
 	return lookup[(guint8) ch] != 0;
 }
 
+static gboolean
+_char_lookup_has_all (const guint8 lookup[static 256],
+                      const char *candidates)
+{
+	if (candidates) {
+		while (candidates[0] != '\0') {
+			if (!_char_lookup_has (lookup, (candidates++)[0]))
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
 /**
  * nm_utils_strsplit_set_full:
  * @str: the string to split.
@@ -1175,18 +1553,7 @@ _char_lookup_has (const guint8 lookup[static 256],
  *
  * Note that for @str %NULL and "", this always returns %NULL too. That differs
  * from g_strsplit_set(), which would return an empty strv array for "".
- *
- * Note that g_strsplit_set() returns empty words as well. By default,
- * nm_utils_strsplit_set_full() strips all empty tokens (that is, repeated
- * delimiters. With %NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, empty tokens
- * are not removed.
- *
- * If @flags has %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING, delimiters prefixed
- * by a backslash are not treated as a separator. Such delimiters and their escape
- * character are copied to the current word without unescaping them. In general,
- * nm_utils_strsplit_set_full() does not remove any backslash escape characters
- * and does not unescaping. It only considers them for skipping to split at
- * an escaped delimiter.
+ * This never returns an empty array.
  *
  * Returns: %NULL if @str is %NULL or "".
  *   If @str only contains delimiters and %NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY
@@ -1196,7 +1563,7 @@ _char_lookup_has (const guint8 lookup[static 256],
  *   The strings to which the result strv array points to are allocated
  *   after the returned result itself. Don't free the strings themself,
  *   but free everything with g_free().
- *   It is however safe and allowed to modify the indiviual strings,
+ *   It is however safe and allowed to modify the individual strings in-place,
  *   like "g_strstrip((char *) iter[0])".
  */
 const char **
@@ -1378,11 +1745,9 @@ done2:
 
 		/* We no longer need ch_lookup for its original purpose. Modify it, so it
 		 * can detect the delimiters, '\\', and (optionally) whitespaces. */
-		ch_lookup[((guint8) '\\')] = 1;
-		if (f_strstrip) {
-			for (i = 0; NM_ASCII_SPACES[i]; i++)
-				ch_lookup[((guint8) (NM_ASCII_SPACES[i]))] = 1;
-		}
+		_char_lookup_table_set_one (ch_lookup, '\\');
+		if (f_strstrip)
+			_char_lookup_table_set_all (ch_lookup, NM_ASCII_SPACES);
 
 		for (i_token = 0; ptr[i_token]; i_token++) {
 			s = (char *) ptr[i_token];
@@ -1403,65 +1768,131 @@ done2:
 /*****************************************************************************/
 
 const char *
-nm_utils_escaped_tokens_escape (const char *str,
-                                const char *delimiters,
-                                char **out_to_free)
+nm_utils_escaped_tokens_escape_full (const char *str,
+                                     const char *delimiters,
+                                     const char *delimiters_as_needed,
+                                     NMUtilsEscapedTokensEscapeFlags flags,
+                                     char **out_to_free)
 {
 	guint8 ch_lookup[256];
+	guint8 ch_lookup_as_needed[256];
+	gboolean has_ch_lookup_as_needed = FALSE;
 	char *ret;
 	gsize str_len;
 	gsize alloc_len;
 	gsize n_escapes;
 	gsize i, j;
+	gboolean escape_leading_space;
 	gboolean escape_trailing_space;
+	gboolean escape_backslash_as_needed;
 
-	if (!delimiters) {
-		nm_assert (delimiters);
-		delimiters = NM_ASCII_SPACES;
-	}
+	nm_assert (   !delimiters_as_needed
+	           || (   delimiters_as_needed[0]
+	               && NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED)));
 
 	if (!str || str[0] == '\0') {
 		*out_to_free = NULL;
 		return str;
 	}
 
+	str_len = strlen (str);
+
 	_char_lookup_table_init (ch_lookup, delimiters);
+	if (   !delimiters
+	    || NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES)) {
+		flags &= ~(  NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE
+		           | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE);
+		_char_lookup_table_set_all (ch_lookup, NM_ASCII_SPACES);
+	}
 
-	/* also mark '\\' as requiring escaping. */
-	ch_lookup[((guint8) '\\')] = 1;
+	if (NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS)) {
+		_char_lookup_table_set_one (ch_lookup, '\\');
+		escape_backslash_as_needed = FALSE;
+	} else if (_char_lookup_has (ch_lookup, '\\'))
+		escape_backslash_as_needed = FALSE;
+	else {
+		escape_backslash_as_needed = NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED);
+		if (escape_backslash_as_needed) {
+			if (    NM_FLAGS_ANY (flags,   NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE
+			                             | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE)
+			    && !_char_lookup_has_all (ch_lookup, NM_ASCII_SPACES)) {
+				/* ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE implies that we escape backslash
+				 * before whitespaces. */
+				if (!has_ch_lookup_as_needed) {
+					has_ch_lookup_as_needed = TRUE;
+					_char_lookup_table_init (ch_lookup_as_needed, NULL);
+				}
+				_char_lookup_table_set_all (ch_lookup_as_needed, NM_ASCII_SPACES);
+			}
+			if (   delimiters_as_needed
+			    && !_char_lookup_has_all (ch_lookup, delimiters_as_needed)) {
+				if (!has_ch_lookup_as_needed) {
+					has_ch_lookup_as_needed = TRUE;
+					_char_lookup_table_init (ch_lookup_as_needed, NULL);
+				}
+				_char_lookup_table_set_all (ch_lookup_as_needed, delimiters_as_needed);
+			}
+		}
+	}
+
+	escape_leading_space =    NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE)
+	                       && g_ascii_isspace (str[0])
+	                       && !_char_lookup_has (ch_lookup, str[0]);
+	if (str_len == 1)
+		escape_trailing_space = FALSE;
+	else {
+		escape_trailing_space =    NM_FLAGS_HAS (flags, NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE)
+		                        && g_ascii_isspace (str[str_len - 1])
+		                        && !_char_lookup_has (ch_lookup, str[str_len - 1]);
+	}
 
 	n_escapes = 0;
 	for (i = 0; str[i] != '\0'; i++) {
 		if (_char_lookup_has (ch_lookup, str[i]))
 			n_escapes++;
+		else if (   str[i] == '\\'
+		         && escape_backslash_as_needed
+		         && (   _char_lookup_has (ch_lookup, str[i + 1])
+		             || NM_IN_SET (str[i + 1], '\0', '\\')
+		             || (   has_ch_lookup_as_needed
+		                 && _char_lookup_has (ch_lookup_as_needed, str[i + 1]))))
+			n_escapes++;
 	}
+	if (escape_leading_space)
+		n_escapes++;
+	if (escape_trailing_space)
+		n_escapes++;
 
-	str_len = i;
-	nm_assert (str_len > 0 && strlen (str) == str_len);
-
-	escape_trailing_space =    !_char_lookup_has (ch_lookup, str[str_len - 1])
-	                        && g_ascii_isspace (str[str_len - 1]);
-
-	if (   n_escapes == 0
-	    && !escape_trailing_space) {
+	if (n_escapes == 0u) {
 		*out_to_free = NULL;
 		return str;
 	}
 
-	alloc_len = str_len + n_escapes + ((gsize) escape_trailing_space) + 1;
+	alloc_len = str_len + n_escapes + 1u;
 	ret = g_new (char, alloc_len);
 
 	j = 0;
-	for (i = 0; str[i] != '\0'; i++) {
-		if (_char_lookup_has (ch_lookup, str[i])) {
-			nm_assert (j < alloc_len);
+	i = 0;
+
+	if (escape_leading_space) {
+		ret[j++] = '\\';
+		ret[j++] = str[i++];
+	}
+	for (; str[i] != '\0'; i++) {
+		if (_char_lookup_has (ch_lookup, str[i]))
+			ret[j++] = '\\';
+		else if (   str[i] == '\\'
+		         && escape_backslash_as_needed
+		         && (   _char_lookup_has (ch_lookup, str[i + 1])
+		             || NM_IN_SET (str[i + 1], '\0', '\\')
+		             || (   has_ch_lookup_as_needed
+		                 && _char_lookup_has (ch_lookup_as_needed, str[i + 1]))))
 			ret[j++] = '\\';
-		}
-		nm_assert (j < alloc_len);
 		ret[j++] = str[i];
 	}
 	if (escape_trailing_space) {
-		nm_assert (!_char_lookup_has (ch_lookup, ret[j - 1]) && g_ascii_isspace (ret[j - 1]));
+		nm_assert (   !_char_lookup_has (ch_lookup, ret[j - 1])
+		           && g_ascii_isspace (ret[j - 1]));
 		ret[j] = ret[j - 1];
 		ret[j - 1] = '\\';
 		j++;
@@ -1469,11 +1900,97 @@ nm_utils_escaped_tokens_escape (const char *str,
 
 	nm_assert (j == alloc_len - 1);
 	ret[j] = '\0';
+	nm_assert (strlen (ret) == j);
 
 	*out_to_free = ret;
 	return ret;
 }
 
+/**
+ * nm_utils_escaped_tokens_options_split:
+ * @str: the src string. This string will be modified in-place.
+ *   The output values will point into @str.
+ * @out_key: (allow-none): the returned output key. This will always be set to @str
+ *   itself. @str will be modified to contain only the unescaped, truncated
+ *   key name.
+ * @out_val: returns the parsed (and unescaped) value or %NULL, if @str contains
+ *   no '=' delimiter.
+ *
+ * Honors backslash escaping to parse @str as "key=value" pairs. Optionally, if no '='
+ * is present, @out_val will be returned as %NULL. Backslash can be used to escape
+ * '=', ',', '\\', and ascii whitespace. Other backslash sequences are taken verbatim.
+ *
+ * For keys, '=' obviously must be escaped. For values, that is optional because an
+ * unescaped '=' is just taken verbatim. For example, in a key, the sequence "\\="
+ * must be escaped as "\\\\\\=". For the value, that works too, but "\\\\=" is also
+ * accepted.
+ *
+ * Unescaped Space around the key and value are also removed. Space in general must
+ * not be escaped, unless they are at the beginning or the end of key/value.
+ */
+void
+nm_utils_escaped_tokens_options_split (char *str,
+                                       const char **out_key,
+                                       const char **out_val)
+{
+	const char *val = NULL;
+	gsize i;
+	gsize j;
+	gsize last_space_idx;
+	gboolean last_space_has;
+
+	nm_assert (str);
+
+	i = 0;
+	while (g_ascii_isspace (str[i]))
+		i++;
+
+	j = 0;
+	last_space_idx = 0;
+	last_space_has = FALSE;
+	while (str[i] != '\0') {
+		if (g_ascii_isspace (str[i])) {
+			if (!last_space_has) {
+				last_space_has = TRUE;
+				last_space_idx = j;
+			}
+		} else {
+			if (str[i] == '\\') {
+				if (   NM_IN_SET (str[i + 1u], '\\', ',', '=')
+				    || g_ascii_isspace (str[i + 1u]))
+					i++;
+			} else if (str[i] == '=') {
+				/* Encounter an unescaped '=' character. When we still parse the key, this
+				 * is the separator we were waiting for. If we are parsing the value,
+				 * we take the character verbatim. */
+				if (!val) {
+					if (last_space_has) {
+						str[last_space_idx] = '\0';
+						j = last_space_idx + 1;
+						last_space_has = FALSE;
+					} else
+						str[j++] = '\0';
+					val = &str[j];
+					i++;
+					while (g_ascii_isspace (str[i]))
+						i++;
+					continue;
+				}
+			}
+			last_space_has = FALSE;
+		}
+		str[j++] = str[i++];
+	}
+
+	if (last_space_has)
+		str[last_space_idx] = '\0';
+	else
+		str[j] = '\0';
+
+	*out_key = str;
+	*out_val = val;
+}
+
 /*****************************************************************************/
 
 /**
@@ -1603,16 +2120,13 @@ nm_utils_error_set_cancelled (GError **error,
 }
 
 gboolean
-nm_utils_error_is_cancelled (GError *error,
-                             gboolean consider_is_disposing)
+nm_utils_error_is_cancelled_or_disposing (GError *error)
 {
 	if (error) {
 		if (error->domain == G_IO_ERROR)
 			return NM_IN_SET (error->code, G_IO_ERROR_CANCELLED);
-		if (consider_is_disposing) {
-			if (error->domain == NM_UTILS_ERROR)
-				return NM_IN_SET (error->code, NM_UTILS_ERROR_CANCELLED_DISPOSING);
-		}
+		if (error->domain == NM_UTILS_ERROR)
+			return NM_IN_SET (error->code, NM_UTILS_ERROR_CANCELLED_DISPOSING);
 	}
 	return FALSE;
 }
@@ -1913,18 +2427,20 @@ nm_g_type_find_implementing_class_for_property (GType gtype,
 /*****************************************************************************/
 
 static void
-_str_append_escape (GString *s, char ch)
+_str_buf_append_c_escape_octal (NMStrBuf *strbuf,
+                                char ch)
 {
-	g_string_append_c (s, '\\');
-	g_string_append_c (s, '0' + ((((guchar) ch) >> 6) & 07));
-	g_string_append_c (s, '0' + ((((guchar) ch) >> 3) & 07));
-	g_string_append_c (s, '0' + ( ((guchar) ch)       & 07));
+	nm_str_buf_append_c4 (strbuf,
+	                      '\\',
+	                      '0' + ((char) ((((guchar) ch) >> 6) & 07)),
+	                      '0' + ((char) ((((guchar) ch) >> 3) & 07)),
+	                      '0' + ((char) ((((guchar) ch)     ) & 07)));
 }
 
 gconstpointer
 nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_free)
 {
-	GString *gstr;
+	NMStrBuf strbuf;
 	gsize len;
 	const char *s;
 
@@ -1946,9 +2462,9 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
 		return str;
 	}
 
-	gstr = g_string_new_len (NULL, len);
+	nm_str_buf_init (&strbuf, len, FALSE);
 
-	g_string_append_len (gstr, str, s - str);
+	nm_str_buf_append_len (&strbuf, str, s - str);
 	str = s;
 
 	for (;;) {
@@ -1971,6 +2487,9 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
 				v = v * 8 + (ch - '0');
 				ch = (++str)[0];
 				if (ch >= '0' && ch <= '7') {
+					/* technically, escape sequences larger than \3FF are out of range
+					 * and invalid. We don't check for that, and do the same as
+					 * g_strcompress(): silently clip the value with & 0xFF. */
 					v = v * 8 + (ch - '0');
 					++str;
 				}
@@ -1992,21 +2511,20 @@ nm_utils_buf_utf8safe_unescape (const char *str, gsize *out_len, gpointer *to_fr
 			str++;
 		}
 
-		g_string_append_c (gstr, ch);
+		nm_str_buf_append_c (&strbuf, ch);
 
 		s = strchr (str, '\\');
 		if (!s) {
-			g_string_append (gstr, str);
+			nm_str_buf_append (&strbuf, str);
 			break;
 		}
 
-		g_string_append_len (gstr, str, s - str);
+		nm_str_buf_append_len (&strbuf, str, s - str);
 		str = s;
 	}
 
-	*out_len = gstr->len;
-	*to_free = gstr->str;
-	return g_string_free (gstr, FALSE);
+	return (*to_free = nm_str_buf_finalize (&strbuf,
+	                                        out_len));
 }
 
 /**
@@ -2047,7 +2565,7 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa
 	const char *p = NULL;
 	const char *s;
 	gboolean nul_terminated = FALSE;
-	GString *gstr;
+	NMStrBuf strbuf;
 
 	g_return_val_if_fail (to_free, NULL);
 
@@ -2078,7 +2596,9 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa
 			return str;
 	}
 
-	gstr = g_string_sized_new (buflen + 5);
+	nm_str_buf_init (&strbuf,
+	                 buflen + 5,
+	                 NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET));
 
 	s = str;
 	do {
@@ -2088,21 +2608,22 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa
 		for (; s < p; s++) {
 			char ch = s[0];
 
+			nm_assert (ch);
 			if (ch == '\\')
-				g_string_append (gstr, "\\\\");
+				nm_str_buf_append_c2 (&strbuf, '\\', '\\');
 			else if (   (   NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) \
 			             && ch < ' ') \
 			         || (   NM_FLAGS_HAS (flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) \
 			             && ((guchar) ch) >= 127))
-				_str_append_escape (gstr, ch);
+				_str_buf_append_c_escape_octal (&strbuf, ch);
 			else
-				g_string_append_c (gstr, ch);
+				nm_str_buf_append_c (&strbuf, ch);
 		}
 
 		if (buflen <= 0)
 			break;
 
-		_str_append_escape (gstr, p[0]);
+		_str_buf_append_c_escape_octal (&strbuf, p[0]);
 
 		buflen--;
 		if (buflen == 0)
@@ -2112,8 +2633,7 @@ nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8Sa
 		(void) g_utf8_validate (s, buflen, &p);
 	} while (TRUE);
 
-	*to_free = g_string_free (gstr, FALSE);
-	return *to_free;
+	return (*to_free = nm_str_buf_finalize (&strbuf, NULL));
 }
 
 const char *
@@ -2137,13 +2657,11 @@ nm_utils_buf_utf8safe_escape_bytes (GBytes *bytes, NMUtilsStrUtf8SafeFlags flags
 const char *
 nm_utils_str_utf8safe_unescape (const char *str, char **to_free)
 {
+	gsize len;
+
 	g_return_val_if_fail (to_free, NULL);
 
-	if (!str || !strchr (str, '\\')) {
-		*to_free = NULL;
-		return str;
-	}
-	return (*to_free = g_strcompress (str));
+	return nm_utils_buf_utf8safe_unescape (str, &len, (gpointer *) to_free);
 }
 
 /**
@@ -2203,7 +2721,10 @@ nm_utils_str_utf8safe_escape_cp (const char *str, NMUtilsStrUtf8SafeFlags flags)
 char *
 nm_utils_str_utf8safe_unescape_cp (const char *str)
 {
-	return str ? g_strcompress (str) : NULL;
+	char *s;
+
+	str = nm_utils_str_utf8safe_unescape (str, &s);
+	return s ?: g_strdup (str);
 }
 
 char *
@@ -2224,7 +2745,7 @@ nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags)
 /* taken from systemd's fd_wait_for_event(). Note that the timeout
  * is here in nano-seconds, not micro-seconds. */
 int
-nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns)
+nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_nsec)
 {
 	struct pollfd pollfd = {
 		.fd = fd,
@@ -2233,11 +2754,11 @@ nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns)
 	struct timespec ts, *pts;
 	int r;
 
-	if (timeout_ns < 0)
+	if (timeout_nsec < 0)
 		pts = NULL;
 	else {
-		ts.tv_sec = (time_t) (timeout_ns / NM_UTILS_NS_PER_SECOND);
-		ts.tv_nsec = (long int) (timeout_ns % NM_UTILS_NS_PER_SECOND);
+		ts.tv_sec = (time_t) (timeout_nsec / NM_UTILS_NSEC_PER_SEC);
+		ts.tv_nsec = (long int) (timeout_nsec % NM_UTILS_NSEC_PER_SEC);
 		pts = &ts;
 	}
 
@@ -2319,7 +2840,10 @@ nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll)
 /*****************************************************************************/
 
 NMUtilsNamedValue *
-nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len)
+nm_utils_named_values_from_str_dict_with_sort (GHashTable *hash,
+                                               guint *out_len,
+                                               GCompareDataFunc compare_func,
+                                               gpointer user_data)
 {
 	GHashTableIter iter;
 	NMUtilsNamedValue *values;
@@ -2342,7 +2866,8 @@ nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len)
 	values[i].name = NULL;
 	values[i].value_ptr = NULL;
 
-	nm_utils_named_value_list_sort (values, len, NULL, NULL);
+	if (compare_func)
+		nm_utils_named_value_list_sort (values, len, compare_func, user_data);
 
 	NM_SET_OUT (out_len, len);
 	return values;
@@ -3593,7 +4118,7 @@ nm_g_idle_source_new (int priority,
 }
 
 GSource *
-nm_g_timeout_source_new (guint timeout_ms,
+nm_g_timeout_source_new (guint timeout_msec,
                          int priority,
                          GSourceFunc func,
                          gpointer user_data,
@@ -3601,7 +4126,7 @@ nm_g_timeout_source_new (guint timeout_ms,
 {
 	GSource *source;
 
-	source = g_timeout_source_new (timeout_ms);
+	source = g_timeout_source_new (timeout_msec);
 	if (priority != G_PRIORITY_DEFAULT)
 		g_source_set_priority (source, priority);
 	g_source_set_callback (source, func, user_data, destroy_notify);
@@ -3625,6 +4150,26 @@ nm_g_unix_signal_source_new (int signum,
 	return source;
 }
 
+GSource *
+nm_g_unix_fd_source_new (int fd,
+                         GIOCondition io_condition,
+                         int priority,
+                         gboolean (*source_func) (int fd,
+                                                  GIOCondition condition,
+                                                  gpointer user_data),
+                         gpointer user_data,
+                         GDestroyNotify destroy_notify)
+{
+	GSource *source;
+
+	source = g_unix_fd_source_new (fd, io_condition);
+
+	if (priority != G_PRIORITY_DEFAULT)
+		g_source_set_priority (source, priority);
+	g_source_set_callback (source, G_SOURCE_FUNC (source_func), user_data, destroy_notify);
+	return source;
+}
+
 /*****************************************************************************/
 
 #define _CTX_LOG(fmt, ...) \
@@ -4038,8 +4583,47 @@ nm_utils_ifname_valid_kernel (const char *name, GError **error)
 	return FALSE;
 }
 
+/*****************************************************************************/
+
+static gboolean
+_nm_utils_ifname_valid_kernel (const char *name, GError **error)
+{
+	if (!nm_utils_ifname_valid_kernel (name, error))
+		return FALSE;
+
+	if (strchr (name, '%')) {
+		/* Kernel's dev_valid_name() accepts (almost) any binary up to 15 chars.
+		 * However, '%' is treated special as a format specifier. Try
+		 *
+		 *   ip link add 'dummy%dx' type dummy
+		 *
+		 * Don't allow that for "connection.interface-name", which either
+		 * matches an existing netdev name (thus, it cannot have a '%') or
+		 * is used to configure a name (in which case we don't want kernel
+		 * to replace the format specifier). */
+		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		                     _("'%%' is not allowed in interface names"));
+		return FALSE;
+	}
+
+	if (NM_IN_STRSET (name, "all",
+	                        "default",
+	                        "bonding_masters")) {
+		/* Certain names are not allowed. The "all" and "default" names are reserved
+		 * due to their directories in "/proc/sys/net/ipv4/conf/" and "/proc/sys/net/ipv6/conf/".
+		 *
+		 * Also, there is "/sys/class/net/bonding_masters" file.
+		 */
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+		                    _("'%s' is not allowed as interface name"), name);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 static gboolean
-_nm_utils_ifname_valid_ovs (const char* name, GError **error)
+_nm_utils_ifname_valid_ovs (const char *name, GError **error)
 {
 	const char *ch;
 
@@ -4081,10 +4665,90 @@ nm_utils_ifname_valid (const char* name,
 
 	switch (type) {
 	case NMU_IFACE_KERNEL:
-		return nm_utils_ifname_valid_kernel (name, error);
+		return _nm_utils_ifname_valid_kernel (name, error);
 	case NMU_IFACE_OVS:
 		return _nm_utils_ifname_valid_ovs (name, error);
+	case NMU_IFACE_OVS_AND_KERNEL:
+		return    _nm_utils_ifname_valid_kernel (name, error)
+		       && _nm_utils_ifname_valid_ovs (name, error);
+	case NMU_IFACE_ANY: {
+		gs_free_error GError *local = NULL;
+
+		if (_nm_utils_ifname_valid_kernel (name, error ? &local : NULL))
+			return TRUE;
+		if (_nm_utils_ifname_valid_ovs (name, NULL))
+			return TRUE;
+		if (error)
+			g_propagate_error (error, g_steal_pointer (&local));
+		return FALSE;
+	}
 	}
 
 	g_return_val_if_reached (FALSE);
 }
+
+/*****************************************************************************/
+
+void
+_nm_str_buf_ensure_size (NMStrBuf *strbuf,
+                         gsize new_size,
+                         gboolean reserve_exact)
+{
+	_nm_str_buf_assert (strbuf);
+
+	/* Currently this only supports strictly growing the buffer. */
+	nm_assert (new_size > strbuf->_priv_allocated);
+
+	if (!reserve_exact) {
+		new_size = nm_utils_get_next_realloc_size (!strbuf->_priv_do_bzero_mem,
+		                                           new_size);
+	}
+
+	strbuf->_priv_str = nm_secret_mem_realloc (strbuf->_priv_str,
+	                                           strbuf->_priv_do_bzero_mem,
+	                                           strbuf->_priv_allocated,
+	                                           new_size);
+	strbuf->_priv_allocated = new_size;
+}
+
+void
+nm_str_buf_append_printf (NMStrBuf *strbuf,
+                          const char *format,
+                          ...)
+{
+	va_list args;
+	gsize available;
+	int l;
+
+	_nm_str_buf_assert (strbuf);
+
+	available = strbuf->_priv_allocated - strbuf->_priv_len;
+
+	va_start (args, format);
+	l = g_vsnprintf (&strbuf->_priv_str[strbuf->_priv_len],
+	                 available,
+	                 format,
+	                 args);
+	va_end (args);
+
+	nm_assert (l >= 0);
+	nm_assert (l < G_MAXINT);
+
+	if ((gsize) l > available) {
+		gsize l2 = ((gsize) l) + 1u;
+
+		nm_str_buf_maybe_expand (strbuf, l2, FALSE);
+
+		va_start (args, format);
+		l = g_vsnprintf (&strbuf->_priv_str[strbuf->_priv_len],
+		                 l2,
+		                 format,
+		                 args);
+		va_end (args);
+
+		nm_assert (l >= 0);
+		nm_assert ((gsize) l == l2 - 1u);
+	}
+
+	strbuf->_priv_len += (gsize) l;
+}
diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h
index 740e61d0..537b402b 100644
--- a/shared/nm-glib-aux/nm-shared-utils.h
+++ b/shared/nm-glib-aux/nm-shared-utils.h
@@ -115,11 +115,31 @@ typedef struct {
 		 * However, as ethernet addresses fit in here nicely, use
 		 * it also for an ethernet MAC address. */
 		guint8 addr_eth[6 /*ETH_ALEN*/];
+
+		guint8 array[sizeof (struct in6_addr)];
 	};
 } NMIPAddr;
 
+#define NM_IP_ADDR_INIT { .array = { 0 } }
+
 extern const NMIPAddr nm_ip_addr_zero;
 
+static inline int
+nm_ip_addr_cmp (int addr_family, gconstpointer a, gconstpointer b)
+{
+	nm_assert_addr_family (addr_family);
+	nm_assert (a);
+	nm_assert (b);
+
+	return memcmp (a, b, nm_utils_addr_family_to_size (addr_family));
+}
+
+static inline gboolean
+nm_ip_addr_equal (int addr_family, gconstpointer a, gconstpointer b)
+{
+	return nm_ip_addr_cmp (addr_family, a, b) == 0;
+}
+
 static inline gboolean
 nm_ip_addr_is_null (int addr_family, gconstpointer addr)
 {
@@ -158,6 +178,72 @@ nm_ip4_addr_is_localhost (in_addr_t addr4)
 
 /*****************************************************************************/
 
+#define NM_UTILS_INET_ADDRSTRLEN INET6_ADDRSTRLEN
+
+static inline const char *
+nm_utils_inet_ntop (int addr_family, gconstpointer addr, char *dst)
+{
+	const char *s;
+
+	const char *inet_ntop (int af,
+	                       const void *src,
+	                       char *dst,
+	                       socklen_t size);
+
+	nm_assert_addr_family (addr_family);
+	nm_assert (addr);
+	nm_assert (dst);
+
+	s = inet_ntop (addr_family,
+	               addr,
+	               dst,
+	               addr_family == AF_INET6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN);
+	nm_assert (s);
+	return s;
+}
+
+static inline const char *
+_nm_utils_inet4_ntop (in_addr_t addr, char dst[static INET_ADDRSTRLEN])
+{
+	return nm_utils_inet_ntop (AF_INET, &addr, dst);
+}
+
+static inline const char *
+_nm_utils_inet6_ntop (const struct in6_addr *addr, char dst[static INET6_ADDRSTRLEN])
+{
+	return nm_utils_inet_ntop (AF_INET6, addr, dst);
+}
+
+static inline char *
+nm_utils_inet_ntop_dup (int addr_family, gconstpointer addr)
+{
+	char buf[NM_UTILS_INET_ADDRSTRLEN];
+
+	return g_strdup (nm_utils_inet_ntop (addr_family, addr, buf));
+}
+
+static inline char *
+nm_utils_inet4_ntop_dup (in_addr_t addr)
+{
+	return nm_utils_inet_ntop_dup (AF_INET, &addr);
+}
+
+static inline char *
+nm_utils_inet6_ntop_dup (const struct in6_addr *addr)
+{
+	return nm_utils_inet_ntop_dup (AF_INET6, addr);
+}
+
+/*****************************************************************************/
+
+gboolean nm_utils_ipaddr_is_valid (int addr_family,
+                                   const char *str_addr);
+
+gboolean nm_utils_ipaddr_is_normalized (int addr_family,
+                                        const char *str_addr);
+
+/*****************************************************************************/
+
 #define NM_CMP_RETURN(c) \
     G_STMT_START { \
         const int _cc = (c); \
@@ -307,6 +393,8 @@ gboolean nm_utils_gbytes_equal_mem (GBytes *bytes,
 
 GVariant *nm_utils_gbytes_to_variant_ay (GBytes *bytes);
 
+GVariant *nm_utils_strdict_to_variant_ass (GHashTable *strdict);
+
 /*****************************************************************************/
 
 GVariant *nm_utils_gvariant_vardict_filter (GVariant *src,
@@ -352,7 +440,25 @@ int nm_utils_dbus_path_cmp (const char *dbus_path_a, const char *dbus_path_b);
 
 typedef enum {
 	NM_UTILS_STRSPLIT_SET_FLAGS_NONE           = 0,
+
+	/* by default, strsplit will coalesce consecutive delimiters and remove
+	 * them from the result. If this flag is present, empty values are preserved
+	 * and returned.
+	 *
+	 * When combined with %NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, if a value gets
+	 * empty after strstrip(), it also gets removed. */
 	NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY = (1u << 0),
+
+	/* %NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING means that delimiters prefixed
+	 * by a backslash are not treated as a separator. Such delimiters and their escape
+	 * character are copied to the current word without unescaping them. In general,
+	 * nm_utils_strsplit_set_full() does not remove any backslash escape characters
+	 * and does no unescaping. It only considers them for skipping to split at
+	 * an escaped delimiter.
+	 *
+	 * If this is combined with (or implied by %NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED), then
+	 * the backslash escapes are removed from the result.
+	 */
 	NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING = (1u << 1),
 
 	/* If flag is set, does the same as g_strstrip() on the returned tokens.
@@ -392,6 +498,7 @@ typedef enum {
 	 * need extra care, and then only if they proceed one of the relevant characters.
 	 */
 	NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED        = (1u << 3),
+
 } NMUtilsStrsplitSetFlags;
 
 const char **nm_utils_strsplit_set_full (const char *str,
@@ -435,9 +542,65 @@ nm_utils_escaped_tokens_split (const char *str,
 	                                   | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP);
 }
 
-const char *nm_utils_escaped_tokens_escape (const char *str,
-                                            const char *delimiters,
-                                            char **out_to_free);
+typedef enum {
+	NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_NONE                       = 0,
+	NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_SPACES              = (1ull << 0),
+	NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE       = (1ull << 1),
+	NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE      = (1ull << 2),
+
+	/* Backslash characters will be escaped as "\\\\" if they precede another
+	 * character that makes it necessary. Such characters are:
+	 *
+	 *  1) before another '\\' backslash.
+	 *  2) before any delimiter in @delimiters.
+	 *  3) before any delimiter in @delimiters_as_needed.
+	 *  4) before a white space, if ESCAPE_LEADING_SPACE or ESCAPE_TRAILING_SPACE is set.
+	 *  5) before the end of the word
+	 *
+	 * Rule 4) is an extension. It's not immediately clear why with ESCAPE_LEADING_SPACE
+	 * and ESCAPE_TRAILING_SPACE we want *all* backslashes before a white space escaped.
+	 * The reason is, that we obviously want to use ESCAPE_LEADING_SPACE and ESCAPE_TRAILING_SPACE
+	 * in cases, where we later parse the backslash escaped strings back, but allowing to strip
+	 * unescaped white spaces. That means, we want that " a " gets escaped as "\\ a\\ ".
+	 * On the other hand, we also want that " a\\ b " gets escaped as "\\ a\\\\ b\\ ",
+	 * and not "\\ a\\ b\\ ". Because otherwise, the parser would need to treat "\\ "
+	 * differently depending on whether the sequence is at the beginning, end or middle
+	 * of the word.
+	 *
+	 * Rule 5) is also not immediately obvious. When used with ESCAPE_TRAILING_SPACE,
+	 * we clearly want to allow that an escaped word can have arbitrary
+	 * whitespace suffixes. That's why this mode exists. So we must escape "a\\" as
+	 * "a\\\\", so that appending " " does not change the meaning.
+	 * Also without ESCAPE_TRAILING_SPACE, we want in general that we can concatenate
+	 * two escaped words without changing their meaning. If the words would be "a\\"
+	 * and "," (with ',' being a delimiter), then the result must be "a\\\\" and "\\,"
+	 * so that the concatenated word ("a\\\\\\,") is still the same. If we would escape
+	 * them instead as "a\\" + "\\,", then the concatenated word would be "a\\\\," and
+	 * different.
+	 * */
+	NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED = (1ull << 3),
+
+	NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS    = (1ull << 4),
+} NMUtilsEscapedTokensEscapeFlags;
+
+const char *nm_utils_escaped_tokens_escape_full (const char *str,
+                                                 const char *delimiters,
+                                                 const char *delimiters_as_needed,
+                                                 NMUtilsEscapedTokensEscapeFlags flags,
+                                                 char **out_to_free);
+
+static inline const char *
+nm_utils_escaped_tokens_escape (const char *str,
+                                const char *delimiters,
+                                char **out_to_free)
+{
+	return nm_utils_escaped_tokens_escape_full (str,
+	                                            delimiters,
+	                                            NULL,
+	                                              NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_ALWAYS
+	                                            | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE,
+	                                            out_to_free);
+}
 
 static inline GString *
 nm_utils_escaped_tokens_escape_gstr_assert (const char *str,
@@ -489,6 +652,47 @@ nm_utils_escaped_tokens_escape_gstr (const char *str,
 
 /*****************************************************************************/
 
+static inline const char **
+nm_utils_escaped_tokens_options_split_list (const char *str)
+{
+	return nm_utils_strsplit_set_full (str,
+	                                   ",",
+	                                     NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP
+	                                   | NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING);
+}
+
+void nm_utils_escaped_tokens_options_split (char *str,
+                                            const char **out_key,
+                                            const char **out_val);
+
+static inline const char *
+nm_utils_escaped_tokens_options_escape_key (const char *key,
+                                            char **out_to_free)
+{
+	return nm_utils_escaped_tokens_escape_full (key,
+	                                            ",=",
+	                                            NULL,
+	                                              NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED
+	                                            | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE
+	                                            | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE,
+	                                            out_to_free);
+}
+
+static inline const char *
+nm_utils_escaped_tokens_options_escape_val (const char *val,
+                                            char **out_to_free)
+{
+	return nm_utils_escaped_tokens_escape_full (val,
+	                                            ",",
+	                                            "=",
+	                                              NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_BACKSLASH_AS_NEEDED
+	                                            | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_LEADING_SPACE
+	                                            | NM_UTILS_ESCAPED_TOKENS_ESCAPE_FLAGS_ESCAPE_TRAILING_SPACE,
+	                                            out_to_free);
+}
+
+/*****************************************************************************/
+
 #define NM_UTILS_CHECKSUM_LENGTH_MD5          16
 #define NM_UTILS_CHECKSUM_LENGTH_SHA1         20
 #define NM_UTILS_CHECKSUM_LENGTH_SHA256       32
@@ -571,6 +775,17 @@ gboolean nm_utils_parse_inaddr_prefix (int addr_family,
                                        char **out_addr,
                                        int *out_prefix);
 
+gint64 nm_g_ascii_strtoll (const char *nptr,
+                           char **endptr,
+                           guint base);
+
+guint64 nm_g_ascii_strtoull (const char *nptr,
+                             char **endptr,
+                             guint base);
+
+double nm_g_ascii_strtod (const char *nptr,
+                          char **endptr);
+
 gint64  _nm_utils_ascii_str_to_int64  (const char *str, guint base, gint64  min, gint64  max, gint64  fallback);
 guint64 _nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64 max, guint64 fallback);
 
@@ -593,8 +808,8 @@ typedef struct {
 
 #define NM_UTILS_FLAGS2STR(f, n) { .flag = f, .name = ""n, }
 
-#define _NM_UTILS_FLAGS2STR_DEFINE(scope, fcn_name, flags_type, ...) \
-scope const char * \
+#define NM_UTILS_FLAGS2STR_DEFINE(fcn_name, flags_type, ...) \
+const char * \
 fcn_name (flags_type flags, char *buf, gsize len) \
 { \
 	static const NMUtilsFlags2StrDesc descs[] = { \
@@ -604,11 +819,6 @@ fcn_name (flags_type flags, char *buf, gsize len) \
 	return nm_utils_flags2str (descs, G_N_ELEMENTS (descs), flags, buf, len); \
 };
 
-#define NM_UTILS_FLAGS2STR_DEFINE(fcn_name, flags_type, ...) \
-	_NM_UTILS_FLAGS2STR_DEFINE (, fcn_name, flags_type, __VA_ARGS__)
-#define NM_UTILS_FLAGS2STR_DEFINE_STATIC(fcn_name, flags_type, ...) \
-	_NM_UTILS_FLAGS2STR_DEFINE (static, fcn_name, flags_type, __VA_ARGS__)
-
 const char *nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs,
                                 gsize n_descs,
                                 unsigned flags,
@@ -620,8 +830,8 @@ const char *nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs,
 #define NM_UTILS_ENUM2STR(v, n)     (void) 0; case v: s = ""n""; break; (void) 0
 #define NM_UTILS_ENUM2STR_IGNORE(v) (void) 0; case v: break; (void) 0
 
-#define _NM_UTILS_ENUM2STR_DEFINE(scope, fcn_name, lookup_type, int_fmt, ...) \
-scope const char * \
+#define NM_UTILS_ENUM2STR_DEFINE_FULL(fcn_name, lookup_type, int_fmt, ...) \
+const char * \
 fcn_name (lookup_type val, char *buf, gsize len) \
 { \
 	nm_utils_to_string_buffer_init (&buf, &len); \
@@ -641,9 +851,7 @@ fcn_name (lookup_type val, char *buf, gsize len) \
 }
 
 #define NM_UTILS_ENUM2STR_DEFINE(fcn_name, lookup_type, ...) \
-	_NM_UTILS_ENUM2STR_DEFINE (, fcn_name, lookup_type, "d", __VA_ARGS__)
-#define NM_UTILS_ENUM2STR_DEFINE_STATIC(fcn_name, lookup_type, ...) \
-	_NM_UTILS_ENUM2STR_DEFINE (static, fcn_name, lookup_type, "d", __VA_ARGS__)
+	NM_UTILS_ENUM2STR_DEFINE_FULL (fcn_name, lookup_type, "d", __VA_ARGS__)
 
 /*****************************************************************************/
 
@@ -797,8 +1005,15 @@ nm_utils_error_new_cancelled (gboolean is_disposing,
 	return error;
 }
 
-gboolean nm_utils_error_is_cancelled (GError *error,
-                                      gboolean consider_is_disposing);
+gboolean nm_utils_error_is_cancelled_or_disposing (GError *error);
+
+static inline gboolean
+nm_utils_error_is_cancelled (GError *error)
+{
+	return    error
+	       && error->code == G_IO_ERROR_CANCELLED
+	       && error->domain == G_IO_ERROR;
+}
 
 gboolean nm_utils_error_is_notfound (GError *error);
 
@@ -927,6 +1142,7 @@ typedef enum {
 	NM_UTILS_STR_UTF8_SAFE_FLAG_NONE                = 0,
 	NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL         = 0x0001,
 	NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII    = 0x0002,
+	NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET              = 0x0004,
 } NMUtilsStrUtf8SafeFlags;
 
 const char *nm_utils_buf_utf8safe_escape (gconstpointer buf, gssize buflen, NMUtilsStrUtf8SafeFlags flags, char **to_free);
@@ -958,6 +1174,32 @@ nm_g_variant_unref_floating (GVariant *var)
 		g_variant_unref (var);
 }
 
+#define nm_g_variant_lookup(dictionary, ...) \
+	({ \
+		GVariant *const _dictionary = (dictionary); \
+		\
+		(   _dictionary \
+		 && g_variant_lookup (_dictionary, __VA_ARGS__)); \
+	})
+
+static inline GVariant *
+nm_g_variant_lookup_value (GVariant *dictionary,
+                           const char *key,
+                           const GVariantType *expected_type)
+{
+	return   dictionary
+	       ? g_variant_lookup_value (dictionary, key, expected_type)
+	       : NULL;
+}
+
+static inline gboolean
+nm_g_variant_is_of_type (GVariant *value,
+                         const GVariantType *type)
+{
+	return    value
+	       && g_variant_is_of_type (value, type);
+}
+
 static inline void
 nm_g_source_destroy_and_unref (GSource *source)
 {
@@ -986,12 +1228,19 @@ GSource *nm_g_idle_source_new (int priority,
                                gpointer user_data,
                                GDestroyNotify destroy_notify);
 
-GSource *nm_g_timeout_source_new (guint timeout_ms,
+GSource *nm_g_timeout_source_new (guint timeout_msec,
                                   int priority,
                                   GSourceFunc func,
                                   gpointer user_data,
                                   GDestroyNotify destroy_notify);
-
+GSource *nm_g_unix_fd_source_new (int fd,
+                                  GIOCondition io_condition,
+                                  int priority,
+                                  gboolean (*source_func) (int fd,
+                                                           GIOCondition condition,
+                                                           gpointer user_data),
+                                  gpointer user_data,
+                                  GDestroyNotify destroy_notify);
 GSource *nm_g_unix_signal_source_new (int signum,
                                       int priority,
                                       GSourceFunc handler,
@@ -1019,6 +1268,25 @@ nm_g_main_context_push_thread_default (GMainContext *context)
 	return context;
 }
 
+static inline gboolean
+nm_g_main_context_is_thread_default (GMainContext *context)
+{
+	GMainContext *cur_context;
+
+	cur_context = g_main_context_get_thread_default ();
+	if (cur_context == context)
+		return TRUE;
+
+	if (G_UNLIKELY (!cur_context))
+		cur_context = g_main_context_default ();
+	else if (G_UNLIKELY (!context))
+		context = g_main_context_default ();
+	else
+		return FALSE;
+
+	return (cur_context == context);
+}
+
 static inline GMainContext *
 nm_g_main_context_push_thread_default_if_necessary (GMainContext *context)
 {
@@ -1077,7 +1345,18 @@ typedef struct {
 	};
 } NMUtilsNamedValue;
 
-NMUtilsNamedValue *nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len);
+NMUtilsNamedValue *nm_utils_named_values_from_str_dict_with_sort (GHashTable *hash,
+                                                                  guint *out_len,
+                                                                  GCompareDataFunc compare_func,
+                                                                  gpointer user_data);
+
+static inline NMUtilsNamedValue *
+nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len)
+{
+	G_STATIC_ASSERT (G_STRUCT_OFFSET (NMUtilsNamedValue, name) == 0);
+
+	return nm_utils_named_values_from_str_dict_with_sort (hash, out_len, nm_strcmp_p_with_data, NULL);
+}
 
 gssize nm_utils_named_value_list_find (const NMUtilsNamedValue *arr,
                                        gsize len,
@@ -1146,6 +1425,26 @@ char *nm_utils_g_slist_strlist_join (const GSList *a, const char *separator);
 
 /*****************************************************************************/
 
+static inline guint
+nm_g_hash_table_size (GHashTable *hash)
+{
+	return hash ? g_hash_table_size (hash) : 0u;
+}
+
+static inline gpointer
+nm_g_hash_table_lookup (GHashTable *hash, gconstpointer key)
+{
+	return hash ? g_hash_table_lookup (hash, key) : NULL;
+}
+
+static inline gboolean
+nm_g_hash_table_remove (GHashTable *hash, gconstpointer key)
+{
+	return hash ? g_hash_table_remove (hash, key) : FALSE;
+}
+
+/*****************************************************************************/
+
 gssize nm_utils_ptrarray_find_binary_search (gconstpointer *list,
                                              gsize len,
                                              gconstpointer needle,
@@ -1190,31 +1489,25 @@ _nm_utils_strv_equal (char **strv1, char **strv2)
 
 /*****************************************************************************/
 
-#define NM_UTILS_NS_PER_SECOND   ((gint64) 1000000000)
-#define NM_UTILS_NS_PER_MSEC     ((gint64) 1000000)
-#define NM_UTILS_MSEC_PER_SECOND ((gint64) 1000)
-#define NM_UTILS_NS_TO_MSEC_CEIL(nsec)      (((nsec) + (NM_UTILS_NS_PER_MSEC - 1)) / NM_UTILS_NS_PER_MSEC)
+#define NM_UTILS_NSEC_PER_SEC  ((gint64) 1000000000)
+#define NM_UTILS_USEC_PER_SEC  ((gint64) 1000000)
+#define NM_UTILS_MSEC_PER_SEC  ((gint64) 1000)
+#define NM_UTILS_NSEC_PER_MSEC ((gint64) 1000000)
+
+static inline gint64
+NM_UTILS_NSEC_TO_MSEC_CEIL (gint64 nsec)
+{
+	return (nsec + (NM_UTILS_NSEC_PER_MSEC - 1)) / NM_UTILS_NSEC_PER_MSEC;
+}
 
 /*****************************************************************************/
 
-int nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns);
+int nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_nsec);
 ssize_t nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll);
 int nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll);
 
 /*****************************************************************************/
 
-static inline const char *
-nm_utils_dbus_normalize_object_path (const char *path)
-{
-	/* D-Bus does not allow an empty object path. Hence, whenever we mean NULL / no-object
-	 * on D-Bus, it's path is actually "/".
-	 *
-	 * Normalize that away, and return %NULL in that case. */
-	if (path && path[0] == '/' && path[1] == '\0')
-		return NULL;
-	return path;
-}
-
 #define NM_DEFINE_GDBUS_ARG_INFO_FULL(name_, ...) \
 	((GDBusArgInfo *) (&((const GDBusArgInfo) { \
 		.ref_count = -1, \
@@ -1412,6 +1705,92 @@ guint8 *nm_utils_hexstr2bin_alloc (const char *hexstr,
 
 /*****************************************************************************/
 
+#define _NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \
+                                             value_type, \
+                                             value_type_result, \
+                                             entry_cmd, \
+                                             unknown_val_cmd, \
+                                             get_operator, \
+                                             ...) \
+value_type_result \
+fcn_name (const char *name) \
+{ \
+	static const struct { \
+		const char *name; \
+		value_type value; \
+	} LIST[] = { \
+		__VA_ARGS__ \
+	}; \
+	\
+	if (NM_MORE_ASSERT_ONCE (5)) { \
+		int i; \
+		\
+		for (i = 0; i < G_N_ELEMENTS (LIST); i++) { \
+			nm_assert (LIST[i].name); \
+			if (i > 0) \
+				nm_assert (strcmp (LIST[i - 1].name, LIST[i].name) < 0); \
+		} \
+	} \
+	\
+	{ entry_cmd; } \
+	\
+	if (G_LIKELY (name)) { \
+		G_STATIC_ASSERT (G_N_ELEMENTS (LIST) > 1); \
+		G_STATIC_ASSERT (G_N_ELEMENTS (LIST) < G_MAXINT / 2 - 10); \
+		int imin = 0; \
+		int imax = (G_N_ELEMENTS (LIST) - 1); \
+		int imid = (G_N_ELEMENTS (LIST) - 1) / 2; \
+		\
+		for (;;) { \
+			const int cmp = strcmp (LIST[imid].name, name); \
+			\
+			if (G_UNLIKELY (cmp == 0)) \
+				return get_operator (LIST[imid].value); \
+			\
+			if (cmp < 0) \
+				imin = imid + 1; \
+			else \
+				imax = imid - 1; \
+			\
+			if (G_UNLIKELY (imin > imax)) \
+				break; \
+			\
+			/* integer overflow cannot happen, because LIST is shorter than G_MAXINT/2. */ \
+			imid = (imin + imax) / 2;\
+		} \
+	} \
+	\
+	{ unknown_val_cmd; } \
+}
+
+#define NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE(fcn_name, \
+                                                   result_type, \
+                                                   entry_cmd, \
+                                                   unknown_val_cmd, \
+                                                   ...) \
+	_NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (fcn_name, \
+	                                      result_type, \
+	                                      const result_type *, \
+	                                      entry_cmd, \
+	                                      unknown_val_cmd, \
+	                                      &, \
+	                                      __VA_ARGS__)
+
+#define NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(fcn_name, \
+                                            result_type, \
+                                            entry_cmd, \
+                                            unknown_val_cmd, \
+                                            ...) \
+	_NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (fcn_name, \
+	                                      result_type, \
+	                                      result_type, \
+	                                      entry_cmd, \
+	                                      unknown_val_cmd, \
+	                                      , \
+	                                      __VA_ARGS__)
+
+/*****************************************************************************/
+
 static inline GTask *
 nm_g_task_new (gpointer source_object,
                GCancellable *cancellable,
@@ -1442,9 +1821,49 @@ guint nm_utils_parse_debug_string (const char *string,
 
 /*****************************************************************************/
 
+static inline gboolean
+nm_utils_strdup_reset (char **dst, const char *src)
+{
+	nm_assert (dst);
+
+	if (nm_streq0 (*dst, src))
+		return FALSE;
+	g_free (*dst);
+	*dst = g_strdup (src);
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+/* nm_utils_get_next_realloc_size() is used to grow buffers exponentially, when
+ * the final size is unknown. As such, it has borders for which it allocates
+ * certain buffer sizes.
+ *
+ * The use of these defines is to get favorable allocation sequences.
+ * For example, nm_str_buf_init() asks for an initial allocation size. Note that
+ * it reserves the exactly requested amount, under the assumption that the
+ * user may know how many bytes will be required. However, often the caller
+ * doesn't know in advance, and NMStrBuf grows exponentially by calling
+ * nm_utils_get_next_realloc_size().
+ * Imagine you call nm_str_buf_init() with an initial buffer size 100, and you
+ * add one character at a time. Then the first reallocation will increase the
+ * buffer size only from 100 to 104.
+ * If you however start with an initial buffer size of 104, then the next reallocation
+ * via nm_utils_get_next_realloc_size() gives you 232, and so on. By using
+ * these sizes, it results in one less allocation, if you anyway don't know the
+ * exact size in advance. */
+#define NM_UTILS_GET_NEXT_REALLOC_SIZE_104     ((gsize) 104)
+#define NM_UTILS_GET_NEXT_REALLOC_SIZE_1000    ((gsize) 1000)
+
+gsize nm_utils_get_next_realloc_size (gboolean true_realloc, gsize requested);
+
+/*****************************************************************************/
+
 typedef enum {
-	NMU_IFACE_KERNEL = 0,
+	NMU_IFACE_ANY,
+	NMU_IFACE_KERNEL,
 	NMU_IFACE_OVS,
+	NMU_IFACE_OVS_AND_KERNEL,
 } NMUtilsIfaceType;
 
 gboolean nm_utils_ifname_valid_kernel (const char *name, GError **error);
diff --git a/shared/nm-glib-aux/nm-str-buf.h b/shared/nm-glib-aux/nm-str-buf.h
new file mode 100644
index 00000000..8c73bfaf
--- /dev/null
+++ b/shared/nm-glib-aux/nm-str-buf.h
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#ifndef __NM_STR_BUF_H__
+#define __NM_STR_BUF_H__
+
+#include "nm-shared-utils.h"
+#include "nm-secret-utils.h"
+
+/*****************************************************************************/
+
+/* NMStrBuf is not unlike GString. The main difference is that it can use
+ * nm_explicit_bzero() when growing the buffer. */
+typedef struct _NMStrBuf {
+
+	char *_priv_str;
+
+	/* The unions only exist because we allow/encourage read-only access
+	 * to the "len" and "allocated" fields, but modifying the fields is
+	 * only allowed to the NMStrBuf implementation itself. */
+	union {
+		/*const*/ gsize len;
+		gsize _priv_len;
+	};
+	union {
+		/*const*/ gsize allocated;
+		gsize _priv_allocated;
+	};
+
+	bool _priv_do_bzero_mem;
+} NMStrBuf;
+
+/*****************************************************************************/
+
+static inline void
+_nm_str_buf_assert (NMStrBuf *strbuf)
+{
+	nm_assert (strbuf);
+	nm_assert (strbuf->_priv_str);
+	nm_assert (strbuf->_priv_allocated > 0);
+	nm_assert (strbuf->_priv_len <= strbuf->_priv_allocated);
+}
+
+static inline void
+nm_str_buf_init (NMStrBuf *strbuf,
+                 gsize len,
+                 bool do_bzero_mem)
+{
+	nm_assert (strbuf);
+	nm_assert (len > 0);
+
+	strbuf->_priv_str          = g_malloc (len);
+	strbuf->_priv_allocated    = len;
+	strbuf->_priv_len          = 0;
+	strbuf->_priv_do_bzero_mem = do_bzero_mem;
+
+	_nm_str_buf_assert (strbuf);
+}
+
+void _nm_str_buf_ensure_size (NMStrBuf *strbuf,
+                              gsize new_size,
+                              gboolean reserve_exact);
+
+static inline void
+nm_str_buf_maybe_expand (NMStrBuf *strbuf,
+                         gsize reserve,
+                         gboolean reserve_exact)
+{
+	_nm_str_buf_assert (strbuf);
+
+	/* currently we always require to reserve a non-zero number of bytes. */
+	nm_assert (reserve > 0);
+	nm_assert (strbuf->_priv_len < G_MAXSIZE - reserve);
+
+	/* @reserve is the extra space that we require. */
+	if (G_UNLIKELY (reserve > strbuf->_priv_allocated - strbuf->_priv_len))
+		_nm_str_buf_ensure_size (strbuf, strbuf->_priv_len + reserve, reserve_exact);
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_str_buf_set_size:
+ * @strbuf: the initialized #NMStrBuf
+ * @new_len: the new length
+ * @honor_do_bzero_mem: if %TRUE, the shrinked memory will be cleared, if
+ *   do_bzero_mem is set. This should be usually set to %TRUE, unless
+ *   you know that the shrinked memory does not contain data that requires to be
+ *   cleared. When growing the size, this value has no effect.
+ * @reserve_exact: when growing the buffer, reserve the exact amount of bytes.
+ *   If %FALSE, the buffer may allocate more memory than requested to grow
+ *   exponentially.
+ *
+ * This is like g_string_set_size(). If new_len is smaller than the
+ * current length, the string gets truncated (excess memory will be cleared).
+ *
+ * When extending the length, the added bytes are undefined (like with
+ * g_string_set_size(). Likewise, if you first pre-allocate a buffer with
+ * nm_str_buf_maybe_expand(), then write to the bytes, and finally set
+ * the appropriate size, then that works as expected (by not clearing the
+ * pre-existing, grown buffer).
+ */
+static inline void
+nm_str_buf_set_size (NMStrBuf *strbuf,
+                     gsize new_len,
+                     gboolean honor_do_bzero_mem,
+                     gboolean reserve_exact)
+{
+	_nm_str_buf_assert (strbuf);
+
+	if (new_len < strbuf->_priv_len) {
+		if (   honor_do_bzero_mem
+		    && strbuf->_priv_do_bzero_mem) {
+			/* we only clear the memory that we wrote to. */
+			nm_explicit_bzero (&strbuf->_priv_str[new_len], strbuf->_priv_len - new_len);
+		}
+	} else if (new_len > strbuf->_priv_len) {
+		nm_str_buf_maybe_expand (strbuf,
+		                         new_len - strbuf->_priv_len + (reserve_exact ? 0u : 1u),
+		                         reserve_exact);
+	} else
+		return;
+
+	strbuf->_priv_len = new_len;
+}
+
+/*****************************************************************************/
+
+static inline void
+nm_str_buf_erase (NMStrBuf *strbuf,
+                  gsize pos,
+                  gssize len,
+                  gboolean honor_do_bzero_mem)
+{
+	gsize new_len;
+
+	_nm_str_buf_assert (strbuf);
+
+	nm_assert (pos <= strbuf->_priv_len);
+
+	if (len == 0)
+		return;
+
+	if (len < 0) {
+		/* truncate the string before pos */
+		nm_assert (len == -1);
+		new_len = pos;
+	} else {
+		gsize l = len;
+
+		nm_assert (l <= strbuf->_priv_len - pos);
+
+		new_len = strbuf->_priv_len - l;
+		if (pos + l < strbuf->_priv_len) {
+			memmove (&strbuf->_priv_str[pos],
+			         &strbuf->_priv_str[pos + l],
+			         strbuf->_priv_len - (pos + l));
+		}
+	}
+
+	nm_assert (new_len <= strbuf->_priv_len);
+	nm_str_buf_set_size (strbuf, new_len, honor_do_bzero_mem, TRUE);
+}
+
+/*****************************************************************************/
+
+static inline void
+nm_str_buf_append_c (NMStrBuf *strbuf,
+                     char ch)
+{
+	nm_str_buf_maybe_expand (strbuf, 2, FALSE);
+	strbuf->_priv_str[strbuf->_priv_len++] = ch;
+}
+
+static inline void
+nm_str_buf_append_c2 (NMStrBuf *strbuf,
+                      char ch0,
+                      char ch1)
+{
+	nm_str_buf_maybe_expand (strbuf, 3, FALSE);
+	strbuf->_priv_str[strbuf->_priv_len++] = ch0;
+	strbuf->_priv_str[strbuf->_priv_len++] = ch1;
+}
+
+static inline void
+nm_str_buf_append_c4 (NMStrBuf *strbuf,
+                      char ch0,
+                      char ch1,
+                      char ch2,
+                      char ch3)
+{
+	nm_str_buf_maybe_expand (strbuf, 5, FALSE);
+	strbuf->_priv_str[strbuf->_priv_len++] = ch0;
+	strbuf->_priv_str[strbuf->_priv_len++] = ch1;
+	strbuf->_priv_str[strbuf->_priv_len++] = ch2;
+	strbuf->_priv_str[strbuf->_priv_len++] = ch3;
+}
+
+static inline void
+nm_str_buf_append_len (NMStrBuf *strbuf,
+                       const char *str,
+                       gsize len)
+{
+	_nm_str_buf_assert (strbuf);
+
+	if (len > 0) {
+		nm_str_buf_maybe_expand (strbuf, len + 1, FALSE);
+		memcpy (&strbuf->_priv_str[strbuf->_priv_len], str, len);
+		strbuf->_priv_len += len;
+	}
+}
+
+static inline void
+nm_str_buf_append (NMStrBuf *strbuf,
+                   const char *str)
+{
+	nm_assert (str);
+
+	nm_str_buf_append_len (strbuf, str, strlen (str));
+}
+
+void nm_str_buf_append_printf (NMStrBuf *strbuf,
+                               const char *format,
+                               ...) _nm_printf (2, 3);
+
+static inline void
+nm_str_buf_ensure_trailing_c (NMStrBuf *strbuf, char ch)
+{
+	_nm_str_buf_assert (strbuf);
+
+	if (   strbuf->_priv_len == 0
+	    || strbuf->_priv_str[strbuf->_priv_len - 1] != ch)
+		nm_str_buf_append_c (strbuf, ch);
+}
+
+/*****************************************************************************/
+
+static inline gboolean
+nm_str_buf_is_initalized (NMStrBuf *strbuf)
+{
+	nm_assert (strbuf);
+#if NM_MORE_ASSERTS
+	if (strbuf->_priv_str)
+		_nm_str_buf_assert (strbuf);
+#endif
+	return !!strbuf->_priv_str;
+}
+
+/**
+ * nm_str_buf_get_str:
+ * @strbuf: the #NMStrBuf instance
+ *
+ * Returns the NUL terminated internal string.
+ *
+ * While constructing the string, the intermediate buffer
+ * is not NUL terminated (this makes it different from GString).
+ * Usually, one would build the string and retrieve it at the
+ * end with nm_str_buf_finalize(). This returns the NUL terminated
+ * buffer that was appended so far. Contrary to nm_str_buf_finalize(), you
+ * can still append more data to the buffer and this does not transfer ownership
+ * of the string.
+ *
+ * Returns: (transfer none): the internal string. The string
+ *   is of length "strbuf->len", which may be larger if the
+ *   returned string contains NUL characters (binary). The terminating
+ *   NUL character is always present after "strbuf->len" characters.
+ */
+static inline const char *
+nm_str_buf_get_str (NMStrBuf *strbuf)
+{
+	nm_str_buf_maybe_expand (strbuf, 1, FALSE);
+	strbuf->_priv_str[strbuf->_priv_len] = '\0';
+	return strbuf->_priv_str;
+}
+
+static inline char *
+nm_str_buf_get_str_unsafe (NMStrBuf *strbuf)
+{
+	_nm_str_buf_assert (strbuf);
+	return strbuf->_priv_str;
+}
+
+/**
+ * nm_str_buf_finalize:
+ * @strbuf: an initilized #NMStrBuf
+ * @out_len: (out): (allow-none): optional output
+ *   argument with the length of the returned string.
+ *
+ * Returns: (transfer full): the string of the buffer
+ *   which must be freed by the caller. The @strbuf
+ *   is afterwards in undefined state, though it can be
+ *   reused after nm_str_buf_init(). */
+static inline char *
+nm_str_buf_finalize (NMStrBuf *strbuf,
+                     gsize *out_len)
+{
+	nm_str_buf_maybe_expand (strbuf, 1, TRUE);
+	strbuf->_priv_str[strbuf->_priv_len] = '\0';
+
+	NM_SET_OUT (out_len, strbuf->_priv_len);
+
+	/* the buffer is in invalid state afterwards, however, we clear it
+	 * so far, that nm_auto_str_buf and nm_str_buf_destroy() is happy.  */
+	return g_steal_pointer (&strbuf->_priv_str);
+}
+
+/**
+ * nm_str_buf_destroy:
+ * @strbuf: an initialized #NMStrBuf
+ *
+ * Frees the associated memory of @strbuf. The buffer
+ * afterwards is in undefined state, but can be re-initialized
+ * with nm_str_buf_init().
+ */
+static inline void
+nm_str_buf_destroy (NMStrBuf *strbuf)
+{
+	if (!strbuf->_priv_str)
+		return;
+	_nm_str_buf_assert (strbuf);
+	if (strbuf->_priv_do_bzero_mem)
+		nm_explicit_bzero (strbuf->_priv_str, strbuf->_priv_len);
+	g_free (strbuf->_priv_str);
+
+	/* the buffer is in invalid state afterwards, however, we clear it
+	 * so far, that nm_auto_str_buf is happy when calling
+	 * nm_str_buf_destroy() again.  */
+	strbuf->_priv_str = NULL;
+}
+
+#define nm_auto_str_buf    nm_auto (nm_str_buf_destroy)
+
+#endif /* __NM_STR_BUF_H__ */
diff --git a/shared/nm-glib-aux/nm-time-utils.c b/shared/nm-glib-aux/nm-time-utils.c
index 20d663bc..356ed1a5 100644
--- a/shared/nm-glib-aux/nm-time-utils.c
+++ b/shared/nm-glib-aux/nm-time-utils.c
@@ -40,19 +40,19 @@ _t_init_global_state (void)
 	/* The only failure we tolerate is that CLOCK_BOOTTIME is not supported.
 	 * Other than that, we rely on kernel to not fail on this. */
 	g_assert (r == 0);
-	g_assert (tp.tv_nsec >= 0 && tp.tv_nsec < NM_UTILS_NS_PER_SECOND);
+	g_assert (tp.tv_nsec >= 0 && tp.tv_nsec < NM_UTILS_NSEC_PER_SEC);
 
 	/* Calculate an offset for the time stamp.
 	 *
 	 * We always want positive values, because then we can initialize
 	 * a timestamp with 0 and be sure, that it will be less then any
 	 * value nm_utils_get_monotonic_timestamp_*() might return.
-	 * For this to be true also for nm_utils_get_monotonic_timestamp_s() at
+	 * For this to be true also for nm_utils_get_monotonic_timestamp_sec() at
 	 * early boot, we have to shift the timestamp to start counting at
 	 * least from 1 second onward.
 	 *
 	 * Another advantage of shifting is, that this way we make use of the whole 31 bit
-	 * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_s()
+	 * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_sec()
 	 * wraps (~68 years).
 	 **/
 	offset_sec = (- ((gint64) tp.tv_sec)) + 1;
@@ -96,7 +96,7 @@ _t_init_global_state (void)
 		_r = clock_gettime (_p2->clk_id, _tp); \
 		\
 		nm_assert (_r == 0); \
-		nm_assert (_tp->tv_nsec >= 0 && _tp->tv_nsec < NM_UTILS_NS_PER_SECOND); \
+		nm_assert (_tp->tv_nsec >= 0 && _tp->tv_nsec < NM_UTILS_NSEC_PER_SEC); \
 		\
 		_p2; \
 	})
@@ -107,7 +107,7 @@ _t_init_global_state (void)
 /*****************************************************************************/
 
 /**
- * nm_utils_get_monotonic_timestamp_ns:
+ * nm_utils_get_monotonic_timestamp_nsec:
  *
  * Returns: a monotonically increasing time stamp in nanoseconds,
  * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
@@ -115,11 +115,11 @@ _t_init_global_state (void)
  * The returned value will start counting at an undefined point
  * in the past and will always be positive.
  *
- * All the nm_utils_get_monotonic_timestamp_*s functions return the same
+ * All the nm_utils_get_monotonic_timestamp_*sec functions return the same
  * timestamp but in different scales (nsec, usec, msec, sec).
  **/
 gint64
-nm_utils_get_monotonic_timestamp_ns (void)
+nm_utils_get_monotonic_timestamp_nsec (void)
 {
 	const GlobalState *p;
 	struct timespec tp;
@@ -130,12 +130,12 @@ nm_utils_get_monotonic_timestamp_ns (void)
 	 * integer, which makes it easier to calculate time differences (when
 	 * you want to subtract signed values).
 	 **/
-	return (((gint64) tp.tv_sec) + p->offset_sec) * NM_UTILS_NS_PER_SECOND +
+	return (((gint64) tp.tv_sec) + p->offset_sec) * NM_UTILS_NSEC_PER_SEC +
 	       tp.tv_nsec;
 }
 
 /**
- * nm_utils_get_monotonic_timestamp_us:
+ * nm_utils_get_monotonic_timestamp_usec:
  *
  * Returns: a monotonically increasing time stamp in microseconds,
  * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
@@ -143,11 +143,11 @@ nm_utils_get_monotonic_timestamp_ns (void)
  * The returned value will start counting at an undefined point
  * in the past and will always be positive.
  *
- * All the nm_utils_get_monotonic_timestamp_*s functions return the same
+ * All the nm_utils_get_monotonic_timestamp_*sec functions return the same
  * timestamp but in different scales (nsec, usec, msec, sec).
  **/
 gint64
-nm_utils_get_monotonic_timestamp_us (void)
+nm_utils_get_monotonic_timestamp_usec (void)
 {
 	const GlobalState *p;
 	struct timespec tp;
@@ -159,11 +159,11 @@ nm_utils_get_monotonic_timestamp_us (void)
 	 * you want to subtract signed values).
 	 **/
 	return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) G_USEC_PER_SEC) +
-	       (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/G_USEC_PER_SEC));
+	       (tp.tv_nsec / (NM_UTILS_NSEC_PER_SEC/G_USEC_PER_SEC));
 }
 
 /**
- * nm_utils_get_monotonic_timestamp_ms:
+ * nm_utils_get_monotonic_timestamp_msec:
  *
  * Returns: a monotonically increasing time stamp in milliseconds,
  * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
@@ -171,11 +171,11 @@ nm_utils_get_monotonic_timestamp_us (void)
  * The returned value will start counting at an undefined point
  * in the past and will always be positive.
  *
- * All the nm_utils_get_monotonic_timestamp_*s functions return the same
+ * All the nm_utils_get_monotonic_timestamp_*sec functions return the same
  * timestamp but in different scales (nsec, usec, msec, sec).
  **/
 gint64
-nm_utils_get_monotonic_timestamp_ms (void)
+nm_utils_get_monotonic_timestamp_msec (void)
 {
 	const GlobalState *p;
 	struct timespec tp;
@@ -187,23 +187,23 @@ nm_utils_get_monotonic_timestamp_ms (void)
 	 * you want to subtract signed values).
 	 **/
 	return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) 1000) +
-	       (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000));
+	       (tp.tv_nsec / (NM_UTILS_NSEC_PER_SEC/1000));
 }
 
 /**
- * nm_utils_get_monotonic_timestamp_s:
+ * nm_utils_get_monotonic_timestamp_sec:
  *
- * Returns: nm_utils_get_monotonic_timestamp_ms() in seconds (throwing
+ * Returns: nm_utils_get_monotonic_timestamp_msec() in seconds (throwing
  * away sub second parts). The returned value will always be positive.
  *
  * This value wraps after roughly 68 years which should be fine for any
  * practical purpose.
  *
- * All the nm_utils_get_monotonic_timestamp_*s functions return the same
+ * All the nm_utils_get_monotonic_timestamp_*sec functions return the same
  * timestamp but in different scales (nsec, usec, msec, sec).
  **/
 gint32
-nm_utils_get_monotonic_timestamp_s (void)
+nm_utils_get_monotonic_timestamp_sec (void)
 {
 	const GlobalState *p;
 	struct timespec tp;
@@ -216,13 +216,13 @@ nm_utils_get_monotonic_timestamp_s (void)
 /**
  * nm_utils_monotonic_timestamp_as_boottime:
  * @timestamp: the monotonic-timestamp that should be converted into CLOCK_BOOTTIME.
- * @timestamp_ns_per_tick: How many nanoseconds make one unit of @timestamp? E.g. if
- *   @timestamp is in unit seconds, pass %NM_UTILS_NS_PER_SECOND; if @timestamp is
- *   in nanoseconds, pass 1; if @timestamp is in milliseconds, pass %NM_UTILS_NS_PER_SECOND/1000.
- *   This must be a multiple of 10, and between 1 and %NM_UTILS_NS_PER_SECOND.
+ * @timestamp_nsec_per_tick: How many nanoseconds make one unit of @timestamp? E.g. if
+ *   @timestamp is in unit seconds, pass %NM_UTILS_NSEC_PER_SEC; if @timestamp is
+ *   in nanoseconds, pass 1; if @timestamp is in milliseconds, pass %NM_UTILS_NSEC_PER_SEC/1000.
+ *   This must be a multiple of 10, and between 1 and %NM_UTILS_NSEC_PER_SEC.
  *
  * Returns: the monotonic-timestamp as CLOCK_BOOTTIME, as returned by clock_gettime().
- *   The unit is the same as the passed in @timestamp based on @timestamp_ns_per_tick.
+ *   The unit is the same as the passed in @timestamp based on @timestamp_nsec_per_tick.
  *   E.g. if you passed @timestamp in as seconds, it will return boottime in seconds.
  *
  *   Note that valid monotonic-timestamps are always positive numbers (counting roughly since
@@ -234,16 +234,16 @@ nm_utils_get_monotonic_timestamp_s (void)
  * On older kernels that don't support CLOCK_BOOTTIME, the returned time is instead CLOCK_MONOTONIC.
  **/
 gint64
-nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_per_tick)
+nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_nsec_per_tick)
 {
 	const GlobalState *p;
 	gint64 offset;
 
-	/* only support ns-per-tick being a multiple of 10. */
-	g_return_val_if_fail (timestamp_ns_per_tick == 1
-	                      || (timestamp_ns_per_tick > 0 &&
-	                          timestamp_ns_per_tick <= NM_UTILS_NS_PER_SECOND &&
-	                          timestamp_ns_per_tick % 10 == 0),
+	/* only support nsec-per-tick being a multiple of 10. */
+	g_return_val_if_fail (   timestamp_nsec_per_tick == 1
+	                      || (timestamp_nsec_per_tick > 0 &&
+	                          timestamp_nsec_per_tick <= NM_UTILS_NSEC_PER_SEC &&
+	                          timestamp_nsec_per_tick % 10 == 0),
 	                      -1);
 
 	/* if the caller didn't yet ever fetch a monotonic-timestamp, he cannot pass any meaningful
@@ -255,7 +255,7 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_
 	nm_assert (p->offset_sec <= 0);
 
 	/* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */
-	offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick);
+	offset = p->offset_sec * (NM_UTILS_NSEC_PER_SEC / timestamp_nsec_per_tick);
 
 	nm_assert (offset <= 0 && offset > G_MININT64);
 
@@ -270,9 +270,9 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_
  * @boottime: the timestamp from CLOCK_BOOTTIME (or CLOCK_MONOTONIC, if
  *   kernel does not support CLOCK_BOOTTIME and monotonic timestamps are based
  *   on CLOCK_MONOTONIC).
- * @timestamp_ns_per_tick: the scale in which @boottime is. If @boottime is in
+ * @timestamp_nsec_per_tick: the scale in which @boottime is. If @boottime is in
  *   nano seconds, this should be 1. If it is in milli seconds, this should be
- *   %NM_UTILS_NS_PER_SECOND/1000, etc.
+ *   %NM_UTILS_NSEC_PER_SEC/1000, etc.
  *
  * Returns: the same timestamp in monotonic timestamp scale.
  *
@@ -284,16 +284,16 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_
  * This is the inverse of nm_utils_monotonic_timestamp_as_boottime().
  */
 gint64
-nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_ns_per_tick)
+nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_nsec_per_tick)
 {
 	const GlobalState *p;
 	gint64 offset;
 
-	/* only support ns-per-tick being a multiple of 10. */
-	g_return_val_if_fail (timestamp_ns_per_tick == 1
-	                      || (timestamp_ns_per_tick > 0 &&
-	                          timestamp_ns_per_tick <= NM_UTILS_NS_PER_SECOND &&
-	                          timestamp_ns_per_tick % 10 == 0),
+	/* only support nsec-per-tick being a multiple of 10. */
+	g_return_val_if_fail (   timestamp_nsec_per_tick == 1
+	                      || (timestamp_nsec_per_tick > 0 &&
+	                          timestamp_nsec_per_tick <= NM_UTILS_NSEC_PER_SEC &&
+	                          timestamp_nsec_per_tick % 10 == 0),
 	                      -1);
 
 	p = _t_get_global_state ();
@@ -301,7 +301,7 @@ nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_n
 	nm_assert (p->offset_sec <= 0);
 
 	/* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */
-	offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick);
+	offset = p->offset_sec * (NM_UTILS_NSEC_PER_SEC / timestamp_nsec_per_tick);
 
 	nm_assert (offset <= 0 && offset > G_MININT64);
 
@@ -312,21 +312,21 @@ nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_n
 }
 
 gint64
-nm_utils_clock_gettime_ns (clockid_t clockid)
+nm_utils_clock_gettime_nsec (clockid_t clockid)
 {
 	struct timespec tp;
 
 	if (clock_gettime (clockid, &tp) != 0)
 		return -NM_ERRNO_NATIVE (errno);
-	return nm_utils_timespec_to_ns (&tp);
+	return nm_utils_timespec_to_nsec (&tp);
 }
 
 gint64
-nm_utils_clock_gettime_ms (clockid_t clockid)
+nm_utils_clock_gettime_msec (clockid_t clockid)
 {
 	struct timespec tp;
 
 	if (clock_gettime (clockid, &tp) != 0)
 		return -NM_ERRNO_NATIVE (errno);
-	return nm_utils_timespec_to_ms (&tp);
+	return nm_utils_timespec_to_msec (&tp);
 }
diff --git a/shared/nm-glib-aux/nm-time-utils.h b/shared/nm-glib-aux/nm-time-utils.h
index 8bf41b96..190d9491 100644
--- a/shared/nm-glib-aux/nm-time-utils.h
+++ b/shared/nm-glib-aux/nm-time-utils.h
@@ -9,34 +9,42 @@
 #include <time.h>
 
 static inline gint64
-nm_utils_timespec_to_ns (const struct timespec *ts)
+nm_utils_timespec_to_nsec (const struct timespec *ts)
 {
-	return   (((gint64) ts->tv_sec) * ((gint64) NM_UTILS_NS_PER_SECOND))
+	return   (((gint64) ts->tv_sec) * ((gint64) NM_UTILS_NSEC_PER_SEC))
 	       +  ((gint64) ts->tv_nsec);
 }
 
 static inline gint64
-nm_utils_timespec_to_ms (const struct timespec *ts)
+nm_utils_timespec_to_msec (const struct timespec *ts)
 {
 	return   (((gint64) ts->tv_sec)  * ((gint64) 1000))
-	       + (((gint64) ts->tv_nsec) / ((gint64) NM_UTILS_NS_PER_SECOND / 1000));
+	       + (((gint64) ts->tv_nsec) / ((gint64) NM_UTILS_NSEC_PER_SEC / 1000));
 }
 
-gint64 nm_utils_get_monotonic_timestamp_ns (void);
-gint64 nm_utils_get_monotonic_timestamp_us (void);
-gint64 nm_utils_get_monotonic_timestamp_ms (void);
-gint32 nm_utils_get_monotonic_timestamp_s (void);
-gint64 nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ticks_per_ns);
-gint64 nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_ns_per_tick);
+gint64 nm_utils_get_monotonic_timestamp_nsec (void);
+gint64 nm_utils_get_monotonic_timestamp_usec (void);
+gint64 nm_utils_get_monotonic_timestamp_msec (void);
+gint32 nm_utils_get_monotonic_timestamp_sec (void);
+
+gint64 nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ticks_per_nsec);
+gint64 nm_utils_monotonic_timestamp_from_boottime (guint64 boottime, gint64 timestamp_nsec_per_tick);
+
+static inline gint64
+nm_utils_get_monotonic_timestamp_nsec_cached (gint64 *cache_now)
+{
+	return    (*cache_now)
+	       ?: (*cache_now = nm_utils_get_monotonic_timestamp_nsec ());
+}
 
 static inline gint64
-nm_utils_get_monotonic_timestamp_ns_cached (gint64 *cache_now)
+nm_utils_get_monotonic_timestamp_msec_cached (gint64 *cache_now)
 {
 	return    (*cache_now)
-	       ?: (*cache_now = nm_utils_get_monotonic_timestamp_ns ());
+	       ?: (*cache_now = nm_utils_get_monotonic_timestamp_msec ());
 }
 
-gint64 nm_utils_clock_gettime_ns (clockid_t clockid);
-gint64 nm_utils_clock_gettime_ms (clockid_t clockid);
+gint64 nm_utils_clock_gettime_nsec (clockid_t clockid);
+gint64 nm_utils_clock_gettime_msec (clockid_t clockid);
 
 #endif /* __NM_TIME_UTILS_H__ */
diff --git a/shared/nm-utils/tests/meson.build b/shared/nm-glib-aux/tests/meson.build
index 1ee5efff..f6328db9 100644
--- a/shared/nm-utils/tests/meson.build
+++ b/shared/nm-glib-aux/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_unit = 'test-shared-general'
 
 c_flags = [
@@ -14,7 +16,7 @@ exe = executable(
 )
 
 test(
-  'shared/nm-utils/' + test_unit,
+  'shared/nm-glib-aux/' + test_unit,
   test_script,
   args: test_args + [exe.full_path()],
   timeout: default_test_timeout,
diff --git a/shared/nm-utils/tests/test-shared-general.c b/shared/nm-glib-aux/tests/test-shared-general.c
index 34da19b3..5c8bdf18 100644
--- a/shared/nm-utils/tests/test-shared-general.c
+++ b/shared/nm-glib-aux/tests/test-shared-general.c
@@ -9,6 +9,7 @@
 
 #include "nm-std-aux/unaligned.h"
 #include "nm-glib-aux/nm-random-utils.h"
+#include "nm-glib-aux/nm-str-buf.h"
 #include "nm-glib-aux/nm-time-utils.h"
 #include "nm-glib-aux/nm-ref-string.h"
 
@@ -44,7 +45,7 @@ test_gpid (void)
 static void
 test_monotonic_timestamp (void)
 {
-	g_assert (nm_utils_get_monotonic_timestamp_s () > 0);
+	g_assert (nm_utils_get_monotonic_timestamp_sec () > 0);
 }
 
 /*****************************************************************************/
@@ -296,7 +297,7 @@ _strv_cmp_fuzz_input (const char *const*in,
 	if (nmtst_get_rand_bool ()) {
 		/* randomly swap the original and the clone. That means, out_s1 is either
 		 * the input argument (as-is) or the sementically equal clone. */
-		NMTST_SWAP (*out_s1, *out_s2);
+		NM_SWAP (*out_s1, *out_s2);
 	}
 	if (nmtst_get_rand_bool ()) {
 		/* randomly make s1 and s2 the same. This is for testing that
@@ -554,6 +555,222 @@ test_nm_ref_string (void)
 
 /*****************************************************************************/
 
+static
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
+	_do_string_table_lookup,
+	int,
+	{ ; },
+	{ return -1; },
+	{ "0", 0 },
+	{ "1", 1 },
+	{ "2", 2 },
+	{ "3", 3 },
+)
+
+static void
+test_string_table_lookup (void)
+{
+	const char *const args[] = { NULL, "0", "1", "2", "3", "x", };
+	int i;
+
+	for (i = 0; i < G_N_ELEMENTS (args); i++) {
+		const char *needle = args[i];
+		const int val2 = _nm_utils_ascii_str_to_int64 (needle, 10, 0, 100, -1);
+		int val;
+
+		val = _do_string_table_lookup (needle);
+		g_assert_cmpint (val, ==, val2);
+	}
+}
+
+/*****************************************************************************/
+
+static void
+test_nm_utils_get_next_realloc_size (void)
+{
+	static const struct {
+		gsize requested;
+		gsize reserved_true;
+		gsize reserved_false;
+	} test_data[] = {
+		{      0,     8,     8 },
+		{      1,     8,     8 },
+		{      8,     8,     8 },
+		{      9,    16,    16 },
+		{     16,    16,    16 },
+		{     17,    32,    32 },
+		{     32,    32,    32 },
+		{     33,    40,    40 },
+		{     40,    40,    40 },
+		{     41,   104,   104 },
+		{    104,   104,   104 },
+		{    105,   232,   232 },
+		{    232,   232,   232 },
+		{    233,   488,   488 },
+		{    488,   488,   488 },
+		{    489,  1000,  1000 },
+		{   1000,  1000,  1000 },
+		{   1001,  2024,  2024 },
+		{   2024,  2024,  2024 },
+		{   2025,  4072,  4072 },
+		{   4072,  4072,  4072 },
+		{   4073,  8168,  8168 },
+		{   8168,  8168,  8168 },
+		{   8169, 12264, 16360 },
+		{  12263, 12264, 16360 },
+		{  12264, 12264, 16360 },
+		{  12265, 16360, 16360 },
+		{  16360, 16360, 16360 },
+		{  16361, 20456, 32744 },
+		{  20456, 20456, 32744 },
+		{  20457, 24552, 32744 },
+		{  24552, 24552, 32744 },
+		{  24553, 28648, 32744 },
+		{  28648, 28648, 32744 },
+		{  28649, 32744, 32744 },
+		{  32744, 32744, 32744 },
+		{  32745, 36840, 65512 },
+		{  36840, 36840, 65512 },
+		{  G_MAXSIZE - 0x1000u,  G_MAXSIZE, G_MAXSIZE },
+		{  G_MAXSIZE - 25u,      G_MAXSIZE, G_MAXSIZE },
+		{  G_MAXSIZE - 24u,      G_MAXSIZE, G_MAXSIZE },
+		{  G_MAXSIZE - 1u,       G_MAXSIZE, G_MAXSIZE },
+		{  G_MAXSIZE,            G_MAXSIZE, G_MAXSIZE },
+		{  NM_UTILS_GET_NEXT_REALLOC_SIZE_104,  NM_UTILS_GET_NEXT_REALLOC_SIZE_104,  NM_UTILS_GET_NEXT_REALLOC_SIZE_104 },
+		{  NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 },
+	};
+	guint i;
+
+	G_STATIC_ASSERT_EXPR (NM_UTILS_GET_NEXT_REALLOC_SIZE_104  == 104u);
+	G_STATIC_ASSERT_EXPR (NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 == 1000u);
+
+	for (i = 0; i < G_N_ELEMENTS (test_data) + 5000u; i++) {
+		gsize requested0;
+
+		if (i < G_N_ELEMENTS (test_data))
+			requested0 = test_data[i].requested;
+		else {
+			/* find some interesting random values for testing. */
+			switch (nmtst_get_rand_uint32 () % 5) {
+			case 0:
+				requested0 = nmtst_get_rand_size ();
+				break;
+			case 1:
+				/* values close to G_MAXSIZE. */
+				requested0 = G_MAXSIZE - (nmtst_get_rand_uint32 () % 12000u);
+				break;
+			case 2:
+				/* values around G_MAXSIZE/2. */
+				requested0 = (G_MAXSIZE / 2u) + 6000u - (nmtst_get_rand_uint32 () % 12000u);
+				break;
+			case 3:
+				/* values around powers of 2. */
+				requested0 = (((gsize) 1) << (nmtst_get_rand_uint32 () % (sizeof (gsize) * 8u))) + 6000u - (nmtst_get_rand_uint32 () % 12000u);
+				break;
+			case 4:
+				/* values around 4k borders. */
+				requested0 = (nmtst_get_rand_size () & ~((gsize) 0xFFFu)) + 30u - (nmtst_get_rand_uint32 () % 60u);
+				break;
+			default: g_assert_not_reached ();
+			}
+		}
+
+		{
+			const gsize requested = requested0;
+			const gsize reserved_true = nm_utils_get_next_realloc_size (TRUE, requested);
+			const gsize reserved_false = nm_utils_get_next_realloc_size (FALSE, requested);
+
+			g_assert_cmpuint (reserved_true, >, 0);
+			g_assert_cmpuint (reserved_false, >, 0);
+			g_assert_cmpuint (reserved_true, >=, requested);
+			g_assert_cmpuint (reserved_false, >=, requested);
+			g_assert_cmpuint (reserved_false, >=, reserved_true);
+
+			if (i < G_N_ELEMENTS (test_data)) {
+				g_assert_cmpuint (reserved_true, ==, test_data[i].reserved_true);
+				g_assert_cmpuint (reserved_false, ==, test_data[i].reserved_false);
+			}
+
+			/* reserved_false is generally the next power of two - 24. */
+			if (reserved_false == G_MAXSIZE)
+				g_assert_cmpuint (requested, >, G_MAXSIZE / 2u - 24u);
+			else {
+				g_assert_cmpuint (reserved_false, <=, G_MAXSIZE - 24u);
+				if (reserved_false >= 40) {
+					const gsize _pow2 = reserved_false + 24u;
+
+					/* reserved_false must always be a power of two minus 24. */
+					g_assert_cmpuint (_pow2, >=, 64u);
+					g_assert_cmpuint (_pow2, >, requested);
+					g_assert (nm_utils_is_power_of_two (_pow2));
+
+					/* but _pow2/2 must also be smaller than what we requested. */
+					g_assert_cmpuint (_pow2 / 2u - 24u, <, requested);
+				} else {
+					/* smaller values are hard-coded. */
+				}
+			}
+
+			/* reserved_true is generally the next 4k border - 24. */
+			if (reserved_true == G_MAXSIZE)
+				g_assert_cmpuint (requested, >, G_MAXSIZE - 0x1000u - 24u);
+			else {
+				g_assert_cmpuint (reserved_true, <=, G_MAXSIZE - 24u);
+				if (reserved_true > 8168u) {
+					const gsize page_border = reserved_true + 24u;
+
+					/* reserved_true must always be aligned to 4k (minus 24). */
+					g_assert_cmpuint (page_border % 0x1000u, ==, 0);
+					if (requested > 0x1000u - 24u) {
+						/* page_border not be more than 4k above requested. */
+						g_assert_cmpuint (page_border, >=, 0x1000u - 24u);
+						g_assert_cmpuint (page_border - 0x1000u - 24u, <, requested);
+					}
+				} else {
+					/* for smaller sizes, reserved_true and reserved_false are the same. */
+					g_assert_cmpuint (reserved_true, ==, reserved_false);
+				}
+			}
+
+		}
+	}
+}
+
+/*****************************************************************************/
+
+static void
+test_nm_str_buf (void)
+{
+	guint i_run;
+
+	for (i_run = 0; TRUE; i_run++) {
+		nm_auto_str_buf NMStrBuf strbuf = { };
+		nm_auto_free_gstring GString *gstr = NULL;
+		int i, j, k;
+		int c;
+
+		nm_str_buf_init (&strbuf,
+		                 nmtst_get_rand_uint32 () % 200u + 1u,
+		                 nmtst_get_rand_bool ());
+
+		if (i_run < 1000) {
+			c = nmtst_get_rand_word_length (NULL);
+			for (i = 0; i < c; i++)
+				nm_str_buf_append_c (&strbuf, '0' + (i % 10));
+			gstr = g_string_new (nm_str_buf_get_str (&strbuf));
+			j = nmtst_get_rand_uint32 () % (strbuf.len + 1);
+			k = nmtst_get_rand_uint32 () % (strbuf.len - j + 2) - 1;
+
+			nm_str_buf_erase (&strbuf, j, k, nmtst_get_rand_bool ());
+			g_string_erase (gstr, j, k);
+			g_assert_cmpstr (gstr->str, ==, nm_str_buf_get_str (&strbuf));
+		} else
+			return;
+	}
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int main (int argc, char **argv)
@@ -572,6 +789,9 @@ int main (int argc, char **argv)
 	g_test_add_func ("/general/test_strstrip_avoid_copy", test_strstrip_avoid_copy);
 	g_test_add_func ("/general/test_nm_utils_bin2hexstr", test_nm_utils_bin2hexstr);
 	g_test_add_func ("/general/test_nm_ref_string", test_nm_ref_string);
+	g_test_add_func ("/general/test_string_table_lookup", test_string_table_lookup);
+	g_test_add_func ("/general/test_nm_utils_get_next_realloc_size", test_nm_utils_get_next_realloc_size);
+	g_test_add_func ("/general/test_nm_str_buf", test_nm_str_buf);
 
 	return g_test_run ();
 }
diff --git a/shared/nm-keyfile/nm-keyfile-internal.h b/shared/nm-keyfile/nm-keyfile-internal.h
new file mode 100644
index 00000000..42003a5f
--- /dev/null
+++ b/shared/nm-keyfile/nm-keyfile-internal.h
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_KEYFILE_INTERNAL_H__
+#define __NM_KEYFILE_INTERNAL_H__
+
+#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)
+#error Cannot use this header.
+#endif
+
+#include <sys/types.h>
+
+#include "nm-connection.h"
+#include "nm-setting-8021x.h"
+
+#include "nm-core-internal.h"
+#include "nm-meta-setting.h"
+
+/*****************************************************************************/
+
+#define NM_KEYFILE_CERT_SCHEME_PREFIX_PATH "file://"
+#define NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11 "pkcs11:"
+#define NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB "data:;base64,"
+
+char *nm_keyfile_detect_unqualified_path_scheme (const char *base_dir,
+                                                 gconstpointer pdata,
+                                                 gsize data_len,
+                                                 gboolean consider_exists,
+                                                 gboolean *out_exists);
+
+typedef enum {
+	NM_KEYFILE_READ_TYPE_WARN               = 1,
+} NMKeyfileReadType;
+
+/**
+ * NMKeyfileReadHandler:
+ *
+ * Hook to nm_keyfile_read(). The user might fail the reading by setting
+ * @error.
+ *
+ * Returns: should return TRUE, if the reading was handled. Otherwise,
+ * a default action will be performed that depends on the @type.
+ * For %NM_KEYFILE_READ_TYPE_WARN type, the default action is doing nothing.
+ */
+typedef gboolean (*NMKeyfileReadHandler) (GKeyFile *keyfile,
+                                          NMConnection *connection,
+                                          NMKeyfileReadType type,
+                                          void *type_data,
+                                          void *user_data,
+                                          GError **error);
+
+typedef enum {
+	NM_KEYFILE_WARN_SEVERITY_DEBUG                  = 1000,
+	NM_KEYFILE_WARN_SEVERITY_INFO                   = 2000,
+	NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE      = 2901,
+	NM_KEYFILE_WARN_SEVERITY_WARN                   = 3000,
+} NMKeyfileWarnSeverity;
+
+/**
+ * NMKeyfileReadTypeDataWarn:
+ *
+ * this struct is passed as @type_data for the @NMKeyfileReadHandler of
+ * type %NM_KEYFILE_READ_TYPE_WARN.
+ */
+typedef struct {
+	/* might be %NULL, if the warning is not about a group. */
+	const char *group;
+
+	/* might be %NULL, if the warning is not about a setting. */
+	NMSetting *setting;
+
+	/* might be %NULL, if the warning is not about a property. */
+	const char *property_name;
+
+	NMKeyfileWarnSeverity severity;
+	const char *message;
+} NMKeyfileReadTypeDataWarn;
+
+NMConnection *nm_keyfile_read (GKeyFile *keyfile,
+                               const char *base_dir,
+                               NMKeyfileReadHandler handler,
+                               void *user_data,
+                               GError **error);
+
+gboolean nm_keyfile_read_ensure_id (NMConnection *connection,
+                                    const char *fallback_id);
+
+gboolean nm_keyfile_read_ensure_uuid (NMConnection *connection,
+                                      const char *fallback_uuid_seed);
+
+/*****************************************************************************/
+
+typedef enum {
+	NM_KEYFILE_WRITE_TYPE_CERT              = 1,
+} NMKeyfileWriteType;
+
+/**
+ * NMKeyfileWriteHandler:
+ *
+ * This is a hook to tweak the serialization.
+ *
+ * Handler for certain properties or events that are not entirely contained
+ * within the keyfile or that might be serialized differently. The @type and
+ * @type_data arguments tell which kind of argument we have at hand.
+ *
+ * Currently only the type %NM_KEYFILE_WRITE_TYPE_CERT is supported, which provides
+ * @type_data as %NMKeyfileWriteTypeDataCert. However, this handler should be generic enough
+ * to support other types as well.
+ *
+ * This don't have to be only "properties". For example, nm_keyfile_read() uses
+ * a similar handler to push warnings to the caller.
+ *
+ * If the handler raises an error, it should set the @error value. This causes
+ * the an overall failure.
+ *
+ * Returns: whether the issue was handled. If the type was unhandled,
+ * a default action will be performed. This might be raise an error,
+ * do some fallback parsing, or do nothing.
+ */
+typedef gboolean (*NMKeyfileWriteHandler) (NMConnection *connection,
+                                           GKeyFile *keyfile,
+                                           NMKeyfileWriteType type,
+                                           void *type_data,
+                                           void *user_data,
+                                           GError **error);
+
+/**
+ * NMKeyfileWriteTypeDataCert:
+ *
+ * this struct is passed as @type_data for the @NMKeyfileWriteHandler of
+ * type %NM_KEYFILE_WRITE_TYPE_CERT.
+ */
+typedef struct {
+	const NMSetting8021xSchemeVtable *vtable;
+	NMSetting8021x *setting;
+} NMKeyfileWriteTypeDataCert;
+
+GKeyFile *nm_keyfile_write (NMConnection *connection,
+                            NMKeyfileWriteHandler handler,
+                            void *user_data,
+                            GError **error);
+
+/*****************************************************************************/
+
+char *nm_keyfile_plugin_kf_get_string (GKeyFile *kf, const char *group, const char *key, GError **error);
+void nm_keyfile_plugin_kf_set_string (GKeyFile *kf, const char *group, const char *key, const char *value);
+
+int nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value);
+
+void _nm_keyfile_copy (GKeyFile *dst, GKeyFile *src);
+gboolean _nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b);
+gboolean _nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order);
+gboolean _nm_keyfile_has_values (GKeyFile *keyfile);
+
+/*****************************************************************************/
+
+#define NM_KEYFILE_GROUP_NMMETA                 ".nmmeta"
+#define NM_KEYFILE_KEY_NMMETA_NM_GENERATED      "nm-generated"
+#define NM_KEYFILE_KEY_NMMETA_VOLATILE          "volatile"
+#define NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE  "shadowed-storage"
+#define NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED    "shadowed-owned"
+
+#define NM_KEYFILE_PATH_NAME_LIB                 NMLIBDIR  "/system-connections"
+#define NM_KEYFILE_PATH_NAME_ETC_DEFAULT         NMCONFDIR "/system-connections"
+#define NM_KEYFILE_PATH_NAME_RUN                 NMRUNDIR  "/system-connections"
+
+#define NM_KEYFILE_PATH_SUFFIX_NMCONNECTION      ".nmconnection"
+
+#define NM_KEYFILE_PATH_SUFFIX_NMMETA            ".nmmeta"
+
+#define NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL      "/dev/null"
+
+gboolean nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension);
+
+char *nm_keyfile_utils_create_filename (const char *filename, gboolean with_extension);
+
+#endif /* __NM_KEYFILE_INTERNAL_H__ */
diff --git a/shared/nm-keyfile/nm-keyfile-utils.c b/shared/nm-keyfile/nm-keyfile-utils.c
new file mode 100644
index 00000000..e18db20d
--- /dev/null
+++ b/shared/nm-keyfile/nm-keyfile-utils.c
@@ -0,0 +1,636 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-keyfile-utils.h"
+
+#include <stdlib.h>
+
+#include "nm-keyfile-internal.h"
+#include "nm-setting-wired.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-wireless-security.h"
+
+/*****************************************************************************/
+
+/**
+ * nm_key_file_get_boolean:
+ * @kf: the #GKeyFile
+ * @group: the group
+ * @key: the key
+ * @default_value: the default value if the value is set or not parsable as a boolean.
+ *
+ * Replacement for g_key_file_get_boolean() (which uses g_key_file_parse_value_as_boolean()).
+ * g_key_file_get_boolean() seems odd to me, because it accepts trailing ASCII whitespace,
+ * but not leading.
+ * This uses _nm_utils_ascii_str_to_bool(), which accepts trailing and leading whitespace,
+ * case-insensitive words, and also strings like "on" and "off".
+ * _nm_utils_ascii_str_to_bool() is our way to parse booleans from string, and we should
+ * use that one consistently.
+ *
+ * Also, it doesn't have g_key_file_get_boolean()'s odd API to require an error argument
+ * to detect parsing failures.
+ *
+ * Returns: either %TRUE or %FALSE if the key exists and is parsable as a boolean.
+ *   Otherwise, @default_value. Sets errno to ENODATA, EINVAL or 0, depending on whether
+ *   the key exists, whether the value is invalid, or success.
+ */
+int
+nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value)
+{
+	int v;
+	gs_free char *value = NULL;
+
+	value = g_key_file_get_value (kf, group, key, NULL);
+
+	if (!value) {
+		errno = ENODATA;
+		return default_value;
+	}
+	v = _nm_utils_ascii_str_to_bool (value, -1);
+	if (v != -1) {
+		errno = 0;
+		return v;
+	}
+	errno = EINVAL;
+	return default_value;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	const char *setting;
+	const char *alias;
+} SettingAlias;
+
+static const SettingAlias alias_list[] = {
+	{ NM_SETTING_WIRED_SETTING_NAME, "ethernet" },
+	{ NM_SETTING_WIRELESS_SETTING_NAME, "wifi" },
+	{ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, "wifi-security" },
+};
+
+const char *
+nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name)
+{
+	guint i;
+
+	g_return_val_if_fail (setting_name != NULL, NULL);
+
+	for (i = 0; i < G_N_ELEMENTS (alias_list); i++) {
+		if (nm_streq (setting_name, alias_list[i].setting))
+			return alias_list[i].alias;
+	}
+	return NULL;
+}
+
+const char *
+nm_keyfile_plugin_get_setting_name_for_alias (const char *alias)
+{
+	guint i;
+
+	g_return_val_if_fail (alias != NULL, NULL);
+
+	for (i = 0; i < G_N_ELEMENTS (alias_list); i++) {
+		if (nm_streq (alias, alias_list[i].alias))
+			return alias_list[i].setting;
+	}
+	return NULL;
+}
+
+/*****************************************************************************/
+
+/* List helpers */
+#define DEFINE_KF_LIST_WRAPPER(stype, get_ctype, set_ctype) \
+get_ctype \
+nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \
+                                         const char *group, \
+                                         const char *key, \
+                                         gsize *out_length, \
+                                         GError **error) \
+{ \
+	get_ctype list; \
+	const char *alias; \
+	GError *local = NULL; \
+	gsize l; \
+ \
+	list = g_key_file_get_##stype##_list (kf, group, key, &l, &local); \
+	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
+		if (alias) { \
+			g_clear_error (&local); \
+			list = g_key_file_get_##stype##_list (kf, alias, key, &l, &local); \
+		} \
+	} \
+	nm_assert ((!local) != (!list)); \
+	if (local) \
+		g_propagate_error (error, local); \
+	if (!list) \
+		l = 0; \
+	NM_SET_OUT (out_length, l); \
+	return list; \
+} \
+ \
+void \
+nm_keyfile_plugin_kf_set_##stype##_list (GKeyFile *kf, \
+                                         const char *group, \
+                                         const char *key, \
+                                         set_ctype list[], \
+                                         gsize length) \
+{ \
+	const char *alias; \
+ \
+	alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
+	g_key_file_set_##stype##_list (kf, alias ?: group, key, list, length); \
+}
+
+DEFINE_KF_LIST_WRAPPER(integer, int*, int);
+DEFINE_KF_LIST_WRAPPER(string, char **, const char* const);
+
+void
+nm_keyfile_plugin_kf_set_integer_list_uint8 (GKeyFile *kf,
+                                             const char *group,
+                                             const char *key,
+                                             const guint8 *data,
+                                             gsize length)
+{
+	gsize i;
+	gsize l = length * 4 + 2;
+	gs_free char *value = g_malloc (l);
+	char *s = value;
+
+	g_return_if_fail (kf);
+	g_return_if_fail (!length || data);
+	g_return_if_fail (group && group[0]);
+	g_return_if_fail (key && key[0]);
+
+	value[0] = '\0';
+	for (i = 0; i < length; i++)
+		nm_utils_strbuf_append (&s, &l, "%d;", (int) data[i]);
+	nm_assert (l > 0);
+	nm_keyfile_plugin_kf_set_value (kf, group, key, value);
+}
+
+/* Single value helpers */
+#define DEFINE_KF_WRAPPER(stype, get_ctype, set_ctype) \
+get_ctype \
+nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \
+                                  const char *group, \
+                                  const char *key, \
+                                  GError **error) \
+{ \
+	get_ctype val; \
+	const char *alias; \
+	GError *local = NULL; \
+ \
+	val = g_key_file_get_##stype (kf, group, key, &local); \
+	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
+		if (alias) { \
+			g_clear_error (&local); \
+			val = g_key_file_get_##stype (kf, alias, key, &local); \
+		} \
+	} \
+	if (local) \
+		g_propagate_error (error, local); \
+	return val; \
+} \
+ \
+void \
+nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \
+                                  const char *group, \
+                                  const char *key, \
+                                  set_ctype value) \
+{ \
+	const char *alias; \
+ \
+	alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
+	g_key_file_set_##stype (kf, alias ?: group, key, value); \
+}
+
+DEFINE_KF_WRAPPER(string, char*, const char*);
+DEFINE_KF_WRAPPER(boolean, gboolean, gboolean);
+DEFINE_KF_WRAPPER(value, char*, const char*);
+
+gint64
+nm_keyfile_plugin_kf_get_int64 (GKeyFile *kf,
+                                const char *group,
+                                const char *key,
+                                guint base,
+                                gint64 min,
+                                gint64 max,
+                                gint64 fallback,
+                                GError **error)
+{
+	gs_free char *s = NULL;
+	int errsv;
+	gint64 v;
+
+	s = nm_keyfile_plugin_kf_get_value (kf, group, key, error);
+	if (!s) {
+		errno = ENODATA;
+		return fallback;
+	}
+
+	v = _nm_utils_ascii_str_to_int64 (s, base, min, max, fallback);
+	errsv = errno;
+	if (   errsv != 0
+	    && error) {
+		g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+		             _("value is not an integer in range [%lld, %lld]"),
+		             (long long) min, (long long) max);
+		errno = errsv;
+	}
+	return v;
+}
+
+char **
+nm_keyfile_plugin_kf_get_keys (GKeyFile *kf,
+                               const char *group,
+                               gsize *out_length,
+                               GError **error)
+{
+	char **keys;
+	const char *alias;
+	GError *local = NULL;
+	gsize l;
+
+	keys = g_key_file_get_keys (kf, group, &l, &local);
+	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
+		if (alias) {
+			g_clear_error (&local);
+			keys = g_key_file_get_keys (kf, alias, &l, error ? &local : NULL);
+		}
+	}
+	nm_assert ((!local) != (!keys));
+	if (!keys)
+		l = 0;
+	nm_assert (l == NM_PTRARRAY_LEN (keys));
+	NM_SET_OUT (out_length, l);
+	if (local)
+		g_propagate_error (error, local);
+	return keys;
+}
+
+gboolean
+nm_keyfile_plugin_kf_has_key (GKeyFile *kf,
+                              const char *group,
+                              const char *key,
+                              GError **error)
+{
+	gboolean has;
+	const char *alias;
+	GError *local = NULL;
+
+	has = g_key_file_has_key (kf, group, key, &local);
+	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
+		alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
+		if (alias) {
+			g_clear_error (&local);
+			has = g_key_file_has_key (kf, alias, key, &local);
+		}
+	}
+	if (local)
+		g_propagate_error (error, local);
+	return has;
+}
+
+/*****************************************************************************/
+
+void
+_nm_keyfile_copy (GKeyFile *dst, GKeyFile *src)
+{
+	gs_strfreev char **groups = NULL;
+	guint g, k;
+
+	groups = g_key_file_get_groups (src, NULL);
+	for (g = 0; groups && groups[g]; g++) {
+		const char *group = groups[g];
+		gs_strfreev char **keys = NULL;
+
+		keys = g_key_file_get_keys (src, group, NULL, NULL);
+		if (!keys)
+			continue;
+
+		for (k = 0; keys[k]; k++) {
+			const char *key = keys[k];
+			gs_free char *value = NULL;
+
+			value = g_key_file_get_value (src, group, key, NULL);
+			if (value)
+				g_key_file_set_value (dst, group, key, value);
+			else
+				g_key_file_remove_key (dst, group, key, NULL);
+		}
+	}
+}
+
+/*****************************************************************************/
+
+gboolean
+_nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b)
+{
+	gs_strfreev char **groups = NULL;
+	guint i, j;
+
+	if (kf_a == kf_b)
+		return TRUE;
+	if (!kf_a || !kf_b)
+		return FALSE;
+
+	groups = g_key_file_get_groups (kf_a, NULL);
+	for (i = 0; groups && groups[i]; i++) {
+		gs_strfreev char **keys = NULL;
+
+		keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL);
+		if (!keys)
+			continue;
+
+		for (j = 0; keys[j]; j++) {
+			gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL);
+			gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL);
+
+			if (g_strcmp0 (key_a, key_b) != 0)
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+static gboolean
+_nm_keyfile_equals_ordered (GKeyFile *kf_a, GKeyFile *kf_b)
+{
+	gs_strfreev char **groups = NULL;
+	gs_strfreev char **groups_b = NULL;
+	guint i, j;
+
+	if (kf_a == kf_b)
+		return TRUE;
+	if (!kf_a || !kf_b)
+		return FALSE;
+
+	groups = g_key_file_get_groups (kf_a, NULL);
+	groups_b = g_key_file_get_groups (kf_b, NULL);
+	if (!groups && !groups_b)
+		return TRUE;
+	if (!groups || !groups_b)
+		return FALSE;
+	for (i = 0; groups[i] && groups_b[i] && !strcmp (groups[i], groups_b[i]); i++)
+		;
+	if (groups[i] || groups_b[i])
+		return FALSE;
+
+	for (i = 0; groups[i]; i++) {
+		gs_strfreev char **keys = NULL;
+		gs_strfreev char **keys_b = NULL;
+
+		keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL);
+		keys_b = g_key_file_get_keys (kf_b, groups[i], NULL, NULL);
+
+		if ((!keys) != (!keys_b))
+			return FALSE;
+		if (!keys)
+			continue;
+
+		for (j = 0; keys[j] && keys_b[j] && !strcmp (keys[j], keys_b[j]); j++)
+			;
+		if (keys[j] || keys_b[j])
+			return FALSE;
+
+		for (j = 0; keys[j]; j++) {
+			gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL);
+			gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL);
+
+			if (g_strcmp0 (key_a, key_b) != 0)
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+gboolean
+_nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order)
+{
+	if (!consider_order) {
+		return    _nm_keyfile_a_contains_all_in_b (kf_a, kf_b)
+		       && _nm_keyfile_a_contains_all_in_b (kf_b, kf_a);
+	} else {
+		return _nm_keyfile_equals_ordered (kf_a, kf_b);
+	}
+}
+
+gboolean
+_nm_keyfile_has_values (GKeyFile *keyfile)
+{
+	gs_strfreev char **groups = NULL;
+
+	g_return_val_if_fail (keyfile, FALSE);
+
+	groups = g_key_file_get_groups (keyfile, NULL);
+	return groups && groups[0];
+}
+
+/*****************************************************************************/
+
+static const char *
+_keyfile_key_encode (const char *name,
+                     char **out_to_free)
+{
+	gsize len, i;
+	GString *str;
+
+	nm_assert (name);
+	nm_assert (out_to_free && !*out_to_free);
+
+	/* See g_key_file_is_key_name().
+	 *
+	 * GKeyFile allows all UTF-8 characters (even non-well formed sequences),
+	 * except:
+	 *  - no empty keys
+	 *  - no leading/trailing ' '
+	 *  - no '=', '[', ']'
+	 *
+	 * We do something more strict here. All non-ASCII characters, all non-printable
+	 * characters, and all invalid characters are escaped with "\\XX".
+	 *
+	 * We don't escape \\, unless it is followed by two hex digits.
+	 */
+
+	if (!name[0]) {
+		/* empty keys are are backslash encoded. Note that usually
+		 * \\00 is not a valid encode, the only exception is the empty
+		 * word. */
+		return "\\00";
+	}
+
+	/* find the first character that needs escaping. */
+	i = 0;
+	if (name[0] != ' ') {
+		for (;; i++) {
+			const guchar ch = (guchar) name[i];
+
+			if (ch == '\0')
+				return name;
+
+			if (   ch < 0x20
+			    || ch >= 127
+			    || NM_IN_SET (ch, '=', '[', ']')
+			    || (   ch == '\\'
+			        && g_ascii_isxdigit (name[i + 1])
+			        && g_ascii_isxdigit (name[i + 2]))
+			    || (   ch == ' '
+			        && name[i + 1] == '\0'))
+				break;
+		}
+	} else if (name[1] == '\0')
+		return "\\20";
+
+	len = i + strlen (&name[i]);
+	nm_assert (len == strlen (name));
+	str = g_string_sized_new (len + 15);
+
+	if (name[0] == ' ') {
+		nm_assert (i == 0);
+		g_string_append (str, "\\20");
+		i = 1;
+	} else
+		g_string_append_len (str, name, i);
+
+	for (;; i++) {
+		const guchar ch = (guchar) name[i];
+
+		if (ch == '\0')
+			break;
+
+		if (   ch < 0x20
+		    || ch >= 127
+		    || NM_IN_SET (ch, '=', '[', ']')
+		    || (   ch == '\\'
+		        && g_ascii_isxdigit (name[i + 1])
+		        && g_ascii_isxdigit (name[i + 2]))
+		    || (   ch == ' '
+		        && name[i + 1] == '\0'))
+			g_string_append_printf (str, "\\%02X", ch);
+		else
+			g_string_append_c (str, (char) ch);
+	}
+
+	return (*out_to_free = g_string_free (str, FALSE));
+}
+
+static const char *
+_keyfile_key_decode (const char *key,
+                     char **out_to_free)
+{
+	gsize i, len;
+	GString *str;
+
+	nm_assert (key);
+	nm_assert (out_to_free && !*out_to_free);
+
+	if (!key[0])
+		return "";
+
+	for (i = 0; TRUE; i++) {
+		const char ch = key[i];
+
+		if (ch == '\0')
+			return key;
+		if (   ch == '\\'
+		    && g_ascii_isxdigit (key[i + 1])
+		    && g_ascii_isxdigit (key[i + 2]))
+			break;
+	}
+
+	len = i + strlen (&key[i]);
+
+	if (   len == 3
+	    && nm_streq (key, "\\00"))
+		return "";
+
+	nm_assert (len == strlen (key));
+	str = g_string_sized_new (len + 3);
+
+	g_string_append_len (str, key, i);
+	for (;;) {
+		const char ch = key[i];
+		char ch1, ch2;
+		unsigned v;
+
+		if (ch == '\0')
+			break;
+
+		if (   ch == '\\'
+		    && g_ascii_isxdigit ((ch1 = key[i + 1]))
+		    && g_ascii_isxdigit ((ch2 = key[i + 2]))) {
+			v = (g_ascii_xdigit_value (ch1) << 4) + g_ascii_xdigit_value (ch2);
+			if (v != 0) {
+				g_string_append_c (str, (char) v);
+				i += 3;
+				continue;
+			}
+		}
+		g_string_append_c (str, ch);
+		i++;
+	}
+
+	return (*out_to_free = g_string_free (str, FALSE));
+}
+
+/*****************************************************************************/
+
+const char *
+nm_keyfile_key_encode (const char *name,
+                       char **out_to_free)
+{
+	const char *key;
+
+	key = _keyfile_key_encode (name, out_to_free);
+#if NM_MORE_ASSERTS > 5
+	nm_assert (key);
+	nm_assert (!*out_to_free || key == *out_to_free);
+	nm_assert (!*out_to_free || !nm_streq0 (name, key));
+	{
+		gs_free char *to_free2 = NULL;
+		const char *name2;
+
+		name2 = _keyfile_key_decode (key, &to_free2);
+		/* name2, the result of encode()+decode() is identical to name.
+		 * That is because
+		 *   - encode() is a injective function.
+		 *   - decode() is a surjective function, however for output
+		 *     values of encode() is behaves injective too. */
+		nm_assert (nm_streq0 (name2, name));
+	}
+#endif
+	return key;
+}
+
+const char *
+nm_keyfile_key_decode (const char *key,
+                       char **out_to_free)
+{
+	const char *name;
+
+	name = _keyfile_key_decode (key, out_to_free);
+#if NM_MORE_ASSERTS > 5
+	nm_assert (name);
+	nm_assert (!*out_to_free || name == *out_to_free);
+	{
+		gs_free char *to_free2 = NULL;
+		const char *key2;
+
+		key2 = _keyfile_key_encode (name, &to_free2);
+		/* key2, the result of decode+encode may not be idential
+		 * to the original key. That is, decode() is a surjective
+		 * function mapping different keys to the same name.
+		 * However, decode() behaves injective for input that
+		 * are valid output of encode(). */
+		nm_assert (key2);
+	}
+#endif
+	return name;
+}
diff --git a/shared/nm-keyfile/nm-keyfile-utils.h b/shared/nm-keyfile/nm-keyfile-utils.h
new file mode 100644
index 00000000..a351262e
--- /dev/null
+++ b/shared/nm-keyfile/nm-keyfile-utils.h
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2010 - 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_KEYFILE_UTILS_H__
+#define __NM_KEYFILE_UTILS_H__
+
+#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)
+#error Cannot use this header.
+#endif
+
+#define NM_KEYFILE_GROUP_VPN_SECRETS          "vpn-secrets"
+#define NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER "wireguard-peer."
+
+const char *nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name);
+
+const char *nm_keyfile_plugin_get_setting_name_for_alias (const char *alias);
+
+/*****************************************************************************/
+
+/* List helpers */
+#define DEFINE_KF_LIST_WRAPPER_PROTO(stype, get_ctype, set_ctype) \
+get_ctype nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \
+                                                   const char *group, \
+                                                   const char *key, \
+                                                   gsize *out_length, \
+                                                   GError **error); \
+\
+void nm_keyfile_plugin_kf_set_##stype##_list  (GKeyFile *kf, \
+                                               const char *group, \
+                                               const char *key, \
+                                               set_ctype list[], \
+                                               gsize length);
+DEFINE_KF_LIST_WRAPPER_PROTO(integer, int*, int)
+DEFINE_KF_LIST_WRAPPER_PROTO(string, char**, const char* const)
+
+void nm_keyfile_plugin_kf_set_integer_list_uint8 (GKeyFile *kf,
+                                                  const char *group,
+                                                  const char *key,
+                                                  const guint8 *list,
+                                                  gsize length);
+
+/* Single-value helpers */
+#define DEFINE_KF_WRAPPER_PROTO(stype, get_ctype, set_ctype) \
+get_ctype nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \
+                                            const char *group, \
+                                            const char *key, \
+                                            GError **error); \
+\
+void nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \
+                                       const char *group, \
+                                       const char *key, \
+                                       set_ctype value);
+DEFINE_KF_WRAPPER_PROTO(string, char*, const char*)
+DEFINE_KF_WRAPPER_PROTO(boolean, gboolean, gboolean)
+DEFINE_KF_WRAPPER_PROTO(value, char*, const char*)
+
+/* Misc */
+gint64 nm_keyfile_plugin_kf_get_int64 (GKeyFile *kf,
+                                       const char *group,
+                                       const char *key,
+                                       guint base,
+                                       gint64 min,
+                                       gint64 max,
+                                       gint64 fallback,
+                                       GError **error);
+
+char ** nm_keyfile_plugin_kf_get_keys    (GKeyFile *kf,
+                                           const char *group,
+                                           gsize *out_length,
+                                           GError **error);
+
+gboolean nm_keyfile_plugin_kf_has_key     (GKeyFile *kf,
+                                           const char *group,
+                                           const char *key,
+                                           GError **error);
+
+const char *nm_keyfile_key_encode (const char *name,
+                                   char **out_to_free);
+
+const char *nm_keyfile_key_decode (const char *key,
+                                   char **out_to_free);
+
+#endif  /* __NM_KEYFILE_UTILS_H__ */
diff --git a/shared/nm-keyfile/nm-keyfile.c b/shared/nm-keyfile/nm-keyfile.c
new file mode 100644
index 00000000..15485747
--- /dev/null
+++ b/shared/nm-keyfile/nm-keyfile.c
@@ -0,0 +1,4089 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2008 - 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-keyfile-internal.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include <linux/pkt_sched.h>
+
+#include "nm-glib-aux/nm-secret-utils.h"
+#include "systemd/nm-sd-utils-shared.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
+#include "nm-core-internal.h"
+#include "nm-keyfile-utils.h"
+
+#include "nm-setting-user.h"
+
+/*****************************************************************************/
+
+typedef struct _ParseInfoProperty ParseInfoProperty;
+
+typedef struct {
+	NMConnection *connection;
+	GKeyFile *keyfile;
+	const char *base_dir;
+	NMKeyfileReadHandler handler;
+	void *user_data;
+	GError *error;
+	const char *group;
+	NMSetting *setting;
+} KeyfileReaderInfo;
+
+typedef struct {
+	NMConnection *connection;
+	GKeyFile *keyfile;
+	GError *error;
+	NMKeyfileWriteHandler handler;
+	void *user_data;
+} KeyfileWriterInfo;
+
+/*****************************************************************************/
+
+static void
+_handle_warn (KeyfileReaderInfo *info,
+              const char *property_name,
+              NMKeyfileWarnSeverity severity,
+              char *message)
+{
+	NMKeyfileReadTypeDataWarn type_data = {
+		.group = info->group,
+		.setting = info->setting,
+		.property_name = property_name,
+		.severity = severity,
+		.message = message,
+	};
+
+	info->handler (info->keyfile,
+	               info->connection,
+	               NM_KEYFILE_READ_TYPE_WARN,
+	               &type_data,
+	               info->user_data,
+	               &info->error);
+	g_free (message);
+}
+#define handle_warn(arg_info, arg_property_name, arg_severity, ...) \
+	({ \
+		KeyfileReaderInfo *_info = (arg_info); \
+		\
+		if (_info->handler) { \
+			_handle_warn (_info, (arg_property_name), (arg_severity), \
+			              g_strdup_printf (__VA_ARGS__)); \
+		} \
+		_info->error == NULL; \
+	})
+
+/*****************************************************************************/
+
+static gboolean
+_secret_flags_persist_secret (NMSettingSecretFlags flags)
+{
+	return flags == NM_SETTING_SECRET_FLAG_NONE;
+}
+
+/*****************************************************************************/
+/* Some setting properties also contain setting names, such as
+ * NMSettingConnection's 'type' property (which specifies the base type of the
+ * connection, e.g. ethernet or wifi) or 'slave-type' (specifies type of slave
+ * connection, e.g. bond or bridge). This function handles translating those
+ * properties' values to the real setting name if they are an alias.
+ */
+static void
+setting_alias_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	const char *key_setting_name;
+	gs_free char *s = NULL;
+
+	s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+	if (!s)
+		return;
+
+	key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias (s);
+	g_object_set (G_OBJECT (setting),
+	              key,
+	              key_setting_name ?: s,
+	              NULL);
+}
+
+static void
+sriov_vfs_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_unref_ptrarray GPtrArray *vfs = NULL;
+	gs_strfreev char **keys = NULL;
+	gsize n_keys = 0;
+	int i;
+
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
+	if (n_keys == 0)
+		return;
+
+	vfs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_sriov_vf_unref);
+
+	for (i = 0; i < n_keys; i++) {
+		gs_free char *value = NULL;
+		NMSriovVF *vf;
+		const char *rest;
+
+		if (!g_str_has_prefix (keys[i], "vf."))
+			continue;
+
+		rest = &keys[i][3];
+
+		if (!NM_STRCHAR_ALL (rest, ch, g_ascii_isdigit (ch)))
+			continue;
+
+		value = nm_keyfile_plugin_kf_get_string (info->keyfile,
+		                                         setting_name,
+		                                         keys[i],
+		                                         NULL);
+
+		vf = _nm_utils_sriov_vf_from_strparts (rest, value, TRUE, NULL);
+		if (vf)
+			g_ptr_array_add (vfs, vf);
+	}
+
+	g_object_set (G_OBJECT (setting),
+	              key, vfs,
+	              NULL);
+}
+
+static void
+read_array_of_uint (GKeyFile *file,
+                    NMSetting *setting,
+                    const char *key)
+{
+	gs_unref_array GArray *array = NULL;
+	gsize length;
+	gsize i;
+	gs_free int *tmp = NULL;
+
+	tmp = nm_keyfile_plugin_kf_get_integer_list (file, nm_setting_get_name (setting), key, &length, NULL);
+	if (length > G_MAXUINT)
+		return;
+
+	array = g_array_sized_new (FALSE, FALSE, sizeof (guint), length);
+
+	for (i = 0; i < length; i++) {
+		if (tmp[i] < 0)
+			return;
+		g_array_append_val (array, tmp[i]);
+	}
+
+	g_object_set (setting, key, array, NULL);
+}
+
+static gboolean
+get_one_int (KeyfileReaderInfo *info, const char *property_name, const char *str, guint32 max_val, guint32 *out)
+{
+	gint64 tmp;
+
+	g_return_val_if_fail (!info == !property_name, FALSE);
+
+	if (!str || !str[0]) {
+		if (property_name)
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("ignoring missing number"));
+		return FALSE;
+	}
+
+	tmp = _nm_utils_ascii_str_to_int64 (str, 10, 0, max_val, -1);
+	if (tmp == -1) {
+		if (property_name) {
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("ignoring invalid number '%s'"),
+			            str);
+		}
+		return FALSE;
+	}
+
+	*out = (guint32) tmp;
+	return TRUE;
+}
+
+static gpointer
+build_address (KeyfileReaderInfo *info, int family, const char *address_str, guint32 plen, const char *property_name)
+{
+	NMIPAddress *addr;
+	GError *error = NULL;
+
+	g_return_val_if_fail (address_str, NULL);
+
+	addr = nm_ip_address_new (family, address_str, plen, &error);
+	if (!addr) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid %s address: %s"),
+		            family == AF_INET ? "IPv4" : "IPv6", error->message);
+		g_error_free (error);
+	}
+
+	return addr;
+}
+
+static gpointer
+build_route (KeyfileReaderInfo *info,
+             const char *property_name,
+             int family,
+             const char *dest_str,
+             guint32 plen,
+             const char *gateway_str,
+             const char *metric_str)
+{
+	NMIPRoute *route;
+	guint32 u32;
+	gint64 metric = -1;
+	GError *error = NULL;
+
+	g_return_val_if_fail (dest_str, NULL);
+
+	/* Next hop */
+	if (gateway_str && gateway_str[0]) {
+		if (!nm_utils_ipaddr_is_valid (family, gateway_str)) {
+			/* Try workaround for routes written by broken keyfile writer.
+			 * Due to bug bgo#719851, an older version of writer would have
+			 * written "a:b:c:d::/plen,metric" if the gateway was ::, instead
+			 * of "a:b:c:d::/plen,,metric" or "a:b:c:d::/plen,::,metric"
+			 * Try workaround by interpreting gateway_str as metric to accept such
+			 * invalid routes. This broken syntax should not be not officially
+			 * supported.
+			 **/
+			if (   family == AF_INET6
+			    && !metric_str
+			    && get_one_int (NULL, NULL, gateway_str, G_MAXUINT32, &u32)) {
+				metric = u32;
+				gateway_str = NULL;
+			} else {
+				if (!info->error) {
+					handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+					             _("ignoring invalid gateway '%s' for %s route"),
+					             gateway_str, family == AF_INET ? "IPv4" : "IPv6");
+				}
+				return NULL;
+			}
+		}
+	} else
+		gateway_str = NULL;
+
+	/* parse metric, default to -1 */
+	if (metric_str) {
+		if (!get_one_int (info, property_name, metric_str, G_MAXUINT32, &u32))
+			return NULL;
+		metric = u32;
+	}
+
+	route = nm_ip_route_new (family,
+	                         dest_str,
+	                         plen,
+	                         gateway_str,
+	                         metric,
+	                         &error);
+	if (!route) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid %s route: %s"),
+		             family == AF_INET ? "IPv4" : "IPv6",
+		             error->message);
+		g_error_free (error);
+	}
+
+	return route;
+}
+
+/* On success, returns pointer to the zero-terminated field (original @current).
+ * The @current * pointer target is set to point to the rest of the input
+ * or %NULL if there is no more input. Sets error to %NULL for convenience.
+ *
+ * On failure, returns %NULL (unspecified). The @current pointer target is
+ * resets to its original value to allow skipping fields. The @error target
+ * is set to the character that breaks the parsing or %NULL if @current was %NULL.
+ *
+ * When @current target is %NULL, gracefully fail returning %NULL while
+ * leaving the @current target %NULL end setting @error to %NULL;
+ */
+static const char *
+read_field (char **current, const char **out_err_str, const char *characters, const char *delimiters)
+{
+	const char *start;
+
+	nm_assert (current);
+	nm_assert (out_err_str);
+	nm_assert (characters);
+	nm_assert (delimiters);
+
+	*out_err_str = NULL;
+
+	if (!*current) {
+		/* graceful failure, leave '*current' NULL */
+		return NULL;
+	}
+
+	/* fail on empty input */
+	if (!**current)
+		return NULL;
+
+	/* remember beginning of input */
+	start = *current;
+
+	while (**current && strchr (characters, **current))
+		(*current)++;
+	if (**current)
+		if (strchr (delimiters, **current)) {
+			/* success, more data available */
+			*(*current)++ = '\0';
+			return start;
+		} else {
+			/* error, bad character */
+			*out_err_str = *current;
+			*current = (char *) start;
+			return NULL;
+		}
+	else {
+		/* success, end of input */
+		*current = NULL;
+		return start;
+	}
+}
+
+/*****************************************************************************/
+
+#define NM_DBUS_SERVICE_OPENCONNECT    "org.freedesktop.NetworkManager.openconnect"
+#define NM_OPENCONNECT_KEY_GATEWAY     "gateway"
+#define NM_OPENCONNECT_KEY_COOKIE      "cookie"
+#define NM_OPENCONNECT_KEY_GWCERT      "gwcert"
+#define NM_OPENCONNECT_KEY_XMLCONFIG   "xmlconfig"
+#define NM_OPENCONNECT_KEY_LASTHOST    "lasthost"
+#define NM_OPENCONNECT_KEY_AUTOCONNECT "autoconnect"
+#define NM_OPENCONNECT_KEY_CERTSIGS    "certsigs"
+
+static void
+openconnect_fix_secret_flags (NMSetting *setting)
+{
+	NMSettingVpn *s_vpn;
+	NMSettingSecretFlags flags;
+
+	/* Huge hack.  There were some openconnect changes that needed to happen
+	 * pretty late, too late to get into distros.  Migration has already
+	 * happened for many people, and their secret flags are wrong.  But we
+	 * don't want to requrie re-migration, so we have to fix it up here. Ugh.
+	 */
+
+	if (!NM_IS_SETTING_VPN (setting))
+		return;
+
+	s_vpn = NM_SETTING_VPN (setting);
+
+	if (!nm_streq0 (nm_setting_vpn_get_service_type (s_vpn), NM_DBUS_SERVICE_OPENCONNECT))
+		return;
+
+	/* These are different for every login session, and should not be stored */
+	flags = NM_SETTING_SECRET_FLAG_NOT_SAVED;
+	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GATEWAY, flags, NULL);
+	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_COOKIE, flags, NULL);
+	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GWCERT, flags, NULL);
+
+	/* These are purely internal data for the auth-dialog, and should be stored */
+	flags = 0;
+	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_XMLCONFIG, flags, NULL);
+	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_LASTHOST, flags, NULL);
+	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_AUTOCONNECT, flags, NULL);
+	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_CERTSIGS, flags, NULL);
+}
+
+/*****************************************************************************/
+
+#define IP_ADDRESS_CHARS "0123456789abcdefABCDEF:.%"
+#define DIGITS "0123456789"
+#define DELIMITERS "/;,"
+
+/* The following IPv4 and IPv6 address formats are supported:
+ *
+ * address (DEPRECATED)
+ * address/plen
+ * address/gateway (DEPRECATED)
+ * address/plen,gateway
+ *
+ * The following IPv4 and IPv6 route formats are supported:
+ *
+ * address/plen (NETWORK dev DEVICE)
+ * address/plen,gateway (NETWORK via GATEWAY dev DEVICE)
+ * address/plen,,metric (NETWORK dev DEVICE metric METRIC)
+ * address/plen,gateway,metric (NETWORK via GATEWAY dev DEVICE metric METRIC)
+ *
+ * For backward, forward and sideward compatibility, slash (/),
+ * semicolon (;) and comma (,) are interchangeable. The choice of
+ * separator in the above examples is therefore not significant.
+ *
+ * Leaving out the prefix length is discouraged and DEPRECATED. The
+ * default value of IPv6 prefix length was 64 and has not been
+ * changed. The default for IPv4 is now 24, which is the closest
+ * IPv4 equivalent. These defaults may just as well be changed to
+ * match the iproute2 defaults (32 for IPv4 and 128 for IPv6).
+ */
+static gpointer
+read_one_ip_address_or_route (KeyfileReaderInfo *info,
+                              const char *property_name,
+                              const char *setting_name,
+                              const char *key_name,
+                              gboolean ipv6,
+                              gboolean route,
+                              char **out_gateway,
+                              NMSetting *setting)
+{
+	guint plen;
+	gpointer result;
+	const char *address_str;
+	const char *plen_str;
+	const char *gateway_str;
+	const char *metric_str;
+	const char *err_str = NULL;
+	char *current;
+	gs_free char *value = NULL;
+	gs_free char *value_orig = NULL;
+
+#define VALUE_ORIG()   (value_orig ?: (value_orig = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL)))
+
+	value = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL);
+	if (!value)
+		return NULL;
+
+	current = value;
+
+	/* get address field */
+	address_str = read_field (&current, &err_str, IP_ADDRESS_CHARS, DELIMITERS);
+	if (err_str) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("unexpected character '%c' for address %s: '%s' (position %td)"),
+		             *err_str, key_name, VALUE_ORIG (), err_str - current);
+		return NULL;
+	}
+	/* get prefix length field (skippable) */
+	plen_str = read_field (&current, &err_str, DIGITS, DELIMITERS);
+	/* get gateway field */
+	gateway_str = read_field (&current, &err_str, IP_ADDRESS_CHARS, DELIMITERS);
+	if (err_str) {
+		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("unexpected character '%c' for %s: '%s' (position %td)"),
+		             *err_str, key_name, VALUE_ORIG (), err_str - current);
+		return NULL;
+	}
+	/* for routes, get metric */
+	if (route) {
+		metric_str = read_field (&current, &err_str, DIGITS, DELIMITERS);
+		if (err_str) {
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"),
+			             *err_str, key_name, VALUE_ORIG (), err_str - current);
+			return NULL;
+		}
+	} else
+		metric_str = NULL;
+	if (current) {
+		/* there is still some data */
+		if (*current) {
+			/* another field follows */
+			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("garbage at the end of value %s: '%s'"),
+			             key_name, VALUE_ORIG ());
+			return NULL;
+		} else {
+			/* semicolon at the end of input */
+			if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_INFO,
+			                  _("deprecated semicolon at the end of value %s: '%s'"),
+			                  key_name, VALUE_ORIG ()))
+				return NULL;
+		}
+	}
+
+#define DEFAULT_PREFIX(for_route, for_ipv6) ( (for_route) ? ( (for_ipv6) ? 128 : 24 ) : ( (for_ipv6) ? 64 : 24 ) )
+
+	/* parse plen, fallback to defaults */
+	if (plen_str) {
+		if (!get_one_int (info, property_name, plen_str, ipv6 ? 128 : 32, &plen)) {
+			plen = DEFAULT_PREFIX (route, ipv6);
+			if (   info->error
+			    || !handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                     _("invalid prefix length for %s '%s', defaulting to %d"),
+			                     key_name, VALUE_ORIG (), plen))
+				return NULL;
+		}
+	} else {
+		plen = DEFAULT_PREFIX (route, ipv6);
+		if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
+		                  _("missing prefix length for %s '%s', defaulting to %d"),
+		                  key_name, VALUE_ORIG (), plen))
+			return NULL;
+	}
+
+	/* build the appropriate data structure for NetworkManager settings */
+	if (route) {
+		result = build_route (info,
+		                      property_name,
+		                      ipv6 ? AF_INET6 : AF_INET,
+		                      address_str,
+		                      plen,
+		                      gateway_str,
+		                      metric_str);
+	} else {
+		result = build_address (info,
+		                        ipv6 ? AF_INET6 : AF_INET,
+		                        address_str,
+		                        plen,
+		                        property_name);
+		if (!result)
+			return NULL;
+		if (gateway_str)
+			NM_SET_OUT (out_gateway, g_strdup (gateway_str));
+	}
+
+#undef VALUE_ORIG
+
+	return result;
+}
+
+static void
+fill_route_attributes (GKeyFile *kf, NMIPRoute *route, const char *setting, const char *key, int family)
+{
+	gs_free char *value = NULL;
+	gs_unref_hashtable GHashTable *hash = NULL;
+	GHashTableIter iter;
+	char *name;
+	GVariant *variant;
+
+	value = nm_keyfile_plugin_kf_get_string (kf, setting, key, NULL);
+	if (!value || !value[0])
+		return;
+
+	hash = nm_utils_parse_variant_attributes (value, ',', '=', TRUE,
+	                                          nm_ip_route_get_variant_attribute_spec (),
+	                                          NULL);
+	if (hash) {
+		g_hash_table_iter_init (&iter, hash);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &variant)) {
+			if (nm_ip_route_attribute_validate (name, variant, family, NULL, NULL))
+				nm_ip_route_set_attribute (route, name, g_variant_ref (variant));
+		}
+	}
+}
+
+typedef struct {
+	const char *s_key;
+	gint32 key_idx;
+	gint8 key_type;
+} BuildListData;
+
+typedef enum {
+	BUILD_LIST_TYPE_ADDRESSES,
+	BUILD_LIST_TYPE_ROUTES,
+	BUILD_LIST_TYPE_ROUTING_RULES,
+} BuildListType;
+
+static int
+_build_list_data_cmp (gconstpointer p_a, gconstpointer p_b, gpointer user_data)
+{
+	const BuildListData *a = p_a;
+	const BuildListData *b = p_b;
+
+	NM_CMP_FIELD (a, b, key_idx);
+	NM_CMP_FIELD (a, b, key_type);
+	NM_CMP_FIELD_STR (a, b, s_key);
+	return 0;
+}
+
+static gboolean
+_build_list_data_is_shadowed (const BuildListData *build_list,
+                              gsize build_list_len,
+                              gsize idx)
+{
+	/* the keyfile contains duplicate keys, which are both returned
+	 * by g_key_file_get_keys() (WHY??).
+	 *
+	 * Skip the earlier one. */
+	return    idx + 1 < build_list_len
+	       && build_list[idx].key_idx == build_list[idx + 1].key_idx
+	       && build_list[idx].key_type == build_list[idx + 1].key_type
+	       && nm_streq (build_list[idx].s_key, build_list[idx + 1].s_key);
+}
+
+static gboolean
+_build_list_match_key_w_name_impl (const char *key,
+                                   const char *base_name,
+                                   gsize base_name_l,
+                                   gint32 *out_key_idx)
+{
+	gint64 v;
+
+	/* some very strict parsing. */
+
+	/* the key must start with base_name. */
+	if (strncmp (key, base_name, base_name_l) != 0)
+		return FALSE;
+
+	key += base_name_l;
+	if (key[0] == '\0') {
+		/* if key is identical to base_name, that's good. */
+		NM_SET_OUT (out_key_idx, -1);
+		return TRUE;
+	}
+
+	/* if base_name is followed by a zero, then it must be
+	 * only a zero, nothing else. */
+	if (key[0] == '0') {
+		if (key[1] != '\0')
+			return FALSE;
+		NM_SET_OUT (out_key_idx, 0);
+		return TRUE;
+	}
+
+	/* otherwise, it can only be followed by a non-zero decimal. */
+	if (!(key[0] >= '1' && key[0] <= '9'))
+		return FALSE;
+	/* and all remaining chars must be decimals too. */
+	if (!NM_STRCHAR_ALL (&key[1], ch, g_ascii_isdigit (ch)))
+		return FALSE;
+
+	/* and it must be convertible to a (positive) int. */
+	v = _nm_utils_ascii_str_to_int64 (key, 10, 0, G_MAXINT32, -1);
+	if (v < 0)
+		return FALSE;
+
+	/* good */
+	NM_SET_OUT (out_key_idx, v);
+	return TRUE;
+}
+
+#define _build_list_match_key_w_name(key, base_name, out_key_idx) \
+	_build_list_match_key_w_name_impl (key, base_name, NM_STRLEN (base_name), out_key_idx)
+
+static BuildListData *
+_build_list_create (GKeyFile *keyfile,
+                    const char *group_name,
+                    BuildListType build_list_type,
+                    gsize *out_build_list_len,
+                    char ***out_keys_strv)
+{
+	gs_strfreev char **keys = NULL;
+	gsize i_keys, n_keys;
+	gs_free BuildListData *build_list = NULL;
+	gsize build_list_len = 0;
+
+	nm_assert (out_build_list_len && *out_build_list_len == 0);
+	nm_assert (out_keys_strv && !*out_keys_strv);
+
+	keys = nm_keyfile_plugin_kf_get_keys (keyfile, group_name, &n_keys, NULL);
+	if (n_keys == 0)
+		return NULL;
+
+	for (i_keys = 0; i_keys < n_keys; i_keys++) {
+		const char *s_key = keys[i_keys];
+		gint32 key_idx;
+		gint8 key_type = 0;
+
+		switch (build_list_type) {
+		case BUILD_LIST_TYPE_ROUTES:
+			if (_build_list_match_key_w_name (s_key, "route", &key_idx))
+				key_type = 0;
+			else if (_build_list_match_key_w_name (s_key, "routes", &key_idx))
+				key_type = 1;
+			else
+				continue;
+			break;
+		case BUILD_LIST_TYPE_ADDRESSES:
+			if (_build_list_match_key_w_name (s_key, "address", &key_idx))
+				key_type = 0;
+			else if (_build_list_match_key_w_name (s_key, "addresses", &key_idx))
+				key_type = 1;
+			else
+				continue;
+			break;
+		case BUILD_LIST_TYPE_ROUTING_RULES:
+			if (_build_list_match_key_w_name (s_key, "routing-rule", &key_idx))
+				key_type = 0;
+			else
+				continue;
+			break;
+		default:
+			nm_assert_not_reached ();
+			break;
+		}
+
+		if (G_UNLIKELY (!build_list))
+			build_list = g_new (BuildListData, n_keys - i_keys);
+
+		build_list[build_list_len++] = (BuildListData) {
+			.s_key    = s_key,
+			.key_idx  = key_idx,
+			.key_type = key_type,
+		};
+	}
+
+	if (build_list_len == 0)
+		return NULL;
+
+	if (build_list_len > 1) {
+		g_qsort_with_data (build_list,
+		                   build_list_len,
+		                   sizeof (BuildListData),
+		                   _build_list_data_cmp,
+		                   NULL);
+	}
+
+	*out_build_list_len = build_list_len;
+	*out_keys_strv = g_steal_pointer (&keys);
+	return g_steal_pointer (&build_list);
+}
+
+static void
+ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *setting_key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gboolean is_ipv6 = nm_streq (setting_name, "ipv6");
+	gboolean is_routes = nm_streq (setting_key, "routes");
+	gs_free char *gateway = NULL;
+	gs_unref_ptrarray GPtrArray *list = NULL;
+	gs_strfreev char **keys = NULL;
+	gs_free BuildListData *build_list = NULL;
+	gsize i_build_list, build_list_len = 0;
+
+	build_list = _build_list_create (info->keyfile,
+	                                 setting_name,
+	                                   is_routes
+	                                 ? BUILD_LIST_TYPE_ROUTES
+	                                 : BUILD_LIST_TYPE_ADDRESSES,
+	                                 &build_list_len,
+	                                 &keys);
+	if (!build_list)
+		return;
+
+	list = g_ptr_array_new_with_free_func (is_routes
+	                                       ? (GDestroyNotify) nm_ip_route_unref
+	                                       : (GDestroyNotify) nm_ip_address_unref);
+
+	for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) {
+		const char *s_key;
+		gpointer item;
+
+		if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list))
+			continue;
+
+		s_key = build_list[i_build_list].s_key;
+		item = read_one_ip_address_or_route (info,
+		                                     setting_key,
+		                                     setting_name,
+		                                     s_key,
+		                                     is_ipv6,
+		                                     is_routes,
+		                                     gateway ? NULL : &gateway,
+		                                     setting);
+		if (item && is_routes) {
+			char options_key[128];
+
+			nm_sprintf_buf (options_key, "%s_options", s_key);
+			fill_route_attributes (info->keyfile,
+			                       item,
+			                       setting_name,
+			                       options_key,
+			                       is_ipv6 ? AF_INET6 : AF_INET);
+		}
+
+		if (info->error)
+			return;
+
+		if (item)
+			g_ptr_array_add (list, item);
+	}
+
+	if (list->len >= 1)
+		g_object_set (setting, setting_key, list, NULL);
+
+	if (gateway)
+		g_object_set (setting, "gateway", gateway, NULL);
+}
+
+static void
+ip_routing_rule_parser_full (KeyfileReaderInfo *info,
+                             const NMMetaSettingInfo *setting_info,
+                             const NMSettInfoProperty *property_info,
+                             const ParseInfoProperty *pip,
+                             NMSetting *setting)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gboolean is_ipv6 = nm_streq (setting_name, "ipv6");
+	gs_strfreev char **keys = NULL;
+	gs_free BuildListData *build_list = NULL;
+	gsize i_build_list, build_list_len = 0;
+
+	build_list = _build_list_create (info->keyfile,
+	                                 setting_name,
+	                                 BUILD_LIST_TYPE_ROUTING_RULES,
+	                                 &build_list_len,
+	                                 &keys);
+	if (!build_list)
+		return;
+
+	for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) {
+		nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL;
+		gs_free char *value = NULL;
+		gs_free_error GError *local = NULL;
+
+		if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list))
+			continue;
+
+		value = nm_keyfile_plugin_kf_get_string (info->keyfile,
+		                                         setting_name,
+		                                         build_list[i_build_list].s_key,
+		                                         NULL);
+		if (!value)
+			continue;
+
+		rule = nm_ip_routing_rule_from_string (value,
+		                                       (  NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE
+		                                        | (  is_ipv6
+		                                           ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6
+		                                           : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET)),
+		                                       NULL,
+		                                       &local);
+		if (!rule) {
+			handle_warn (info, property_info->name, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid value for \"%s\": %s"),
+			             build_list[i_build_list].s_key,
+			             local->message);
+			if (info->error)
+				return;
+			continue;
+		}
+
+		nm_setting_ip_config_add_routing_rule (NM_SETTING_IP_CONFIG (setting), rule);
+	}
+}
+
+static void
+ip_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	int addr_family;
+	gs_strfreev char **list = NULL;
+	gsize i, n, length;
+
+	nm_assert (NM_IS_SETTING_IP4_CONFIG (setting) || NM_IS_SETTING_IP6_CONFIG (setting));
+
+	list = nm_keyfile_plugin_kf_get_string_list (info->keyfile,
+	                                             nm_setting_get_name (setting),
+	                                             key,
+	                                             &length,
+	                                             NULL);
+	nm_assert (length == NM_PTRARRAY_LEN (list));
+	if (length == 0)
+		return;
+
+	addr_family = NM_IS_SETTING_IP4_CONFIG (setting) ? AF_INET : AF_INET6;
+
+	n = 0;
+	for (i = 0; i < length; i++) {
+		NMIPAddr addr;
+
+		if (inet_pton (addr_family, list[i], &addr) <= 0) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("ignoring invalid DNS server IPv%c address '%s'"),
+			                  nm_utils_addr_family_to_char (addr_family),
+			                  list[i])) {
+				do {
+					nm_clear_g_free (&list[i]);
+				} while (++i < length);
+				return;
+			}
+			nm_clear_g_free (&list[i]);
+			continue;
+		}
+
+		if (n != i)
+			list[n] = g_steal_pointer (&list[i]);
+		n++;
+	}
+
+	g_object_set (setting, key, list, NULL);
+}
+
+static void
+ip6_addr_gen_mode_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	NMSettingIP6ConfigAddrGenMode addr_gen_mode;
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_free char *s = NULL;
+
+	s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+	if (s) {
+		if (!nm_utils_enum_from_str (nm_setting_ip6_config_addr_gen_mode_get_type (), s,
+		                             (int *) &addr_gen_mode, NULL)) {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid option '%s', use one of [%s]"),
+			             s, "eui64,stable-privacy");
+			return;
+		}
+	} else
+		addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64;
+
+	g_object_set (G_OBJECT (setting), key, (int) addr_gen_mode, NULL);
+}
+
+static void
+mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key, gsize enforce_length, gboolean cloned_mac_addr)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_free char *tmp_string = NULL;
+	const char *p, *mac_str;
+	gs_free guint8 *buf_arr = NULL;
+	guint buf_len = 0;
+
+	tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+
+	if (   cloned_mac_addr
+	    && NM_CLONED_MAC_IS_SPECIAL (tmp_string)) {
+		mac_str = tmp_string;
+		goto out;
+	}
+
+	if (tmp_string && tmp_string[0]) {
+		/* Look for enough ':' characters to signify a MAC address */
+		guint i = 0;
+
+		p = tmp_string;
+		while (*p) {
+			if (*p == ':')
+				i++;
+			p++;
+		}
+
+		if (enforce_length == 0 || enforce_length == i+1) {
+			/* If we found enough it's probably a string-format MAC address */
+			buf_len = i + 1;
+			buf_arr = g_new (guint8, buf_len);
+			if (!nm_utils_hwaddr_aton (tmp_string, buf_arr, buf_len))
+				nm_clear_g_free (&buf_arr);
+		}
+	}
+	nm_clear_g_free (&tmp_string);
+
+	if (!buf_arr) {
+		gs_free int *tmp_list = NULL;
+		gsize length;
+
+		/* Old format; list of ints */
+		tmp_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL);
+		if (length > 0 && (enforce_length == 0 || enforce_length == length)) {
+			gsize i;
+
+			buf_len = length;
+			buf_arr = g_new (guint8, buf_len);
+			for (i = 0; i < length; i++) {
+				int val = tmp_list[i];
+
+				if (val < 0 || val > 255) {
+					handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+					             _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
+					             val);
+					return;
+				}
+				buf_arr[i] = (guint8) val;
+			}
+		}
+	}
+
+	if (!buf_arr) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid MAC address"));
+		return;
+	}
+
+	tmp_string = nm_utils_hwaddr_ntoa (buf_arr, buf_len);
+	mac_str = tmp_string;
+
+out:
+	g_object_set (setting, key, mac_str, NULL);
+}
+
+static void
+mac_address_parser_ETHER (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	mac_address_parser (info, setting, key, ETH_ALEN, FALSE);
+}
+
+static void
+mac_address_parser_ETHER_cloned (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	mac_address_parser (info, setting, key, ETH_ALEN, TRUE);
+}
+
+static void
+mac_address_parser_INFINIBAND (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	mac_address_parser (info, setting, key, INFINIBAND_ALEN, FALSE);
+}
+
+static void
+read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
+{
+	gs_strfreev char **keys = NULL;
+	const char *const*iter;
+	const char *setting_name = nm_setting_get_name (setting);
+	gboolean is_vpn;
+	gsize n_keys;
+
+	nm_assert (   (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_DATA))
+	           || (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_SECRETS))
+	           || (NM_IS_SETTING_BOND (setting) && nm_streq (key, NM_SETTING_BOND_OPTIONS))
+	           || (NM_IS_SETTING_USER (setting) && nm_streq (key, NM_SETTING_USER_DATA)));
+
+	keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, &n_keys, NULL);
+	if (n_keys == 0)
+		return;
+
+	if (   (is_vpn = NM_IS_SETTING_VPN (setting))
+	    || NM_IS_SETTING_BOND (setting)) {
+		for (iter = (const char *const*) keys; *iter; iter++) {
+			gs_free char *to_free = NULL;
+			gs_free char *value = NULL;
+			const char *name;
+
+			value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL);
+			if (!value)
+				continue;
+
+			name = nm_keyfile_key_decode (*iter, &to_free);
+
+			if (is_vpn) {
+				/* Add any item that's not a class property to the data hash */
+				if (!g_object_class_find_property (G_OBJECT_GET_CLASS (setting), name))
+					nm_setting_vpn_add_data_item (NM_SETTING_VPN (setting), name, value);
+			} else {
+				if (!nm_streq (name, "interface-name"))
+					nm_setting_bond_add_option (NM_SETTING_BOND (setting), name, value);
+			}
+		}
+		openconnect_fix_secret_flags (setting);
+		return;
+	}
+
+	if (NM_IS_SETTING_USER (setting)) {
+		gs_unref_hashtable GHashTable *data = NULL;
+
+		data = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
+		for (iter = (const char *const*) keys; *iter; iter++) {
+			gs_free char *to_free = NULL;
+			char *value = NULL;
+			const char *name;
+
+			value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL);
+			if (!value)
+				continue;
+			name = nm_keyfile_key_decode (*iter, &to_free);
+			g_hash_table_insert (data,
+			                     g_steal_pointer (&to_free) ?: g_strdup (name),
+			                     value);
+		}
+		g_object_set (setting, NM_SETTING_USER_DATA, data, NULL);
+		return;
+	}
+
+	nm_assert_not_reached ();
+}
+
+static gsize
+unescape_semicolons (char *str)
+{
+	gsize i, j;
+
+	for (i = 0, j = 0; str[i]; ) {
+		if (str[i] == '\\' && str[i+1] == ';')
+			i++;
+		str[j++] = str[i++];;
+	}
+	nm_explicit_bzero (&str[j], i - j);
+	return j;
+}
+
+static GBytes *
+get_bytes (KeyfileReaderInfo *info,
+           const char *setting_name,
+           const char *key,
+           gboolean zero_terminate,
+           gboolean unescape_semicolon)
+{
+	nm_auto_free_secret char *tmp_string = NULL;
+	gboolean may_be_int_list = TRUE;
+	gsize length;
+	GBytes *result;
+
+	/* New format: just a string
+	 * Old format: integer list; e.g. 11;25;38;
+	 */
+	tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+	if (!tmp_string)
+		return NULL;
+
+	/* if the string is empty, we return an empty GBytes array.
+	 * Note that for NM_SETTING_802_1X_PASSWORD_RAW both %NULL and
+	 * an empty GBytes are valid, and shall be destinguished. */
+	if (!tmp_string[0]) {
+		/* note that even if @zero_terminate is TRUE, we return an empty
+		 * byte-array. The reason is that zero_terminate is there to terminate
+		 * *valid* strings. It's not there to terminated invalid (empty) strings.
+		 */
+		return g_bytes_new_static ("", 0);
+	}
+
+	for (length = 0; tmp_string[length]; length++) {
+		const char ch = tmp_string[length];
+
+		if (   !g_ascii_isspace (ch)
+		    && !g_ascii_isdigit (ch)
+		    && ch != ';') {
+			may_be_int_list = FALSE;
+			length += strlen (&tmp_string[length]);
+			break;
+		}
+	}
+
+	/* Try to parse the string as a integer list. */
+	if (may_be_int_list && length > 0) {
+		nm_auto_free_secret_buf NMSecretBuf *bin = NULL;
+		const char *const s = tmp_string;
+		gsize i, d;
+
+		bin = nm_secret_buf_new (length / 2 + 3);
+
+#define DIGIT(c) ((c) - '0')
+		i = 0;
+		d = 0;
+		while (TRUE) {
+			int n;
+
+			/* leading whitespace */
+			while (g_ascii_isspace (s[i]))
+				i++;
+			if (s[i] == '\0')
+				break;
+			/* then expect 1 to 3 digits */
+			if (!g_ascii_isdigit (s[i])) {
+				d = 0;
+				break;
+			}
+			n = DIGIT (s[i]);
+			i++;
+			if (g_ascii_isdigit (s[i])) {
+				n = 10 * n + DIGIT (s[i]);
+				i++;
+				if (g_ascii_isdigit (s[i])) {
+					n = 10 * n + DIGIT (s[i]);
+					i++;
+				}
+			}
+			if (n > 255) {
+				d = 0;
+				break;
+			}
+
+			nm_assert (d < bin->len);
+			bin->bin[d++] = n;
+
+			/* allow whitespace after the digit. */
+			while (g_ascii_isspace (s[i]))
+				i++;
+			/* need a semicolon as separator. */
+			if (s[i] != ';') {
+				d = 0;
+				break;
+			}
+			i++;
+		}
+#undef DIGIT
+
+		/* Old format; list of ints. We already did a strict validation of the
+		 * string format before. We expect that this conversion cannot fail. */
+		if (d > 0) {
+			/* note that @zero_terminate does not add a terminating '\0' to
+			 * binary data as an integer list. If the bytes are expressed as
+			 * an integer list, all potential NUL characters are supposed to
+			 * be included there explicitly.
+			 *
+			 * However, in the spirit of defensive programming, we do append a
+			 * NUL character to the buffer, although this character is hidden
+			 * and only a mitigation for bugs. */
+
+			if (d + 10 < bin->len) {
+				/* hm, too much unused memory. Copy the memory to a suitable
+				 * sized buffer. */
+				return nm_secret_copy_to_gbytes (bin->bin, d);
+			}
+
+			nm_assert (d < bin->len);
+			bin->bin[d] = '\0';
+			return nm_secret_buf_to_gbytes_take (g_steal_pointer (&bin), d);
+		}
+	}
+
+	/* Handle as a simple string (ie, new format) */
+	if (unescape_semicolon)
+		length = unescape_semicolons (tmp_string);
+	if (zero_terminate)
+		length++;
+	if (length == 0)
+		return NULL;
+
+	result = g_bytes_new_with_free_func (tmp_string,
+	                                     length,
+	                                     (GDestroyNotify) nm_free_secret,
+	                                     tmp_string);
+	tmp_string = NULL;
+	return result;
+}
+
+static void
+ssid_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GBytes *bytes;
+
+	bytes = get_bytes (info, setting_name, key, FALSE, TRUE);
+	if (bytes) {
+		g_object_set (setting, key, bytes, NULL);
+		g_bytes_unref (bytes);
+	} else if (!info->error) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid SSID"));
+	}
+}
+
+static void
+password_raw_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GBytes *bytes;
+
+	bytes = get_bytes (info, setting_name, key, FALSE, TRUE);
+	if (bytes) {
+		g_object_set (setting, key, bytes, NULL);
+		g_bytes_unref (bytes);
+	} else if (!info->error) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid raw password"));
+	}
+}
+
+static char *
+get_cert_path (const char *base_dir, const guint8 *cert_path, gsize cert_path_len)
+{
+	const char *base;
+	char *p = NULL, *path, *tmp;
+
+	g_return_val_if_fail (base_dir != NULL, NULL);
+	g_return_val_if_fail (cert_path != NULL, NULL);
+
+	path = g_strndup ((char *) cert_path, cert_path_len);
+
+	if (path[0] == '/')
+		return path;
+
+	base = path;
+	p = strrchr (path, '/');
+	if (p)
+		base = p + 1;
+
+	tmp = g_build_path ("/", base_dir, base, NULL);
+	g_free (path);
+	return tmp;
+}
+
+static const char *certext[] = { ".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key" };
+
+static gboolean
+has_cert_ext (const char *path)
+{
+	int i;
+
+	for (i = 0; i < G_N_ELEMENTS (certext); i++) {
+		if (g_str_has_suffix (path, certext[i]))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+char *
+nm_keyfile_detect_unqualified_path_scheme (const char *base_dir,
+                                           gconstpointer pdata,
+                                           gsize data_len,
+                                           gboolean consider_exists,
+                                           gboolean *out_exists)
+{
+	const char *data = pdata;
+	gboolean exists = FALSE;
+	gsize validate_len;
+	gsize path_len, pathuri_len;
+	gs_free char *path = NULL;
+	gs_free char *pathuri = NULL;
+
+	g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL);
+
+	if (!pdata)
+		return NULL;
+	if (data_len == -1)
+		data_len = strlen (data);
+	if (data_len > 500 || data_len < 1)
+		return NULL;
+
+	/* If there's a trailing zero tell g_utf8_validate() to validate until the zero */
+	if (data[data_len - 1] == '\0') {
+		/* setting it to -1, would mean we accept data to contain NUL characters before the
+		 * end. Don't accept any NUL in [0 .. data_len-1[ . */
+		validate_len = data_len - 1;
+	} else
+		validate_len = data_len;
+	if (   validate_len == 0
+	    || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE)
+		 return NULL;
+
+	/* Might be a bare path without the file:// prefix; in that case
+	 * if it's an absolute path, use that, otherwise treat it as a
+	 * relative path to the current directory.
+	 */
+
+	path = get_cert_path (base_dir, (const guint8 *) data, data_len);
+
+	/* FIXME(keyfile-parse-in-memory): it is wrong that keyfile reader makes decisions based on
+	 * the file systems content. The serialization/parsing should be entirely in-memory. */
+	if (   !memchr (data, '/', data_len)
+	    && !has_cert_ext (path)) {
+		if (!consider_exists)
+			return NULL;
+		exists = g_file_test (path, G_FILE_TEST_EXISTS);
+		if (!exists)
+			return NULL;
+	} else if (out_exists)
+		exists = g_file_test (path, G_FILE_TEST_EXISTS);
+
+	/* Construct the proper value as required for the PATH scheme.
+	 *
+	 * When returning TRUE, we must also be sure that @data_len does not look like
+	 * the deprecated format of list of integers. With this implementation that is the
+	 * case, as long as @consider_exists is FALSE. */
+	path_len = strlen (path);
+	pathuri_len = (NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + 1) + path_len;
+	pathuri = g_new (char, pathuri_len);
+	memcpy (pathuri, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH));
+	memcpy (&pathuri[NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)], path, path_len + 1);
+	if (nm_setting_802_1x_check_cert_scheme (pathuri, pathuri_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH)
+		return NULL;
+
+	NM_SET_OUT (out_exists, exists);
+	return g_steal_pointer (&pathuri);
+}
+
+#define HAS_SCHEME_PREFIX(bin, bin_len, scheme) \
+	({ \
+		const char *const _bin = (bin); \
+		const gsize _bin_len = (bin_len); \
+		\
+		nm_assert (_bin && _bin_len > 0); \
+		\
+		(   _bin_len > NM_STRLEN (scheme) + 1 \
+		 && _bin[_bin_len - 1] == '\0' \
+		 && memcmp (_bin, scheme, NM_STRLEN (scheme)) == 0); \
+	})
+
+static void
+cert_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_unref_bytes GBytes *bytes = NULL;
+	const char *bin = NULL;
+	gsize bin_len = 0;
+	char *path;
+	gboolean path_exists;
+
+	bytes = get_bytes (info, setting_name, key, TRUE, FALSE);
+	if (bytes)
+		bin = g_bytes_get_data (bytes, &bin_len);
+	if (bin_len == 0) {
+		if (!info->error) {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value"));
+		}
+		return;
+	}
+
+	if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) {
+		const char *path2 = &bin[NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)];
+		gs_free char *path2_free = NULL;
+
+		if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH) {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value path \"%s\""), bin);
+			return;
+		}
+
+		g_object_set (setting, key, bytes, NULL);
+
+		if (path2[0] != '/') {
+			/* we want to read absolute paths because we use keyfile as exchange
+			 * between different processes which might not have the same cwd. */
+			path2_free = get_cert_path (info->base_dir, (const guint8 *) path2,
+			                            bin_len - NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) - 1);
+			path2 = path2_free;
+		}
+
+		/* FIXME(keyfile-parse-in-memory): keyfile reader must not access the file system and
+		 * (in a first step) only operate in memory-only. If the presence of files should be checked,
+		 * then by invoking a callback (and possibly keyfile settings plugin would
+		 * collect the file names to be checked and check them later). */
+		if (!g_file_test (path2, G_FILE_TEST_EXISTS)) {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
+			             _("certificate or key file '%s' does not exist"),
+			             path2);
+		}
+		return;
+	}
+
+	if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11)) {
+		if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid PKCS#11 URI \"%s\""), bin);
+			return;
+		}
+
+		g_object_set (setting, key, bytes, NULL);
+		return;
+	}
+
+	if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)) {
+		const char *cdata = bin + NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB);
+		gsize cdata_len = bin_len - NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB) - 1;
+		gs_free guchar *bin_decoded = NULL;
+		gsize bin_decoded_len = 0;
+		gsize i;
+		gboolean valid_base64;
+		gs_unref_bytes GBytes *val = NULL;
+
+		/* Let's be strict here. We expect valid base64, no funny stuff!!
+		 * We didn't write such invalid data ourselfes and refuse to read it as blob. */
+		if ((valid_base64 = (cdata_len % 4 == 0))) {
+			for (i = 0; i < cdata_len; i++) {
+				char c = cdata[i];
+
+				if (!(   (c >= 'a' && c <= 'z')
+				      || (c >= 'A' && c <= 'Z')
+				      || (c >= '0' && c <= '9')
+				      || (c == '+' || c == '/'))) {
+					if (c != '=' || i < cdata_len - 2)
+						valid_base64 = FALSE;
+					else {
+						for (; i < cdata_len; i++) {
+							if (cdata[i] != '=')
+								valid_base64 = FALSE;
+						}
+					}
+					break;
+				}
+			}
+		}
+		if (valid_base64)
+			bin_decoded = g_base64_decode (cdata, &bin_decoded_len);
+
+		if (bin_decoded_len == 0) {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value data:;base64, is not base64"));
+			return;
+		}
+
+		if (nm_setting_802_1x_check_cert_scheme (bin_decoded, bin_decoded_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+			/* The blob probably starts with "file://". Setting the cert data will confuse NMSetting8021x.
+			 * In fact this is a limitation of NMSetting8021x which does not support setting blobs that start
+			 * with file://. Just warn and return TRUE to signal that we ~handled~ the setting. */
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid key/cert value data:;base64,file://"));
+			return;
+		}
+
+		val = g_bytes_new_take (g_steal_pointer (&bin_decoded), bin_decoded_len);
+		g_object_set (setting, key, val, NULL);
+		return;
+	}
+
+	/* If not, it might be a plain path */
+	path = nm_keyfile_detect_unqualified_path_scheme (info->base_dir, bin, bin_len, TRUE, &path_exists);
+	if (path) {
+		gs_unref_bytes GBytes *val = NULL;
+
+		/* Construct the proper value as required for the PATH scheme */
+		val = g_bytes_new_take (path, strlen (path) + 1);
+		g_object_set (setting, key, val, NULL);
+
+		/* Warn if the certificate didn't exist */
+		if (!path_exists) {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
+			             _("certificate or key file '%s' does not exist"),
+			             path);
+		}
+		return;
+	}
+
+	if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+		/* The blob probably starts with "file://" but contains invalid characters for a path.
+		 * Setting the cert data will confuse NMSetting8021x.
+		 * In fact, NMSetting8021x does not support setting such binary data, so just warn and
+		 * continue. */
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("invalid key/cert value is not a valid blob"));
+		return;
+	}
+
+	g_object_set (setting, key, bytes, NULL);
+}
+
+static int
+_parity_from_char (int ch)
+{
+#if NM_MORE_ASSERTS > 5
+	{
+		static char check = 0;
+
+		if (check == 0) {
+			nm_auto_unref_gtypeclass GEnumClass *klass = g_type_class_ref (NM_TYPE_SETTING_SERIAL_PARITY);
+			guint i;
+
+			check = 1;
+
+			/* In older versions, parity was G_TYPE_CHAR/gint8, and the character
+			 * value was stored as integer.
+			 * For example parity=69 equals parity=E, meaning NM_SETTING_SERIAL_PARITY_EVEN.
+			 *
+			 * That means, certain values are reserved. Assert that these numbers
+			 * are not reused when we extend NMSettingSerialParity enum.
+			 * Actually, since NM_SETTING_SERIAL_PARITY is g_param_spec_enum(),
+			 * we anyway cannot extend the enum without breaking API...
+			 *
+			 * [1] commit "a91e60902e libnm-core: make NMSettingSerial:parity an enum"
+			 * [2] https://cgit.freedesktop.org/NetworkManager/NetworkManager/commit/?id=a91e60902eabae1de93d61323dae6ac894b5d40f
+			 */
+			g_assert (G_IS_ENUM_CLASS (klass));
+			for (i = 0; i < klass->n_values; i++) {
+				const GEnumValue *v = &klass->values[i];
+				int num = v->value;
+
+				g_assert (_parity_from_char (num) == -1);
+				g_assert (!NM_IN_SET (num, 'e', 'E', 'o', 'O', 'n', 'N'));
+			}
+		}
+	}
+#endif
+
+	switch (ch) {
+	case 'E':
+	case 'e':
+		return NM_SETTING_SERIAL_PARITY_EVEN;
+	case 'O':
+	case 'o':
+		return NM_SETTING_SERIAL_PARITY_ODD;
+	case 'N':
+	case 'n':
+		return NM_SETTING_SERIAL_PARITY_NONE;
+	}
+
+	return -1;
+}
+
+static void
+parity_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_free_error GError *err = NULL;
+	int parity;
+	gs_free char *tmp_str = NULL;
+	gint64 i64;
+
+	/* Keyfile traditionally stored this as the ASCII value for 'E', 'o', or 'n'.
+	 * We now accept either that or the (case-insensitive) character itself (but
+	 * still always write it the old way, for backward compatibility).
+	 */
+	tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, &err);
+	if (err)
+		goto out_err;
+
+	if (   tmp_str
+	    && tmp_str[0] != '\0'
+	    && tmp_str[1] == '\0') {
+		/* the ASCII characters like 'E' are taken directly... */
+		parity = _parity_from_char (tmp_str[0]);
+		if (parity >= 0)
+			goto parity_good;
+	}
+
+	i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64);
+	if (   i64 != G_MININT64
+	    && errno == 0) {
+
+		if ((parity = _parity_from_char (i64)) >= 0) {
+			/* another oddity: the string is a valid number. However, if the numeric values
+			 * is one of the supported ASCII codes, accept it (like 69 for 'E').
+			 */
+			goto parity_good;
+		}
+
+		/* Finally, take the numeric value as is. */
+		parity = i64;
+		goto parity_good;
+	}
+
+	handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+	             _("invalid parity value '%s'"),
+	             tmp_str ?: "");
+	return;
+
+parity_good:
+	nm_g_object_set_property_enum (G_OBJECT (setting), key, NM_TYPE_SETTING_SERIAL_PARITY, parity, &err);
+
+out_err:
+	if (!err)
+		return;
+	if (   err->domain == G_KEY_FILE_ERROR
+	    && NM_IN_SET (err->code, G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
+	                             G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
+		/* ignore such errors. The key is not present. */
+		return;
+	}
+	handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+	             _("invalid setting: %s"), err->message);
+}
+
+static void
+team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_free char *conf = NULL;
+	gs_free_error GError *error = NULL;
+
+	conf = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
+
+	g_object_set (G_OBJECT (setting), key, conf, NULL);
+
+	if (   conf
+	    && !nm_setting_verify (setting, NULL, &error)) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("ignoring invalid team configuration: %s"),
+		             error->message);
+		g_object_set (G_OBJECT (setting), key, NULL, NULL);
+	}
+}
+
+static void
+bridge_vlan_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	gs_unref_ptrarray GPtrArray *vlans = NULL;
+	gs_free char *value = NULL;
+	gs_free const char **strv = NULL;
+	const char *const *iter;
+	GError *local = NULL;
+	NMBridgeVlan *vlan;
+
+	value = nm_keyfile_plugin_kf_get_string (info->keyfile,
+	                                         nm_setting_get_name (setting),
+	                                         key,
+	                                         NULL);
+	if (!value || !value[0])
+		return;
+
+	vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
+
+	strv = nm_utils_escaped_tokens_split (value, ",");
+	if (strv) {
+		for (iter = strv; *iter; iter++) {
+			vlan = nm_bridge_vlan_from_str (*iter, &local);
+			if (!vlan) {
+				handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+				             "invalid bridge VLAN: %s", local->message);
+				g_clear_error (&local);
+				continue;
+			}
+			g_ptr_array_add (vlans, vlan);
+		}
+	}
+
+	if (vlans->len > 0)
+		g_object_set (setting, key, vlans, NULL);
+}
+
+static void
+qdisc_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_unref_ptrarray GPtrArray *qdiscs = NULL;
+	gs_strfreev char **keys = NULL;
+	gsize n_keys = 0;
+	int i;
+
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
+	if (n_keys == 0)
+		return;
+
+	qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref);
+
+	for (i = 0; i < n_keys; i++) {
+		NMTCQdisc *qdisc;
+		const char *qdisc_parent;
+		gs_free char *qdisc_rest = NULL;
+		gs_free char *qdisc_str = NULL;
+		gs_free_error GError *err = NULL;
+
+		if (!g_str_has_prefix (keys[i], "qdisc."))
+			continue;
+
+		qdisc_parent = keys[i] + sizeof ("qdisc.") - 1;
+		qdisc_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL);
+		qdisc_str = g_strdup_printf ("%s%s %s",
+		                             _nm_utils_parse_tc_handle (qdisc_parent, NULL) != TC_H_UNSPEC ? "parent " : "",
+		                             qdisc_parent,
+		                             qdisc_rest);
+
+		qdisc = nm_utils_tc_qdisc_from_str (qdisc_str, &err);
+		if (!qdisc) {
+			handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid qdisc: %s"),
+			             err->message);
+		} else {
+			g_ptr_array_add (qdiscs, qdisc);
+		}
+	}
+
+	if (qdiscs->len >= 1)
+		g_object_set (setting, key, qdiscs, NULL);
+}
+
+static void
+tfilter_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	gs_unref_ptrarray GPtrArray *tfilters = NULL;
+	gs_strfreev char **keys = NULL;
+	gsize n_keys = 0;
+	int i;
+
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
+	if (n_keys == 0)
+		return;
+
+	tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref);
+
+	for (i = 0; i < n_keys; i++) {
+		NMTCTfilter *tfilter;
+		const char *tfilter_parent;
+		gs_free char *tfilter_rest = NULL;
+		gs_free char *tfilter_str = NULL;
+		gs_free_error GError *err = NULL;
+
+		if (!g_str_has_prefix (keys[i], "tfilter."))
+			continue;
+
+		tfilter_parent = keys[i] + sizeof ("tfilter.") - 1;
+		tfilter_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL);
+		tfilter_str = g_strdup_printf ("%s%s %s",
+		                             _nm_utils_parse_tc_handle (tfilter_parent, NULL) != TC_H_UNSPEC ? "parent " : "",
+		                             tfilter_parent,
+		                             tfilter_rest);
+
+		tfilter = nm_utils_tc_tfilter_from_str (tfilter_str, &err);
+		if (!tfilter) {
+			handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid tfilter: %s"),
+			             err->message);
+		} else {
+			g_ptr_array_add (tfilters, tfilter);
+		}
+	}
+
+	if (tfilters->len >= 1)
+		g_object_set (setting, key, tfilters, NULL);
+}
+
+/*****************************************************************************/
+
+/* Some setting properties also contain setting names, such as
+ * NMSettingConnection's 'type' property (which specifies the base type of the
+ * connection, eg ethernet or wifi) or the 802-11-wireless setting's
+ * 'security' property which specifies whether or not the AP requires
+ * encryption.  This function handles translating those properties' values
+ * from the real setting name to the more-readable alias.
+ */
+static void
+setting_alias_writer (KeyfileWriterInfo *info,
+                      NMSetting *setting,
+                      const char *key,
+                      const GValue *value)
+{
+	const char *str, *alias;
+
+	str = g_value_get_string (value);
+	alias = nm_keyfile_plugin_get_alias_for_setting_name (str);
+	nm_keyfile_plugin_kf_set_string (info->keyfile,
+	                                 nm_setting_get_name (setting),
+	                                 key,
+	                                 alias ?: str);
+}
+
+static void
+sriov_vfs_writer (KeyfileWriterInfo *info,
+                  NMSetting *setting,
+                  const char *key,
+                  const GValue *value)
+{
+	GPtrArray *vfs;
+	guint i;
+
+	vfs = g_value_get_boxed (value);
+	if (!vfs)
+		return;
+
+	for (i = 0; i < vfs->len; i++) {
+		const NMSriovVF *vf = vfs->pdata[i];
+		gs_free char *kf_value = NULL;
+		char kf_key[32];
+
+		kf_value = nm_utils_sriov_vf_to_str (vf, TRUE, NULL);
+		if (!kf_value)
+			continue;
+
+		nm_sprintf_buf (kf_key, "vf.%u", nm_sriov_vf_get_index (vf));
+
+		nm_keyfile_plugin_kf_set_string (info->keyfile,
+		                                 nm_setting_get_name (setting),
+		                                 kf_key,
+		                                 kf_value);
+	}
+}
+
+static void
+write_array_of_uint (GKeyFile *file,
+                     NMSetting *setting,
+                     const char *key,
+                     const GValue *value)
+{
+	GArray *array;
+	guint i;
+	gs_free int *tmp_array = NULL;
+
+	array = (GArray *) g_value_get_boxed (value);
+	if (!array || !array->len)
+		return;
+
+	g_return_if_fail (g_array_get_element_size (array) == sizeof (guint));
+
+	tmp_array = g_new (int, array->len);
+	for (i = 0; i < array->len; i++) {
+		guint v = g_array_index (array, guint, i);
+
+		if (v > G_MAXINT)
+			g_return_if_reached ();
+		tmp_array[i] = (int) v;
+	}
+
+	nm_keyfile_plugin_kf_set_integer_list (file, nm_setting_get_name (setting), key, tmp_array, array->len);
+}
+
+static void
+dns_writer (KeyfileWriterInfo *info,
+            NMSetting *setting,
+            const char *key,
+            const GValue *value)
+{
+	char **list;
+
+	list = g_value_get_boxed (value);
+	if (list && list[0]) {
+		nm_keyfile_plugin_kf_set_string_list (info->keyfile, nm_setting_get_name (setting), key,
+		                                      (const char **) list, g_strv_length (list));
+	}
+}
+
+static void
+ip6_addr_gen_mode_writer (KeyfileWriterInfo *info,
+                          NMSetting *setting,
+                          const char *key,
+                          const GValue *value)
+{
+	NMSettingIP6ConfigAddrGenMode addr_gen_mode;
+	gs_free char *str = NULL;
+
+	addr_gen_mode = (NMSettingIP6ConfigAddrGenMode) g_value_get_int (value);
+	str = nm_utils_enum_to_str (nm_setting_ip6_config_addr_gen_mode_get_type (),
+	                            addr_gen_mode);
+	nm_keyfile_plugin_kf_set_string (info->keyfile,
+	                                 nm_setting_get_name (setting),
+	                                 key,
+	                                 str);
+}
+
+static void
+write_ip_values (GKeyFile *file,
+                 const char *setting_name,
+                 GPtrArray *array,
+                 const char *gateway,
+                 gboolean is_route)
+{
+	nm_auto_free_gstring GString *output = NULL;
+	int addr_family;
+	guint i;
+	const char *addr;
+	const char *gw;
+	guint32 plen;
+	char key_name[64];
+	char *key_name_idx;
+
+	if (!array->len)
+		return;
+
+	addr_family =   nm_streq (setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME)
+	              ? AF_INET
+	              : AF_INET6;
+
+	strcpy (key_name, is_route ? "route" : "address");
+	key_name_idx = key_name + strlen (key_name);
+
+	output = g_string_sized_new (2*INET_ADDRSTRLEN + 10);
+	for (i = 0; i < array->len; i++) {
+		gint64 metric = -1;
+
+		if (is_route) {
+			NMIPRoute *route = array->pdata[i];
+
+			addr = nm_ip_route_get_dest (route);
+			plen = nm_ip_route_get_prefix (route);
+			gw = nm_ip_route_get_next_hop (route);
+			metric = nm_ip_route_get_metric (route);
+		} else {
+			NMIPAddress *address = array->pdata[i];
+
+			addr = nm_ip_address_get_address (address);
+			plen = nm_ip_address_get_prefix (address);
+			gw =   (i == 0)
+			     ? gateway
+			     : NULL;
+		}
+
+		g_string_set_size (output, 0);
+		g_string_append_printf (output, "%s/%u", addr, plen);
+		if (   metric != -1
+		    || gw) {
+			/* Older versions of the plugin do not support the form
+			 * "a.b.c.d/plen,,metric", so, we always have to write the
+			 * gateway, even if there isn't one.
+			 * The current version supports reading of the above form.
+			 */
+			if (!gw) {
+				if (addr_family == AF_INET)
+					gw = "0.0.0.0";
+				else
+					gw = "::";
+			}
+
+			g_string_append_printf (output, ",%s", gw);
+			if (   is_route
+			    && metric != -1)
+				g_string_append_printf (output, ",%lu", (unsigned long) metric);
+		}
+
+		sprintf (key_name_idx, "%u", i + 1);
+		nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, output->str);
+
+		if (is_route) {
+			gs_free char *attributes = NULL;
+
+			attributes = nm_utils_format_variant_attributes (_nm_ip_route_get_attributes (array->pdata[i]),
+			                                                 ',', '=');
+			if (attributes) {
+				g_strlcat (key_name, "_options", sizeof (key_name));
+				nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, attributes);
+			}
+		}
+	}
+}
+
+static void
+addr_writer (KeyfileWriterInfo *info,
+             NMSetting *setting,
+             const char *key,
+             const GValue *value)
+{
+	GPtrArray *array;
+	const char *setting_name = nm_setting_get_name (setting);
+	const char *gateway = nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (setting));
+
+	array = (GPtrArray *) g_value_get_boxed (value);
+	if (array && array->len)
+		write_ip_values (info->keyfile, setting_name, array, gateway, FALSE);
+}
+
+static void
+route_writer (KeyfileWriterInfo *info,
+              NMSetting *setting,
+              const char *key,
+              const GValue *value)
+{
+	GPtrArray *array;
+	const char *setting_name = nm_setting_get_name (setting);
+
+	array = (GPtrArray *) g_value_get_boxed (value);
+	if (array && array->len)
+		write_ip_values (info->keyfile, setting_name, array, NULL, TRUE);
+}
+
+static void
+bridge_vlan_writer (KeyfileWriterInfo *info,
+                    NMSetting *setting,
+                    const char *key,
+                    const GValue *value)
+{
+	NMBridgeVlan *vlan;
+	GPtrArray *vlans;
+	GString *string;
+	guint i;
+
+	vlans = (GPtrArray *) g_value_get_boxed (value);
+	if (!vlans || !vlans->len)
+		return;
+
+	string = g_string_new ("");
+	for (i = 0; i < vlans->len; i++) {
+		gs_free char *vlan_str = NULL;
+
+		vlan = vlans->pdata[i];
+		vlan_str = nm_bridge_vlan_to_str (vlan, NULL);
+		if (!vlan_str)
+			continue;
+		if (string->len > 0)
+			g_string_append (string, ",");
+		nm_utils_escaped_tokens_escape_gstr_assert (vlan_str, ",", string);
+	}
+
+	nm_keyfile_plugin_kf_set_string (info->keyfile,
+	                                 nm_setting_get_name (setting),
+	                                 "vlans",
+	                                 string->str);
+
+	g_string_free (string, TRUE);
+}
+
+
+#define ETHERNET_S390_OPTIONS_GROUP_NAME "ethernet-s390-options"
+
+static void
+wired_s390_options_parser_full (KeyfileReaderInfo *info,
+                                const NMMetaSettingInfo *setting_info,
+                                const NMSettInfoProperty *property_info,
+                                const ParseInfoProperty *pip,
+                                NMSetting *setting)
+{
+	NMSettingWired *s_wired = NM_SETTING_WIRED (setting);
+	gs_strfreev char **keys = NULL;
+	gsize n_keys;
+	gsize i;
+
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, ETHERNET_S390_OPTIONS_GROUP_NAME, &n_keys, NULL);
+	for (i = 0; i < n_keys; i++) {
+		gs_free char *value = NULL;
+		gs_free char *key_to_free = NULL;
+
+		value = nm_keyfile_plugin_kf_get_string (info->keyfile,
+		                                         ETHERNET_S390_OPTIONS_GROUP_NAME,
+		                                         keys[i],
+		                                         NULL);
+		if (!value)
+			continue;
+
+		nm_setting_wired_add_s390_option (s_wired,
+		                                  nm_keyfile_key_decode (keys[i],
+		                                                         &key_to_free),
+		                                  value);
+	}
+}
+
+static void
+wired_s390_options_writer_full (KeyfileWriterInfo *info,
+                                const NMMetaSettingInfo *setting_info,
+                                const NMSettInfoProperty *property_info,
+                                const ParseInfoProperty *pip,
+                                NMSetting *setting)
+{
+	NMSettingWired *s_wired = NM_SETTING_WIRED (setting);
+	guint i, n;
+
+	n = nm_setting_wired_get_num_s390_options (s_wired);
+	for (i = 0; i < n; i++) {
+		const char *opt_key;
+		const char *opt_val;
+		gs_free char *key_to_free = NULL;
+
+		nm_setting_wired_get_s390_option (s_wired, i, &opt_key, &opt_val);
+		nm_keyfile_plugin_kf_set_string (info->keyfile,
+		                                 ETHERNET_S390_OPTIONS_GROUP_NAME,
+		                                 nm_keyfile_key_encode (opt_key, &key_to_free),
+		                                 opt_val);
+	}
+}
+
+static void
+ip_routing_rule_writer_full (KeyfileWriterInfo *info,
+                             const NMMetaSettingInfo *setting_info,
+                             const NMSettInfoProperty *property_info,
+                             const ParseInfoProperty *pip,
+                             NMSetting *setting)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG (setting);
+	guint i, j, n;
+	char key_name_full[100] = "routing-rule";
+	char *key_name_num = &key_name_full[NM_STRLEN ("routing-rule")];
+
+	n = nm_setting_ip_config_get_num_routing_rules (s_ip);
+	j = 0;
+	for (i = 0; i < n; i++) {
+		NMIPRoutingRule *rule = nm_setting_ip_config_get_routing_rule (s_ip, i);
+		gs_free char *str = NULL;
+
+		str = nm_ip_routing_rule_to_string (rule,
+		                                    NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE,
+		                                    NULL,
+		                                    NULL);
+		if (!str)
+			continue;
+
+		sprintf (key_name_num, "%u", ++j);
+		nm_keyfile_plugin_kf_set_string (info->keyfile,
+		                                 setting_name,
+		                                 key_name_full,
+		                                 str);
+	}
+}
+
+static void
+qdisc_writer (KeyfileWriterInfo *info,
+              NMSetting *setting,
+              const char *key,
+              const GValue *value)
+{
+	gsize i;
+	GPtrArray *array;
+
+	array = (GPtrArray *) g_value_get_boxed (value);
+	if (!array || !array->len)
+		return;
+
+	for (i = 0; i < array->len; i++) {
+		NMTCQdisc *qdisc = array->pdata[i];
+		GString *key_name = g_string_sized_new (16);
+		GString *value_str = g_string_sized_new (60);
+
+		g_string_append (key_name, "qdisc.");
+		_nm_utils_string_append_tc_parent (key_name, NULL,
+		                                   nm_tc_qdisc_get_parent (qdisc));
+		_nm_utils_string_append_tc_qdisc_rest (value_str, qdisc);
+
+		nm_keyfile_plugin_kf_set_string (info->keyfile,
+		                                 NM_SETTING_TC_CONFIG_SETTING_NAME,
+		                                 key_name->str,
+		                                 value_str->str);
+
+		g_string_free (key_name, TRUE);
+		g_string_free (value_str, TRUE);
+	}
+}
+
+static void
+tfilter_writer (KeyfileWriterInfo *info,
+              NMSetting *setting,
+              const char *key,
+              const GValue *value)
+{
+	gsize i;
+	GPtrArray *array;
+
+	array = (GPtrArray *) g_value_get_boxed (value);
+	if (!array || !array->len)
+		return;
+
+	for (i = 0; i < array->len; i++) {
+		NMTCTfilter *tfilter = array->pdata[i];
+		GString *key_name = g_string_sized_new (16);
+		GString *value_str = g_string_sized_new (60);
+
+		g_string_append (key_name, "tfilter.");
+		_nm_utils_string_append_tc_parent (key_name, NULL,
+		                                   nm_tc_tfilter_get_parent (tfilter));
+		_nm_utils_string_append_tc_tfilter_rest (value_str, tfilter, NULL);
+
+		nm_keyfile_plugin_kf_set_string (info->keyfile,
+		                                 NM_SETTING_TC_CONFIG_SETTING_NAME,
+		                                 key_name->str,
+		                                 value_str->str);
+
+		g_string_free (key_name, TRUE);
+		g_string_free (value_str, TRUE);
+	}
+}
+
+static void
+write_hash_of_string (GKeyFile *file,
+                      NMSetting *setting,
+                      const char *key,
+                      const GValue *value)
+{
+	GHashTable *hash;
+	const char *group_name = nm_setting_get_name (setting);
+	gboolean vpn_secrets = FALSE;
+	gs_free const char **keys = NULL;
+	guint i, l;
+
+	nm_assert (   (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_DATA))
+	           || (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_SECRETS))
+	           || (NM_IS_SETTING_BOND (setting) && nm_streq (key, NM_SETTING_BOND_OPTIONS))
+	           || (NM_IS_SETTING_USER (setting) && nm_streq (key, NM_SETTING_USER_DATA)));
+
+	/* Write VPN secrets out to a different group to keep them separate */
+	if (   NM_IS_SETTING_VPN (setting)
+	    && nm_streq (key, NM_SETTING_VPN_SECRETS)) {
+		group_name = NM_KEYFILE_GROUP_VPN_SECRETS;
+		vpn_secrets = TRUE;
+	}
+
+	hash = g_value_get_boxed (value);
+
+	keys = nm_utils_strdict_get_keys (hash, TRUE, &l);
+	for (i = 0; i < l; i++) {
+		gs_free char *to_free = NULL;
+		const char *property, *data;
+
+		property = keys[i];
+
+		/* Handle VPN secrets specially; they are nested in the property's hash;
+		 * we don't want to write them if the secret is not saved, not required,
+		 * or owned by a user's secret agent.
+		 */
+		if (vpn_secrets) {
+			NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+
+			if (!nm_setting_get_secret_flags (setting, property, &secret_flags, NULL))
+				nm_assert_not_reached ();
+			if (!_secret_flags_persist_secret (secret_flags))
+				continue;
+		}
+
+		data = g_hash_table_lookup (hash, property);
+		nm_keyfile_plugin_kf_set_string (file, group_name,
+		                                 nm_keyfile_key_encode (property, &to_free),
+		                                 data);
+	}
+}
+
+static void
+ssid_writer (KeyfileWriterInfo *info,
+             NMSetting *setting,
+             const char *key,
+             const GValue *value)
+{
+	GBytes *bytes;
+	const guint8 *ssid_data;
+	gsize ssid_len;
+	const char *setting_name = nm_setting_get_name (setting);
+	gboolean new_format = TRUE;
+	gsize semicolons = 0;
+	gsize i;
+
+	g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES));
+
+	bytes = g_value_get_boxed (value);
+	if (!bytes)
+		return;
+	ssid_data = g_bytes_get_data (bytes, &ssid_len);
+	if (!ssid_data || !ssid_len) {
+		nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, "");
+		return;
+	}
+
+	/* Check whether each byte is printable.  If not, we have to use an
+	 * integer list, otherwise we can just use a string.
+	 */
+	for (i = 0; i < ssid_len; i++) {
+		const char c = ssid_data[i];
+
+		if (!g_ascii_isprint (c)) {
+			new_format = FALSE;
+			break;
+		}
+		if (c == ';')
+			semicolons++;
+	}
+
+	if (new_format) {
+		gs_free char *ssid = NULL;
+
+		if (semicolons == 0)
+			ssid = g_strndup ((char *) ssid_data, ssid_len);
+		else {
+			/* Escape semicolons with backslashes to make strings
+			 * containing ';', such as '16;17;' unambiguous */
+			gsize j = 0;
+
+			ssid = g_malloc (ssid_len + semicolons + 1);
+			for (i = 0; i < ssid_len; i++) {
+				if (ssid_data[i] == ';')
+					ssid[j++] = '\\';
+				ssid[j++] = ssid_data[i];
+			}
+			ssid[j] = '\0';
+		}
+		nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, ssid);
+	} else
+		nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_name, key, ssid_data, ssid_len);
+}
+
+static void
+password_raw_writer (KeyfileWriterInfo *info,
+                     NMSetting *setting,
+                     const char *key,
+                     const GValue *value)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GBytes *array;
+	gsize len;
+	const guint8 *data;
+
+	g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES));
+
+	array = (GBytes *) g_value_get_boxed (value);
+	if (!array)
+		return;
+	data = g_bytes_get_data (array, &len);
+	if (!data)
+		len = 0;
+	nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_name, key, data, len);
+}
+
+/*****************************************************************************/
+
+static void
+cert_writer_default (NMConnection *connection,
+                     GKeyFile *file,
+                     NMKeyfileWriteTypeDataCert *cert_data)
+{
+	const char *setting_name = nm_setting_get_name (NM_SETTING (cert_data->setting));
+	NMSetting8021xCKScheme scheme;
+
+	scheme = cert_data->vtable->scheme_func (cert_data->setting);
+	if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+		gs_free char *path_free = NULL;
+		gs_free char *base_dir = NULL;
+		gs_free char *tmp = NULL;
+		const char *path;
+
+		path = cert_data->vtable->path_func (cert_data->setting);
+		g_assert (path);
+
+		/* If the path is relative, make it an absolute path.
+		 * Relative paths make a keyfile not easily usable in another
+		 * context. */
+		if (path[0] && path[0] != '/') {
+			base_dir = g_get_current_dir ();
+			path_free = g_strconcat (base_dir, "/", path, NULL);
+			path = path_free;
+		} else
+			base_dir = g_path_get_dirname (path);
+
+		/* path cannot start with "file://" or "data:;base64,", because it is an absolute path.
+		 * Still, make sure that a prefix-less path will be recognized. This can happen
+		 * for example if the path is longer then 500 chars. */
+		tmp = nm_keyfile_detect_unqualified_path_scheme (base_dir, path, -1, FALSE, NULL);
+		if (tmp)
+			nm_clear_g_free (&tmp);
+		else {
+			tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL);
+			path = tmp;
+		}
+
+		/* Path contains at least a '/', hence it cannot be recognized as the old
+		 * binary format consisting of a list of integers. */
+
+		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, path);
+	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+		GBytes *blob;
+		const guint8 *blob_data;
+		gsize blob_len;
+		gs_free char *blob_base64 = NULL;
+		gs_free char *val = NULL;
+
+		blob = cert_data->vtable->blob_func (cert_data->setting);
+		g_assert (blob);
+		blob_data = g_bytes_get_data (blob, &blob_len);
+
+		blob_base64 = g_base64_encode (blob_data, blob_len);
+		val = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB, blob_base64, NULL);
+
+		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, val);
+	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
+		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key,
+		                                 cert_data->vtable->uri_func (cert_data->setting));
+	} 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 void
+cert_writer (KeyfileWriterInfo *info,
+             NMSetting *setting,
+             const char *key,
+             const GValue *value)
+{
+	const NMSetting8021xSchemeVtable *objtype = NULL;
+	guint i;
+	NMKeyfileWriteTypeDataCert type_data = { 0 };
+
+	for (i = 0; nm_setting_8021x_scheme_vtable[i].setting_key; i++) {
+		if (nm_streq0 (nm_setting_8021x_scheme_vtable[i].setting_key, key)) {
+			objtype = &nm_setting_8021x_scheme_vtable[i];
+			break;
+		}
+	}
+	if (!objtype)
+		g_return_if_reached ();
+
+	type_data.setting = NM_SETTING_802_1X (setting);
+	type_data.vtable = objtype;
+
+	if (info->handler) {
+		if (info->handler (info->connection,
+		                   info->keyfile,
+		                   NM_KEYFILE_WRITE_TYPE_CERT,
+		                   &type_data,
+		                   info->user_data,
+		                   &info->error))
+			return;
+		if (info->error)
+			return;
+	}
+
+	cert_writer_default (info->connection, info->keyfile, &type_data);
+}
+
+/*****************************************************************************/
+
+struct _ParseInfoProperty {
+	const char *property_name;
+	union {
+		void (*parser) (KeyfileReaderInfo *info,
+		                NMSetting *setting,
+		                const char *key);
+		void (*parser_full) (KeyfileReaderInfo *info,
+		                     const NMMetaSettingInfo *setting_info,
+		                     const NMSettInfoProperty *property_info,
+		                     const ParseInfoProperty *pip,
+		                     NMSetting *setting);
+	};
+	union {
+		void (*writer) (KeyfileWriterInfo *info,
+		                NMSetting *setting,
+		                const char *key,
+		                const GValue *value);
+		void (*writer_full) (KeyfileWriterInfo *info,
+		                     const NMMetaSettingInfo *setting_info,
+		                     const NMSettInfoProperty *property_info,
+		                     const ParseInfoProperty *pip,
+		                     NMSetting *setting);
+	};
+	bool parser_skip;
+	bool parser_no_check_key:1;
+	bool writer_skip:1;
+	bool has_writer_full:1;
+	bool has_parser_full:1;
+
+	/* usually, we skip to write values that have their
+	 * default value. By setting this flag to TRUE, also
+	 * default values are written. */
+	bool writer_persist_default:1;
+};
+
+#define PARSE_INFO_PROPERTY(_property_name, ...) \
+	(&((const ParseInfoProperty) { \
+		.property_name = _property_name, \
+		__VA_ARGS__ \
+	}))
+
+#define PARSE_INFO_PROPERTIES(...) \
+	.properties = ((const ParseInfoProperty*const[]) { \
+		__VA_ARGS__ \
+		NULL, \
+	})
+
+typedef struct {
+	const ParseInfoProperty*const*properties;
+} ParseInfoSetting;
+
+#define PARSE_INFO_SETTING(setting_type, ...) \
+	[setting_type] = (&((const ParseInfoSetting) { \
+		__VA_ARGS__ \
+	}))
+
+static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = {
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIRELESS,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_BSSID,
+				.parser        = mac_address_parser_ETHER,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
+				.parser        = mac_address_parser_ETHER_cloned,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_MAC_ADDRESS,
+				.parser        = mac_address_parser_ETHER,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_SSID,
+				.parser        = ssid_parser,
+				.writer        = ssid_writer,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_802_1X,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_CA_CERT,
+				.parser        = cert_parser,
+				.writer        = cert_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_CLIENT_CERT,
+				.parser        = cert_parser,
+				.writer        = cert_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PASSWORD_RAW,
+				.parser        = password_raw_parser,
+				.writer        = password_raw_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_CA_CERT,
+				.parser        = cert_parser,
+				.writer        = cert_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
+				.parser        = cert_parser,
+				.writer        = cert_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
+				.parser        = cert_parser,
+				.writer        = cert_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PRIVATE_KEY,
+				.parser        = cert_parser,
+				.writer        = cert_writer,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIRED,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
+				.parser        = mac_address_parser_ETHER_cloned,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_WIRED_MAC_ADDRESS,
+				.parser        = mac_address_parser_ETHER,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_WIRED_S390_OPTIONS,
+				.parser_no_check_key = TRUE,
+				.parser_full   = wired_s390_options_parser_full,
+				.writer_full   = wired_s390_options_writer_full,
+				.has_parser_full = TRUE,
+				.has_writer_full = TRUE,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BLUETOOTH,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_BLUETOOTH_BDADDR,
+				.parser        = mac_address_parser_ETHER,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BOND,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_BOND_OPTIONS,
+				.parser_no_check_key = TRUE,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BRIDGE,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_MAC_ADDRESS,
+				.parser        = mac_address_parser_ETHER,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_VLANS,
+				.parser_no_check_key = TRUE,
+				.parser        = bridge_vlan_parser,
+				.writer        = bridge_vlan_writer,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BRIDGE_PORT,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_PORT_VLANS,
+				.parser_no_check_key = TRUE,
+				.parser        = bridge_vlan_parser,
+				.writer        = bridge_vlan_writer,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_CONNECTION,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_CONNECTION_READ_ONLY,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_CONNECTION_TYPE,
+				.parser        = setting_alias_parser,
+				.writer        = setting_alias_writer,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_INFINIBAND,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_INFINIBAND_MAC_ADDRESS,
+				.parser        = mac_address_parser_INFINIBAND,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_IP4_CONFIG,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ADDRESSES,
+				.parser_no_check_key = TRUE,
+				.parser        = ip_address_or_route_parser,
+				.writer        = addr_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_DNS,
+				.parser_no_check_key = TRUE,
+				.parser        = ip_dns_parser,
+				.writer        = dns_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_GATEWAY,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTES,
+				.parser_no_check_key = TRUE,
+				.parser        = ip_address_or_route_parser,
+				.writer        = route_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES,
+				.parser_no_check_key = TRUE,
+				.parser_full   = ip_routing_rule_parser_full,
+				.writer_full   = ip_routing_rule_writer_full,
+				.has_parser_full = TRUE,
+				.has_writer_full = TRUE,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_IP6_CONFIG,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
+				.parser_no_check_key = TRUE,
+				.parser        = ip6_addr_gen_mode_parser,
+				.writer        = ip6_addr_gen_mode_writer,
+				.writer_persist_default = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ADDRESSES,
+				.parser_no_check_key = TRUE,
+				.parser        = ip_address_or_route_parser,
+				.writer        = addr_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_DNS,
+				.parser_no_check_key = TRUE,
+				.parser        = ip_dns_parser,
+				.writer        = dns_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_GATEWAY,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTES,
+				.parser_no_check_key = TRUE,
+				.parser        = ip_address_or_route_parser,
+				.writer        = route_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES,
+				.parser_no_check_key = TRUE,
+				.parser_full   = ip_routing_rule_parser_full,
+				.writer_full   = ip_routing_rule_writer_full,
+				.has_parser_full = TRUE,
+				.has_writer_full = TRUE,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_SERIAL,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_SERIAL_PARITY,
+				.parser        = parity_parser,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_SRIOV,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_SRIOV_VFS,
+				.parser_no_check_key = TRUE,
+				.parser        = sriov_vfs_parser,
+				.writer        = sriov_vfs_writer,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TC_CONFIG,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_TC_CONFIG_QDISCS,
+				.parser_no_check_key = TRUE,
+				.parser        = qdisc_parser,
+				.writer        = qdisc_writer,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TC_CONFIG_TFILTERS,
+				.parser_no_check_key = TRUE,
+				.parser        = tfilter_parser,
+				.writer        = tfilter_writer,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TEAM,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_CONFIG,
+				.parser        = team_config_parser,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_LINK_WATCHERS,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_MCAST_REJOIN_COUNT,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_NOTIFY_PEERS_COUNT,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_ACTIVE,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_FAST_RATE,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_HWADDR_POLICY,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_MIN_PORTS,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_SYS_PRIO,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_BALANCER,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_HASH,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TEAM_PORT,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_CONFIG,
+				.parser        = team_config_parser,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LACP_KEY,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LACP_PRIO,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LINK_WATCHERS,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_PRIO,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_QUEUE_ID,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_STICKY,
+				.parser_skip   = TRUE,
+				.writer_skip   = TRUE,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_USER,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_USER_DATA,
+				.parser_no_check_key = TRUE,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_VLAN,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_VLAN_FLAGS,
+				.writer_persist_default = TRUE,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_VPN,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_VPN_DATA,
+				.parser_no_check_key = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_VPN_PERSISTENT,
+				.parser_no_check_key = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_VPN_SECRETS,
+				.parser_no_check_key = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_VPN_SERVICE_TYPE,
+				.parser_no_check_key = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_VPN_TIMEOUT,
+				.parser_no_check_key = TRUE,
+			),
+			PARSE_INFO_PROPERTY (NM_SETTING_VPN_USER_NAME,
+				.parser_no_check_key = TRUE,
+			),
+		),
+	),
+	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIMAX,
+		PARSE_INFO_PROPERTIES (
+			PARSE_INFO_PROPERTY (NM_SETTING_WIMAX_MAC_ADDRESS,
+				.parser        = mac_address_parser_ETHER,
+			),
+		),
+	),
+};
+
+static void
+_parse_info_find (NMSetting *setting,
+                  const char *property_name,
+                  const NMMetaSettingInfo **out_setting_info,
+                  const ParseInfoSetting **out_parse_info_setting,
+                  const ParseInfoProperty **out_parse_info_property)
+{
+	const NMMetaSettingInfo *setting_info;
+	const ParseInfoSetting *pis;
+	const ParseInfoProperty *pip;
+
+#if NM_MORE_ASSERTS > 10
+	{
+		guint i, j;
+		static int asserted = FALSE;
+
+		if (!asserted) {
+			for (i = 0; i < G_N_ELEMENTS (parse_infos); i++) {
+				pis = parse_infos[i];
+
+				if (!pis)
+					continue;
+				if (!pis->properties)
+					continue;
+
+				g_assert (pis->properties[0]);
+				for (j = 0; pis->properties[j]; j++) {
+					const ParseInfoProperty *pip0;
+					const ParseInfoProperty *pipj = pis->properties[j];
+
+					g_assert (pipj->property_name);
+					if (   j > 0
+					    && (pip0 = pis->properties[j - 1])
+					    && strcmp (pip0->property_name, pipj->property_name) >= 0) {
+						g_error ("Wrong order at index #%d.%d: \"%s.%s\" before \"%s.%s\"",
+						         i, j - 1,
+						         nm_meta_setting_infos[i].setting_name, pip0->property_name,
+						         nm_meta_setting_infos[i].setting_name, pipj->property_name);
+					}
+				}
+			}
+			asserted = TRUE;
+		}
+	}
+#endif
+
+	if (   !NM_IS_SETTING (setting)
+	    || !(setting_info = NM_SETTING_GET_CLASS (setting)->setting_info)) {
+		/* handle invalid setting objects gracefully. */
+		NM_SET_OUT (out_setting_info, NULL);
+		NM_SET_OUT (out_parse_info_setting, NULL);
+		NM_SET_OUT (out_parse_info_property, NULL);
+		return;
+	}
+
+	nm_assert (setting_info->setting_name);
+	nm_assert (_NM_INT_NOT_NEGATIVE (setting_info->meta_type));
+	nm_assert (setting_info->meta_type < G_N_ELEMENTS (parse_infos));
+
+	pis = parse_infos[setting_info->meta_type];
+
+	pip = NULL;
+	if (   pis
+	    && property_name) {
+		gssize idx;
+
+		G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (ParseInfoProperty, property_name) == 0);
+
+		idx = nm_utils_ptrarray_find_binary_search ((gconstpointer *) pis->properties,
+		                                            NM_PTRARRAY_LEN (pis->properties),
+		                                            &property_name,
+		                                            nm_strcmp_p_with_data,
+		                                            NULL,
+		                                            NULL,
+		                                            NULL);
+		if (idx >= 0)
+			pip = pis->properties[idx];
+	}
+
+	NM_SET_OUT (out_setting_info, setting_info);
+	NM_SET_OUT (out_parse_info_setting, pis);
+	NM_SET_OUT (out_parse_info_property, pip);
+}
+
+/*****************************************************************************/
+
+static void
+read_one_setting_value (KeyfileReaderInfo *info,
+                        NMSetting *setting,
+                        const NMSettInfoProperty *property_info)
+{
+	GKeyFile *keyfile = info->keyfile;
+	gs_free_error GError *err = NULL;
+	const NMMetaSettingInfo *setting_info;
+	const ParseInfoProperty *pip;
+	gs_free char *tmp_str = NULL;
+	const char *key;
+	GType type;
+	guint64 u64;
+	gint64 i64;
+
+	nm_assert (!info->error);
+	nm_assert (   !property_info->param_spec
+	           || nm_streq (property_info->param_spec->name, property_info->name));
+
+	key = property_info->name;
+
+	_parse_info_find (setting, key, &setting_info, NULL, &pip);
+
+	nm_assert (setting_info);
+
+	if (!pip) {
+		if (nm_streq (key, NM_SETTING_NAME))
+			return;
+		if (!property_info->param_spec)
+			return;
+		if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE)
+			return;
+	} else {
+		if (pip->parser_skip)
+			return;
+		if (pip->has_parser_full) {
+			pip->parser_full (info, setting_info, property_info, pip, setting);
+			return;
+		}
+	}
+
+	nm_assert (property_info->param_spec);
+	nm_assert ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) == G_PARAM_WRITABLE);
+
+	/* Check for the exact key in the GKeyFile if required.  Most setting
+	 * properties map 1:1 to a key in the GKeyFile, but for those properties
+	 * like IP addresses and routes where more than one value is actually
+	 * encoded by the setting property, this won't be true.
+	 */
+	if (   (!pip || !pip->parser_no_check_key)
+	    && !nm_keyfile_plugin_kf_has_key (keyfile, setting_info->setting_name, key, &err)) {
+		/* Key doesn't exist or an error occurred, thus nothing to do. */
+		if (err) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("error loading setting value: %s"),
+			                  err->message))
+				return;
+		}
+		return;
+	}
+
+	if (   pip
+	    && pip->parser) {
+		pip->parser (info, setting, key);
+		return;
+	}
+
+	type = G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec);
+
+	if (type == G_TYPE_STRING) {
+		gs_free char *str_val = NULL;
+
+		str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_info->setting_name, key, &err);
+		if (!err)
+			nm_g_object_set_property_string_take (G_OBJECT (setting), key, g_steal_pointer (&str_val), &err);
+	} else if (type == G_TYPE_UINT) {
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
+		if (!err) {
+			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64);
+			if (   u64 == G_MAXUINT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_uint (G_OBJECT (setting), key, u64, &err);
+		}
+	} else if (type == G_TYPE_INT) {
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
+		if (!err) {
+			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64);
+			if (   i64 == G_MININT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_int (G_OBJECT (setting), key, i64, &err);
+		}
+	} else if (type == G_TYPE_BOOLEAN) {
+		gboolean bool_val;
+
+		bool_val = nm_keyfile_plugin_kf_get_boolean (keyfile, setting_info->setting_name, key, &err);
+		if (!err)
+			nm_g_object_set_property_boolean (G_OBJECT (setting), key, bool_val, &err);
+	} else if (type == G_TYPE_CHAR) {
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
+		if (!err) {
+			/* As documented by glib, G_TYPE_CHAR is really a (signed!) gint8. */
+			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT8, G_MAXINT8, G_MININT64);
+			if (   i64 == G_MININT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_char (G_OBJECT (setting), key, i64, &err);
+		}
+	} else if (type == G_TYPE_UINT64) {
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
+		if (!err) {
+			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT64, G_MAXUINT64);
+			if (   u64 == G_MAXUINT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_uint64 (G_OBJECT (setting), key, u64, &err);
+		}
+	} else if (type == G_TYPE_INT64) {
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
+		if (!err) {
+			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT64, G_MAXINT64, G_MAXINT64);
+			if (   i64 == G_MAXINT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_int64 (G_OBJECT (setting), key, i64, &err);
+		}
+	} else if (type == G_TYPE_BYTES) {
+		gs_free int *tmp = NULL;
+		GByteArray *array;
+		GBytes *bytes;
+		gsize length;
+		int i;
+		gboolean already_warned = FALSE;
+
+		tmp = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_info->setting_name, key, &length, NULL);
+
+		array = g_byte_array_sized_new (length);
+		for (i = 0; i < length; i++) {
+			const int val = tmp[i];
+			unsigned char v = (unsigned char) (val & 0xFF);
+
+			if (val < 0 || val > 255) {
+				if (   !already_warned
+				    && !handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+				                     _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
+				                     val)) {
+					g_byte_array_unref (array);
+					return;
+				}
+				already_warned = TRUE;
+			} else
+				g_byte_array_append (array, (const unsigned char *) &v, sizeof (v));
+		}
+
+		bytes = g_byte_array_free_to_bytes (array);
+		g_object_set (setting, key, bytes, NULL);
+		g_bytes_unref (bytes);
+	} else if (type == G_TYPE_STRV) {
+		gs_strfreev char **sa = NULL;
+		gsize length;
+
+		sa = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_info->setting_name, key, &length, NULL);
+		g_object_set (setting, key, sa, NULL);
+	} else if (type == G_TYPE_HASH_TABLE) {
+		read_hash_of_string (keyfile, setting, key);
+	} else if (type == G_TYPE_ARRAY) {
+		read_array_of_uint (keyfile, setting, key);
+	} else if (G_TYPE_IS_FLAGS (type)) {
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
+		if (!err) {
+			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64);
+			if (   u64 == G_MAXUINT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_flags (G_OBJECT (setting), key, type, u64, &err);
+		}
+	} else if (G_TYPE_IS_ENUM (type)) {
+		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
+		if (!err) {
+			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MAXINT64);
+			if (   i64 == G_MAXINT64
+			    && errno != 0) {
+				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
+				                     _("value cannot be interpreted as integer"));
+			} else
+				nm_g_object_set_property_enum (G_OBJECT (setting), key, type, i64, &err);
+		}
+	} else
+		g_return_if_reached ();
+
+	if (err) {
+		if (   err->domain == G_KEY_FILE_ERROR
+		    && NM_IN_SET (err->code, G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
+		                             G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
+			/* ignore such errors. The key is not present. */
+		} else {
+			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid setting: %s"), err->message);
+		}
+	}
+}
+
+static void
+_read_setting (KeyfileReaderInfo *info)
+{
+	const NMSettInfoSetting *sett_info;
+	gs_unref_object NMSetting *setting = NULL;
+	const char *alias;
+	GType type;
+	guint i;
+
+	alias = nm_keyfile_plugin_get_setting_name_for_alias (info->group);
+	if (!alias)
+		alias = info->group;
+
+	type = nm_setting_lookup_type (alias);
+	if (!type) {
+		handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("invalid setting name '%s'"), info->group);
+		return;
+	}
+
+	setting = g_object_new (type, NULL);
+
+	info->setting = setting;
+
+	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
+
+	if (sett_info->detail.gendata_info) {
+		gs_free char **keys = NULL;
+		gsize k, n_keys;
+
+		keys = g_key_file_get_keys (info->keyfile, info->group, &n_keys, NULL);
+		if (!keys)
+			n_keys = 0;
+		if (n_keys > 0) {
+			GHashTable *h = _nm_setting_gendata_hash (setting, TRUE);
+
+			nm_utils_strv_sort (keys, n_keys);
+			for (k = 0; k < n_keys; k++) {
+				gs_free char *key = keys[k];
+				gs_free_error GError *local = NULL;
+				const GVariantType *variant_type;
+				GVariant *variant;
+
+				/* a GKeyFile can return duplicate keys, there is just no API to make sense
+				 * of them. Skip them. */
+				if (   k + 1 < n_keys
+				    && nm_streq (key, keys[k + 1]))
+					continue;
+
+				/* currently, the API is very simple. The setting class just returns
+				 * the desired variant type, and keyfile reader will try to parse
+				 * it accordingly. Note, that this does currently not allow, that
+				 * a particular key can contain different variant types, nor is it
+				 * very flexible in general.
+				 *
+				 * We add flexibility when we need it. Keep it simple for now. */
+				variant_type = sett_info->detail.gendata_info->get_variant_type (sett_info,
+				                                                                 key,
+				                                                                 &local);
+				if (!variant_type) {
+					if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+					                  _("invalid key '%s.%s'"),
+					                  info->group, key))
+						break;
+					continue;
+				}
+
+				if (g_variant_type_equal (variant_type, G_VARIANT_TYPE_BOOLEAN)) {
+					gboolean v;
+
+					v = g_key_file_get_boolean (info->keyfile,
+					                            info->group,
+					                            key,
+					                            &local);
+					if (local) {
+						if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+						                  _("key '%s.%s' is not boolean"),
+						                  info->group, key))
+							break;
+						continue;
+					}
+					variant = g_variant_new_boolean (v);
+				} else {
+					nm_assert_not_reached ();
+					continue;
+				}
+
+				g_hash_table_insert (h,
+				                     g_steal_pointer (&key),
+				                     g_variant_take_ref (variant));
+			}
+			for (; k < n_keys; k++)
+				g_free (keys[k]);
+		}
+	}
+
+	for (i = 0; i < sett_info->property_infos_len; i++) {
+		read_one_setting_value (info,
+		                        setting,
+		                        &sett_info->property_infos[i]);
+		if (info->error)
+			goto out;
+	}
+
+out:
+	info->setting = NULL;
+	if (!info->error)
+		nm_connection_add_setting (info->connection, g_steal_pointer (&setting));
+}
+
+static void
+_read_setting_wireguard_peer (KeyfileReaderInfo *info)
+{
+	gs_unref_object NMSettingWireGuard *s_wg_new = NULL;
+	nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+	gs_free_error GError *error = NULL;
+	NMSettingWireGuard *s_wg;
+	gs_free char *str = NULL;
+	const char *cstr = NULL;
+	const char *key;
+	gint64 i64;
+	gs_strfreev char **sa = NULL;
+	gsize n_sa;
+
+	peer = nm_wireguard_peer_new ();
+
+	nm_assert (g_str_has_prefix (info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER));
+	cstr = &info->group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)];
+	if (   !nm_utils_base64secret_normalize (cstr, NM_WIREGUARD_PUBLIC_KEY_LEN, &str)
+	    || !nm_streq0 (str, cstr)) {
+		/* the group name must be identical to the normalized(!) key, so that it
+		 * is uniquely identified. */
+		handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("invalid peer public key in section '%s'"),
+		             info->group);
+		return;
+	}
+	nm_wireguard_peer_set_public_key (peer, cstr, TRUE);
+	nm_clear_g_free (&str);
+
+	key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY;
+	str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL);
+	if (str) {
+		if (!nm_wireguard_peer_set_preshared_key (peer, str, FALSE)) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' is not a valid 256 bit key in base64 encoding"),
+			                  info->group, key))
+				return;
+		}
+		nm_clear_g_free (&str);
+	}
+
+	key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS;
+	i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, NM_SETTING_SECRET_FLAG_ALL, -1, NULL);
+	if (errno != ENODATA) {
+		if (   i64 == -1
+		    || !_nm_setting_secret_flags_valid (i64)) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' is not a valid secret flag"),
+			                  info->group, key))
+				return;
+		} else
+			nm_wireguard_peer_set_preshared_key_flags (peer, i64);
+	}
+
+	key = NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE;
+	i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, G_MAXUINT32, -1, NULL);
+	if (errno != ENODATA) {
+		if (i64 == -1) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' is not a integer in range 0 to 2^32"),
+			                  info->group, key))
+				return;
+		} else
+			nm_wireguard_peer_set_persistent_keepalive (peer, i64);
+	}
+
+	key = NM_WIREGUARD_PEER_ATTR_ENDPOINT;
+	str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL);
+	if (str && str[0]) {
+		if (!nm_wireguard_peer_set_endpoint (peer, str, FALSE)) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' is not a valid endpoint"),
+			                  info->group, key))
+				return;
+		}
+	}
+	nm_clear_g_free (&str);
+
+	key = NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS;
+	sa = nm_keyfile_plugin_kf_get_string_list (info->keyfile, info->group, key, &n_sa, NULL);
+	if (n_sa > 0) {
+		gboolean has_error = FALSE;
+		gsize i;
+
+		for (i = 0; i < n_sa; i++) {
+			if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC, sa[i], NULL, NULL, NULL)) {
+				has_error = TRUE;
+				continue;
+			}
+			nm_wireguard_peer_append_allowed_ip (peer, sa[i], TRUE);
+		}
+		if (has_error) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' has invalid allowed-ips"),
+			                  info->group, key))
+				return;
+		}
+	}
+	nm_clear_pointer (&sa, g_strfreev);
+
+	if (info->error)
+		return;
+
+	if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, &error)) {
+		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		             _("peer '%s' is invalid: %s"),
+		             info->group, error->message);
+		return;
+	}
+
+	s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (info->connection, NM_TYPE_SETTING_WIREGUARD));
+	if (!s_wg) {
+		s_wg_new = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ());
+		s_wg = s_wg_new;
+	}
+
+	nm_setting_wireguard_append_peer (s_wg, peer);
+
+	if (s_wg_new) {
+		nm_connection_add_setting (info->connection,
+		                           NM_SETTING (g_steal_pointer (&s_wg_new)));
+	}
+}
+
+static void
+_read_setting_vpn_secrets (KeyfileReaderInfo *info)
+{
+	gs_strfreev char **keys = NULL;
+	gsize i, n_keys;
+	NMSettingVpn *s_vpn;
+
+	s_vpn = nm_connection_get_setting_vpn (info->connection);
+	if (!s_vpn) {
+		/* if we don't also have a [vpn] section (which must be parsed earlier),
+		 * we don't do anything. */
+		nm_assert (!g_key_file_has_group (info->keyfile, "vpn"));
+		return;
+	}
+
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, &n_keys, NULL);
+	for (i = 0; i < n_keys; i++) {
+		gs_free char *secret = NULL;
+
+		secret = nm_keyfile_plugin_kf_get_string (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, keys[i], NULL);
+		if (secret)
+			nm_setting_vpn_add_secret (s_vpn, keys[i], secret);
+	}
+}
+
+gboolean
+nm_keyfile_read_ensure_id (NMConnection *connection,
+                           const char *fallback_id)
+{
+	NMSettingConnection *s_con;
+
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+	g_return_val_if_fail (fallback_id, FALSE);
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_return_val_if_fail (NM_IS_SETTING_CONNECTION (s_con), FALSE);
+
+	if (nm_setting_connection_get_id (s_con))
+		return FALSE;
+
+	g_object_set (s_con, NM_SETTING_CONNECTION_ID, fallback_id, NULL);
+	return TRUE;
+}
+
+gboolean
+nm_keyfile_read_ensure_uuid (NMConnection *connection,
+                             const char *fallback_uuid_seed)
+{
+	NMSettingConnection *s_con;
+	gs_free char *hashed_uuid = NULL;
+
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+	g_return_val_if_fail (fallback_uuid_seed, FALSE);
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_return_val_if_fail (NM_IS_SETTING_CONNECTION (s_con), FALSE);
+
+	if (nm_setting_connection_get_uuid (s_con))
+		return FALSE;
+
+	hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", fallback_uuid_seed, NULL);
+	g_object_set (s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL);
+	return TRUE;
+}
+
+/**
+ * nm_keyfile_read:
+ * @keyfile: the keyfile from which to create the connection
+ * @base_dir: when reading certificates from files with relative name,
+ *   the relative path is made absolute using @base_dir. This must
+ *   be an absolute path.
+ * @handler: read handler
+ * @user_data: user data for read handler
+ * @error: error
+ *
+ * Tries to create a NMConnection from a keyfile. The resulting keyfile is
+ * not normalized and might not even verify.
+ *
+ * Returns: (transfer full): on success, returns the created connection.
+ */
+NMConnection *
+nm_keyfile_read (GKeyFile *keyfile,
+                 const char *base_dir,
+                 NMKeyfileReadHandler handler,
+                 void *user_data,
+                 GError **error)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	NMSettingConnection *s_con;
+	gs_strfreev char **groups = NULL;
+	gsize n_groups;
+	gsize i;
+	gboolean vpn_secrets = FALSE;
+	KeyfileReaderInfo info;
+
+	g_return_val_if_fail (keyfile, NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+	g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL);
+
+	connection = nm_simple_connection_new ();
+
+	info = (KeyfileReaderInfo) {
+		.connection = connection,
+		.keyfile    = keyfile,
+		.base_dir   = base_dir,
+		.handler    = handler,
+		.user_data  = user_data,
+	};
+
+	groups = g_key_file_get_groups (keyfile, &n_groups);
+	if (!groups)
+		n_groups = 0;
+
+	for (i = 0; i < n_groups; i++) {
+
+		info.group = groups[i];
+
+		if (nm_streq (groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) {
+			/* Only read out secrets when needed */
+			vpn_secrets = TRUE;
+		} else if (NM_STR_HAS_PREFIX (groups[i], NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER))
+			_read_setting_wireguard_peer (&info);
+		else if (nm_streq (groups[i], NM_KEYFILE_GROUP_NMMETA)) {
+			/* pass */
+		} else
+			_read_setting (&info);
+
+		info.group = NULL;
+
+		if (info.error)
+			goto out_with_info_error;
+	}
+
+	s_con = nm_connection_get_setting_connection (connection);
+	if (!s_con) {
+		s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
+		nm_connection_add_setting (connection, NM_SETTING (s_con));
+	}
+
+	/* Make sure that we have 'interface-name' even if it was specified in the
+	 * "wrong" (ie, deprecated) group.
+	 */
+	if (   !nm_setting_connection_get_interface_name (s_con)
+	    && nm_setting_connection_get_connection_type (s_con)) {
+		gs_free char *interface_name = NULL;
+
+		interface_name = g_key_file_get_string (keyfile,
+		                                        nm_setting_connection_get_connection_type (s_con),
+		                                        "interface-name",
+		                                        NULL);
+		if (interface_name)
+			g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name, NULL);
+	}
+
+	if (vpn_secrets) {
+		info.group = NM_KEYFILE_GROUP_VPN_SECRETS;
+		_read_setting_vpn_secrets (&info);
+		info.group = NULL;;
+		if (info.error)
+			goto out_with_info_error;
+	}
+
+	return g_steal_pointer (&connection);
+
+out_with_info_error:
+	g_propagate_error (error, info.error);
+	return NULL;
+}
+
+/*****************************************************************************/
+
+static void
+write_setting_value (KeyfileWriterInfo *info,
+                     NMSetting *setting,
+                     const NMSettInfoProperty *property_info)
+{
+	const NMMetaSettingInfo *setting_info;
+	const ParseInfoProperty *pip;
+	const char *key;
+	char numstr[64];
+	GValue value;
+	GType type;
+
+	nm_assert (!info->error);
+	nm_assert (   !property_info->param_spec
+	           || nm_streq (property_info->param_spec->name, property_info->name));
+
+	key = property_info->name;
+
+	_parse_info_find (setting, key, &setting_info, NULL, &pip);
+
+	if (!pip) {
+		if (!setting_info) {
+			/* the setting type is unknown. That is highly unexpected
+			 * (and as this is currently only called from NetworkManager
+			 * daemon, not possible).
+			 *
+			 * Still, handle it gracefully, because later keyfile writer will become
+			 * public API of libnm, where @setting is (untrusted) user input.
+			 *
+			 * Gracefully here just means: ignore the setting. */
+			return;
+		}
+		if (!property_info->param_spec)
+			return;
+		if (nm_streq (key, NM_SETTING_NAME))
+			return;
+	} else {
+		if (pip->has_writer_full) {
+			pip->writer_full (info, setting_info, property_info, pip, setting);
+			return;
+		}
+		if (pip->writer_skip)
+			return;
+	}
+
+	nm_assert (property_info->param_spec);
+
+	/* Don't write secrets that are owned by user secret agents or aren't
+	 * supposed to be saved.  VPN secrets are handled specially though since
+	 * the secret flags there are in a third-level hash in the 'secrets'
+	 * property.
+	 */
+	if (   (property_info->param_spec->flags & NM_SETTING_PARAM_SECRET)
+	    && !NM_IS_SETTING_VPN (setting)) {
+		NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+
+		if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL))
+			g_return_if_reached ();
+		if (!_secret_flags_persist_secret (secret_flags))
+			return;
+	}
+
+	value = (GValue) { 0 };
+
+	g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec));
+	g_object_get_property (G_OBJECT (setting), property_info->param_spec->name, &value);
+
+	if (   (!pip || !pip->writer_persist_default)
+	    && g_param_value_defaults (property_info->param_spec, &value)) {
+		nm_assert (!g_key_file_has_key (info->keyfile, setting_info->setting_name, key, NULL));
+		goto out_unset_value;
+	}
+
+	if (   pip
+	    && pip->writer) {
+		pip->writer (info, setting, key, &value);
+		goto out_unset_value;
+	}
+
+	type = G_VALUE_TYPE (&value);
+	if (type == G_TYPE_STRING) {
+		const char *str;
+
+		str = g_value_get_string (&value);
+		if (str)
+			nm_keyfile_plugin_kf_set_string (info->keyfile, setting_info->setting_name, key, str);
+	} else if (type == G_TYPE_UINT) {
+		nm_sprintf_buf (numstr, "%u", g_value_get_uint (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
+	} else if (type == G_TYPE_INT) {
+		nm_sprintf_buf (numstr, "%d", g_value_get_int (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
+	} else if (type == G_TYPE_UINT64) {
+		nm_sprintf_buf (numstr, "%" G_GUINT64_FORMAT, g_value_get_uint64 (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
+	} else if (type == G_TYPE_INT64) {
+		nm_sprintf_buf (numstr, "%" G_GINT64_FORMAT, g_value_get_int64 (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
+	} else if (type == G_TYPE_BOOLEAN) {
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key,
+		                                  g_value_get_boolean (&value)
+		                                ? "true"
+		                                : "false");
+	} else if (type == G_TYPE_CHAR) {
+		nm_sprintf_buf (numstr, "%d", (int) g_value_get_schar (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
+	} else if (type == G_TYPE_BYTES) {
+		GBytes *bytes;
+		const guint8 *data;
+		gsize len = 0;
+
+		bytes = g_value_get_boxed (&value);
+		data = bytes ? g_bytes_get_data (bytes, &len) : NULL;
+
+		if (data != NULL && len > 0)
+			nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_info->setting_name, key, data, len);
+	} else if (type == G_TYPE_STRV) {
+		char **array;
+
+		array = (char **) g_value_get_boxed (&value);
+		nm_keyfile_plugin_kf_set_string_list (info->keyfile, setting_info->setting_name, key, (const char **const) array, g_strv_length (array));
+	} else if (type == G_TYPE_HASH_TABLE) {
+		write_hash_of_string (info->keyfile, setting, key, &value);
+	} else if (type == G_TYPE_ARRAY) {
+		write_array_of_uint (info->keyfile, setting, key, &value);
+	} else if (G_VALUE_HOLDS_FLAGS (&value)) {
+		nm_sprintf_buf (numstr, "%u", g_value_get_flags (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
+	} else if (G_VALUE_HOLDS_ENUM (&value)) {
+		nm_sprintf_buf (numstr, "%d", g_value_get_enum (&value));
+		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
+	} else
+		g_return_if_reached ();
+
+out_unset_value:
+	g_value_unset (&value);
+}
+
+static void
+_write_setting_wireguard (NMSetting *setting, KeyfileWriterInfo *info)
+{
+	NMSettingWireGuard *s_wg;
+	guint i_peer, n_peers;
+
+	s_wg = NM_SETTING_WIREGUARD (setting);
+
+	n_peers = nm_setting_wireguard_get_peers_len (s_wg);
+	for (i_peer = 0; i_peer < n_peers; i_peer++) {
+		NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i_peer);
+		const char *public_key;
+		char group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER) + 200];
+		NMSettingSecretFlags secret_flags;
+		gboolean any_key = FALSE;
+		guint i_aip, n_aip;
+		const char *cstr;
+		guint32 u32;
+
+		public_key = nm_wireguard_peer_get_public_key (peer);
+		if (   !public_key
+		    || !public_key[0]
+		    || !NM_STRCHAR_ALL (public_key, ch, nm_sd_utils_unbase64char (ch, TRUE) >= 0)) {
+			/* invalid peer. Skip it */
+			continue;
+		}
+
+		if (g_snprintf (group,
+		                sizeof (group),
+		                "%s%s",
+		                NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER,
+		                nm_wireguard_peer_get_public_key (peer)) >= sizeof (group)) {
+			/* Too long. Not a valid public key. Skip the peer. */
+			continue;
+		}
+
+		cstr = nm_wireguard_peer_get_endpoint (peer);
+		if (cstr) {
+			g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, cstr);
+			any_key = TRUE;
+		}
+
+		secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer);
+		if (_secret_flags_persist_secret (secret_flags)) {
+			cstr = nm_wireguard_peer_get_preshared_key (peer);
+			if (cstr) {
+				g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, cstr);
+				any_key = TRUE;
+			}
+		}
+
+		/* usually, we don't persist the secret-flags 0 (because they are the default).
+		 * For WireGuard peers, the default secret-flags for preshared-key are 4 (not-required).
+		 * So, in this case behave differently: a missing preshared-key-flag setting means
+		 * "not-required". */
+		if (secret_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED) {
+			g_key_file_set_int64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, secret_flags);
+			any_key = TRUE;
+		}
+
+		u32 = nm_wireguard_peer_get_persistent_keepalive (peer);
+		if (u32) {
+			g_key_file_set_uint64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, u32);
+			any_key = TRUE;
+		}
+
+		n_aip = nm_wireguard_peer_get_allowed_ips_len (peer);
+		if (n_aip > 0) {
+			gs_free const char **strv = NULL;
+
+			strv = g_new (const char *, ((gsize) n_aip) + 1);
+			for (i_aip = 0; i_aip < n_aip; i_aip++)
+				strv[i_aip] = nm_wireguard_peer_get_allowed_ip (peer, i_aip, NULL);
+			strv[n_aip] = NULL;
+			g_key_file_set_string_list (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS,
+			                            strv, n_aip);
+			any_key = TRUE;
+		}
+
+		if (!any_key) {
+			/* we cannot omit all keys. At an empty endpoint. */
+			g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, "");
+		}
+	}
+}
+
+GKeyFile *
+nm_keyfile_write (NMConnection *connection,
+                  NMKeyfileWriteHandler handler,
+                  void *user_data,
+                  GError **error)
+{
+	gs_unref_keyfile GKeyFile *keyfile = NULL;
+	KeyfileWriterInfo info;
+	gs_free NMSetting **settings = NULL;
+	guint i, j, n_settings = 0;
+
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	if (!nm_connection_verify (connection, error))
+		return NULL;
+
+	keyfile = g_key_file_new ();
+
+	info = (KeyfileWriterInfo) {
+		.connection = connection,
+		.keyfile    = keyfile,
+		.error      = NULL,
+		.handler    = handler,
+		.user_data  = user_data,
+	};
+
+	settings = nm_connection_get_settings (connection, &n_settings);
+	for (i = 0; i < n_settings; i++) {
+		const NMSettInfoSetting *sett_info;
+		NMSetting *setting = settings[i];
+		const char *setting_name;
+		const char *setting_alias;
+
+		sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
+
+		setting_name = sett_info->setting_class->setting_info->setting_name;
+
+		if (sett_info->detail.gendata_info) {
+			guint k, n_keys;
+			const char *const*keys;
+
+			nm_assert (!nm_keyfile_plugin_get_alias_for_setting_name (sett_info->setting_class->setting_info->setting_name));
+
+			n_keys = _nm_setting_gendata_get_all (setting, &keys, NULL);
+
+			if (n_keys > 0) {
+				GHashTable *h = _nm_setting_gendata_hash (setting, FALSE);
+
+				for (k = 0; k < n_keys; k++) {
+					const char *key = keys[k];
+					GVariant *v;
+
+					v = g_hash_table_lookup (h, key);
+
+					if (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) {
+						g_key_file_set_boolean (info.keyfile,
+						                        setting_name,
+						                        key,
+						                        g_variant_get_boolean (v));
+					} else {
+						/* BUG: The variant type is not implemented. Since the connection
+						 * verifies, this can only mean we either wrongly didn't reject
+						 * the connection as invalid, or we didn't properly implement the
+						 * variant type. */
+						nm_assert_not_reached ();
+						continue;
+					}
+				}
+			}
+		}
+
+		for (j = 0; j < sett_info->property_infos_len; j++) {
+			const NMSettInfoProperty *property_info = _nm_sett_info_property_info_get_sorted (sett_info, j);
+
+			write_setting_value (&info, setting, property_info);
+			if (info.error)
+				goto out_with_info_error;
+		}
+
+		setting_alias = nm_keyfile_plugin_get_alias_for_setting_name (setting_name);
+		if (   (   setting_alias
+		        && g_key_file_has_group (info.keyfile, setting_alias))
+		    || g_key_file_has_group (info.keyfile, setting_name)) {
+			/* we have a section for the setting. Nothing to do. */
+		} else {
+			/* ensure the group is present. There is no API for that, so add and remove
+			 * a dummy key. */
+			g_key_file_set_value  (info.keyfile, setting_alias ?: setting_name, ".X", "1");
+			g_key_file_remove_key (info.keyfile, setting_alias ?: setting_name, ".X", NULL);
+		}
+
+		if (NM_IS_SETTING_WIREGUARD (setting)) {
+			_write_setting_wireguard (setting, &info);
+			if (info.error)
+				goto out_with_info_error;
+		}
+
+		nm_assert (!info.error);
+	}
+
+	nm_assert (!info.error);
+
+	return g_steal_pointer (&keyfile);
+
+out_with_info_error:
+	g_propagate_error (error, info.error);
+	return NULL;
+}
+
+/*****************************************************************************/
+
+static const char temp_letters[] =
+"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+/*
+ * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()).
+ */
+static gboolean
+check_mkstemp_suffix (const char *path)
+{
+	const char *ptr;
+
+	nm_assert (path);
+
+	/* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */
+	ptr = strrchr (path, '.');
+	if (   ptr
+	    && strspn (&ptr[1], temp_letters) == 6
+	    && ptr[7] == '\0')
+		return TRUE;
+	return FALSE;
+}
+
+static gboolean
+_check_suffix_impl (const char *base, const char *tag, gsize tag_len)
+{
+	gsize len;
+
+	nm_assert (base);
+	nm_assert (tag);
+	nm_assert (strlen (tag) == tag_len);
+
+	len = strlen (base);
+	if (   len > tag_len
+	    && !g_ascii_strcasecmp (base + len - tag_len, tag))
+		return TRUE;
+	return FALSE;
+}
+#define check_suffix(base, tag) _check_suffix_impl ((base), ""tag"", NM_STRLEN (tag))
+
+#define SWP_TAG ".swp"
+#define SWPX_TAG ".swpx"
+#define PEM_TAG ".pem"
+#define DER_TAG ".der"
+
+gboolean
+nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension)
+{
+	const char *base;
+	gsize l;
+
+	/* ignore_filename() must mirror nm_keyfile_utils_create_filename() */
+
+	g_return_val_if_fail (filename, TRUE);
+
+	base = strrchr (filename, '/');
+	if (base)
+		base++;
+	else
+		base = filename;
+
+	if (!base[0]) {
+		/* this check above with strrchr() also rejects "/some/path/with/trailing/slash/",
+		 * but that is fine, because such a path would name a directory, and we are not
+		 * interested in directories. */
+		return TRUE;
+	}
+
+	if (base[0] == '.') {
+		/* don't allow hidden files */
+		return TRUE;
+	}
+
+	l = strlen (base);
+
+	if (require_extension) {
+		if (   l <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)
+		    || !NM_STR_HAS_SUFFIX (base, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION))
+			return TRUE;
+		return FALSE;
+	}
+
+	/* Ignore backup files */
+	if (base[l - 1] == '~')
+		return TRUE;
+
+	/* Ignore temporary files
+	 *
+	 * This check is also important to ignore .nmload files (see
+	 * %NM_KEYFILE_PATH_SUFFIX_NMMETA). */
+	if (check_mkstemp_suffix (base))
+		return TRUE;
+
+	/* Ignore 802.1x certificates and keys */
+	if (   check_suffix (base, PEM_TAG)
+	    || check_suffix (base, DER_TAG))
+		return TRUE;
+
+	return FALSE;
+}
+
+char *
+nm_keyfile_utils_create_filename (const char *name,
+                                  gboolean with_extension)
+{
+	GString *str;
+	const char *f = name;
+	/* keyfile used to escape with '*', do not change that behavior.
+	 *
+	 * But for newly added escapings, use '_' instead.
+	 * Also, @with_extension is new-style. */
+	const char ESCAPE_CHAR = with_extension ? '_' : '*';
+	const char ESCAPE_CHAR2 = '_';
+
+	g_return_val_if_fail (name && name[0], NULL);
+
+	str = g_string_sized_new (60);
+
+	/* Convert '/' to ESCAPE_CHAR */
+	for (f = name; f[0]; f++) {
+		if (f[0] == '/')
+			g_string_append_c (str, ESCAPE_CHAR);
+		else
+			g_string_append_c (str, f[0]);
+	}
+
+	/* nm_keyfile_utils_create_filename() must avoid anything that ignore_filename() would reject.
+	 * We can escape here more aggressivly then what we would read back. */
+	if (str->str[0] == '.')
+		str->str[0] = ESCAPE_CHAR2;
+	if (str->str[str->len - 1] == '~')
+		str->str[str->len - 1] = ESCAPE_CHAR2;
+	if (   check_mkstemp_suffix (str->str)
+	    || check_suffix (str->str, PEM_TAG)
+	    || check_suffix (str->str, DER_TAG))
+		g_string_append_c (str, ESCAPE_CHAR2);
+
+	if (with_extension)
+		g_string_append (str, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
+
+	/* nm_keyfile_utils_create_filename() must mirror ignore_filename() */
+	nm_assert (!strchr (str->str, '/'));
+	nm_assert (!nm_keyfile_utils_ignore_filename (str->str, with_extension));
+
+	return g_string_free (str, FALSE);;
+}
diff --git a/shared/nm-libnm-aux/nm-libnm-aux.c b/shared/nm-libnm-aux/nm-libnm-aux.c
new file mode 100644
index 00000000..169416b9
--- /dev/null
+++ b/shared/nm-libnm-aux/nm-libnm-aux.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#include "nm-default.h"
+
+#include "nm-libnm-aux.h"
+
+/*****************************************************************************/
+
+NMClient *
+nmc_client_new_async_valist (GCancellable *cancellable,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data,
+                             const char *first_property_name,
+                             va_list ap)
+{
+	NMClient *nmc;
+
+	nmc = NM_CLIENT (g_object_new_valist (NM_TYPE_CLIENT, first_property_name, ap));
+	g_async_initable_init_async (G_ASYNC_INITABLE (nmc),
+	                             G_PRIORITY_DEFAULT,
+	                             cancellable,
+	                             callback,
+	                             user_data);
+	return nmc;
+}
+
+NMClient *
+nmc_client_new_async (GCancellable *cancellable,
+                      GAsyncReadyCallback callback,
+                      gpointer user_data,
+                      const char *first_property_name,
+                      ...)
+{
+	NMClient *nmc;
+	va_list ap;
+
+	va_start (ap, first_property_name);
+	nmc = nmc_client_new_async_valist (cancellable,
+	                                   callback,
+	                                   user_data,
+	                                   first_property_name,
+	                                   ap);
+	va_end (ap);
+	return nmc;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	GMainLoop *main_loop;
+	NMClient *nmc;
+	GError *error;
+} ClientCreateData;
+
+static void
+_nmc_client_new_waitsync_cb (GObject *source_object,
+                             GAsyncResult *result,
+                             gpointer user_data)
+{
+	ClientCreateData *data = user_data;
+
+	g_async_initable_init_finish (G_ASYNC_INITABLE (source_object),
+	                              result,
+	                              &data->error);
+	g_main_loop_quit (data->main_loop);
+}
+
+/**
+ * nmc_client_new:
+ * @cancellable: the cancellable to abort the creation.
+ * @out_nmc: (out): (transfer full): if give, transfers a reference
+ *   to the NMClient instance. Note that this never fails to create
+ *   the NMClient GObject, but depending on the return value,
+ *   the instance was successfully initialized or not.
+ * @error: the error if creation fails.
+ * @first_property_name: the name of the first property
+ * @...: the value of the first property, followed optionally by more
+ *  name/value pairs, followed by %NULL
+ *
+ * Returns: %TRUE, if the client was successfully initalized.
+ *
+ * This uses nm_client_new_async() to create a NMClient instance,
+ * but it iterates the current GMainContext until the client is
+ * ready. As such, it waits for the client creation to complete
+ * (like sync nm_client_new()) but it iterates the caller's GMainContext
+ * (unlike sync nm_client_new()). This is often preferable, because
+ * sync nm_client_new() needs to create an additional internal GMainContext
+ * that it can iterate instead. That has a performance overhead that
+ * is often unnecessary.
+ */
+gboolean
+nmc_client_new_waitsync (GCancellable *cancellable,
+                         NMClient **out_nmc,
+                         GError **error,
+                         const char *first_property_name,
+                         ...)
+{
+	gs_unref_object NMClient *nmc = NULL;
+	nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE);
+	ClientCreateData data = {
+		.main_loop = main_loop,
+	};
+	va_list ap;
+
+#if NM_MORE_ASSERTS > 10
+	/* The sync initialization of NMClient is generally a bad idea, because it
+	 * brings the overhead of an additional GMainContext. Anyway, since our own
+	 * code no longer uses that, we hardly test those code paths. But they should
+	 * work just the same. Randomly use instead the sync initialization in a debug
+	 * build... */
+	if ((g_random_int () % 2) == 0) {
+		gboolean success;
+
+		va_start (ap, first_property_name);
+		nmc = NM_CLIENT (g_object_new_valist (NM_TYPE_CLIENT, first_property_name, ap));
+		va_end (ap);
+
+		/* iterate the context at least once, just so that the behavior from POV of the
+		 * caller is roughly the same. */
+		g_main_context_iteration (nm_client_get_main_context (nmc), FALSE);
+
+		success = g_initable_init (G_INITABLE (nmc),
+		                           cancellable,
+		                           error);
+		NM_SET_OUT (out_nmc, g_steal_pointer (&nmc));
+		return success;
+	}
+#endif
+
+	va_start (ap, first_property_name);
+	nmc = nmc_client_new_async_valist (cancellable,
+	                                   _nmc_client_new_waitsync_cb,
+	                                   &data,
+	                                   first_property_name,
+	                                   ap);
+	va_end (ap);
+
+	g_main_loop_run (main_loop);
+
+	NM_SET_OUT (out_nmc, g_steal_pointer (&nmc));
+	if (data.error) {
+		g_propagate_error (error, data.error);
+		return FALSE;
+	}
+	return TRUE;
+}
diff --git a/shared/nm-libnm-aux/nm-libnm-aux.h b/shared/nm-libnm-aux/nm-libnm-aux.h
new file mode 100644
index 00000000..a0aff19f
--- /dev/null
+++ b/shared/nm-libnm-aux/nm-libnm-aux.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#ifndef __NM_LIBNM_AUX_H__
+#define __NM_LIBNM_AUX_H__
+
+NMClient *nmc_client_new_async_valist (GCancellable *cancellable,
+                                       GAsyncReadyCallback callback,
+                                       gpointer user_data,
+                                       const char *first_property_name,
+                                       va_list ap);
+
+NMClient *nmc_client_new_async (GCancellable *cancellable,
+                                GAsyncReadyCallback callback,
+                                gpointer user_data,
+                                const char *first_property_name,
+                                ...);
+
+gboolean nmc_client_new_waitsync (GCancellable *cancellable,
+                                  NMClient **out_nmc,
+                                  GError **error,
+                                  const char *first_property_name,
+                                  ...);
+
+#endif /* __NM_LIBNM_AUX_H__ */
diff --git a/shared/nm-libnm-core-intern/nm-auth-subject.c b/shared/nm-libnm-core-intern/nm-auth-subject.c
new file mode 100644
index 00000000..15c2d652
--- /dev/null
+++ b/shared/nm-libnm-core-intern/nm-auth-subject.c
@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2013 - 2014 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nm-auth-subject
+ * @short_description: Encapsulates authentication information about a requestor
+ *
+ * #NMAuthSubject encpasulates identifying information about an entity that
+ * makes requests, like process identifier and user UID.
+ */
+
+#include "nm-default.h"
+
+#include "nm-auth-subject.h"
+
+#include <stdlib.h>
+
+enum {
+	PROP_0,
+	PROP_SUBJECT_TYPE,
+	PROP_UNIX_PROCESS_DBUS_SENDER,
+	PROP_UNIX_PROCESS_PID,
+	PROP_UNIX_PROCESS_UID,
+	PROP_UNIX_SESSION_ID,
+
+	PROP_LAST,
+};
+
+typedef struct {
+	NMAuthSubjectType subject_type;
+	struct {
+		gulong pid;
+		gulong uid;
+		guint64 start_time;
+		char *dbus_sender;
+	} unix_process;
+
+	struct {
+		char *id;
+	} unix_session;
+} NMAuthSubjectPrivate;
+
+struct _NMAuthSubject {
+	GObject parent;
+	NMAuthSubjectPrivate _priv;
+};
+
+struct _NMAuthSubjectClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMAuthSubject, nm_auth_subject, G_TYPE_OBJECT)
+
+#define NM_AUTH_SUBJECT_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMAuthSubject, NM_IS_AUTH_SUBJECT)
+
+/*****************************************************************************/
+
+#define CHECK_SUBJECT(self, error_value) \
+	NMAuthSubjectPrivate *priv; \
+	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (self), error_value); \
+	priv = NM_AUTH_SUBJECT_GET_PRIVATE (self); \
+
+#define CHECK_SUBJECT_TYPED(self, expected_subject_type, error_value) \
+	CHECK_SUBJECT (self, error_value); \
+	g_return_val_if_fail (priv->subject_type == (expected_subject_type), error_value);
+
+const char *
+nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len)
+{
+	CHECK_SUBJECT (self, NULL);
+
+	switch (priv->subject_type) {
+	case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS:
+		g_snprintf (buf, buf_len, "unix-process[pid=%lu, uid=%lu, start=%llu]",
+		            (unsigned long) priv->unix_process.pid,
+		            (unsigned long) priv->unix_process.uid,
+		            (unsigned long long) priv->unix_process.start_time);
+		break;
+	case NM_AUTH_SUBJECT_TYPE_INTERNAL:
+		g_strlcpy (buf, "internal", buf_len);
+		break;
+	case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION:
+		g_snprintf (buf, buf_len, "unix-session[id=%s]",
+		            priv->unix_session.id);
+		break;
+	default:
+		g_strlcpy (buf, "invalid", buf_len);
+		break;
+	}
+	return buf;
+}
+
+/* returns a floating variant */
+GVariant *
+nm_auth_subject_unix_to_polkit_gvariant (NMAuthSubject *self)
+{
+	GVariantBuilder builder;
+	CHECK_SUBJECT (self, NULL);
+
+	switch (priv->subject_type) {
+
+	case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION:
+		g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+		g_variant_builder_add (&builder, "{sv}", "session-id",
+		                       g_variant_new_string (priv->unix_session.id));
+		return g_variant_new ("(sa{sv})", "unix-session", &builder);
+
+	case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS:
+		g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+		g_variant_builder_add (&builder, "{sv}", "pid",
+		                       g_variant_new_uint32 (priv->unix_process.pid));
+		g_variant_builder_add (&builder, "{sv}", "start-time",
+		                       g_variant_new_uint64 (priv->unix_process.start_time));
+		g_variant_builder_add (&builder, "{sv}", "uid",
+		                       g_variant_new_int32 (priv->unix_process.uid));
+		return g_variant_new ("(sa{sv})", "unix-process", &builder);
+
+	default:
+		g_return_val_if_reached (NULL);
+	}
+}
+
+NMAuthSubjectType
+nm_auth_subject_get_subject_type (NMAuthSubject *subject)
+{
+	CHECK_SUBJECT (subject, NM_AUTH_SUBJECT_TYPE_INVALID);
+
+	return priv->subject_type;
+}
+
+gulong
+nm_auth_subject_get_unix_process_pid (NMAuthSubject *subject)
+{
+	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG);
+
+	return priv->unix_process.pid;
+}
+
+gulong
+nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject)
+{
+	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG);
+
+	return priv->unix_process.uid;
+}
+
+const char *
+nm_auth_subject_get_unix_process_dbus_sender (NMAuthSubject *subject)
+{
+	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, NULL);
+
+	return priv->unix_process.dbus_sender;
+}
+
+const char *
+nm_auth_subject_get_unix_session_id (NMAuthSubject *subject)
+{
+	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_SESSION, NULL);
+
+	return priv->unix_session.id;
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_auth_subject_new_internal():
+ *
+ * Creates a new auth subject representing the NetworkManager process itself.
+ *
+ * Returns: the new #NMAuthSubject
+ */
+NMAuthSubject *
+nm_auth_subject_new_internal (void)
+{
+	return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT,
+                                          NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_INTERNAL,
+                                          NULL));
+}
+
+/**
+ * nm_auth_subject_new_unix_session():
+ *
+ * Creates a new auth subject representing a given unix session.
+ *
+ * Returns: the new #NMAuthSubject
+ */
+NMAuthSubject *
+nm_auth_subject_new_unix_session (const char *session_id)
+{
+	return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT,
+	                                      NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_UNIX_SESSION,
+	                                      NM_AUTH_SUBJECT_UNIX_SESSION_ID, session_id,
+	                                      NULL));
+}
+
+/**
+ * nm_auth_subject_new_unix_process():
+ *
+ * Creates a new auth subject representing a given unix process.
+ *
+ * Returns: the new #NMAuthSubject
+ */
+NMAuthSubject *
+nm_auth_subject_new_unix_process (const char *dbus_sender, gulong pid, gulong uid)
+{
+	return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT,
+	                                      NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
+	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, dbus_sender,
+	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_PID, pid,
+	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_UID, uid,
+	                                      NULL));
+}
+
+/**
+ * nm_auth_subject_new_unix_process_self():
+ *
+ * Creates a new auth subject representing the current executing process.
+ *
+ * Returns: the new #NMAuthSubject
+ */
+NMAuthSubject *
+nm_auth_subject_new_unix_process_self (void)
+{
+	return nm_auth_subject_new_unix_process (NULL, getpid(), getuid());
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (object);
+
+	switch (prop_id) {
+	case PROP_SUBJECT_TYPE:
+		g_value_set_int (value, priv->subject_type);
+		break;
+	case PROP_UNIX_PROCESS_DBUS_SENDER:
+		g_value_set_string (value, priv->unix_process.dbus_sender);
+		break;
+	case PROP_UNIX_PROCESS_PID:
+		g_value_set_ulong (value, priv->unix_process.pid);
+		break;
+	case PROP_UNIX_PROCESS_UID:
+		g_value_set_ulong (value, priv->unix_process.uid);
+		break;
+	case PROP_UNIX_SESSION_ID:
+		g_value_set_string (value, priv->unix_session.id);
+		break;
+	default:
+		 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		 break;
+	}
+}
+
+static void
+set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (object);
+	NMAuthSubjectType subject_type;
+	int i;
+	const char *str;
+	gulong id;
+
+	switch (prop_id) {
+	case PROP_SUBJECT_TYPE:
+		/* construct-only */
+		i = g_value_get_int (value);
+		g_return_if_fail (NM_IN_SET (i,
+		                             (int) NM_AUTH_SUBJECT_TYPE_INTERNAL,
+		                             (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
+		                             (int) NM_AUTH_SUBJECT_TYPE_UNIX_SESSION));
+		subject_type = i;
+		priv->subject_type |= subject_type;
+		g_return_if_fail (priv->subject_type == subject_type);
+		break;
+	case PROP_UNIX_PROCESS_DBUS_SENDER:
+		/* construct-only */
+		if ((str = g_value_get_string (value))) {
+			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
+			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
+			priv->unix_process.dbus_sender = g_strdup (str);
+		}
+		break;
+	case PROP_UNIX_PROCESS_PID:
+		/* construct-only */
+		if ((id = g_value_get_ulong (value)) != G_MAXULONG) {
+			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
+			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
+			priv->unix_process.pid = id;
+		}
+		break;
+	case PROP_UNIX_PROCESS_UID:
+		/* construct-only */
+		if ((id = g_value_get_ulong (value)) != G_MAXULONG) {
+			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
+			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
+			priv->unix_process.uid = id;
+		}
+		break;
+	case PROP_UNIX_SESSION_ID:
+		/* construct-only */
+		if ((str = g_value_get_string (value))) {
+			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_SESSION;
+			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_SESSION);
+			priv->unix_session.id = g_strdup (str);
+		}
+		break;
+	default:
+		 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		 break;
+	}
+}
+
+static void
+_clear_private (NMAuthSubject *self)
+{
+	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self);
+
+	priv->subject_type = NM_AUTH_SUBJECT_TYPE_INVALID;
+	priv->unix_process.pid = G_MAXULONG;
+	priv->unix_process.uid = G_MAXULONG;
+	nm_clear_g_free (&priv->unix_process.dbus_sender);
+
+	nm_clear_g_free (&priv->unix_session.id);
+}
+
+static void
+nm_auth_subject_init (NMAuthSubject *self)
+{
+	_clear_private (self);
+}
+
+static void
+constructed (GObject *object)
+{
+	NMAuthSubject *self = NM_AUTH_SUBJECT (object);
+	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self);
+
+	/* validate that the created instance. */
+
+	switch (priv->subject_type) {
+	case NM_AUTH_SUBJECT_TYPE_INTERNAL:
+		priv->unix_process.pid = G_MAXULONG;
+		priv->unix_process.uid = 0;  /* internal uses 'root' user */
+		return;
+	case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS:
+		/* Ensure pid and uid to be representable as int32.
+		 * DBUS treats them as uint32, polkit library as int. */
+		if (priv->unix_process.pid > MIN (G_MAXINT, G_MAXINT32))
+			break;
+		if (priv->unix_process.uid > MIN (G_MAXINT, G_MAXINT32)) {
+			/* for uid==-1, libpolkit-gobject-1 detects the user based on the process id.
+			 * Don't bother and require the user id as parameter. */
+			break;
+		}
+
+		priv->unix_process.start_time = nm_utils_get_start_time_for_pid (priv->unix_process.pid, NULL, NULL);
+
+		if (!priv->unix_process.start_time) {
+			/* Is the process already gone? Then fail creation of the auth subject
+			 * by clearing the type. */
+			if (kill (priv->unix_process.pid, 0) != 0)
+				_clear_private (self);
+
+			/* Otherwise, although we didn't detect a start_time, the process is still around.
+			 * That could be due to procfs mounted with hidepid. So just accept the request.
+			 *
+			 * Polkit on the other side, will accept 0 and try to lookup /proc/$PID/stat
+			 * itself (and if it fails to do so, assume a start-time of 0 and proceed).
+			 * The only combination that would fail here, is when NM is able to read the
+			 * start-time, but polkit is not. */
+		}
+		return;
+	case NM_AUTH_SUBJECT_TYPE_UNIX_SESSION:
+		return;
+	default:
+		break;
+	}
+
+	_clear_private (self);
+	g_return_if_reached ();
+}
+
+static void
+finalize (GObject *object)
+{
+	_clear_private ((NMAuthSubject *) object);
+
+	G_OBJECT_CLASS (nm_auth_subject_parent_class)->finalize (object);
+}
+
+static void
+nm_auth_subject_class_init (NMAuthSubjectClass *config_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
+
+	object_class->get_property = get_property;
+	object_class->set_property = set_property;
+	object_class->constructed = constructed;
+	object_class->finalize = finalize;
+
+	g_object_class_install_property
+	    (object_class, PROP_SUBJECT_TYPE,
+	     g_param_spec_int (NM_AUTH_SUBJECT_SUBJECT_TYPE, "", "",
+	                       NM_AUTH_SUBJECT_TYPE_INVALID,
+	                       NM_AUTH_SUBJECT_TYPE_UNIX_SESSION,
+	                       NM_AUTH_SUBJECT_TYPE_INVALID,
+	                       G_PARAM_READWRITE |
+	                       G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property
+	    (object_class, PROP_UNIX_PROCESS_DBUS_SENDER,
+	     g_param_spec_string (NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, "", "",
+	                          NULL,
+	                          G_PARAM_READWRITE |
+	                          G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property
+	     (object_class, PROP_UNIX_PROCESS_PID,
+	      g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_PID, "", "",
+	                          0, G_MAXULONG, G_MAXULONG,
+	                          G_PARAM_READWRITE |
+	                          G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property
+	     (object_class, PROP_UNIX_PROCESS_UID,
+	      g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_UID, "", "",
+	                          0, G_MAXULONG, G_MAXULONG,
+	                          G_PARAM_READWRITE |
+	                          G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property
+	     (object_class, PROP_UNIX_SESSION_ID,
+	      g_param_spec_string (NM_AUTH_SUBJECT_UNIX_SESSION_ID, "", "",
+	                           NULL,
+	                           G_PARAM_READWRITE |
+	                           G_PARAM_CONSTRUCT_ONLY |
+	                           G_PARAM_STATIC_STRINGS));
+}
diff --git a/shared/nm-libnm-core-intern/nm-auth-subject.h b/shared/nm-libnm-core-intern/nm-auth-subject.h
new file mode 100644
index 00000000..fc37b8e2
--- /dev/null
+++ b/shared/nm-libnm-core-intern/nm-auth-subject.h
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_AUTH_SUBJECT_H__
+#define __NETWORKMANAGER_AUTH_SUBJECT_H__
+
+#define NM_TYPE_AUTH_SUBJECT            (nm_auth_subject_get_type ())
+#define NM_AUTH_SUBJECT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_AUTH_SUBJECT, NMAuthSubject))
+#define NM_AUTH_SUBJECT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_AUTH_SUBJECT, NMAuthSubjectClass))
+#define NM_IS_AUTH_SUBJECT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_AUTH_SUBJECT))
+#define NM_IS_AUTH_SUBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_AUTH_SUBJECT))
+#define NM_AUTH_SUBJECT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_AUTH_SUBJECT, NMAuthSubjectClass))
+
+typedef enum {
+	NM_AUTH_SUBJECT_TYPE_INVALID      = 0,
+	NM_AUTH_SUBJECT_TYPE_INTERNAL     = 1,
+	NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS = 2,
+	NM_AUTH_SUBJECT_TYPE_UNIX_SESSION = 4,
+} NMAuthSubjectType;
+
+#define NM_AUTH_SUBJECT_SUBJECT_TYPE                           "subject-type"
+#define NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER               "unix-process-dbus-sender"
+#define NM_AUTH_SUBJECT_UNIX_PROCESS_PID                       "unix-process-pid"
+#define NM_AUTH_SUBJECT_UNIX_PROCESS_UID                       "unix-process-uid"
+#define NM_AUTH_SUBJECT_UNIX_SESSION_ID                        "unix-session-id"
+
+typedef struct _NMAuthSubjectClass NMAuthSubjectClass;
+typedef struct _NMAuthSubject NMAuthSubject;
+
+GType nm_auth_subject_get_type (void);
+
+NMAuthSubject *nm_auth_subject_new_internal (void);
+
+NMAuthSubject *nm_auth_subject_new_unix_session (const char *session_id);
+
+NMAuthSubject *nm_auth_subject_new_unix_process (const char *dbus_sender, gulong pid, gulong uid);
+
+NMAuthSubject *nm_auth_subject_new_unix_process_self (void);
+
+NMAuthSubjectType nm_auth_subject_get_subject_type (NMAuthSubject *subject);
+
+gulong nm_auth_subject_get_unix_process_pid (NMAuthSubject *subject);
+
+const char *nm_auth_subject_get_unix_process_dbus_sender (NMAuthSubject *subject);
+
+gulong nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject);
+
+const char *nm_auth_subject_get_unix_session_id (NMAuthSubject *subject);
+
+const char *nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len);
+
+GVariant *nm_auth_subject_unix_to_polkit_gvariant (NMAuthSubject *self);
+
+#endif /* __NETWORKMANAGER_AUTH_SUBJECT_H__ */
diff --git a/shared/nm-libnm-core-intern/nm-ethtool-utils.h b/shared/nm-libnm-core-intern/nm-ethtool-utils.h
index 463d8e8f..b3413de2 100644
--- a/shared/nm-libnm-core-intern/nm-ethtool-utils.h
+++ b/shared/nm-libnm-core-intern/nm-ethtool-utils.h
@@ -79,7 +79,7 @@ typedef struct {
 	NMEthtoolID id;
 } NMEthtoolData;
 
-extern const NMEthtoolData *const nm_ethtool_data[/*_NM_ETHTOOL_ID_NUM + NULL-terminated*/];
+extern const NMEthtoolData *const nm_ethtool_data[_NM_ETHTOOL_ID_NUM + 1];
 
 const NMEthtoolData *nm_ethtool_data_get_by_optname (const char *optname);
 
diff --git a/shared/nm-libnm-core-intern/nm-libnm-core-utils.c b/shared/nm-libnm-core-intern/nm-libnm-core-utils.c
index 9bf0b120..df2f2e77 100644
--- a/shared/nm-libnm-core-intern/nm-libnm-core-utils.c
+++ b/shared/nm-libnm-core-intern/nm-libnm-core-utils.c
@@ -4,6 +4,8 @@
 
 #include "nm-libnm-core-utils.h"
 
+#include "nm-common-macros.h"
+
 /*****************************************************************************/
 
 gboolean
@@ -59,3 +61,120 @@ nm_utils_vlan_priority_map_parse_str (NMVlanPriorityMap map_type,
 	NM_SET_OUT (out_has_wildcard_to, v2 < 0);
 	return TRUE;
 }
+
+/*****************************************************************************/
+
+const char *const nm_auth_permission_names_by_idx[NM_CLIENT_PERMISSION_LAST] = {
+	[NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK - 1]               = NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK,
+	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK - 1] = NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK,
+	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK - 1]            = NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK,
+	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS - 1]         = NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS,
+	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI - 1]               = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI,
+	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX - 1]              = NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX,
+	[NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN - 1]               = NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN,
+	[NM_CLIENT_PERMISSION_NETWORK_CONTROL - 1]                   = NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	[NM_CLIENT_PERMISSION_RELOAD - 1]                            = NM_AUTH_PERMISSION_RELOAD,
+	[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS - 1]        = NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS,
+	[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME - 1]          = NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME,
+	[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN - 1]               = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN,
+	[NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM - 1]            = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM,
+	[NM_CLIENT_PERMISSION_SLEEP_WAKE - 1]                        = NM_AUTH_PERMISSION_SLEEP_WAKE,
+	[NM_CLIENT_PERMISSION_WIFI_SCAN - 1]                         = NM_AUTH_PERMISSION_WIFI_SCAN,
+	[NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN - 1]                   = NM_AUTH_PERMISSION_WIFI_SHARE_OPEN,
+	[NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED - 1]              = NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED,
+};
+
+const NMClientPermission nm_auth_permission_sorted[NM_CLIENT_PERMISSION_LAST] = {
+	NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN,
+	NM_CLIENT_PERMISSION_NETWORK_CONTROL,
+	NM_CLIENT_PERMISSION_RELOAD,
+	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS,
+	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME,
+	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN,
+	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM,
+	NM_CLIENT_PERMISSION_SLEEP_WAKE,
+	NM_CLIENT_PERMISSION_WIFI_SCAN,
+	NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN,
+	NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED,
+};
+
+const char *
+nm_auth_permission_to_string (NMClientPermission permission)
+{
+	if (permission < 1)
+		return NULL;
+	if (permission > NM_CLIENT_PERMISSION_LAST)
+		return NULL;
+	return nm_auth_permission_names_by_idx[permission - 1];
+}
+
+#define AUTH_PERMISSION_PREFIX "org.freedesktop.NetworkManager."
+
+static int
+_nm_auth_permission_from_string_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+	const NMClientPermission *const p = a;
+	const char *const needle = b;
+	const char *ss = nm_auth_permission_names_by_idx[*p - 1];
+
+	nm_assert (NM_STR_HAS_PREFIX (ss, AUTH_PERMISSION_PREFIX));
+	nm_assert (ss[NM_STRLEN (AUTH_PERMISSION_PREFIX)] != '\0');
+
+	return strcmp (&ss[NM_STRLEN (AUTH_PERMISSION_PREFIX)], needle);
+}
+
+NMClientPermission
+nm_auth_permission_from_string (const char *str)
+{
+	gssize idx;
+
+	if (!str)
+		return NM_CLIENT_PERMISSION_NONE;
+
+	if (!NM_STR_HAS_PREFIX (str, AUTH_PERMISSION_PREFIX))
+		return NM_CLIENT_PERMISSION_NONE;
+	idx = nm_utils_array_find_binary_search (nm_auth_permission_sorted,
+	                                         sizeof (nm_auth_permission_sorted[0]),
+	                                         G_N_ELEMENTS (nm_auth_permission_sorted),
+	                                         &str[NM_STRLEN (AUTH_PERMISSION_PREFIX)],
+	                                         _nm_auth_permission_from_string_cmp,
+	                                         NULL);
+	if (idx < 0)
+		return NM_CLIENT_PERMISSION_NONE;
+	return nm_auth_permission_sorted[idx];
+}
+
+/*****************************************************************************/
+
+NMClientPermissionResult
+nm_client_permission_result_from_string (const char *nm)
+{
+	if (!nm)
+		return NM_CLIENT_PERMISSION_RESULT_UNKNOWN;
+	if (nm_streq (nm, "yes"))
+		return NM_CLIENT_PERMISSION_RESULT_YES;
+	if (nm_streq (nm, "no"))
+		return NM_CLIENT_PERMISSION_RESULT_NO;
+	if (nm_streq (nm, "auth"))
+		return NM_CLIENT_PERMISSION_RESULT_AUTH;
+	return NM_CLIENT_PERMISSION_RESULT_UNKNOWN;
+}
+
+const char *
+nm_client_permission_result_to_string (NMClientPermissionResult permission)
+{
+	switch (permission) {
+	case NM_CLIENT_PERMISSION_RESULT_YES:     return "yes";
+	case NM_CLIENT_PERMISSION_RESULT_NO:      return "no";
+	case NM_CLIENT_PERMISSION_RESULT_AUTH:    return "auth";
+	case NM_CLIENT_PERMISSION_RESULT_UNKNOWN: return "unknown";
+	}
+	nm_assert_not_reached ();
+	return NULL;
+}
diff --git a/shared/nm-libnm-core-intern/nm-libnm-core-utils.h b/shared/nm-libnm-core-intern/nm-libnm-core-utils.h
index 1449a8ba..a35a5e15 100644
--- a/shared/nm-libnm-core-intern/nm-libnm-core-utils.h
+++ b/shared/nm-libnm-core-intern/nm-libnm-core-utils.h
@@ -99,4 +99,17 @@ nm_setting_ip_config_get_addr_family (NMSettingIPConfig *s_ip)
 
 #define _NM_CAPABILITY_MAX NM_CAPABILITY_OVS
 
+/*****************************************************************************/
+
+extern const char *const nm_auth_permission_names_by_idx[NM_CLIENT_PERMISSION_LAST];
+extern const NMClientPermission nm_auth_permission_sorted[NM_CLIENT_PERMISSION_LAST];
+
+const char *nm_auth_permission_to_string (NMClientPermission permission);
+NMClientPermission nm_auth_permission_from_string (const char *str);
+
+/*****************************************************************************/
+
+NMClientPermissionResult nm_client_permission_result_from_string (const char *nm);
+const char *nm_client_permission_result_to_string (NMClientPermissionResult permission);
+
 #endif /* __NM_LIBNM_SHARED_UTILS_H__ */
diff --git a/shared/nm-meta-setting.c b/shared/nm-meta-setting.c
index af5aa842..cf6309b1 100644
--- a/shared/nm-meta-setting.c
+++ b/shared/nm-meta-setting.c
@@ -46,6 +46,7 @@
 #include "nm-setting-user.h"
 #include "nm-setting-vlan.h"
 #include "nm-setting-vpn.h"
+#include "nm-setting-vrf.h"
 #include "nm-setting-vxlan.h"
 #include "nm-setting-wifi-p2p.h"
 #include "nm-setting-wimax.h"
@@ -387,6 +388,12 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
 		.setting_name =             NM_SETTING_VPN_SETTING_NAME,
 		.get_setting_gtype =        nm_setting_vpn_get_type,
 	},
+	[NM_META_SETTING_TYPE_VRF] = {
+		.meta_type =                NM_META_SETTING_TYPE_VRF,
+		.setting_priority =         NM_SETTING_PRIORITY_HW_BASE,
+		.setting_name =             NM_SETTING_VRF_SETTING_NAME,
+		.get_setting_gtype =        nm_setting_vrf_get_type,
+	},
 	[NM_META_SETTING_TYPE_VXLAN] = {
 		.meta_type =                NM_META_SETTING_TYPE_VXLAN,
 		.setting_priority =         NM_SETTING_PRIORITY_HW_BASE,
diff --git a/shared/nm-meta-setting.h b/shared/nm-meta-setting.h
index 3976bfe1..8b0c7d35 100644
--- a/shared/nm-meta-setting.h
+++ b/shared/nm-meta-setting.h
@@ -142,6 +142,7 @@ typedef enum {
 	NM_META_SETTING_TYPE_USER,
 	NM_META_SETTING_TYPE_VLAN,
 	NM_META_SETTING_TYPE_VPN,
+	NM_META_SETTING_TYPE_VRF,
 	NM_META_SETTING_TYPE_VXLAN,
 	NM_META_SETTING_TYPE_WIFI_P2P,
 	NM_META_SETTING_TYPE_WIMAX,
diff --git a/shared/nm-std-aux/nm-dbus-compat.h b/shared/nm-std-aux/nm-dbus-compat.h
index bd010f45..293e15cc 100644
--- a/shared/nm-std-aux/nm-dbus-compat.h
+++ b/shared/nm-std-aux/nm-dbus-compat.h
@@ -1,62 +1,25 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2015 Red Hat, Inc.
- */
+// SPDX-License-Identifier: LGPL-2.1+
 
 #ifndef __NM_DBUS_COMPAT_H__
 #define __NM_DBUS_COMPAT_H__
 
-/* Copied from <dbus/dbus-shared.h> */
+#define DBUS_SERVICE_DBUS             "org.freedesktop.DBus"
 
-/* Bus names */
+#define DBUS_PATH_DBUS                "/org/freedesktop/DBus"
 
-/** The bus name used to talk to the bus itself. */
-#define DBUS_SERVICE_DBUS      "org.freedesktop.DBus"
-
-/* Paths */
-/** The object path used to talk to the bus itself. */
-#define DBUS_PATH_DBUS  "/org/freedesktop/DBus"
-/** The object path used in local/in-process-generated messages. */
-#define DBUS_PATH_LOCAL "/org/freedesktop/DBus/Local"
-
-/* Interfaces, these #define don't do much other than
- * catch typos at compile time
- */
-/** The interface exported by the object with #DBUS_SERVICE_DBUS and #DBUS_PATH_DBUS */
 #define DBUS_INTERFACE_DBUS           "org.freedesktop.DBus"
-/** The interface supported by introspectable objects */
 #define DBUS_INTERFACE_INTROSPECTABLE "org.freedesktop.DBus.Introspectable"
-/** The interface supported by objects with properties */
-#define DBUS_INTERFACE_PROPERTIES     "org.freedesktop.DBus.Properties"
-/** The interface supported by most dbus peers */
-#define DBUS_INTERFACE_PEER           "org.freedesktop.DBus.Peer"
-
 #define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager"
+#define DBUS_INTERFACE_PEER           "org.freedesktop.DBus.Peer"
+#define DBUS_INTERFACE_PROPERTIES     "org.freedesktop.DBus.Properties"
 
-/** This is a special interface whose methods can only be invoked
- * by the local implementation (messages from remote apps aren't
- * allowed to specify this interface).
- */
-#define DBUS_INTERFACE_LOCAL "org.freedesktop.DBus.Local"
-
-/* Owner flags */
-#define DBUS_NAME_FLAG_ALLOW_REPLACEMENT 0x1 /**< Allow another service to become the primary owner if requested */
-#define DBUS_NAME_FLAG_REPLACE_EXISTING  0x2 /**< Request to replace the current primary owner */
-#define DBUS_NAME_FLAG_DO_NOT_QUEUE      0x4 /**< If we can not become the primary owner do not place us in the queue */
-
-/* Replies to request for a name */
-#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER  1 /**< Service has become the primary owner of the requested name */
-#define DBUS_REQUEST_NAME_REPLY_IN_QUEUE       2 /**< Service could not become the primary owner and has been placed in the queue */
-#define DBUS_REQUEST_NAME_REPLY_EXISTS         3 /**< Service is already in the queue */
-#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER  4 /**< Service is already the primary owner */
-
-/* Replies to releasing a name */
-#define DBUS_RELEASE_NAME_REPLY_RELEASED        1 /**< Service was released from the given name */
-#define DBUS_RELEASE_NAME_REPLY_NON_EXISTENT    2 /**< The given name does not exist on the bus */
-#define DBUS_RELEASE_NAME_REPLY_NOT_OWNER       3 /**< Service is not an owner of the given name */
+#define DBUS_NAME_FLAG_ALLOW_REPLACEMENT 0x1
+#define DBUS_NAME_FLAG_REPLACE_EXISTING  0x2
+#define DBUS_NAME_FLAG_DO_NOT_QUEUE      0x4
 
-/* Replies to service starts */
-#define DBUS_START_REPLY_SUCCESS         1 /**< Service was auto started */
-#define DBUS_START_REPLY_ALREADY_RUNNING 2 /**< Service was already running */
+#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER  1
+#define DBUS_REQUEST_NAME_REPLY_IN_QUEUE       2
+#define DBUS_REQUEST_NAME_REPLY_EXISTS         3
+#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER  4
 
 #endif  /* __NM_DBUS_COMPAT_H__ */
diff --git a/shared/nm-test-libnm-utils.h b/shared/nm-test-libnm-utils.h
index 63e3b4c7..06dbc72d 100644
--- a/shared/nm-test-libnm-utils.h
+++ b/shared/nm-test-libnm-utils.h
@@ -67,4 +67,18 @@ void nmtstc_service_update_connection_variant (NMTstcServiceInfo *sinfo,
                                                GVariant *connection,
                                                gboolean verify_connection);
 
-NMClient *nmtstc_client_new (gboolean allow_iterate_main_context);
+gpointer nmtstc_context_object_new_valist (GType gtype,
+                                           gboolean allow_iterate_main_context,
+                                           const char *first_property_name,
+                                           va_list var_args);
+
+gpointer nmtstc_context_object_new (GType gtype,
+                                    gboolean allow_iterate_main_context,
+                                    const char *first_property_name,
+                                    ...);
+
+static inline NMClient *
+nmtstc_client_new (gboolean allow_iterate_main_context)
+{
+	return nmtstc_context_object_new (NM_TYPE_CLIENT, allow_iterate_main_context, NULL);
+}
diff --git a/shared/nm-test-utils-impl.c b/shared/nm-test-utils-impl.c
index 0805d0b9..0ef6591f 100644
--- a/shared/nm-test-utils-impl.c
+++ b/shared/nm-test-utils-impl.c
@@ -127,7 +127,7 @@ nmtstc_service_init (void)
 		g_source_attach (timeout_source, context);
 
 		child_source = g_child_watch_source_new (info->pid);
-		g_source_set_callback (child_source, (GSourceFunc)(void (*) (void)) _service_init_wait_child_wait, &data, NULL);
+		g_source_set_callback (child_source, G_SOURCE_FUNC (_service_init_wait_child_wait), &data, NULL);
 		g_source_attach (child_source, context);
 
 		had_timeout = !nmtst_main_loop_run (data.mainloop, 30000);
@@ -216,7 +216,7 @@ again_wait:
 typedef struct {
 	GMainLoop *loop;
 	const char *ifname;
-	char *path;
+	const char *path;
 	NMDevice *device;
 } AddDeviceInfo;
 
@@ -227,21 +227,17 @@ device_added_cb (NMClient *client,
 {
 	AddDeviceInfo *info = user_data;
 
-	g_assert (device);
+	g_assert (info);
+	g_assert (!info->device);
+
+	g_assert (NM_IS_DEVICE (device));
 	g_assert_cmpstr (nm_object_get_path (NM_OBJECT (device)), ==, info->path);
 	g_assert_cmpstr (nm_device_get_iface (device), ==, info->ifname);
 
-	info->device = device;
+	info->device = g_object_ref (device);
 	g_main_loop_quit (info->loop);
 }
 
-static gboolean
-timeout (gpointer user_data)
-{
-	g_assert_not_reached ();
-	return G_SOURCE_REMOVE;
-}
-
 static GVariant *
 call_add_wired_device (GDBusProxy *proxy, const char *ifname, const char *hwaddr,
                        const char **subchannels, GError **error)
@@ -275,39 +271,53 @@ call_add_device (GDBusProxy *proxy, const char *method, const char *ifname, GErr
 }
 
 static NMDevice *
-add_device_common (NMTstcServiceInfo *sinfo, NMClient *client,
-                   const char *method, const char *ifname,
-                   const char *hwaddr, const char **subchannels)
+add_device_common (NMTstcServiceInfo *sinfo,
+                   NMClient *client,
+                   const char *method,
+                   const char *ifname,
+                   const char *hwaddr,
+                   const char **subchannels)
 {
+	nm_auto_unref_gmainloop GMainLoop *loop = NULL;
+	gs_unref_variant GVariant *ret = NULL;
+	gs_free_error GError *error = NULL;
 	AddDeviceInfo info;
-	GError *error = NULL;
-	GVariant *ret;
-	guint timeout_id;
 
-	if (g_strcmp0 (method, "AddWiredDevice") == 0)
+	g_assert (sinfo);
+	g_assert (NM_IS_CLIENT (client));
+
+	if (nm_streq0 (method, "AddWiredDevice"))
 		ret = call_add_wired_device (sinfo->proxy, ifname, hwaddr, subchannels, &error);
 	else
 		ret = call_add_device (sinfo->proxy, method, ifname, &error);
 
-	g_assert_no_error (error);
-	g_assert (ret);
+	nmtst_assert_success (ret, error);
 	g_assert_cmpstr (g_variant_get_type_string (ret), ==, "(o)");
-	g_variant_get (ret, "(o)", &info.path);
-	g_variant_unref (ret);
-
-	/* Wait for libnm to find the device */
-	info.ifname = ifname;
-	info.loop = g_main_loop_new (NULL, FALSE);
-	g_signal_connect (client, "device-added",
-	                  G_CALLBACK (device_added_cb), &info);
-	timeout_id = g_timeout_add_seconds (5, timeout, NULL);
-	g_main_loop_run (info.loop);
-
-	g_source_remove (timeout_id);
+
+	/* Wait for NMClient to find the device */
+
+	loop = g_main_loop_new (nm_client_get_main_context (client), FALSE);
+
+	info = (AddDeviceInfo) {
+		.ifname = ifname,
+		.loop   = loop,
+	};
+	g_variant_get (ret, "(&o)", &info.path);
+
+	g_signal_connect (client,
+	                  NM_CLIENT_DEVICE_ADDED,
+	                  G_CALLBACK (device_added_cb),
+	                  &info);
+
+	if (!nmtst_main_loop_run (loop, 5000))
+		g_assert_not_reached ();
+
 	g_signal_handlers_disconnect_by_func (client, device_added_cb, &info);
-	g_free (info.path);
-	g_main_loop_unref (info.loop);
 
+	g_assert (NM_IS_DEVICE (info.device));
+
+	g_assert (info.device == nm_client_get_device_by_path (client, nm_object_get_path (NM_OBJECT (info.device))));
+	g_object_unref (info.device);
 	return info.device;
 }
 
@@ -410,35 +420,47 @@ nmtstc_service_update_connection_variant (NMTstcServiceInfo *sinfo,
 /*****************************************************************************/
 
 typedef struct {
+	GType gtype;
 	GMainLoop *loop;
-	NMClient *client;
-} NMTstcClientNewData;
+	GObject *obj;
+	bool call_nm_client_new_async:1;
+} NMTstcObjNewData;
 
 static void
-_nmtstc_client_new_cb (GObject *source_object,
-                       GAsyncResult *res,
-                       gpointer user_data)
+_context_object_new_do_cb (GObject *source_object,
+                           GAsyncResult *res,
+                           gpointer user_data)
 {
-	NMTstcClientNewData *d = user_data;
+	NMTstcObjNewData *d = user_data;
 	gs_free_error GError *error = NULL;
 
-	g_assert (!d->client);
+	g_assert (!d->obj);
 
-	d->client = nm_client_new_finish (res,
-	                                  nmtst_get_rand_bool () ? &error : NULL);
+	if (d->call_nm_client_new_async) {
+		d->obj = G_OBJECT (nm_client_new_finish (res,
+		                                         nmtst_get_rand_bool () ? &error : NULL));
+	} else {
+		d->obj = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+		                                      res,
+		                                      nmtst_get_rand_bool () ? &error : NULL);
+	}
 
-	nmtst_assert_success (NM_IS_CLIENT (d->client), error);
+	nmtst_assert_success (G_IS_OBJECT (d->obj), error);
+	g_assert (G_OBJECT_TYPE (d->obj) == d->gtype);
 
 	g_main_loop_quit (d->loop);
 }
 
-static NMClient *
-_nmtstc_client_new (gboolean sync)
+static GObject *
+_context_object_new_do (GType gtype,
+                        gboolean sync,
+                        const gchar *first_property_name,
+                        va_list var_args)
 {
 	gs_free_error GError *error = NULL;
-	NMClient *client;
+	GObject *obj;
 
-	/* Create a NMClient instance synchronously, and arbitrarily use either
+	/* Create a GObject instance synchronously, and arbitrarily use either
 	 * the sync or async constructor.
 	 *
 	 * Note that the sync and async construct differ in one important aspect:
@@ -456,125 +478,128 @@ _nmtstc_client_new (gboolean sync)
 			g_source_attach (source, g_main_context_get_thread_default ());
 		}
 
-		if (nmtst_get_rand_bool ()) {
+		if (   gtype != NM_TYPE_CLIENT
+		    || first_property_name
+		    || nmtst_get_rand_bool ()) {
 			gboolean success;
 
-			client = g_object_new (NM_TYPE_CLIENT, NULL);
-			g_assert (NM_IS_CLIENT (client));
+			if (   first_property_name
+			    || nmtst_get_rand_bool ())
+				obj = g_object_new_valist (gtype, first_property_name, var_args);
+			else
+				obj = g_object_new (gtype, NULL);
 
-			success = g_initable_init (G_INITABLE (client),
+			success = g_initable_init (G_INITABLE (obj),
 			                           NULL,
 			                           nmtst_get_rand_bool () ? &error : NULL);
 			nmtst_assert_success (success, error);
 		} else {
-			client = nm_client_new (NULL,
-			                        nmtst_get_rand_bool () ? &error : NULL);
+			obj = G_OBJECT (nm_client_new (NULL,
+			                               nmtst_get_rand_bool () ? &error : NULL));
 		}
 	} else {
 		nm_auto_unref_gmainloop GMainLoop *loop = NULL;
-		NMTstcClientNewData d = { .loop = NULL, };
+		NMTstcObjNewData d = {
+			.gtype                    = gtype,
+			.loop                     = NULL,
+		};
+		gs_unref_object GObject *obj2 = NULL;
 
 		loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE);
-
 		d.loop = loop;
-		nm_client_new_async (NULL,
-		                     _nmtstc_client_new_cb,
-		                     &d);
+
+		if (   gtype != NM_TYPE_CLIENT
+		    || first_property_name
+		    || nmtst_get_rand_bool ()) {
+			if (   first_property_name
+			    || nmtst_get_rand_bool ())
+				obj2 = g_object_new_valist (gtype, first_property_name, var_args);
+			else
+				obj2 = g_object_new (gtype, NULL);
+
+			g_async_initable_init_async (G_ASYNC_INITABLE (obj2),
+			                             G_PRIORITY_DEFAULT,
+			                             NULL,
+			                             _context_object_new_do_cb,
+			                             &d);
+		} else {
+			d.call_nm_client_new_async = TRUE;
+			nm_client_new_async (NULL,
+			                     _context_object_new_do_cb,
+			                     &d);
+		}
 		g_main_loop_run (loop);
-		g_assert (NM_IS_CLIENT (d.client));
-		client = d.client;
+		obj = d.obj;
+		g_assert (!obj2 || obj == obj2);
 	}
 
-	nmtst_assert_success (NM_IS_CLIENT (client), error);
-	return client;
+	nmtst_assert_success (G_IS_OBJECT (obj), error);
+	g_assert (G_OBJECT_TYPE (obj) == gtype);
+	return obj;
 }
 
 typedef struct {
+	GType gtype;
+	const char *first_property_name;
+	va_list var_args;
 	GMainLoop *loop;
-	NMClient *client;
+	GObject *obj;
 	bool sync;
 } NewSyncInsideDispatchedData;
 
 static gboolean
-_nmtstc_client_new_inside_loop_do (gpointer user_data)
+_context_object_new_inside_loop_do (gpointer user_data)
 {
 	NewSyncInsideDispatchedData *d = user_data;
 
 	g_assert (d->loop);
-	g_assert (!d->client);
+	g_assert (!d->obj);
 
-	d->client = nmtstc_client_new (d->sync);
+	d->obj = nmtstc_context_object_new_valist (d->gtype, d->sync, d->first_property_name, d->var_args);
 	g_main_loop_quit (d->loop);
 	return G_SOURCE_CONTINUE;
 }
 
-static NMClient *
-_nmtstc_client_new_inside_loop (gboolean sync)
+static GObject *
+_context_object_new_inside_loop (GType gtype,
+                                 gboolean sync,
+                                 const char *first_property_name,
+                                 va_list var_args)
 {
 	GMainContext *context = g_main_context_get_thread_default ();
 	nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new (context, FALSE);
 	NewSyncInsideDispatchedData d = {
-		.sync = sync,
-		.loop = loop,
+		.gtype               = gtype,
+		.first_property_name = first_property_name,
+		.sync                = sync,
+		.loop                = loop,
 	};
 	nm_auto_destroy_and_unref_gsource GSource *source = NULL;
 
+	va_copy (d.var_args, var_args);
+
 	source = g_idle_source_new ();
-	g_source_set_callback (source, _nmtstc_client_new_inside_loop_do, &d, NULL);
+	g_source_set_callback (source, _context_object_new_inside_loop_do, &d, NULL);
 	g_source_attach (source, context);
 
 	g_main_loop_run (loop);
-	g_assert (NM_IS_CLIENT (d.client));
-	return d.client;
-}
-
-static NMClient *
-_nmtstc_client_new_extra_context (void)
-{
-	GMainContext *inner_context;
-	NMClient *client;
-	GSource *source;
-	guint key_idx;
-
-	inner_context = g_main_context_new ();
-	g_main_context_push_thread_default (inner_context);
-
-	client = nmtstc_client_new (TRUE);
 
-	source = nm_utils_g_main_context_create_integrate_source (inner_context);
+	va_end (d.var_args);
 
-	g_main_context_pop_thread_default (inner_context);
-	g_main_context_unref (inner_context);
-
-	g_source_attach (source, g_main_context_get_thread_default ());
-
-	for (key_idx = 0; TRUE; key_idx++) {
-		char s[100];
-
-		/* nmtstc_client_new() may call _nmtstc_client_new_extra_context() repeatedly. We
-		 * need to attach the source to a previously unused key. */
-		nm_sprintf_buf (s, "nm-test-extra-context-%u", key_idx);
-		if (!g_object_get_data (G_OBJECT (client), s)) {
-			g_object_set_data_full (G_OBJECT (client),
-			                        s,
-			                        source,
-			                        (GDestroyNotify) nm_g_source_destroy_and_unref);
-			break;
-		}
-	}
-
-	return client;
+	g_assert (G_IS_OBJECT (d.obj));
+	g_assert (G_OBJECT_TYPE (d.obj) == gtype);
+	return d.obj;
 }
 
-NMClient *
-nmtstc_client_new (gboolean allow_iterate_main_context)
+gpointer
+nmtstc_context_object_new_valist (GType gtype,
+                                  gboolean allow_iterate_main_context,
+                                  const char *first_property_name,
+                                  va_list var_args)
 {
 	gboolean inside_loop;
 	gboolean sync;
 
-	if (nmtst_get_rand_uint32 () % 5 == 0)
-		return _nmtstc_client_new_extra_context ();
-
 	if (!allow_iterate_main_context) {
 		sync = TRUE;
 		inside_loop = FALSE;
@@ -587,11 +612,26 @@ nmtstc_client_new (gboolean allow_iterate_main_context)
 	}
 
 	if (inside_loop) {
-		/* Create the client on an idle handler of the current context.
+		/* Create the obj on an idle handler of the current context.
 		 * In practice, it should make no difference, which this check
 		 * tries to prove. */
-		return _nmtstc_client_new_inside_loop (sync);
+		return _context_object_new_inside_loop (gtype, sync, first_property_name, var_args);
 	}
 
-	return _nmtstc_client_new (sync);
+	return _context_object_new_do (gtype, sync, first_property_name, var_args);
+}
+
+gpointer
+nmtstc_context_object_new (GType gtype,
+                           gboolean allow_iterate_main_context,
+                           const char *first_property_name,
+                           ...)
+{
+	GObject *obj;
+	va_list var_args;
+
+	va_start (var_args, first_property_name);
+	obj = nmtstc_context_object_new_valist (gtype, allow_iterate_main_context, first_property_name, var_args);
+	va_end (var_args);
+	return obj;
 }
diff --git a/shared/nm-udev-aux/nm-udev-utils.c b/shared/nm-udev-aux/nm-udev-utils.c
index 1b0927df..c00dcd33 100644
--- a/shared/nm-udev-aux/nm-udev-utils.c
+++ b/shared/nm-udev-aux/nm-udev-utils.c
@@ -164,7 +164,7 @@ nm_udev_client_enumerate_new (NMUdevClient *self)
 /*****************************************************************************/
 
 static gboolean
-monitor_event (GIOChannel *source,
+monitor_event (int fd,
                GIOCondition condition,
                gpointer user_data)
 {
@@ -204,7 +204,6 @@ nm_udev_client_new (const char *const*subsystems,
                     gpointer event_user_data)
 {
 	NMUdevClient *self;
-	GIOChannel *channel;
 	guint n;
 
 	self = g_slice_new0 (NMUdevClient);
@@ -237,12 +236,14 @@ nm_udev_client_new (const char *const*subsystems,
 			/* listen to events, and buffer them */
 			udev_monitor_set_receive_buffer_size (self->monitor, 4*1024*1024);
 			udev_monitor_enable_receiving (self->monitor);
-			channel = g_io_channel_unix_new (udev_monitor_get_fd (self->monitor));
-			self->watch_source = g_io_create_watch (channel, G_IO_IN);
-			g_io_channel_unref (channel);
-			g_source_set_callback (self->watch_source, (GSourceFunc)(void (*) (void)) monitor_event, self, NULL);
+
+			self->watch_source = nm_g_unix_fd_source_new (udev_monitor_get_fd (self->monitor),
+			                                              G_IO_IN,
+			                                              G_PRIORITY_DEFAULT,
+			                                              monitor_event,
+			                                              self,
+			                                              NULL);
 			g_source_attach (self->watch_source, g_main_context_get_thread_default ());
-			g_source_unref (self->watch_source);
 		}
 	}
 
@@ -258,10 +259,7 @@ nm_udev_client_unref (NMUdevClient *self)
 	if (!self)
 		return NULL;
 
-	if (self->watch_source) {
-		g_source_destroy (self->watch_source);
-		self->watch_source = NULL;
-	}
+	nm_clear_g_source_inst (&self->watch_source);
 
 	udev_monitor_unref (self->monitor);
 	self->monitor = NULL;
diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h
index f0ea1179..96e36135 100644
--- a/shared/nm-utils/nm-test-utils.h
+++ b/shared/nm-utils/nm-test-utils.h
@@ -622,7 +622,7 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_
 
 #ifdef __NETWORKMANAGER_UTILS_H__
 	/* ensure that monotonic timestamp is called (because it initially logs a line) */
-	nm_utils_get_monotonic_timestamp_s ();
+	nm_utils_get_monotonic_timestamp_sec ();
 #endif
 
 #ifdef NM_UTILS_H
@@ -848,6 +848,15 @@ nmtst_get_rand_uint32 (void)
 	return g_rand_int (nmtst_get_rand ());
 }
 
+static inline guint64
+nmtst_get_rand_uint64 (void)
+{
+	GRand *rand = nmtst_get_rand ();
+
+	return   (((guint64) g_rand_int (rand))      )
+	       | (((guint64) g_rand_int (rand)) << 32);
+}
+
 static inline guint
 nmtst_get_rand_uint (void)
 {
@@ -855,6 +864,16 @@ nmtst_get_rand_uint (void)
 	return nmtst_get_rand_uint32 ();
 }
 
+static inline gsize
+nmtst_get_rand_size (void)
+{
+	G_STATIC_ASSERT_EXPR (   sizeof (gsize) == sizeof (guint32)
+	                      || sizeof (gsize) == sizeof (guint64));
+	if (sizeof (gsize) == sizeof (guint32))
+		return nmtst_get_rand_uint32 ();
+	return nmtst_get_rand_uint64 ();
+}
+
 static inline gboolean
 nmtst_get_rand_bool (void)
 {
@@ -967,6 +986,89 @@ nmtst_rand_perm_gslist (GRand *rand, GSList *list)
 
 /*****************************************************************************/
 
+/**
+ * nmtst_get_rand_word_length:
+ * @rand: (allow-none): #GRand instance or %NULL to use the singleton.
+ *
+ * Returns: a random integer >= 0, that most frequently is somewhere between
+ * 0 and 16, but (with decreasing) probability, it can be larger. This can
+ * be used when we generate random input for unit tests.
+ */
+static inline guint
+nmtst_get_rand_word_length (GRand *rand)
+{
+	guint n;
+
+	if (!rand)
+		rand = nmtst_get_rand ();
+
+	n = 0;
+	while (TRUE) {
+		guint32 rnd = g_rand_int (rand);
+		guint probability;
+
+		/* The following python code implements a random sample with this
+		 * distribution:
+		 *
+		 *    def random_histogram(n_tries, scale = None):
+		 *        def probability(n_tok):
+		 *            import math
+		 *            return max(2, math.floor(100 / (2*(n_tok+1))))
+		 *        def n_tokens():
+		 *            import random
+		 *            n_tok = 0
+		 *            while True:
+		 *                if random.randint(0, 0xFFFFFFFF) % probability(n_tok) == 0:
+		 *                   return n_tok
+		 *                n_tok += 1
+		 *        hist = []
+		 *        i = 0;
+		 *        while i < n_tries:
+		 *            n_tok = n_tokens()
+		 *            while n_tok >= len(hist):
+		 *                hist.append(0)
+		 *            hist[n_tok] = hist[n_tok] + 1
+		 *            i += 1
+		 *        if scale is not None:
+		 *            hist = list([round(x / n_tries * scale) for x in hist])
+		 *        return hist
+		 *
+		 * For example, random_histogram(n_tries = 1000000, scale = 1000) may give
+		 *
+		 *   IDX:  [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
+		 *   SEEN: [20, 39, 59, 73, 80, 91, 92, 90, 91, 73, 73, 54, 55, 36, 24, 16, 16,  8,  4,  2,  1,  1,  0,  0,  0,  0,  0,  0,  0,  0]
+		 *
+		 * which give a sense of the probability with this individual results are returned.
+		 */
+		probability = NM_MAX (2u, (100u / (2u * (n + 1u))));
+		if ((rnd % probability) == 0)
+			return n;
+		n++;
+	}
+}
+
+/*****************************************************************************/
+
+static inline gboolean
+nmtst_g_source_assert_not_called (gpointer user_data)
+{
+	g_assert_not_reached ();
+	return G_SOURCE_CONTINUE;
+}
+
+static inline gboolean
+nmtst_g_source_set_boolean_true (gpointer user_data)
+{
+	gboolean *ptr = user_data;
+
+	g_assert (ptr);
+	g_assert (!*ptr);
+	*ptr = TRUE;
+	return G_SOURCE_CONTINUE;
+}
+
+/*****************************************************************************/
+
 static inline gboolean
 _nmtst_main_loop_run_timeout (gpointer user_data)
 {
@@ -978,13 +1080,13 @@ _nmtst_main_loop_run_timeout (gpointer user_data)
 }
 
 static inline gboolean
-nmtst_main_loop_run (GMainLoop *loop, guint timeout_ms)
+nmtst_main_loop_run (GMainLoop *loop, guint timeout_msec)
 {
 	nm_auto_unref_gsource GSource *source = NULL;
 	GMainLoop *loopx = loop;
 
-	if (timeout_ms > 0) {
-		source = g_timeout_source_new (timeout_ms);
+	if (timeout_msec > 0) {
+		source = g_timeout_source_new (timeout_msec);
 		g_source_set_callback (source, _nmtst_main_loop_run_timeout, &loopx, NULL);
 		g_source_attach (source, g_main_loop_get_context (loop));
 	}
@@ -1010,36 +1112,123 @@ _nmtst_main_loop_quit_on_notify (GObject *object, GParamSpec *pspec, gpointer us
 }
 #define nmtst_main_loop_quit_on_notify ((GCallback) _nmtst_main_loop_quit_on_notify)
 
-static inline gboolean
-_nmtst_main_context_iterate_until_timeout (gpointer user_data)
-{
-	gboolean *p_had_pointer = user_data;
-
-	g_assert (!*p_had_pointer);
-	*p_had_pointer = TRUE;
-	return G_SOURCE_CONTINUE;
-}
-
-#define nmtst_main_context_iterate_until(context, timeout_ms, condition) \
-	G_STMT_START { \
+#define nmtst_main_context_iterate_until(context, timeout_msec, condition) \
+	({ \
 		nm_auto_destroy_and_unref_gsource GSource *_source = NULL; \
 		GMainContext *_context = (context); \
 		gboolean _had_timeout = FALSE; \
 		\
-		_source = g_timeout_source_new (timeout_ms); \
-		g_source_set_callback (_source, _nmtst_main_context_iterate_until_timeout, &_had_timeout, NULL); \
+		_source = g_timeout_source_new (timeout_msec); \
+		g_source_set_callback (_source, nmtst_g_source_set_boolean_true, &_had_timeout, NULL); \
 		g_source_attach (_source, _context); \
 		\
 		while (TRUE) { \
 			if (condition) \
 				break; \
 			g_main_context_iteration (_context, TRUE); \
-			g_assert (!_had_timeout && #condition); \
+			if (_had_timeout) \
+				break; \
 		} \
+		\
+		!_had_timeout; \
+	})
+
+#define nmtst_main_context_iterate_until_assert(context, timeout_msec, condition) \
+	G_STMT_START { \
+		if (!nmtst_main_context_iterate_until (context, timeout_msec, condition)) \
+			g_assert (FALSE && #condition); \
 	} G_STMT_END
 
 /*****************************************************************************/
 
+static inline void
+nmtst_main_context_assert_no_dispatch (GMainContext *context,
+                                       guint timeout_msec)
+{
+	nm_auto_destroy_and_unref_gsource GSource *source = NULL;
+	gboolean timeout_hit = FALSE;
+
+	source = g_timeout_source_new (timeout_msec);
+	g_source_set_callback (source, nmtst_g_source_set_boolean_true, &timeout_hit, NULL);
+	g_source_attach (source, context);
+
+	while (g_main_context_iteration (context, TRUE)) {
+		if (timeout_hit)
+			return;
+		g_assert_not_reached ();
+	}
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	GMainLoop *_main_loop;
+	union {
+		GSList *_list;
+		const void *const is_waiting;
+	};
+} NMTstContextBusyWatcherData;
+
+static inline void
+_nmtst_context_busy_watcher_add_cb (gpointer data,
+                                    GObject *where_the_object_was)
+{
+	NMTstContextBusyWatcherData *watcher_data = data;
+	GSList *l;
+
+	g_assert (watcher_data);
+
+	l = g_slist_find (watcher_data->_list, where_the_object_was);
+	g_assert (l);
+
+	watcher_data->_list = g_slist_delete_link (watcher_data->_list, l);
+	if (!watcher_data->_list)
+		g_main_loop_quit (watcher_data->_main_loop);
+}
+
+static inline void
+nmtst_context_busy_watcher_add (NMTstContextBusyWatcherData *watcher_data,
+                                GObject *object)
+{
+	g_assert (watcher_data);
+	g_assert (G_IS_OBJECT (object));
+
+	if (!watcher_data->_main_loop) {
+		watcher_data->_main_loop = g_main_loop_new (g_main_context_get_thread_default (),
+		                                            FALSE);
+		g_assert (!watcher_data->_list);
+	} else {
+		g_assert (   g_main_loop_get_context (watcher_data->_main_loop)
+		          == (g_main_context_get_thread_default () ?: g_main_context_default ()));
+	}
+
+	g_object_weak_ref (object,
+	                   _nmtst_context_busy_watcher_add_cb,
+	                   watcher_data);
+	watcher_data->_list = g_slist_prepend (watcher_data->_list, object);
+}
+
+static inline void
+nmtst_context_busy_watcher_wait (NMTstContextBusyWatcherData *watcher_data)
+{
+	g_assert (watcher_data);
+
+	if (!watcher_data->_main_loop) {
+		g_assert (!watcher_data->_list);
+		return;
+	}
+
+	if (watcher_data->_list) {
+		if (!nmtst_main_loop_run (watcher_data->_main_loop, 5000))
+			g_error ("timeout running mainloop waiting for GObject to destruct");
+	}
+
+	g_assert (!watcher_data->_list);
+	nm_clear_pointer (&watcher_data->_main_loop, g_main_loop_unref);
+}
+
+/*****************************************************************************/
+
 static inline const char *
 nmtst_get_sudo_cmd (void)
 {
@@ -1156,15 +1345,6 @@ nmtst_uuid_generate (void)
 
 #endif
 
-#define NMTST_SWAP(x, y) \
-	G_STMT_START { \
-		char __nmtst_swap_temp[sizeof((x)) == sizeof((y)) ? (signed) sizeof((x)) : -1]; \
-		\
-		memcpy(__nmtst_swap_temp, &(y),              sizeof (__nmtst_swap_temp)); \
-		memcpy(&(y),              &(x),              sizeof (__nmtst_swap_temp)); \
-		memcpy(&(x),              __nmtst_swap_temp, sizeof (__nmtst_swap_temp)); \
-	} G_STMT_END
-
 #define nmtst_assert_str_has_substr(str, substr) \
 	G_STMT_START { \
 		const char *__str = (str); \
@@ -1362,15 +1542,27 @@ nmtst_file_get_contents (const char *filename)
 	return contents;
 }
 
-#define nmtst_file_set_contents(filename, content) \
+#define nmtst_file_set_contents_size(filename, content, size) \
 	G_STMT_START { \
 		GError *_error = NULL; \
 		gboolean _success; \
+		const char *_content = (content); \
+		gssize _size = (size); \
+		\
+		g_assert (_content); \
 		\
-		_success = g_file_set_contents ((filename), (content), -1, &_error); \
+		if (_size < 0) { \
+			g_assert (_size == -1); \
+			_size = strlen (_content); \
+		} \
+		\
+		_success = g_file_set_contents ((filename), _content, _size, &_error); \
 		nmtst_assert_success (_success, _error); \
 	} G_STMT_END
 
+#define nmtst_file_set_contents(filename, content) \
+	nmtst_file_set_contents_size (filename, content, -1)
+
 /*****************************************************************************/
 
 static inline void
@@ -1478,9 +1670,9 @@ nmtst_setting_ip_config_add_address (NMSettingIPConfig *s_ip,
 
 	g_assert (s_ip);
 
-	if (nm_utils_ipaddr_valid (AF_INET, address))
+	if (nm_utils_ipaddr_is_valid (AF_INET, address))
 		family = AF_INET;
-	else if (nm_utils_ipaddr_valid (AF_INET6, address))
+	else if (nm_utils_ipaddr_is_valid (AF_INET6, address))
 		family = AF_INET6;
 	else
 		g_assert_not_reached ();
@@ -1503,9 +1695,9 @@ nmtst_setting_ip_config_add_route (NMSettingIPConfig *s_ip,
 
 	g_assert (s_ip);
 
-	if (nm_utils_ipaddr_valid (AF_INET, dest))
+	if (nm_utils_ipaddr_is_valid (AF_INET, dest))
 		family = AF_INET;
-	else if (nm_utils_ipaddr_valid (AF_INET6, dest))
+	else if (nm_utils_ipaddr_is_valid (AF_INET6, dest))
 		family = AF_INET6;
 	else
 		g_assert_not_reached ();
@@ -1611,8 +1803,12 @@ nmtst_create_minimal_connection (const char *id, const char *uuid, const char *t
 	con = nm_connection_new ();
 #endif
 
+	g_assert (con);
+
 	s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
 
+	g_assert (s_con);
+
 	g_object_set (s_con,
 	              NM_SETTING_CONNECTION_ID, id,
 	              NM_SETTING_CONNECTION_UUID, uuid,
@@ -1959,14 +2155,14 @@ nmtst_assert_setting_is_equal (gconstpointer /* const NMSetting * */ a,
 	g_assert (NM_IS_SETTING (b));
 
 	if (NM_FLAGS_HAS (r, 0x4))
-		NMTST_SWAP (a, b);
+		NM_SWAP (a, b);
 
 	g_assert (nm_setting_compare ((NMSetting *) a,
 	                              (NMSetting *) b,
 	                              flags));
 
 	if (NM_FLAGS_HAS (r, 0x8))
-		NMTST_SWAP (a, b);
+		NM_SWAP (a, b);
 
 	g_assert (nm_setting_diff ((NMSetting *) a,
 	                           (NMSetting *) b,
@@ -2311,13 +2507,4 @@ nmtst_keyfile_get_num_keys (GKeyFile *keyfile,
 
 /*****************************************************************************/
 
-static inline gboolean
-nmtst_g_source_assert_not_called (gpointer user_data)
-{
-	g_assert_not_reached ();
-	return G_SOURCE_CONTINUE;
-}
-
-/*****************************************************************************/
-
 #endif /* __NM_TEST_UTILS_H__ */
diff --git a/shared/nm-utils/nm-vpn-editor-plugin-call.h b/shared/nm-utils/nm-vpn-editor-plugin-call.h
index ab473083..b2c39e29 100644
--- a/shared/nm-utils/nm-vpn-editor-plugin-call.h
+++ b/shared/nm-utils/nm-vpn-editor-plugin-call.h
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0+
+// SPDX-License-Identifier: LGPL-2.1+
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h
index 70ef84f9..a085c115 100644
--- a/shared/nm-version-macros.h
+++ b/shared/nm-version-macros.h
@@ -22,7 +22,7 @@
  * Evaluates to the minor version number of NetworkManager which this source
  * is compiled against.
  */
-#define NM_MINOR_VERSION (22)
+#define NM_MINOR_VERSION (23)
 
 /**
  * NM_MICRO_VERSION:
@@ -30,7 +30,7 @@
  * Evaluates to the micro version number of NetworkManager which this source
  * compiled against.
  */
-#define NM_MICRO_VERSION (10)
+#define NM_MICRO_VERSION (90)
 
 /**
  * NM_CHECK_VERSION:
@@ -63,8 +63,7 @@
 #define NM_VERSION_1_18   (NM_ENCODE_VERSION (1, 18, 0))
 #define NM_VERSION_1_20   (NM_ENCODE_VERSION (1, 20, 0))
 #define NM_VERSION_1_22   (NM_ENCODE_VERSION (1, 22, 0))
-#define NM_VERSION_1_22_2 (NM_ENCODE_VERSION (1, 22, 2))
-#define NM_VERSION_1_22_8 (NM_ENCODE_VERSION (1, 22, 8))
+#define NM_VERSION_1_24   (NM_ENCODE_VERSION (1, 24, 0))
 
 /* For releases, NM_API_VERSION is equal to NM_VERSION.
  *
diff --git a/shared/nm-version-macros.h.in b/shared/nm-version-macros.h.in
index 8704ac57..e97f506c 100644
--- a/shared/nm-version-macros.h.in
+++ b/shared/nm-version-macros.h.in
@@ -63,8 +63,7 @@
 #define NM_VERSION_1_18   (NM_ENCODE_VERSION (1, 18, 0))
 #define NM_VERSION_1_20   (NM_ENCODE_VERSION (1, 20, 0))
 #define NM_VERSION_1_22   (NM_ENCODE_VERSION (1, 22, 0))
-#define NM_VERSION_1_22_2 (NM_ENCODE_VERSION (1, 22, 2))
-#define NM_VERSION_1_22_8 (NM_ENCODE_VERSION (1, 22, 8))
+#define NM_VERSION_1_24   (NM_ENCODE_VERSION (1, 24, 0))
 
 /* For releases, NM_API_VERSION is equal to NM_VERSION.
  *
diff --git a/shared/systemd/sd-adapt-shared/missing_syscall.h b/shared/systemd/sd-adapt-shared/arphrd-list.h
index 637892c2..637892c2 100644
--- a/shared/systemd/sd-adapt-shared/missing_syscall.h
+++ b/shared/systemd/sd-adapt-shared/arphrd-list.h
diff --git a/shared/systemd/sd-adapt-shared/missing_keyctl.h b/shared/systemd/sd-adapt-shared/missing_keyctl.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/missing_keyctl.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h b/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h
index 490cae92..26a043f5 100644
--- a/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h
+++ b/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0+
+// SPDX-License-Identifier: LGPL-2.1+
 /*
  * Copyright (C) 2014 - 2018 Red Hat, Inc.
  */
@@ -84,6 +84,8 @@ G_STMT_START { \
 #include <sys/syscall.h>
 #include <sys/ioctl.h>
 
+#define ENABLE_GSHADOW FALSE
+
 /*****************************************************************************/
 
 /* systemd cannot be compiled with "-Wdeclaration-after-statement". In particular
@@ -122,6 +124,24 @@ static inline pid_t _nm_gettid(void) {
 #define HAVE_EXPLICIT_BZERO 0
 #endif
 
+#if defined(HAVE_DECL_PIDFD_OPEN) && HAVE_DECL_PIDFD_OPEN == 1
+#define HAVE_PIDFD_OPEN 1
+#else
+#define HAVE_PIDFD_OPEN 0
+#endif
+
+#if defined(HAVE_DECL_PIDFD_SEND_SIGNAL) && HAVE_DECL_PIDFD_SEND_SIGNAL == 1
+#define HAVE_PIDFD_SEND_SIGNAL 1
+#else
+#define HAVE_PIDFD_SEND_SIGNAL 0
+#endif
+
+#if defined(HAVE_DECL_RT_SIGQUEUEINFO) && HAVE_DECL_RT_SIGQUEUEINFO == 1
+#define HAVE_RT_SIGQUEUEINFO 1
+#else
+#define HAVE_RT_SIGQUEUEINFO 0
+#endif
+
 #endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */
 
 /*****************************************************************************/
diff --git a/shared/systemd/src/basic/cgroup-util.h b/shared/systemd/src/basic/cgroup-util.h
new file mode 100644
index 00000000..237139fa
--- /dev/null
+++ b/shared/systemd/src/basic/cgroup-util.h
@@ -0,0 +1,254 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <dirent.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+
+#include "def.h"
+#include "set.h"
+
+#define SYSTEMD_CGROUP_CONTROLLER_LEGACY "name=systemd"
+#define SYSTEMD_CGROUP_CONTROLLER_HYBRID "name=unified"
+#define SYSTEMD_CGROUP_CONTROLLER "_systemd"
+
+/* An enum of well known cgroup controllers */
+typedef enum CGroupController {
+        /* Original cgroup controllers */
+        CGROUP_CONTROLLER_CPU,
+        CGROUP_CONTROLLER_CPUACCT,    /* v1 only */
+        CGROUP_CONTROLLER_CPUSET,     /* v2 only */
+        CGROUP_CONTROLLER_IO,         /* v2 only */
+        CGROUP_CONTROLLER_BLKIO,      /* v1 only */
+        CGROUP_CONTROLLER_MEMORY,
+        CGROUP_CONTROLLER_DEVICES,    /* v1 only */
+        CGROUP_CONTROLLER_PIDS,
+
+        /* BPF-based pseudo-controllers, v2 only */
+        CGROUP_CONTROLLER_BPF_FIREWALL,
+        CGROUP_CONTROLLER_BPF_DEVICES,
+
+        _CGROUP_CONTROLLER_MAX,
+        _CGROUP_CONTROLLER_INVALID = -1,
+} CGroupController;
+
+#define CGROUP_CONTROLLER_TO_MASK(c) (1U << (c))
+
+/* A bit mask of well known cgroup controllers */
+typedef enum CGroupMask {
+        CGROUP_MASK_CPU = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPU),
+        CGROUP_MASK_CPUACCT = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPUACCT),
+        CGROUP_MASK_CPUSET = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPUSET),
+        CGROUP_MASK_IO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_IO),
+        CGROUP_MASK_BLKIO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BLKIO),
+        CGROUP_MASK_MEMORY = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_MEMORY),
+        CGROUP_MASK_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_DEVICES),
+        CGROUP_MASK_PIDS = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_PIDS),
+        CGROUP_MASK_BPF_FIREWALL = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_FIREWALL),
+        CGROUP_MASK_BPF_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BPF_DEVICES),
+
+        /* All real cgroup v1 controllers */
+        CGROUP_MASK_V1 = CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT|CGROUP_MASK_BLKIO|CGROUP_MASK_MEMORY|CGROUP_MASK_DEVICES|CGROUP_MASK_PIDS,
+
+        /* All real cgroup v2 controllers */
+        CGROUP_MASK_V2 = CGROUP_MASK_CPU|CGROUP_MASK_CPUSET|CGROUP_MASK_IO|CGROUP_MASK_MEMORY|CGROUP_MASK_PIDS,
+
+        /* All cgroup v2 BPF pseudo-controllers */
+        CGROUP_MASK_BPF = CGROUP_MASK_BPF_FIREWALL|CGROUP_MASK_BPF_DEVICES,
+
+        _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1
+} CGroupMask;
+
+static inline CGroupMask CGROUP_MASK_EXTEND_JOINED(CGroupMask mask) {
+        /* We always mount "cpu" and "cpuacct" in the same hierarchy. Hence, when one bit is set also set the other */
+
+        if (mask & (CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT))
+                mask |= (CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT);
+
+        return mask;
+}
+
+CGroupMask get_cpu_accounting_mask(void);
+bool cpu_accounting_is_cheap(void);
+
+/* Special values for all weight knobs on unified hierarchy */
+#define CGROUP_WEIGHT_INVALID ((uint64_t) -1)
+#define CGROUP_WEIGHT_MIN UINT64_C(1)
+#define CGROUP_WEIGHT_MAX UINT64_C(10000)
+#define CGROUP_WEIGHT_DEFAULT UINT64_C(100)
+
+#define CGROUP_LIMIT_MIN UINT64_C(0)
+#define CGROUP_LIMIT_MAX ((uint64_t) -1)
+
+static inline bool CGROUP_WEIGHT_IS_OK(uint64_t x) {
+        return
+            x == CGROUP_WEIGHT_INVALID ||
+            (x >= CGROUP_WEIGHT_MIN && x <= CGROUP_WEIGHT_MAX);
+}
+
+/* IO limits on unified hierarchy */
+typedef enum CGroupIOLimitType {
+        CGROUP_IO_RBPS_MAX,
+        CGROUP_IO_WBPS_MAX,
+        CGROUP_IO_RIOPS_MAX,
+        CGROUP_IO_WIOPS_MAX,
+
+        _CGROUP_IO_LIMIT_TYPE_MAX,
+        _CGROUP_IO_LIMIT_TYPE_INVALID = -1
+} CGroupIOLimitType;
+
+extern const uint64_t cgroup_io_limit_defaults[_CGROUP_IO_LIMIT_TYPE_MAX];
+
+const char* cgroup_io_limit_type_to_string(CGroupIOLimitType t) _const_;
+CGroupIOLimitType cgroup_io_limit_type_from_string(const char *s) _pure_;
+
+/* Special values for the cpu.shares attribute */
+#define CGROUP_CPU_SHARES_INVALID ((uint64_t) -1)
+#define CGROUP_CPU_SHARES_MIN UINT64_C(2)
+#define CGROUP_CPU_SHARES_MAX UINT64_C(262144)
+#define CGROUP_CPU_SHARES_DEFAULT UINT64_C(1024)
+
+static inline bool CGROUP_CPU_SHARES_IS_OK(uint64_t x) {
+        return
+            x == CGROUP_CPU_SHARES_INVALID ||
+            (x >= CGROUP_CPU_SHARES_MIN && x <= CGROUP_CPU_SHARES_MAX);
+}
+
+/* Special values for the blkio.weight attribute */
+#define CGROUP_BLKIO_WEIGHT_INVALID ((uint64_t) -1)
+#define CGROUP_BLKIO_WEIGHT_MIN UINT64_C(10)
+#define CGROUP_BLKIO_WEIGHT_MAX UINT64_C(1000)
+#define CGROUP_BLKIO_WEIGHT_DEFAULT UINT64_C(500)
+
+static inline bool CGROUP_BLKIO_WEIGHT_IS_OK(uint64_t x) {
+        return
+            x == CGROUP_BLKIO_WEIGHT_INVALID ||
+            (x >= CGROUP_BLKIO_WEIGHT_MIN && x <= CGROUP_BLKIO_WEIGHT_MAX);
+}
+
+typedef enum CGroupUnified {
+        CGROUP_UNIFIED_UNKNOWN = -1,
+        CGROUP_UNIFIED_NONE = 0,        /* Both systemd and controllers on legacy */
+        CGROUP_UNIFIED_SYSTEMD = 1,     /* Only systemd on unified */
+        CGROUP_UNIFIED_ALL = 2,         /* Both systemd and controllers on unified */
+} CGroupUnified;
+
+/*
+ * General rules:
+ *
+ * We accept named hierarchies in the syntax "foo" and "name=foo".
+ *
+ * We expect that named hierarchies do not conflict in name with a
+ * kernel hierarchy, modulo the "name=" prefix.
+ *
+ * We always generate "normalized" controller names, i.e. without the
+ * "name=" prefix.
+ *
+ * We require absolute cgroup paths. When returning, we will always
+ * generate paths with multiple adjacent / removed.
+ */
+
+int cg_enumerate_processes(const char *controller, const char *path, FILE **_f);
+int cg_read_pid(FILE *f, pid_t *_pid);
+int cg_read_event(const char *controller, const char *path, const char *event,
+                  char **val);
+
+int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d);
+int cg_read_subgroup(DIR *d, char **fn);
+
+typedef enum CGroupFlags {
+        CGROUP_SIGCONT     = 1 << 0,
+        CGROUP_IGNORE_SELF = 1 << 1,
+        CGROUP_REMOVE      = 1 << 2,
+} CGroupFlags;
+
+typedef int (*cg_kill_log_func_t)(pid_t pid, int sig, void *userdata);
+
+int cg_kill(const char *controller, const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata);
+int cg_kill_recursive(const char *controller, const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata);
+
+int cg_split_spec(const char *spec, char **ret_controller, char **ret_path);
+int cg_mangle_path(const char *path, char **result);
+
+int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs);
+int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs);
+
+int cg_pid_get_path(const char *controller, pid_t pid, char **path);
+
+int cg_rmdir(const char *controller, const char *path);
+
+int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value);
+int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret);
+int cg_get_keyed_attribute(const char *controller, const char *path, const char *attribute, char **keys, char **values);
+
+int cg_get_attribute_as_uint64(const char *controller, const char *path, const char *attribute, uint64_t *ret);
+
+int cg_set_access(const char *controller, const char *path, uid_t uid, gid_t gid);
+
+int cg_set_xattr(const char *controller, const char *path, const char *name, const void *value, size_t size, int flags);
+int cg_get_xattr(const char *controller, const char *path, const char *name, void *value, size_t size);
+int cg_get_xattr_malloc(const char *controller, const char *path, const char *name, char **ret);
+int cg_remove_xattr(const char *controller, const char *path, const char *name);
+
+int cg_install_release_agent(const char *controller, const char *agent);
+int cg_uninstall_release_agent(const char *controller);
+
+int cg_is_empty(const char *controller, const char *path);
+int cg_is_empty_recursive(const char *controller, const char *path);
+
+int cg_get_root_path(char **path);
+
+int cg_path_get_session(const char *path, char **session);
+int cg_path_get_owner_uid(const char *path, uid_t *uid);
+int cg_path_get_unit(const char *path, char **unit);
+int cg_path_get_user_unit(const char *path, char **unit);
+int cg_path_get_machine_name(const char *path, char **machine);
+int cg_path_get_slice(const char *path, char **slice);
+int cg_path_get_user_slice(const char *path, char **slice);
+
+int cg_shift_path(const char *cgroup, const char *cached_root, const char **shifted);
+int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **cgroup);
+
+int cg_pid_get_session(pid_t pid, char **session);
+int cg_pid_get_owner_uid(pid_t pid, uid_t *uid);
+int cg_pid_get_unit(pid_t pid, char **unit);
+int cg_pid_get_user_unit(pid_t pid, char **unit);
+int cg_pid_get_machine_name(pid_t pid, char **machine);
+int cg_pid_get_slice(pid_t pid, char **slice);
+int cg_pid_get_user_slice(pid_t pid, char **slice);
+
+int cg_path_decode_unit(const char *cgroup, char **unit);
+
+char *cg_escape(const char *p);
+char *cg_unescape(const char *p) _pure_;
+
+bool cg_controller_is_valid(const char *p);
+
+int cg_slice_to_path(const char *unit, char **ret);
+
+typedef const char* (*cg_migrate_callback_t)(CGroupMask mask, void *userdata);
+
+int cg_mask_supported(CGroupMask *ret);
+int cg_mask_from_string(const char *s, CGroupMask *ret);
+int cg_mask_to_string(CGroupMask mask, char **ret);
+
+int cg_kernel_controllers(Set **controllers);
+
+bool cg_ns_supported(void);
+
+int cg_all_unified(void);
+int cg_hybrid_unified(void);
+int cg_unified_controller(const char *controller);
+int cg_unified_cached(bool flush);
+static inline int cg_unified(void) {
+        return cg_unified_cached(true);
+}
+
+const char* cgroup_controller_to_string(CGroupController c) _const_;
+CGroupController cgroup_controller_from_string(const char *s) _pure_;
+
+bool is_cgroup_fs(const struct statfs *s);
+bool fd_is_cgroup_fs(int fd);
diff --git a/shared/systemd/src/basic/errno-util.h b/shared/systemd/src/basic/errno-util.h
index 8f1be6c0..65a6384e 100644
--- a/shared/systemd/src/basic/errno-util.h
+++ b/shared/systemd/src/basic/errno-util.h
@@ -101,3 +101,11 @@ static inline bool ERRNO_IS_PRIVILEGE(int r) {
                       EACCES,
                       EPERM);
 }
+
+/* Three difference errors for "not enough disk space" */
+static inline bool ERRNO_IS_DISK_SPACE(int r) {
+        return IN_SET(abs(r),
+                      ENOSPC,
+                      EDQUOT,
+                      EFBIG);
+}
diff --git a/shared/systemd/src/basic/escape.c b/shared/systemd/src/basic/escape.c
index 06d823c7..2cc5be18 100644
--- a/shared/systemd/src/basic/escape.c
+++ b/shared/systemd/src/basic/escape.c
@@ -104,7 +104,7 @@ char *cescape(const char *s) {
         return cescape_length(s, strlen(s));
 }
 
-int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) {
+int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul) {
         int r = 1;
 
         assert(p);
@@ -173,7 +173,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit)
                         return -EINVAL;
 
                 /* Don't allow NUL bytes */
-                if (a == 0 && b == 0)
+                if (a == 0 && b == 0 && !accept_nul)
                         return -EINVAL;
 
                 *ret = (a << 4U) | b;
@@ -201,7 +201,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit)
                 c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3];
 
                 /* Don't allow 0 chars */
-                if (c == 0)
+                if (c == 0 && !accept_nul)
                         return -EINVAL;
 
                 *ret = c;
@@ -229,7 +229,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit)
                     ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] <<  8U) | ((uint32_t) a[6] <<  4U) |  (uint32_t) a[7];
 
                 /* Don't allow 0 chars */
-                if (c == 0)
+                if (c == 0 && !accept_nul)
                         return -EINVAL;
 
                 /* Don't allow invalid code points */
@@ -269,7 +269,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit)
                         return -EINVAL;
 
                 /* don't allow NUL bytes */
-                if (a == 0 && b == 0 && c == 0)
+                if (a == 0 && b == 0 && c == 0 && !accept_nul)
                         return -EINVAL;
 
                 /* Don't allow bytes above 255 */
@@ -290,6 +290,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit)
         return r;
 }
 
+#if 0 /* NM_IGNORED */
 int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) {
         char *r, *t;
         const char *f;
@@ -335,7 +336,7 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi
                         return -EINVAL;
                 }
 
-                k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit);
+                k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL);
                 if (k < 0) {
                         if (flags & UNESCAPE_RELAX) {
                                 /* Invalid escape code, let's take it literal then */
@@ -362,15 +363,6 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi
         return t - r;
 }
 
-int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) {
-        return cunescape_length_with_prefix(s, length, NULL, flags, ret);
-}
-
-int cunescape(const char *s, UnescapeFlags flags, char **ret) {
-        return cunescape_length(s, strlen(s), flags, ret);
-}
-
-#if 0 /* NM_IGNORED */
 char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits) {
         char *ans, *t, *prev, *prev2;
         const char *f;
diff --git a/shared/systemd/src/basic/escape.h b/shared/systemd/src/basic/escape.h
index b26054c5..b8eb137c 100644
--- a/shared/systemd/src/basic/escape.h
+++ b/shared/systemd/src/basic/escape.h
@@ -29,22 +29,27 @@
 #define SHELL_NEED_ESCAPE_POSIX "\\\'"
 
 typedef enum UnescapeFlags {
-        UNESCAPE_RELAX = 1,
+        UNESCAPE_RELAX      = 1 << 0,
+        UNESCAPE_ACCEPT_NUL = 1 << 1,
 } UnescapeFlags;
 
 typedef enum EscapeStyle {
         ESCAPE_BACKSLASH = 1,
-        ESCAPE_POSIX = 2,
+        ESCAPE_POSIX     = 2,
 } EscapeStyle;
 
 char *cescape(const char *s);
 char *cescape_length(const char *s, size_t n);
 int cescape_char(char c, char *buf);
 
-int cunescape(const char *s, UnescapeFlags flags, char **ret);
-int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret);
 int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret);
-int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit);
+static inline int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) {
+        return cunescape_length_with_prefix(s, length, NULL, flags, ret);
+}
+static inline int cunescape(const char *s, UnescapeFlags flags, char **ret) {
+        return cunescape_length(s, strlen(s), flags, ret);
+}
+int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul);
 
 char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits);
 static inline char *xescape(const char *s, const char *bad) {
diff --git a/shared/systemd/src/basic/extract-word.c b/shared/systemd/src/basic/extract-word.c
index 2da25b03..62f015f3 100644
--- a/shared/systemd/src/basic/extract-word.c
+++ b/shared/systemd/src/basic/extract-word.c
@@ -92,7 +92,7 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
                                 bool eight_bit = false;
                                 char32_t u;
 
-                                r = cunescape_one(*p, (size_t) -1, &u, &eight_bit);
+                                r = cunescape_one(*p, (size_t) -1, &u, &eight_bit, false);
                                 if (r < 0) {
                                         if (flags & EXTRACT_CUNESCAPE_RELAX) {
                                                 s[sz++] = '\\';
diff --git a/shared/systemd/src/basic/fileio.c b/shared/systemd/src/basic/fileio.c
index 3bffa1c5..543dd4eb 100644
--- a/shared/systemd/src/basic/fileio.c
+++ b/shared/systemd/src/basic/fileio.c
@@ -56,6 +56,44 @@ int fdopen_unlocked(int fd, const char *options, FILE **ret) {
         return 0;
 }
 
+int take_fdopen_unlocked(int *fd, const char *options, FILE **ret) {
+        int     r;
+
+        assert(fd);
+
+        r = fdopen_unlocked(*fd, options, ret);
+        if (r < 0)
+                return r;
+
+        *fd = -1;
+
+        return 0;
+}
+
+FILE* take_fdopen(int *fd, const char *options) {
+        assert(fd);
+
+        FILE *f = fdopen(*fd, options);
+        if (!f)
+                return NULL;
+
+        *fd = -1;
+
+        return f;
+}
+
+DIR* take_fdopendir(int *dfd) {
+        assert(dfd);
+
+        DIR *d = fdopendir(*dfd);
+        if (!d)
+                return NULL;
+
+        *dfd = -1;
+
+        return d;
+}
+
 FILE* open_memstream_unlocked(char **ptr, size_t *sizeloc) {
         FILE *f = open_memstream(ptr, sizeloc);
         if (!f)
@@ -139,16 +177,21 @@ static int write_string_file_atomic(
         assert(fn);
         assert(line);
 
+        /* Note that we'd really like to use O_TMPFILE here, but can't really, since we want replacement
+         * semantics here, and O_TMPFILE can't offer that. i.e. rename() replaces but linkat() doesn't. */
+
         r = fopen_temporary(fn, &f, &p);
         if (r < 0)
                 return r;
 
-        (void) fchmod_umask(fileno(f), 0644);
-
         r = write_string_stream_ts(f, line, flags, ts);
         if (r < 0)
                 goto fail;
 
+        r = fchmod_umask(fileno(f), FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0644);
+        if (r < 0)
+                goto fail;
+
         if (rename(p, fn) < 0) {
                 r = -errno;
                 goto fail;
@@ -168,7 +211,7 @@ int write_string_file_ts(
                 struct timespec *ts) {
 
         _cleanup_fclose_ FILE *f = NULL;
-        int q, r;
+        int q, r, fd;
 
         assert(fn);
         assert(line);
@@ -193,26 +236,20 @@ int write_string_file_ts(
         } else
                 assert(!ts);
 
-        if (flags & WRITE_STRING_FILE_CREATE) {
-                r = fopen_unlocked(fn, "we", &f);
-                if (r < 0)
-                        goto fail;
-        } else {
-                int fd;
-
-                /* We manually build our own version of fopen(..., "we") that
-                 * works without O_CREAT */
-                fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY | ((flags & WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0));
-                if (fd < 0) {
-                        r = -errno;
-                        goto fail;
-                }
+        /* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */
+        fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY |
+                  (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) |
+                  (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0),
+                  (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666));
+        if (fd < 0) {
+                r = -errno;
+                goto fail;
+        }
 
-                r = fdopen_unlocked(fd, "w", &f);
-                if (r < 0) {
-                        safe_close(fd);
-                        goto fail;
-                }
+        r = fdopen_unlocked(fd, "w", &f);
+        if (r < 0) {
+                safe_close(fd);
+                goto fail;
         }
 
         if (flags & WRITE_STRING_FILE_DISABLE_BUFFER)
@@ -547,17 +584,19 @@ finalize:
         return r;
 }
 
-int read_full_file_full(const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) {
+int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) {
         _cleanup_fclose_ FILE *f = NULL;
         int r;
 
         assert(filename);
         assert(contents);
 
-        r = fopen_unlocked(filename, "re", &f);
+        r = xfopenat(dir_fd, filename, "re", 0, &f);
         if (r < 0)
                 return r;
 
+        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         return read_full_stream_full(f, filename, flags, contents, size);
 }
 
@@ -686,6 +725,81 @@ DIR *xopendirat(int fd, const char *name, int flags) {
         return d;
 }
 
+static int mode_to_flags(const char *mode) {
+        const char *p;
+        int flags;
+
+        if ((p = startswith(mode, "r+")))
+                flags = O_RDWR;
+        else if ((p = startswith(mode, "r")))
+                flags = O_RDONLY;
+        else if ((p = startswith(mode, "w+")))
+                flags = O_RDWR|O_CREAT|O_TRUNC;
+        else if ((p = startswith(mode, "w")))
+                flags = O_WRONLY|O_CREAT|O_TRUNC;
+        else if ((p = startswith(mode, "a+")))
+                flags = O_RDWR|O_CREAT|O_APPEND;
+        else if ((p = startswith(mode, "a")))
+                flags = O_WRONLY|O_CREAT|O_APPEND;
+        else
+                return -EINVAL;
+
+        for (; *p != 0; p++) {
+
+                switch (*p) {
+
+                case 'e':
+                        flags |= O_CLOEXEC;
+                        break;
+
+                case 'x':
+                        flags |= O_EXCL;
+                        break;
+
+                case 'm':
+                        /* ignore this here, fdopen() might care later though */
+                        break;
+
+                case 'c': /* not sure what to do about this one */
+                default:
+                        return -EINVAL;
+                }
+        }
+
+        return flags;
+}
+
+int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) {
+        FILE *f;
+
+        /* A combination of fopen() with openat() */
+
+        if (dir_fd == AT_FDCWD && flags == 0) {
+                f = fopen(path, mode);
+                if (!f)
+                        return -errno;
+        } else {
+                int fd, mode_flags;
+
+                mode_flags = mode_to_flags(mode);
+                if (mode_flags < 0)
+                        return mode_flags;
+
+                fd = openat(dir_fd, path, mode_flags | flags);
+                if (fd < 0)
+                        return -errno;
+
+                f = fdopen(fd, mode);
+                if (!f) {
+                        safe_close(fd);
+                        return -errno;
+                }
+        }
+
+        *ret = f;
+        return 0;
+}
+
 #if 0 /* NM_IGNORED */
 static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) {
         char **i;
diff --git a/shared/systemd/src/basic/fileio.h b/shared/systemd/src/basic/fileio.h
index 31bfef33..58daabaa 100644
--- a/shared/systemd/src/basic/fileio.h
+++ b/shared/systemd/src/basic/fileio.h
@@ -6,6 +6,7 @@
 #include <stddef.h>
 #include <stdio.h>
 #include <sys/stat.h>
+#include <sys/fcntl.h>
 #include <sys/types.h>
 
 #include "macro.h"
@@ -22,6 +23,7 @@ typedef enum {
         WRITE_STRING_FILE_DISABLE_BUFFER    = 1 << 5,
         WRITE_STRING_FILE_NOFOLLOW          = 1 << 6,
         WRITE_STRING_FILE_MKDIR_0755        = 1 << 7,
+        WRITE_STRING_FILE_MODE_0600         = 1 << 8,
 
         /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one
            more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file()
@@ -37,6 +39,9 @@ typedef enum {
 
 int fopen_unlocked(const char *path, const char *options, FILE **ret);
 int fdopen_unlocked(int fd, const char *options, FILE **ret);
+int take_fdopen_unlocked(int *fd, const char *options, FILE **ret);
+FILE* take_fdopen(int *fd, const char *options);
+DIR* take_fdopendir(int *dfd);
 FILE* open_memstream_unlocked(char **ptr, size_t *sizeloc);
 FILE* fmemopen_unlocked(void *buf, size_t size, const char *mode);
 
@@ -52,9 +57,9 @@ static inline int write_string_file(const char *fn, const char *line, WriteStrin
 int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4);
 
 int read_one_line_file(const char *filename, char **line);
-int read_full_file_full(const char *filename, ReadFullFileFlags flags, char **contents, size_t *size);
+int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size);
 static inline int read_full_file(const char *filename, char **contents, size_t *size) {
-        return read_full_file_full(filename, 0, contents, size);
+        return read_full_file_full(AT_FDCWD, filename, 0, contents, size);
 }
 int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size);
 int read_full_stream_full(FILE *f, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size);
@@ -69,6 +74,7 @@ int executable_is_script(const char *path, char **interpreter);
 int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field);
 
 DIR *xopendirat(int dirfd, const char *name, int flags);
+int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret);
 
 int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f);
 int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f);
diff --git a/shared/systemd/src/basic/format-util.h b/shared/systemd/src/basic/format-util.h
index 59622508..c47fa76e 100644
--- a/shared/systemd/src/basic/format-util.h
+++ b/shared/systemd/src/basic/format-util.h
@@ -5,30 +5,18 @@
 #include <net/if.h>
 #include <stdbool.h>
 
-#if SIZEOF_PID_T == 4
-#  define PID_PRI PRIi32
-#elif SIZEOF_PID_T == 2
-#  define PID_PRI PRIi16
-#else
-#  error Unknown pid_t size
-#endif
+#include "cgroup-util.h"
+#include "macro.h"
+
+assert_cc(sizeof(pid_t) == sizeof(int32_t));
+#define PID_PRI PRIi32
 #define PID_FMT "%" PID_PRI
 
-#if SIZEOF_UID_T == 4
-#  define UID_FMT "%" PRIu32
-#elif SIZEOF_UID_T == 2
-#  define UID_FMT "%" PRIu16
-#else
-#  error Unknown uid_t size
-#endif
+assert_cc(sizeof(uid_t) == sizeof(uint32_t));
+#define UID_FMT "%" PRIu32
 
-#if SIZEOF_GID_T == 4
-#  define GID_FMT "%" PRIu32
-#elif SIZEOF_GID_T == 2
-#  define GID_FMT "%" PRIu16
-#else
-#  error Unknown gid_t size
-#endif
+assert_cc(sizeof(gid_t) == sizeof(uint32_t));
+#define GID_FMT "%" PRIu32
 
 #if SIZEOF_TIME_T == 8
 #  define PRI_TIME PRIi64
@@ -84,8 +72,15 @@ typedef enum {
         FORMAT_BYTES_TRAILING_B  = 1 << 2,
 } FormatBytesFlag;
 
-#define FORMAT_BYTES_MAX 8
+#define FORMAT_BYTES_MAX 16
 char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag);
 static inline char *format_bytes(char *buf, size_t l, uint64_t t) {
         return format_bytes_full(buf, l, t, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | FORMAT_BYTES_TRAILING_B);
 }
+static inline char *format_bytes_cgroup_protection(char *buf, size_t l, uint64_t t) {
+        if (t == CGROUP_LIMIT_MAX) {
+                (void) snprintf(buf, l, "%s", "infinity");
+                return buf;
+        }
+        return format_bytes(buf, l, t);
+}
diff --git a/shared/systemd/src/basic/fs-util.c b/shared/systemd/src/basic/fs-util.c
index e30f40fb..79966571 100644
--- a/shared/systemd/src/basic/fs-util.c
+++ b/shared/systemd/src/basic/fs-util.c
@@ -276,7 +276,52 @@ int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) {
 
         return do_chown || do_chmod;
 }
-#endif /* NM_IGNORED */
+
+int chmod_and_chown_unsafe(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+        bool do_chown, do_chmod;
+        struct stat st;
+
+        assert(path);
+
+        /* Change ownership and access mode of the specified path, see description of fchmod_and_chown().
+         * Should only be used on trusted paths. */
+
+        if (lstat(path, &st) < 0)
+                return -errno;
+
+        do_chown =
+                (uid != UID_INVALID && st.st_uid != uid) ||
+                (gid != GID_INVALID && st.st_gid != gid);
+
+        do_chmod =
+                !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
+                ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
+                 do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown()
+                             * modifies the access mode too */
+
+        if (mode == MODE_INVALID)
+                mode = st.st_mode; /* If we only shall do a chown(), save original mode, since chown() might break it. */
+        else if ((mode & S_IFMT) != 0 && ((mode ^ st.st_mode) & S_IFMT) != 0)
+                return -EINVAL; /* insist on the right file type if it was specified */
+
+        if (do_chown && do_chmod) {
+                mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
+
+                if (((minimal ^ st.st_mode) & 07777) != 0)
+                        if (chmod(path, minimal & 07777) < 0)
+                                return -errno;
+        }
+
+        if (do_chown)
+                if (lchown(path, uid, gid) < 0)
+                        return -errno;
+
+        if (do_chmod)
+                if (chmod(path, mode & 07777) < 0)
+                        return -errno;
+
+        return do_chown || do_chmod;
+}
 
 int fchmod_umask(int fd, mode_t m) {
         mode_t u;
@@ -289,7 +334,6 @@ int fchmod_umask(int fd, mode_t m) {
         return r;
 }
 
-#if 0 /* NM_IGNORED */
 int fchmod_opath(int fd, mode_t m) {
         char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
 
@@ -551,6 +595,7 @@ int get_files_in_directory(const char *path, char ***list) {
 
         return n;
 }
+#endif /* NM_IGNORED */
 
 static int getenv_tmp_dir(const char **ret_path) {
         const char *n;
@@ -622,6 +667,7 @@ static int tmp_dir_internal(const char *def, const char **ret) {
         return 0;
 }
 
+#if 0 /* NM_IGNORED */
 int var_tmp_dir(const char **ret) {
 
         /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus
@@ -631,6 +677,7 @@ int var_tmp_dir(const char **ret) {
 
         return tmp_dir_internal("/var/tmp", ret);
 }
+#endif /* NM_IGNORED */
 
 int tmp_dir(const char **ret) {
 
@@ -640,6 +687,7 @@ int tmp_dir(const char **ret) {
         return tmp_dir_internal("/tmp", ret);
 }
 
+#if 0 /* NM_IGNORED */
 int unlink_or_warn(const char *filename) {
         if (unlink(filename) < 0 && errno != ENOENT)
                 /* If the file doesn't exist and the fs simply was read-only (in which
@@ -806,6 +854,14 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                 if (r < 0)
                         return r;
 
+                /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
+                 * end. While we won't resolve the root path we still simplify it. Note that dropping the
+                 * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
+                 * anyway. Moreover at the end of this function after processing everything we'll always turn
+                 * the empty string back to "/". */
+                delete_trailing_chars(root, "/");
+                path_simplify(root, true);
+
                 if (flags & CHASE_PREFIX_ROOT) {
                         /* We don't support relative paths in combination with a root directory */
                         if (!path_is_absolute(path))
@@ -819,7 +875,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
         if (r < 0)
                 return r;
 
-        fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+        fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH);
         if (fd < 0)
                 return -errno;
 
@@ -828,6 +884,31 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                         return -errno;
         }
 
+        if (root) {
+                _cleanup_free_ char *absolute = NULL;
+                const char *e;
+
+                /* If we are operating on a root directory, let's take the root directory as it is. */
+
+                e = path_startswith(buffer, root);
+                if (!e)
+                        return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG,
+                                              SYNTHETIC_ERRNO(ECHRNG),
+                                              "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
+                                              path, root);
+
+                done = strdup(root);
+                if (!done)
+                        return -ENOMEM;
+
+                /* Make sure "todo" starts with a slash */
+                absolute = strjoin("/", e);
+                if (!absolute)
+                        return -ENOMEM;
+
+                free_and_replace(buffer, absolute);
+        }
+
         todo = buffer;
         for (;;) {
                 _cleanup_free_ char *first = NULL;
@@ -837,6 +918,15 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
 
                 /* Determine length of first component in the path */
                 n = strspn(todo, "/");                  /* The slashes */
+
+                if (n > 1) {
+                        /* If we are looking at more than a single slash then skip all but one, so that when
+                         * we are done with everything we have a normalized path with only single slashes
+                         * separating the path components. */
+                        todo += n - 1;
+                        n = 1;
+                }
+
                 m = n + strcspn(todo + n, "/");         /* The entire length of the component */
 
                 /* Extract the first component. */
@@ -939,7 +1029,6 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                 if (fstat(child, &st) < 0)
                         return -errno;
                 if ((flags & CHASE_SAFE) &&
-                    (empty_or_root(root) || (size_t)(todo - buffer) > strlen(root)) &&
                     unsafe_transition(&previous_stat, &st))
                         return log_unsafe_transition(fd, child, path, flags);
 
@@ -970,7 +1059,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                                  * directory as base. */
 
                                 safe_close(fd);
-                                fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+                                fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH);
                                 if (fd < 0)
                                         return -errno;
 
diff --git a/shared/systemd/src/basic/fs-util.h b/shared/systemd/src/basic/fs-util.h
index 78d68be9..6b9ade2e 100644
--- a/shared/systemd/src/basic/fs-util.h
+++ b/shared/systemd/src/basic/fs-util.h
@@ -34,6 +34,7 @@ int readlink_and_make_absolute(const char *p, char **r);
 
 int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
 int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid);
+int chmod_and_chown_unsafe(const char *path, mode_t mode, uid_t uid, gid_t gid);
 
 int fchmod_umask(int fd, mode_t mode);
 int fchmod_opath(int fd, mode_t m);
diff --git a/shared/systemd/src/basic/in-addr-util.c b/shared/systemd/src/basic/in-addr-util.c
index 91d687c2..0e770621 100644
--- a/shared/systemd/src/basic/in-addr-util.c
+++ b/shared/systemd/src/basic/in-addr-util.c
@@ -445,54 +445,6 @@ int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union
         return -EINVAL;
 }
 
-#if 0 /* NM_IGNORED */
-int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex) {
-        _cleanup_free_ char *buf = NULL;
-        const char *suffix;
-        int r, ifi = 0;
-
-        assert(s);
-        assert(family);
-        assert(ret);
-
-        /* Similar to in_addr_from_string_auto() but also parses an optionally appended IPv6 zone suffix ("scope id")
-         * if one is found. */
-
-        suffix = strchr(s, '%');
-        if (suffix) {
-
-                if (ifindex) {
-                        /* If we shall return the interface index, try to parse it */
-                        r = parse_ifindex(suffix + 1, &ifi);
-                        if (r < 0) {
-                                unsigned u;
-
-                                u = if_nametoindex(suffix + 1);
-                                if (u <= 0)
-                                        return -errno;
-
-                                ifi = (int) u;
-                        }
-                }
-
-                buf = strndup(s, suffix - s);
-                if (!buf)
-                        return -ENOMEM;
-
-                s = buf;
-        }
-
-        r = in_addr_from_string_auto(s, family, ret);
-        if (r < 0)
-                return r;
-
-        if (ifindex)
-                *ifindex = ifi;
-
-        return r;
-}
-#endif /* NM_IGNORED */
-
 unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr) {
         assert(addr);
 
diff --git a/shared/systemd/src/basic/in-addr-util.h b/shared/systemd/src/basic/in-addr-util.h
index 28afc7d8..ae2dad0b 100644
--- a/shared/systemd/src/basic/in-addr-util.h
+++ b/shared/systemd/src/basic/in-addr-util.h
@@ -42,7 +42,7 @@ int in_addr_prefix_to_string(int family, const union in_addr_union *u, unsigned
 int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret);
 int in_addr_from_string(int family, const char *s, union in_addr_union *ret);
 int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union *ret);
-int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex);
+
 unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr);
 struct in_addr* in4_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen);
 int in4_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen);
diff --git a/shared/systemd/src/basic/log.h b/shared/systemd/src/basic/log.h
index b81c93a5..3725b08f 100644
--- a/shared/systemd/src/basic/log.h
+++ b/shared/systemd/src/basic/log.h
@@ -59,9 +59,12 @@ void log_show_color(bool b);
 bool log_get_show_color(void) _pure_;
 void log_show_location(bool b);
 bool log_get_show_location(void) _pure_;
+void log_show_time(bool b);
+bool log_get_show_time(void) _pure_;
 
 int log_show_color_from_string(const char *e);
 int log_show_location_from_string(const char *e);
+int log_show_time_from_string(const char *e);
 
 LogTarget log_get_target(void) _pure_;
 #if 0 /* NM_IGNORED */
@@ -77,7 +80,7 @@ int log_get_max_level_realm(LogRealm realm) _pure_;
 
 #if 0 /* NM_IGNORED */
 assert_cc(STRLEN(__FILE__) > STRLEN(RELATIVE_SOURCE_PATH) + 1);
-#define PROJECT_FILE (__FILE__ + STRLEN(RELATIVE_SOURCE_PATH) + 1)
+#define PROJECT_FILE (&__FILE__[STRLEN(RELATIVE_SOURCE_PATH) + 1])
 #endif /* NM_IGNORED */
 #define PROJECT_FILE __FILE__
 
diff --git a/shared/systemd/src/basic/macro.h b/shared/systemd/src/basic/macro.h
index fc733366..6cd9c516 100644
--- a/shared/systemd/src/basic/macro.h
+++ b/shared/systemd/src/basic/macro.h
@@ -172,6 +172,11 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
 
 /* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */
 static inline unsigned long ALIGN_POWER2(unsigned long u) {
+
+        /* Avoid subtraction overflow */
+        if (u == 0)
+                return 0;
+
         /* clz(0) is undefined */
         if (u == 1)
                 return 1;
@@ -183,6 +188,29 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
         return 1UL << (sizeof(u) * 8 - __builtin_clzl(u - 1UL));
 }
 
+static inline size_t GREEDY_ALLOC_ROUND_UP(size_t l) {
+        size_t m;
+
+        /* Round up allocation sizes a bit to some reasonable, likely larger value. This is supposed to be
+         * used for cases which are likely called in an allocation loop of some form, i.e. that repetitively
+         * grow stuff, for example strv_extend() and suchlike.
+         *
+         * Note the difference to GREEDY_REALLOC() here, as this helper operates on a single size value only,
+         * and rounds up to next multiple of 2, needing no further counter.
+         *
+         * Note the benefits of direct ALIGN_POWER2() usage: type-safety for size_t, sane handling for very
+         * small (i.e. <= 2) and safe handling for very large (i.e. > SSIZE_MAX) values. */
+
+        if (l <= 2)
+                return 2; /* Never allocate less than 2 of something.  */
+
+        m = ALIGN_POWER2(l);
+        if (m == 0) /* overflow? */
+                return l;
+
+        return m;
+}
+
 #ifndef __COVERITY__
 #  define VOID_0 ((void)0)
 #else
diff --git a/shared/systemd/src/basic/memory-util.h b/shared/systemd/src/basic/memory-util.h
index 46a6907a..b7e2e67e 100644
--- a/shared/systemd/src/basic/memory-util.h
+++ b/shared/systemd/src/basic/memory-util.h
@@ -7,6 +7,7 @@
 #include <string.h>
 #include <sys/types.h>
 
+#include "alloc-util.h"
 #include "macro.h"
 
 size_t page_size(void) _pure_;
@@ -80,14 +81,19 @@ static inline void* explicit_bzero_safe(void *p, size_t l) {
 void *explicit_bzero_safe(void *p, size_t l);
 #endif
 
-static inline void erase_and_freep(void *p) {
-        void *ptr = *(void**) p;
+static inline void* erase_and_free(void *p) {
+        size_t l;
+
+        if (!p)
+                return NULL;
 
-        if (ptr) {
-                size_t l = malloc_usable_size(ptr);
-                explicit_bzero_safe(ptr, l);
-                free(ptr);
-        }
+        l = malloc_usable_size(p);
+        explicit_bzero_safe(p, l);
+        return mfree(p);
+}
+
+static inline void erase_and_freep(void *p) {
+        erase_and_free(*(void**) p);
 }
 
 /* Use with _cleanup_ to erase a single 'char' when leaving scope */
diff --git a/shared/systemd/src/basic/missing_syscall.h b/shared/systemd/src/basic/missing_syscall.h
new file mode 100644
index 00000000..4c42e665
--- /dev/null
+++ b/shared/systemd/src/basic/missing_syscall.h
@@ -0,0 +1,574 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+/* Missing glibc definitions to access certain kernel APIs */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#ifdef ARCH_MIPS
+#include <asm/sgidefs.h>
+#endif
+
+#include "missing_keyctl.h"
+#include "missing_stat.h"
+
+#if 0 /* NM_IGNORED */
+
+/* linux/kcmp.h */
+#ifndef KCMP_FILE /* 3f4994cfc15f38a3159c6e3a4b3ab2e1481a6b02 (3.19) */
+#define KCMP_FILE 0
+#endif
+
+#if !HAVE_PIVOT_ROOT
+static inline int missing_pivot_root(const char *new_root, const char *put_old) {
+        return syscall(__NR_pivot_root, new_root, put_old);
+}
+
+#  define pivot_root missing_pivot_root
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_MEMFD_CREATE
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#  if ! (defined __NR_memfd_create && __NR_memfd_create >= 0)
+#    if defined __NR_memfd_create
+#      undef __NR_memfd_create
+#    endif
+#    if defined __x86_64__
+#      define __NR_memfd_create 319
+#    elif defined __arm__
+#      define __NR_memfd_create 385
+#    elif defined __aarch64__
+#      define __NR_memfd_create 279
+#    elif defined __s390__
+#      define __NR_memfd_create 350
+#    elif defined _MIPS_SIM
+#      if _MIPS_SIM == _MIPS_SIM_ABI32
+#        define __NR_memfd_create 4354
+#      endif
+#      if _MIPS_SIM == _MIPS_SIM_NABI32
+#        define __NR_memfd_create 6318
+#      endif
+#      if _MIPS_SIM == _MIPS_SIM_ABI64
+#        define __NR_memfd_create 5314
+#      endif
+#    elif defined __i386__
+#      define __NR_memfd_create 356
+#    elif defined __arc__
+#      define __NR_memfd_create 279
+#    else
+#      warning "__NR_memfd_create unknown for your architecture"
+#    endif
+#  endif
+
+static inline int missing_memfd_create(const char *name, unsigned int flags) {
+#  ifdef __NR_memfd_create
+        return syscall(__NR_memfd_create, name, flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define memfd_create missing_memfd_create
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_GETRANDOM
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#  if ! (defined __NR_getrandom && __NR_getrandom >= 0)
+#    if defined __NR_getrandom
+#      undef __NR_getrandom
+#    endif
+#    if defined __x86_64__
+#      define __NR_getrandom 318
+#    elif defined(__i386__)
+#      define __NR_getrandom 355
+#    elif defined(__arm__)
+#      define __NR_getrandom 384
+#   elif defined(__aarch64__)
+#      define __NR_getrandom 278
+#    elif defined(__ia64__)
+#      define __NR_getrandom 1339
+#    elif defined(__m68k__)
+#      define __NR_getrandom 352
+#    elif defined(__s390x__)
+#      define __NR_getrandom 349
+#    elif defined(__powerpc__)
+#      define __NR_getrandom 359
+#    elif defined _MIPS_SIM
+#      if _MIPS_SIM == _MIPS_SIM_ABI32
+#        define __NR_getrandom 4353
+#      endif
+#      if _MIPS_SIM == _MIPS_SIM_NABI32
+#        define __NR_getrandom 6317
+#      endif
+#      if _MIPS_SIM == _MIPS_SIM_ABI64
+#        define __NR_getrandom 5313
+#      endif
+#    elif defined(__arc__)
+#      define __NR_getrandom 278
+#    else
+#      warning "__NR_getrandom unknown for your architecture"
+#    endif
+#  endif
+
+static inline int missing_getrandom(void *buffer, size_t count, unsigned flags) {
+#  ifdef __NR_getrandom
+        return syscall(__NR_getrandom, buffer, count, flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define getrandom missing_getrandom
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_GETTID
+static inline pid_t missing_gettid(void) {
+        return (pid_t) syscall(__NR_gettid);
+}
+
+#  define gettid missing_gettid
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_NAME_TO_HANDLE_AT
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#  if ! (defined __NR_name_to_handle_at && __NR_name_to_handle_at >= 0)
+#    if defined __NR_name_to_handle_at
+#      undef __NR_name_to_handle_at
+#    endif
+#    if defined(__x86_64__)
+#      define __NR_name_to_handle_at 303
+#    elif defined(__i386__)
+#      define __NR_name_to_handle_at 341
+#    elif defined(__arm__)
+#      define __NR_name_to_handle_at 370
+#    elif defined(__powerpc__)
+#      define __NR_name_to_handle_at 345
+#    elif defined(__arc__)
+#      define __NR_name_to_handle_at 264
+#    else
+#      error "__NR_name_to_handle_at is not defined"
+#    endif
+#  endif
+
+struct file_handle {
+        unsigned int handle_bytes;
+        int handle_type;
+        unsigned char f_handle[0];
+};
+
+static inline int missing_name_to_handle_at(int fd, const char *name, struct file_handle *handle, int *mnt_id, int flags) {
+#  ifdef __NR_name_to_handle_at
+        return syscall(__NR_name_to_handle_at, fd, name, handle, mnt_id, flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define name_to_handle_at missing_name_to_handle_at
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_SETNS
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#  if ! (defined __NR_setns && __NR_setns >= 0)
+#    if defined __NR_setns
+#      undef __NR_setns
+#    endif
+#    if defined(__x86_64__)
+#      define __NR_setns 308
+#    elif defined(__i386__)
+#      define __NR_setns 346
+#    elif defined(__arc__)
+#      define __NR_setns 268
+#    else
+#      error "__NR_setns is not defined"
+#    endif
+#  endif
+
+static inline int missing_setns(int fd, int nstype) {
+#  ifdef __NR_setns
+        return syscall(__NR_setns, fd, nstype);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define setns missing_setns
+#endif
+
+/* ======================================================================= */
+
+static inline pid_t raw_getpid(void) {
+#if defined(__alpha__)
+        return (pid_t) syscall(__NR_getxpid);
+#else
+        return (pid_t) syscall(__NR_getpid);
+#endif
+}
+
+/* ======================================================================= */
+
+#if !HAVE_RENAMEAT2
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#  if ! (defined __NR_renameat2 && __NR_renameat2 >= 0)
+#    if defined __NR_renameat2
+#      undef __NR_renameat2
+#    endif
+#    if defined __x86_64__
+#      define __NR_renameat2 316
+#    elif defined __arm__
+#      define __NR_renameat2 382
+#    elif defined __aarch64__
+#      define __NR_renameat2 276
+#    elif defined _MIPS_SIM
+#      if _MIPS_SIM == _MIPS_SIM_ABI32
+#        define __NR_renameat2 4351
+#      endif
+#      if _MIPS_SIM == _MIPS_SIM_NABI32
+#        define __NR_renameat2 6315
+#      endif
+#      if _MIPS_SIM == _MIPS_SIM_ABI64
+#        define __NR_renameat2 5311
+#      endif
+#    elif defined __i386__
+#      define __NR_renameat2 353
+#    elif defined __powerpc64__
+#      define __NR_renameat2 357
+#    elif defined __s390__ || defined __s390x__
+#      define __NR_renameat2 347
+#    elif defined __arc__
+#      define __NR_renameat2 276
+#    else
+#      warning "__NR_renameat2 unknown for your architecture"
+#    endif
+#  endif
+
+static inline int missing_renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) {
+#  ifdef __NR_renameat2
+        return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define renameat2 missing_renameat2
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_KCMP
+static inline int missing_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) {
+#  if defined __NR_kcmp && __NR_kcmp >= 0
+        return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define kcmp missing_kcmp
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_KEYCTL
+static inline long missing_keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) {
+#  if defined __NR_keyctl && __NR_keyctl >= 0
+        return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+
+#  define keyctl missing_keyctl
+}
+
+static inline key_serial_t missing_add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) {
+#  if defined __NR_add_key && __NR_add_key >= 0
+        return syscall(__NR_add_key, type, description, payload, plen, ringid);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+
+#  define add_key missing_add_key
+}
+
+static inline key_serial_t missing_request_key(const char *type, const char *description, const char * callout_info, key_serial_t destringid) {
+#  if defined __NR_request_key && __NR_request_key >= 0
+        return syscall(__NR_request_key, type, description, callout_info, destringid);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+
+#  define request_key missing_request_key
+}
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_COPY_FILE_RANGE
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#  if ! (defined __NR_copy_file_range && __NR_copy_file_range >= 0)
+#    if defined __NR_copy_file_range
+#      undef __NR_copy_file_range
+#    endif
+#    if defined(__x86_64__)
+#      define __NR_copy_file_range 326
+#    elif defined(__i386__)
+#      define __NR_copy_file_range 377
+#    elif defined __s390__
+#      define __NR_copy_file_range 375
+#    elif defined __arm__
+#      define __NR_copy_file_range 391
+#    elif defined __aarch64__
+#      define __NR_copy_file_range 285
+#    elif defined __powerpc__
+#      define __NR_copy_file_range 379
+#    elif defined __arc__
+#      define __NR_copy_file_range 285
+#    else
+#      warning "__NR_copy_file_range not defined for your architecture"
+#    endif
+#  endif
+
+static inline ssize_t missing_copy_file_range(int fd_in, loff_t *off_in,
+                                              int fd_out, loff_t *off_out,
+                                              size_t len,
+                                              unsigned int flags) {
+#  ifdef __NR_copy_file_range
+        return syscall(__NR_copy_file_range, fd_in, off_in, fd_out, off_out, len, flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define copy_file_range missing_copy_file_range
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_BPF
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#  if ! (defined __NR_bpf && __NR_bpf >= 0)
+#    if defined __NR_bpf
+#      undef __NR_bpf
+#    endif
+#    if defined __i386__
+#      define __NR_bpf 357
+#    elif defined __x86_64__
+#      define __NR_bpf 321
+#    elif defined __aarch64__
+#      define __NR_bpf 280
+#    elif defined __arm__
+#      define __NR_bpf 386
+#    elif defined __sparc__
+#      define __NR_bpf 349
+#    elif defined __s390__
+#      define __NR_bpf 351
+#    elif defined __tilegx__
+#      define __NR_bpf 280
+#    else
+#      warning "__NR_bpf not defined for your architecture"
+#    endif
+#  endif
+
+union bpf_attr;
+
+static inline int missing_bpf(int cmd, union bpf_attr *attr, size_t size) {
+#ifdef __NR_bpf
+        return (int) syscall(__NR_bpf, cmd, attr, size);
+#else
+        errno = ENOSYS;
+        return -1;
+#endif
+}
+
+#  define bpf missing_bpf
+#endif
+
+/* ======================================================================= */
+
+#ifndef __IGNORE_pkey_mprotect
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#  if ! (defined __NR_pkey_mprotect && __NR_pkey_mprotect >= 0)
+#    if defined __NR_pkey_mprotect
+#      undef __NR_pkey_mprotect
+#    endif
+#    if defined __i386__
+#      define __NR_pkey_mprotect 380
+#    elif defined __x86_64__
+#      define __NR_pkey_mprotect 329
+#    elif defined __arm__
+#      define __NR_pkey_mprotect 394
+#    elif defined __aarch64__
+#      define __NR_pkey_mprotect 394
+#    elif defined __powerpc__
+#      define __NR_pkey_mprotect 386
+#    elif defined __s390__
+#      define __NR_pkey_mprotect 384
+#    elif defined _MIPS_SIM
+#      if _MIPS_SIM == _MIPS_SIM_ABI32
+#        define __NR_pkey_mprotect 4363
+#      endif
+#      if _MIPS_SIM == _MIPS_SIM_NABI32
+#        define __NR_pkey_mprotect 6327
+#      endif
+#      if _MIPS_SIM == _MIPS_SIM_ABI64
+#        define __NR_pkey_mprotect 5323
+#      endif
+#    else
+#      warning "__NR_pkey_mprotect not defined for your architecture"
+#    endif
+#  endif
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_STATX
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#  if ! (defined __NR_statx && __NR_statx >= 0)
+#    if defined __NR_statx
+#      undef __NR_statx
+#    endif
+#    if defined __aarch64__ || defined __arm__
+#      define __NR_statx 397
+#    elif defined __alpha__
+#      define __NR_statx 522
+#    elif defined __i386__ || defined __powerpc64__
+#      define __NR_statx 383
+#    elif defined __sparc__
+#      define __NR_statx 360
+#    elif defined __x86_64__
+#      define __NR_statx 332
+#    else
+#      warning "__NR_statx not defined for your architecture"
+#    endif
+#  endif
+
+struct statx;
+#endif
+
+/* This typedef is supposed to be always defined. */
+typedef struct statx struct_statx;
+
+#if !HAVE_STATX
+static inline ssize_t missing_statx(int dfd, const char *filename, unsigned flags, unsigned int mask, struct statx *buffer) {
+#  ifdef __NR_statx
+        return syscall(__NR_statx, dfd, filename, flags, mask, buffer);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define statx missing_statx
+#endif
+
+#if !HAVE_SET_MEMPOLICY
+
+enum {
+        MPOL_DEFAULT,
+        MPOL_PREFERRED,
+        MPOL_BIND,
+        MPOL_INTERLEAVE,
+        MPOL_LOCAL,
+};
+
+static inline long missing_set_mempolicy(int mode, const unsigned long *nodemask,
+                           unsigned long maxnode) {
+        long i;
+#  if defined __NR_set_mempolicy && __NR_set_mempolicy >= 0
+        i = syscall(__NR_set_mempolicy, mode, nodemask, maxnode);
+#  else
+        errno = ENOSYS;
+        i = -1;
+#  endif
+        return i;
+}
+
+#  define set_mempolicy missing_set_mempolicy
+#endif
+
+#if !HAVE_GET_MEMPOLICY
+static inline long missing_get_mempolicy(int *mode, unsigned long *nodemask,
+                           unsigned long maxnode, void *addr,
+                           unsigned long flags) {
+        long i;
+#  ifdef __NR_get_mempolicy
+        i = syscall(__NR_get_mempolicy, mode, nodemask, maxnode, addr, flags);
+#  else
+        errno = ENOSYS;
+        i = -1;
+#  endif
+        return i;
+}
+
+#define get_mempolicy missing_get_mempolicy
+#endif
+
+#endif /* NM_IGNORED */
+
+#if !HAVE_PIDFD_OPEN
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#  if ! (defined __NR_pidfd_open && __NR_pidfd_open >= 0)
+#    if defined __NR_pidfd_open
+#      undef __NR_pidfd_open
+#    endif
+#    define __NR_pidfd_open 434
+#endif
+static inline int pidfd_open(pid_t pid, unsigned flags) {
+#ifdef __NR_pidfd_open
+        return syscall(__NR_pidfd_open, pid, flags);
+#else
+        errno = ENOSYS;
+        return -1;
+#endif
+}
+#endif
+
+#if !HAVE_PIDFD_SEND_SIGNAL
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#  if ! (defined __NR_pidfd_send_signal && __NR_pidfd_send_signal >= 0)
+#    if defined __NR_pidfd_send_signal
+#      undef __NR_pidfd_send_signal
+#    endif
+#    define __NR_pidfd_send_signal 424
+#endif
+static inline int pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags) {
+#ifdef __NR_pidfd_open
+        return syscall(__NR_pidfd_send_signal, fd, sig, info, flags);
+#else
+        errno = ENOSYS;
+        return -1;
+#endif
+}
+#endif
+
+#if !HAVE_RT_SIGQUEUEINFO
+static inline int rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t *info) {
+        return syscall(__NR_rt_sigqueueinfo, tgid, sig, info);
+}
+#endif
diff --git a/shared/systemd/src/basic/parse-util.c b/shared/systemd/src/basic/parse-util.c
index 96cc43a2..475a06cc 100644
--- a/shared/systemd/src/basic/parse-util.c
+++ b/shared/systemd/src/basic/parse-util.c
@@ -82,11 +82,10 @@ int parse_mode(const char *s, mode_t *ret) {
         return 0;
 }
 
-int parse_ifindex(const char *s, int *ret) {
+int parse_ifindex(const char *s) {
         int ifi, r;
 
         assert(s);
-        assert(ret);
 
         r = safe_atoi(s, &ifi);
         if (r < 0)
@@ -94,26 +93,7 @@ int parse_ifindex(const char *s, int *ret) {
         if (ifi <= 0)
                 return -EINVAL;
 
-        *ret = ifi;
-        return 0;
-}
-
-int parse_ifindex_or_ifname(const char *s, int *ret) {
-        int r;
-
-        assert(s);
-        assert(ret);
-
-        r = parse_ifindex(s, ret);
-        if (r >= 0)
-                return r;
-
-        r = (int) if_nametoindex(s);
-        if (r <= 0)
-                return -errno;
-
-        *ret = r;
-        return 0;
+        return ifi;
 }
 
 int parse_mtu(int family, const char *s, uint32_t *ret) {
@@ -369,7 +349,6 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
         unsigned long l;
 
         assert(s);
-        assert(ret_u);
         assert(base <= 16);
 
         /* strtoul() is happy to parse negative values, and silently
@@ -393,7 +372,9 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
         if ((unsigned long) (unsigned) l != l)
                 return -ERANGE;
 
-        *ret_u = (unsigned) l;
+        if (ret_u)
+                *ret_u = (unsigned) l;
+
         return 0;
 }
 
@@ -402,7 +383,6 @@ int safe_atoi(const char *s, int *ret_i) {
         long l;
 
         assert(s);
-        assert(ret_i);
 
         errno = 0;
         l = strtol(s, &x, 0);
@@ -413,7 +393,9 @@ int safe_atoi(const char *s, int *ret_i) {
         if ((long) (int) l != l)
                 return -ERANGE;
 
-        *ret_i = (int) l;
+        if (ret_i)
+                *ret_i = (int) l;
+
         return 0;
 }
 
@@ -422,7 +404,6 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) {
         unsigned long long l;
 
         assert(s);
-        assert(ret_llu);
 
         s += strspn(s, WHITESPACE);
 
@@ -435,7 +416,9 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) {
         if (*s == '-')
                 return -ERANGE;
 
-        *ret_llu = l;
+        if (ret_llu)
+                *ret_llu = l;
+
         return 0;
 }
 
@@ -444,7 +427,6 @@ int safe_atolli(const char *s, long long int *ret_lli) {
         long long l;
 
         assert(s);
-        assert(ret_lli);
 
         errno = 0;
         l = strtoll(s, &x, 0);
@@ -453,7 +435,9 @@ int safe_atolli(const char *s, long long int *ret_lli) {
         if (!x || x == s || *x != 0)
                 return -EINVAL;
 
-        *ret_lli = l;
+        if (ret_lli)
+                *ret_lli = l;
+
         return 0;
 }
 
@@ -462,7 +446,6 @@ int safe_atou8(const char *s, uint8_t *ret) {
         unsigned long l;
 
         assert(s);
-        assert(ret);
 
         s += strspn(s, WHITESPACE);
 
@@ -477,7 +460,8 @@ int safe_atou8(const char *s, uint8_t *ret) {
         if ((unsigned long) (uint8_t) l != l)
                 return -ERANGE;
 
-        *ret = (uint8_t) l;
+        if (ret)
+                *ret = (uint8_t) l;
         return 0;
 }
 
@@ -511,7 +495,6 @@ int safe_atoi16(const char *s, int16_t *ret) {
         long l;
 
         assert(s);
-        assert(ret);
 
         errno = 0;
         l = strtol(s, &x, 0);
@@ -522,7 +505,9 @@ int safe_atoi16(const char *s, int16_t *ret) {
         if ((long) (int16_t) l != l)
                 return -ERANGE;
 
-        *ret = (int16_t) l;
+        if (ret)
+                *ret = (int16_t) l;
+
         return 0;
 }
 
@@ -533,7 +518,6 @@ int safe_atod(const char *s, double *ret_d) {
         double d = 0;
 
         assert(s);
-        assert(ret_d);
 
         loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
         if (loc == (locale_t) 0)
@@ -546,7 +530,9 @@ int safe_atod(const char *s, double *ret_d) {
         if (!x || x == s || *x != 0)
                 return -EINVAL;
 
-        *ret_d = (double) d;
+        if (ret_d)
+                *ret_d = (double) d;
+
         return 0;
 }
 
@@ -717,6 +703,22 @@ int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) {
         return 0;
 }
 
+int parse_ip_prefix_length(const char *s, int *ret) {
+        unsigned l;
+        int r;
+
+        r = safe_atou(s, &l);
+        if (r < 0)
+                return r;
+
+        if (l > 128)
+                return -ERANGE;
+
+        *ret = (int) l;
+
+        return 0;
+}
+
 int parse_dev(const char *s, dev_t *ret) {
         const char *major;
         unsigned x, y;
diff --git a/shared/systemd/src/basic/parse-util.h b/shared/systemd/src/basic/parse-util.h
index 3a70b792..36d76ba5 100644
--- a/shared/systemd/src/basic/parse-util.h
+++ b/shared/systemd/src/basic/parse-util.h
@@ -13,8 +13,7 @@ int parse_boolean(const char *v) _pure_;
 int parse_dev(const char *s, dev_t *ret);
 int parse_pid(const char *s, pid_t* ret_pid);
 int parse_mode(const char *s, mode_t *ret);
-int parse_ifindex(const char *s, int *ret);
-int parse_ifindex_or_ifname(const char *s, int *ret);
+int parse_ifindex(const char *s);
 int parse_mtu(int family, const char *s, uint32_t *ret);
 
 int parse_size(const char *t, uint64_t base, uint64_t *size);
@@ -46,9 +45,13 @@ static inline int safe_atoux16(const char *s, uint16_t *ret) {
 
 int safe_atoi16(const char *s, int16_t *ret);
 
-static inline int safe_atou32(const char *s, uint32_t *ret_u) {
+static inline int safe_atou32_full(const char *s, unsigned base, uint32_t *ret_u) {
         assert_cc(sizeof(uint32_t) == sizeof(unsigned));
-        return safe_atou(s, (unsigned*) ret_u);
+        return safe_atou_full(s, base, (unsigned*) ret_u);
+}
+
+static inline int safe_atou32(const char *s, uint32_t *ret_u) {
+        return safe_atou32_full(s, 0, (unsigned*) ret_u);
 }
 
 static inline int safe_atoi32(const char *s, int32_t *ret_i) {
@@ -113,4 +116,6 @@ int parse_nice(const char *p, int *ret);
 int parse_ip_port(const char *s, uint16_t *ret);
 int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high);
 
+int parse_ip_prefix_length(const char *s, int *ret);
+
 int parse_oom_score_adjust(const char *s, int *ret);
diff --git a/shared/systemd/src/basic/path-util.c b/shared/systemd/src/basic/path-util.c
index 5bcc35e5..7baab4be 100644
--- a/shared/systemd/src/basic/path-util.c
+++ b/shared/systemd/src/basic/path-util.c
@@ -540,6 +540,7 @@ bool path_equal(const char *a, const char *b) {
 bool path_equal_or_files_same(const char *a, const char *b, int flags) {
         return path_equal(a, b) || files_same(a, b, flags) > 0;
 }
+#endif /* NM_IGNORED */
 
 char* path_join_internal(const char *first, ...) {
         char *joined, *q;
@@ -599,6 +600,7 @@ char* path_join_internal(const char *first, ...) {
         return joined;
 }
 
+#if 0 /* NM_IGNORED */
 int find_binary(const char *name, char **ret) {
         int last_error, r;
         const char *p;
@@ -1126,4 +1128,30 @@ bool empty_or_root(const char *root) {
 
         return root[strspn(root, "/")] == 0;
 }
+
+bool path_strv_contains(char **l, const char *path) {
+        char **i;
+
+        STRV_FOREACH(i, l)
+                if (path_equal(*i, path))
+                        return true;
+
+        return false;
+}
+
+bool prefixed_path_strv_contains(char **l, const char *path) {
+        char **i, *j;
+
+        STRV_FOREACH(i, l) {
+                j = *i;
+                if (*j == '-')
+                        j++;
+                if (*j == '+')
+                        j++;
+                if (path_equal(j, path))
+                        return true;
+        }
+
+        return false;
+}
 #endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/path-util.h b/shared/systemd/src/basic/path-util.h
index 88aef2f3..aba8ad00 100644
--- a/shared/systemd/src/basic/path-util.h
+++ b/shared/systemd/src/basic/path-util.h
@@ -73,17 +73,7 @@ static inline bool path_equal_ptr(const char *a, const char *b) {
 }
 
 /* Note: the search terminates on the first NULL item. */
-#define PATH_IN_SET(p, ...)                                     \
-        ({                                                      \
-                char **_s;                                      \
-                bool _found = false;                            \
-                STRV_FOREACH(_s, STRV_MAKE(__VA_ARGS__))        \
-                        if (path_equal(p, *_s)) {               \
-                               _found = true;                   \
-                               break;                           \
-                        }                                       \
-                _found;                                         \
-        })
+#define PATH_IN_SET(p, ...) path_strv_contains(STRV_MAKE(__VA_ARGS__), p)
 
 char* path_startswith_strv(const char *p, char **set);
 #define PATH_STARTSWITH_SET(p, ...) path_startswith_strv(p, STRV_MAKE(__VA_ARGS__))
@@ -183,3 +173,6 @@ bool empty_or_root(const char *root);
 static inline const char *empty_to_root(const char *path) {
         return isempty(path) ? "/" : path;
 }
+
+bool path_strv_contains(char **l, const char *path);
+bool prefixed_path_strv_contains(char **l, const char *path);
diff --git a/shared/systemd/src/basic/process-util.c b/shared/systemd/src/basic/process-util.c
index 14561670..2de3541c 100644
--- a/shared/systemd/src/basic/process-util.c
+++ b/shared/systemd/src/basic/process-util.c
@@ -26,6 +26,7 @@
 #include "alloc-util.h"
 #include "architecture.h"
 #include "env-util.h"
+#include "errno-util.h"
 #include "escape.h"
 #include "fd-util.h"
 #include "fileio.h"
@@ -44,6 +45,7 @@
 #include "rlimit-util.h"
 #include "signal-util.h"
 #include "stat-util.h"
+#include "stdio-util.h"
 #include "string-table.h"
 #include "string-util.h"
 #include "terminal-util.h"
@@ -1186,6 +1188,11 @@ int must_be_root(void) {
         return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be root.");
 }
 
+static void restore_sigsetp(sigset_t **ssp) {
+        if (*ssp)
+                (void) sigprocmask(SIG_SETMASK, *ssp, NULL);
+}
+
 int safe_fork_full(
                 const char *name,
                 const int except_fds[],
@@ -1195,7 +1202,8 @@ int safe_fork_full(
 
         pid_t original_pid, pid;
         sigset_t saved_ss, ss;
-        bool block_signals = false;
+        _cleanup_(restore_sigsetp) sigset_t *saved_ssp = NULL;
+        bool block_signals = false, block_all = false;
         int prio, r;
 
         /* A wrapper around fork(), that does a couple of important initializations in addition to mere forking. Always
@@ -1210,7 +1218,7 @@ int safe_fork_full(
                  * be sure that SIGTERMs are not lost we might send to the child. */
 
                 assert_se(sigfillset(&ss) >= 0);
-                block_signals = true;
+                block_signals = block_all = true;
 
         } else if (flags & FORK_WAIT) {
                 /* Let's block SIGCHLD at least, so that we can safely watch for the child process */
@@ -1220,28 +1228,31 @@ int safe_fork_full(
                 block_signals = true;
         }
 
-        if (block_signals)
+        if (block_signals) {
                 if (sigprocmask(SIG_SETMASK, &ss, &saved_ss) < 0)
                         return log_full_errno(prio, errno, "Failed to set signal mask: %m");
+                saved_ssp = &saved_ss;
+        }
 
         if (flags & FORK_NEW_MOUNTNS)
                 pid = raw_clone(SIGCHLD|CLONE_NEWNS);
         else
                 pid = fork();
-        if (pid < 0) {
-                r = -errno;
-
-                if (block_signals) /* undo what we did above */
-                        (void) sigprocmask(SIG_SETMASK, &saved_ss, NULL);
-
-                return log_full_errno(prio, r, "Failed to fork: %m");
-        }
+        if (pid < 0)
+                return log_full_errno(prio, errno, "Failed to fork: %m");
         if (pid > 0) {
                 /* We are in the parent process */
 
                 log_debug("Successfully forked off '%s' as PID " PID_FMT ".", strna(name), pid);
 
                 if (flags & FORK_WAIT) {
+                        if (block_all) {
+                                /* undo everything except SIGCHLD */
+                                ss = saved_ss;
+                                assert_se(sigaddset(&ss, SIGCHLD) >= 0);
+                                (void) sigprocmask(SIG_SETMASK, &ss, NULL);
+                        }
+
                         r = wait_for_terminate_and_check(name, pid, (flags & FORK_LOG ? WAIT_LOG : 0));
                         if (r < 0)
                                 return r;
@@ -1249,9 +1260,6 @@ int safe_fork_full(
                                 return -EPROTO;
                 }
 
-                if (block_signals) /* undo what we did above */
-                        (void) sigprocmask(SIG_SETMASK, &saved_ss, NULL);
-
                 if (ret_pid)
                         *ret_pid = pid;
 
@@ -1260,6 +1268,9 @@ int safe_fork_full(
 
         /* We are in the child process */
 
+        /* Restore signal mask manually */
+        saved_ssp = NULL;
+
         if (flags & FORK_REOPEN_LOG) {
                 /* Close the logs if requested, before we log anything. And make sure we reopen it if needed. */
                 log_close();
@@ -1347,6 +1358,12 @@ int safe_fork_full(
                         log_full_errno(prio, r, "Failed to connect stdin/stdout to /dev/null: %m");
                         _exit(EXIT_FAILURE);
                 }
+
+        } else if (flags & FORK_STDOUT_TO_STDERR) {
+                if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) {
+                        log_full_errno(prio, errno, "Failed to connect stdout to stderr: %m");
+                        _exit(EXIT_FAILURE);
+                }
         }
 
         if (flags & FORK_RLIMIT_NOFILE_SAFE) {
@@ -1498,6 +1515,94 @@ int set_oom_score_adjust(int value) {
                                  WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER);
 }
 
+int pidfd_get_pid(int fd, pid_t *ret) {
+        char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
+        _cleanup_free_ char *fdinfo = NULL;
+        char *p;
+        int r;
+
+        if (fd < 0)
+                return -EBADF;
+
+        xsprintf(path, "/proc/self/fdinfo/%i", fd);
+
+        r = read_full_file(path, &fdinfo, NULL);
+        if (r == -ENOENT) /* if fdinfo doesn't exist we assume the process does not exist */
+                return -ESRCH;
+        if (r < 0)
+                return r;
+
+        p = startswith(fdinfo, "Pid:");
+        if (!p) {
+                p = strstr(fdinfo, "\nPid:");
+                if (!p)
+                        return -ENOTTY; /* not a pidfd? */
+
+                p += 5;
+        }
+
+        p += strspn(p, WHITESPACE);
+        p[strcspn(p, WHITESPACE)] = 0;
+
+        return parse_pid(p, ret);
+}
+
+static int rlimit_to_nice(rlim_t limit) {
+        if (limit <= 1)
+                return PRIO_MAX-1; /* i.e. 19 */
+
+        if (limit >= -PRIO_MIN + PRIO_MAX)
+                return PRIO_MIN; /* i.e. -20 */
+
+        return PRIO_MAX - (int) limit;
+}
+
+int setpriority_closest(int priority) {
+        int current, limit, saved_errno;
+        struct rlimit highest;
+
+        /* Try to set requested nice level */
+        if (setpriority(PRIO_PROCESS, 0, priority) >= 0)
+                return 1;
+
+        /* Permission failed */
+        saved_errno = -errno;
+        if (!ERRNO_IS_PRIVILEGE(saved_errno))
+                return saved_errno;
+
+        errno = 0;
+        current = getpriority(PRIO_PROCESS, 0);
+        if (errno != 0)
+                return -errno;
+
+        if (priority == current)
+                return 1;
+
+       /* Hmm, we'd expect that raising the nice level from our status quo would always work. If it doesn't,
+        * then the whole setpriority() system call is blocked to us, hence let's propagate the error
+        * right-away */
+        if (priority > current)
+                return saved_errno;
+
+        if (getrlimit(RLIMIT_NICE, &highest) < 0)
+                return -errno;
+
+        limit = rlimit_to_nice(highest.rlim_cur);
+
+        /* We are already less nice than limit allows us */
+        if (current < limit) {
+                log_debug("Cannot raise nice level, permissions and the resource limit do not allow it.");
+                return 0;
+        }
+
+        /* Push to the allowed limit */
+        if (setpriority(PRIO_PROCESS, 0, limit) < 0)
+                return -errno;
+
+        log_debug("Cannot set requested nice level (%i), used next best (%i).", priority, limit);
+        return 0;
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",
diff --git a/shared/systemd/src/basic/process-util.h b/shared/systemd/src/basic/process-util.h
index 66853c6e..7b70c9f3 100644
--- a/shared/systemd/src/basic/process-util.h
+++ b/shared/systemd/src/basic/process-util.h
@@ -149,16 +149,17 @@ void reset_cached_pid(void);
 int must_be_root(void);
 
 typedef enum ForkFlags {
-        FORK_RESET_SIGNALS      = 1 << 0, /* Reset all signal handlers and signal mask */
-        FORK_CLOSE_ALL_FDS      = 1 << 1, /* Close all open file descriptors in the child, except for 0,1,2 */
-        FORK_DEATHSIG           = 1 << 2, /* Set PR_DEATHSIG in the child */
-        FORK_NULL_STDIO         = 1 << 3, /* Connect 0,1,2 to /dev/null */
-        FORK_REOPEN_LOG         = 1 << 4, /* Reopen log connection */
-        FORK_LOG                = 1 << 5, /* Log above LOG_DEBUG log level about failures */
-        FORK_WAIT               = 1 << 6, /* Wait until child exited */
-        FORK_NEW_MOUNTNS        = 1 << 7, /* Run child in its own mount namespace */
-        FORK_MOUNTNS_SLAVE      = 1 << 8, /* Make child's mount namespace MS_SLAVE */
-        FORK_RLIMIT_NOFILE_SAFE = 1 << 9, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */
+        FORK_RESET_SIGNALS      = 1 <<  0, /* Reset all signal handlers and signal mask */
+        FORK_CLOSE_ALL_FDS      = 1 <<  1, /* Close all open file descriptors in the child, except for 0,1,2 */
+        FORK_DEATHSIG           = 1 <<  2, /* Set PR_DEATHSIG in the child */
+        FORK_NULL_STDIO         = 1 <<  3, /* Connect 0,1,2 to /dev/null */
+        FORK_REOPEN_LOG         = 1 <<  4, /* Reopen log connection */
+        FORK_LOG                = 1 <<  5, /* Log above LOG_DEBUG log level about failures */
+        FORK_WAIT               = 1 <<  6, /* Wait until child exited */
+        FORK_NEW_MOUNTNS        = 1 <<  7, /* Run child in its own mount namespace */
+        FORK_MOUNTNS_SLAVE      = 1 <<  8, /* Make child's mount namespace MS_SLAVE */
+        FORK_RLIMIT_NOFILE_SAFE = 1 <<  9, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */
+        FORK_STDOUT_TO_STDERR   = 1 << 10, /* Make stdout a copy of stderr */
 } ForkFlags;
 
 int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid);
@@ -173,7 +174,6 @@ int fork_agent(const char *name, const int except[], size_t n_except, pid_t *pid
 
 int set_oom_score_adjust(int value);
 
-#if SIZEOF_PID_T == 4
 /* The highest possibly (theoretic) pid_t value on this architecture. */
 #define PID_T_MAX ((pid_t) INT32_MAX)
 /* The maximum number of concurrent processes Linux allows on this architecture, as well as the highest valid PID value
@@ -183,12 +183,6 @@ int set_oom_score_adjust(int value);
  * these values are documented in proc(5) we feel quite confident that they are stable enough for the near future at
  * least to define them here too. */
 #define TASKS_MAX 4194303U
-#elif SIZEOF_PID_T == 2
-#define PID_T_MAX ((pid_t) INT16_MAX)
-#define TASKS_MAX 32767U
-#else
-#error "Unknown pid_t size"
-#endif
 
 assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX);
 
@@ -199,3 +193,7 @@ assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX);
                 (pid) = 0;                      \
                 _pid_;                          \
         })
+
+int pidfd_get_pid(int fd, pid_t *ret);
+
+int setpriority_closest(int priority);
diff --git a/shared/systemd/src/basic/random-util.c b/shared/systemd/src/basic/random-util.c
index 86917ca3..1a294944 100644
--- a/shared/systemd/src/basic/random-util.c
+++ b/shared/systemd/src/basic/random-util.c
@@ -9,6 +9,7 @@
 #include <elf.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <pthread.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdlib.h>
@@ -30,6 +31,8 @@
 #include "siphash24.h"
 #include "time-util.h"
 
+static bool srand_called = false;
+
 int rdrand(unsigned long *ret) {
 
         /* So, you are a "security researcher", and you wonder why we bother with using raw RDRAND here,
@@ -281,8 +284,12 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) {
         return loop_read_exact(fd, p, n, true);
 }
 
+static void clear_srand_initialization(void) {
+        srand_called = false;
+}
+
 void initialize_srand(void) {
-        static bool srand_called = false;
+        static bool pthread_atfork_registered = false;
         unsigned x;
 #if HAVE_SYS_AUXV_H
         const void *auxv;
@@ -318,6 +325,11 @@ void initialize_srand(void) {
 
         srand(x);
         srand_called = true;
+
+        if (!pthread_atfork_registered) {
+                (void) pthread_atfork(NULL, NULL, clear_srand_initialization);
+                pthread_atfork_registered = true;
+        }
 }
 
 /* INT_MAX gives us only 31 bits, so use 24 out of that. */
diff --git a/shared/systemd/src/basic/signal-util.c b/shared/systemd/src/basic/signal-util.c
new file mode 100644
index 00000000..a4b8163c
--- /dev/null
+++ b/shared/systemd/src/basic/signal-util.c
@@ -0,0 +1,308 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <stdarg.h>
+
+#include "macro.h"
+#include "parse-util.h"
+#include "signal-util.h"
+#include "stdio-util.h"
+#include "string-table.h"
+#include "string-util.h"
+
+#if 0 /* NM_IGNORED */
+int reset_all_signal_handlers(void) {
+        static const struct sigaction sa = {
+                .sa_handler = SIG_DFL,
+                .sa_flags = SA_RESTART,
+        };
+        int sig, r = 0;
+
+        for (sig = 1; sig < _NSIG; sig++) {
+
+                /* These two cannot be caught... */
+                if (IN_SET(sig, SIGKILL, SIGSTOP))
+                        continue;
+
+                /* On Linux the first two RT signals are reserved by
+                 * glibc, and sigaction() will return EINVAL for them. */
+                if (sigaction(sig, &sa, NULL) < 0)
+                        if (errno != EINVAL && r >= 0)
+                                r = -errno;
+        }
+
+        return r;
+}
+
+int reset_signal_mask(void) {
+        sigset_t ss;
+
+        if (sigemptyset(&ss) < 0)
+                return -errno;
+
+        if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int sigaction_many_ap(const struct sigaction *sa, int sig, va_list ap) {
+        int r = 0;
+
+        /* negative signal ends the list. 0 signal is skipped. */
+
+        if (sig < 0)
+                return 0;
+
+        if (sig > 0) {
+                if (sigaction(sig, sa, NULL) < 0)
+                        r = -errno;
+        }
+
+        while ((sig = va_arg(ap, int)) >= 0) {
+
+                if (sig == 0)
+                        continue;
+
+                if (sigaction(sig, sa, NULL) < 0) {
+                        if (r >= 0)
+                                r = -errno;
+                }
+        }
+
+        return r;
+}
+
+int sigaction_many(const struct sigaction *sa, ...) {
+        va_list ap;
+        int r;
+
+        va_start(ap, sa);
+        r = sigaction_many_ap(sa, 0, ap);
+        va_end(ap);
+
+        return r;
+}
+
+int ignore_signals(int sig, ...) {
+
+        static const struct sigaction sa = {
+                .sa_handler = SIG_IGN,
+                .sa_flags = SA_RESTART,
+        };
+
+        va_list ap;
+        int r;
+
+        va_start(ap, sig);
+        r = sigaction_many_ap(&sa, sig, ap);
+        va_end(ap);
+
+        return r;
+}
+
+int default_signals(int sig, ...) {
+
+        static const struct sigaction sa = {
+                .sa_handler = SIG_DFL,
+                .sa_flags = SA_RESTART,
+        };
+
+        va_list ap;
+        int r;
+
+        va_start(ap, sig);
+        r = sigaction_many_ap(&sa, sig, ap);
+        va_end(ap);
+
+        return r;
+}
+
+static int sigset_add_many_ap(sigset_t *ss, va_list ap) {
+        int sig, r = 0;
+
+        assert(ss);
+
+        while ((sig = va_arg(ap, int)) >= 0) {
+
+                if (sig == 0)
+                        continue;
+
+                if (sigaddset(ss, sig) < 0) {
+                        if (r >= 0)
+                                r = -errno;
+                }
+        }
+
+        return r;
+}
+
+int sigset_add_many(sigset_t *ss, ...) {
+        va_list ap;
+        int r;
+
+        va_start(ap, ss);
+        r = sigset_add_many_ap(ss, ap);
+        va_end(ap);
+
+        return r;
+}
+
+int sigprocmask_many(int how, sigset_t *old, ...) {
+        va_list ap;
+        sigset_t ss;
+        int r;
+
+        if (sigemptyset(&ss) < 0)
+                return -errno;
+
+        va_start(ap, old);
+        r = sigset_add_many_ap(&ss, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return r;
+
+        if (sigprocmask(how, &ss, old) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static const char *const __signal_table[] = {
+        [SIGHUP] = "HUP",
+        [SIGINT] = "INT",
+        [SIGQUIT] = "QUIT",
+        [SIGILL] = "ILL",
+        [SIGTRAP] = "TRAP",
+        [SIGABRT] = "ABRT",
+        [SIGBUS] = "BUS",
+        [SIGFPE] = "FPE",
+        [SIGKILL] = "KILL",
+        [SIGUSR1] = "USR1",
+        [SIGSEGV] = "SEGV",
+        [SIGUSR2] = "USR2",
+        [SIGPIPE] = "PIPE",
+        [SIGALRM] = "ALRM",
+        [SIGTERM] = "TERM",
+#ifdef SIGSTKFLT
+        [SIGSTKFLT] = "STKFLT",  /* Linux on SPARC doesn't know SIGSTKFLT */
+#endif
+        [SIGCHLD] = "CHLD",
+        [SIGCONT] = "CONT",
+        [SIGSTOP] = "STOP",
+        [SIGTSTP] = "TSTP",
+        [SIGTTIN] = "TTIN",
+        [SIGTTOU] = "TTOU",
+        [SIGURG] = "URG",
+        [SIGXCPU] = "XCPU",
+        [SIGXFSZ] = "XFSZ",
+        [SIGVTALRM] = "VTALRM",
+        [SIGPROF] = "PROF",
+        [SIGWINCH] = "WINCH",
+        [SIGIO] = "IO",
+        [SIGPWR] = "PWR",
+        [SIGSYS] = "SYS"
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int);
+
+const char *signal_to_string(int signo) {
+        static thread_local char buf[STRLEN("RTMIN+") + DECIMAL_STR_MAX(int) + 1];
+        const char *name;
+
+        name = __signal_to_string(signo);
+        if (name)
+                return name;
+
+        if (signo >= SIGRTMIN && signo <= SIGRTMAX)
+                xsprintf(buf, "RTMIN+%d", signo - SIGRTMIN);
+        else
+                xsprintf(buf, "%d", signo);
+
+        return buf;
+}
+
+int signal_from_string(const char *s) {
+        const char *p;
+        int signo, r;
+
+        /* Check that the input is a signal number. */
+        if (safe_atoi(s, &signo) >= 0) {
+                if (SIGNAL_VALID(signo))
+                        return signo;
+                else
+                        return -ERANGE;
+        }
+
+        /* Drop "SIG" prefix. */
+        if (startswith(s, "SIG"))
+                s += 3;
+
+        /* Check that the input is a signal name. */
+        signo = __signal_from_string(s);
+        if (signo > 0)
+                return signo;
+
+        /* Check that the input is RTMIN or
+         * RTMIN+n (0 <= n <= SIGRTMAX-SIGRTMIN). */
+        p = startswith(s, "RTMIN");
+        if (p) {
+                if (*p == '\0')
+                        return SIGRTMIN;
+                if (*p != '+')
+                        return -EINVAL;
+
+                r = safe_atoi(p, &signo);
+                if (r < 0)
+                        return r;
+
+                if (signo < 0 || signo > SIGRTMAX - SIGRTMIN)
+                        return -ERANGE;
+
+                return signo + SIGRTMIN;
+        }
+
+        /* Check that the input is RTMAX or
+         * RTMAX-n (0 <= n <= SIGRTMAX-SIGRTMIN). */
+        p = startswith(s, "RTMAX");
+        if (p) {
+                if (*p == '\0')
+                        return SIGRTMAX;
+                if (*p != '-')
+                        return -EINVAL;
+
+                r = safe_atoi(p, &signo);
+                if (r < 0)
+                        return r;
+
+                if (signo > 0 || signo < SIGRTMIN - SIGRTMAX)
+                        return -ERANGE;
+
+                return signo + SIGRTMAX;
+        }
+
+        return -EINVAL;
+}
+
+void nop_signal_handler(int sig) {
+        /* nothing here */
+}
+#endif /* NM_IGNORED */
+
+int signal_is_blocked(int sig) {
+        sigset_t ss;
+        int r;
+
+        r = pthread_sigmask(SIG_SETMASK, NULL, &ss);
+        if (r != 0)
+                return -r;
+
+        r = sigismember(&ss, sig);
+        if (r < 0)
+                return -errno;
+
+        return r;
+}
diff --git a/shared/systemd/src/basic/signal-util.h b/shared/systemd/src/basic/signal-util.h
index 92f2804c..3909ee34 100644
--- a/shared/systemd/src/basic/signal-util.h
+++ b/shared/systemd/src/basic/signal-util.h
@@ -41,3 +41,5 @@ static inline const char* signal_to_string_with_check(int n) {
 
         return signal_to_string(n);
 }
+
+int signal_is_blocked(int sig);
diff --git a/shared/systemd/src/basic/socket-util.c b/shared/systemd/src/basic/socket-util.c
index cded4545..452b9ced 100644
--- a/shared/systemd/src/basic/socket-util.c
+++ b/shared/systemd/src/basic/socket-util.c
@@ -15,6 +15,9 @@
 #include <stdlib.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
+#if 0 /* NM_IGNORED */
+#include <linux/if.h>
+#endif /* NM_IGNORED */
 
 #include "alloc-util.h"
 #include "errno-util.h"
@@ -44,237 +47,16 @@
 #endif
 
 static const char* const socket_address_type_table[] = {
-        [SOCK_STREAM] = "Stream",
-        [SOCK_DGRAM] = "Datagram",
-        [SOCK_RAW] = "Raw",
-        [SOCK_RDM] = "ReliableDatagram",
+        [SOCK_STREAM] =    "Stream",
+        [SOCK_DGRAM] =     "Datagram",
+        [SOCK_RAW] =       "Raw",
+        [SOCK_RDM] =       "ReliableDatagram",
         [SOCK_SEQPACKET] = "SequentialPacket",
-        [SOCK_DCCP] = "DatagramCongestionControl",
+        [SOCK_DCCP] =      "DatagramCongestionControl",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(socket_address_type, int);
 
-int socket_address_parse(SocketAddress *a, const char *s) {
-        _cleanup_free_ char *n = NULL;
-        char *e;
-        int r;
-
-        assert(a);
-        assert(s);
-
-        *a = (SocketAddress) {
-                .type = SOCK_STREAM,
-        };
-
-        if (*s == '[') {
-                uint16_t port;
-
-                /* IPv6 in [x:.....:z]:p notation */
-
-                e = strchr(s+1, ']');
-                if (!e)
-                        return -EINVAL;
-
-                n = strndup(s+1, e-s-1);
-                if (!n)
-                        return -ENOMEM;
-
-                errno = 0;
-                if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0)
-                        return errno_or_else(EINVAL);
-
-                e++;
-                if (*e != ':')
-                        return -EINVAL;
-
-                e++;
-                r = parse_ip_port(e, &port);
-                if (r < 0)
-                        return r;
-
-                a->sockaddr.in6.sin6_family = AF_INET6;
-                a->sockaddr.in6.sin6_port = htobe16(port);
-                a->size = sizeof(struct sockaddr_in6);
-
-        } else if (*s == '/') {
-                /* AF_UNIX socket */
-
-                size_t l;
-
-                l = strlen(s);
-                if (l >= sizeof(a->sockaddr.un.sun_path)) /* Note that we refuse non-NUL-terminated sockets when
-                                                           * parsing (the kernel itself is less strict here in what it
-                                                           * accepts) */
-                        return -EINVAL;
-
-                a->sockaddr.un.sun_family = AF_UNIX;
-                memcpy(a->sockaddr.un.sun_path, s, l);
-                a->size = offsetof(struct sockaddr_un, sun_path) + l + 1;
-
-        } else if (*s == '@') {
-                /* Abstract AF_UNIX socket */
-                size_t l;
-
-                l = strlen(s+1);
-                if (l >= sizeof(a->sockaddr.un.sun_path) - 1) /* Note that we refuse non-NUL-terminated sockets here
-                                                               * when parsing, even though abstract namespace sockets
-                                                               * explicitly allow embedded NUL bytes and don't consider
-                                                               * them special. But it's simply annoying to debug such
-                                                               * sockets. */
-                        return -EINVAL;
-
-                a->sockaddr.un.sun_family = AF_UNIX;
-                memcpy(a->sockaddr.un.sun_path+1, s+1, l);
-                a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l;
-
-        } else if (startswith(s, "vsock:")) {
-                /* AF_VSOCK socket in vsock:cid:port notation */
-                const char *cid_start = s + STRLEN("vsock:");
-                unsigned port;
-
-                e = strchr(cid_start, ':');
-                if (!e)
-                        return -EINVAL;
-
-                r = safe_atou(e+1, &port);
-                if (r < 0)
-                        return r;
-
-                n = strndup(cid_start, e - cid_start);
-                if (!n)
-                        return -ENOMEM;
-
-                if (!isempty(n)) {
-                        r = safe_atou(n, &a->sockaddr.vm.svm_cid);
-                        if (r < 0)
-                                return r;
-                } else
-                        a->sockaddr.vm.svm_cid = VMADDR_CID_ANY;
-
-                a->sockaddr.vm.svm_family = AF_VSOCK;
-                a->sockaddr.vm.svm_port = port;
-                a->size = sizeof(struct sockaddr_vm);
-
-        } else {
-                uint16_t port;
-
-                e = strchr(s, ':');
-                if (e) {
-                        r = parse_ip_port(e + 1, &port);
-                        if (r < 0)
-                                return r;
-
-                        n = strndup(s, e-s);
-                        if (!n)
-                                return -ENOMEM;
-
-                        /* IPv4 in w.x.y.z:p notation? */
-                        r = inet_pton(AF_INET, n, &a->sockaddr.in.sin_addr);
-                        if (r < 0)
-                                return -errno;
-
-                        if (r > 0) {
-                                /* Gotcha, it's a traditional IPv4 address */
-                                a->sockaddr.in.sin_family = AF_INET;
-                                a->sockaddr.in.sin_port = htobe16(port);
-                                a->size = sizeof(struct sockaddr_in);
-                        } else {
-                                unsigned idx;
-
-                                if (strlen(n) > IF_NAMESIZE-1)
-                                        return -EINVAL;
-
-                                /* Uh, our last resort, an interface name */
-                                idx = if_nametoindex(n);
-                                if (idx == 0)
-                                        return -EINVAL;
-
-                                a->sockaddr.in6.sin6_family = AF_INET6;
-                                a->sockaddr.in6.sin6_port = htobe16(port);
-                                a->sockaddr.in6.sin6_scope_id = idx;
-                                a->sockaddr.in6.sin6_addr = in6addr_any;
-                                a->size = sizeof(struct sockaddr_in6);
-                        }
-                } else {
-
-                        /* Just a port */
-                        r = parse_ip_port(s, &port);
-                        if (r < 0)
-                                return r;
-
-                        if (socket_ipv6_is_supported()) {
-                                a->sockaddr.in6.sin6_family = AF_INET6;
-                                a->sockaddr.in6.sin6_port = htobe16(port);
-                                a->sockaddr.in6.sin6_addr = in6addr_any;
-                                a->size = sizeof(struct sockaddr_in6);
-                        } else {
-                                a->sockaddr.in.sin_family = AF_INET;
-                                a->sockaddr.in.sin_port = htobe16(port);
-                                a->sockaddr.in.sin_addr.s_addr = INADDR_ANY;
-                                a->size = sizeof(struct sockaddr_in);
-                        }
-                }
-        }
-
-        return 0;
-}
-
-int socket_address_parse_and_warn(SocketAddress *a, const char *s) {
-        SocketAddress b;
-        int r;
-
-        /* Similar to socket_address_parse() but warns for IPv6 sockets when we don't support them. */
-
-        r = socket_address_parse(&b, s);
-        if (r < 0)
-                return r;
-
-        if (!socket_ipv6_is_supported() && b.sockaddr.sa.sa_family == AF_INET6) {
-                log_warning("Binding to IPv6 address not available since kernel does not support IPv6.");
-                return -EAFNOSUPPORT;
-        }
-
-        *a = b;
-        return 0;
-}
-
-int socket_address_parse_netlink(SocketAddress *a, const char *s) {
-        _cleanup_free_ char *word = NULL;
-        unsigned group = 0;
-        int family, r;
-
-        assert(a);
-        assert(s);
-
-        zero(*a);
-        a->type = SOCK_RAW;
-
-        r = extract_first_word(&s, &word, NULL, 0);
-        if (r < 0)
-                return r;
-        if (r == 0)
-                return -EINVAL;
-
-        family = netlink_family_from_string(word);
-        if (family < 0)
-                return -EINVAL;
-
-        if (!isempty(s)) {
-                r = safe_atou(s, &group);
-                if (r < 0)
-                        return r;
-        }
-
-        a->sockaddr.nl.nl_family = AF_NETLINK;
-        a->sockaddr.nl.nl_groups = group;
-
-        a->type = SOCK_RAW;
-        a->size = sizeof(struct sockaddr_nl);
-        a->protocol = family;
-
-        return 0;
-}
-
 int socket_address_verify(const SocketAddress *a, bool strict) {
         assert(a);
 
@@ -484,32 +266,6 @@ bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) {
         return true;
 }
 
-bool socket_address_is(const SocketAddress *a, const char *s, int type) {
-        struct SocketAddress b;
-
-        assert(a);
-        assert(s);
-
-        if (socket_address_parse(&b, s) < 0)
-                return false;
-
-        b.type = type;
-
-        return socket_address_equal(a, &b);
-}
-
-bool socket_address_is_netlink(const SocketAddress *a, const char *s) {
-        struct SocketAddress b;
-
-        assert(a);
-        assert(s);
-
-        if (socket_address_parse_netlink(&b, s) < 0)
-                return false;
-
-        return socket_address_equal(a, &b);
-}
-
 const char* socket_address_get_path(const SocketAddress *a) {
         assert(a);
 
@@ -912,7 +668,7 @@ static const char* const ip_tos_table[] = {
 
 DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff);
 
-bool ifname_valid(const char *p) {
+bool ifname_valid_full(const char *p, bool alternative) {
         bool numeric = true;
 
         /* Checks whether a network interface name is valid. This is inspired by dev_valid_name() in the kernel sources
@@ -922,8 +678,13 @@ bool ifname_valid(const char *p) {
         if (isempty(p))
                 return false;
 
-        if (strlen(p) >= IFNAMSIZ)
-                return false;
+        if (alternative) {
+                if (strlen(p) >= ALTIFNAMSIZ)
+                        return false;
+        } else {
+                if (strlen(p) >= IFNAMSIZ)
+                        return false;
+        }
 
         if (dot_or_dot_dot(p))
                 return false;
diff --git a/shared/systemd/src/basic/socket-util.h b/shared/systemd/src/basic/socket-util.h
index a0886e0e..2596c540 100644
--- a/shared/systemd/src/basic/socket-util.h
+++ b/shared/systemd/src/basic/socket-util.h
@@ -43,6 +43,8 @@ union sockaddr_union {
         uint8_t un_buffer[sizeof(struct sockaddr_un) + 1];
 };
 
+#define SUN_PATH_LEN (sizeof(((struct sockaddr_un){}).sun_path))
+
 typedef struct SocketAddress {
         union sockaddr_union sockaddr;
 
@@ -70,12 +72,6 @@ typedef enum SocketAddressBindIPv6Only {
 const char* socket_address_type_to_string(int t) _const_;
 int socket_address_type_from_string(const char *s) _pure_;
 
-int socket_address_parse(SocketAddress *a, const char *s);
-int socket_address_parse_and_warn(SocketAddress *a, const char *s);
-int socket_address_parse_netlink(SocketAddress *a, const char *s);
-int socket_address_print(const SocketAddress *a, char **p);
-int socket_address_verify(const SocketAddress *a, bool strict) _pure_;
-
 int sockaddr_un_unlink(const struct sockaddr_un *sa);
 
 static inline int socket_address_unlink(const SocketAddress *a) {
@@ -96,11 +92,9 @@ int socket_address_listen(
                 mode_t directory_mode,
                 mode_t socket_mode,
                 const char *label);
-int make_socket_fd(int log_level, const char* address, int type, int flags);
-
-bool socket_address_is(const SocketAddress *a, const char *s, int type);
-bool socket_address_is_netlink(const SocketAddress *a, const char *s);
 
+int socket_address_verify(const SocketAddress *a, bool strict) _pure_;
+int socket_address_print(const SocketAddress *a, char **p);
 bool socket_address_matches_fd(const SocketAddress *a, int fd);
 
 bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_;
@@ -132,7 +126,10 @@ int fd_inc_rcvbuf(int fd, size_t n);
 int ip_tos_to_string_alloc(int i, char **s);
 int ip_tos_from_string(const char *s);
 
-bool ifname_valid(const char *p);
+bool ifname_valid_full(const char *p, bool alternative);
+static inline bool ifname_valid(const char *p) {
+        return ifname_valid_full(p, false);
+}
 bool address_label_valid(const char *p);
 
 int getpeercred(int fd, struct ucred *ucred);
diff --git a/shared/systemd/src/basic/stat-util.c b/shared/systemd/src/basic/stat-util.c
index 071050f2..a895c1c3 100644
--- a/shared/systemd/src/basic/stat-util.c
+++ b/shared/systemd/src/basic/stat-util.c
@@ -12,6 +12,7 @@
 #include "alloc-util.h"
 #include "dirent-util.h"
 #include "fd-util.h"
+#include "fileio.h"
 #include "fs-util.h"
 #include "macro.h"
 #include "missing_fs.h"
@@ -31,6 +32,7 @@ int is_symlink(const char *path) {
 
         return !!S_ISLNK(info.st_mode);
 }
+#endif /* NM_IGNORED */
 
 int is_dir(const char* path, bool follow) {
         struct stat st;
@@ -48,6 +50,7 @@ int is_dir(const char* path, bool follow) {
         return !!S_ISDIR(st.st_mode);
 }
 
+#if 0 /* NM_IGNORED */
 int is_dir_fd(int fd) {
         struct stat st;
 
@@ -80,10 +83,9 @@ int dir_is_empty_at(int dir_fd, const char *path) {
         if (fd < 0)
                 return -errno;
 
-        d = fdopendir(fd);
+        d = take_fdopendir(&fd);
         if (!d)
                 return -errno;
-        fd = -1;
 
         FOREACH_DIRENT(de, d, return -errno)
                 return 0;
diff --git a/shared/systemd/src/basic/string-table.h b/shared/systemd/src/basic/string-table.h
index 2d3cf814..96924778 100644
--- a/shared/systemd/src/basic/string-table.h
+++ b/shared/systemd/src/basic/string-table.h
@@ -44,7 +44,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
                 char *s;                                                \
                 if (i < 0 || i > max)                                   \
                         return -ERANGE;                                 \
-                if (i < (type) ELEMENTSOF(name##_table)) {              \
+                if (i < (type) ELEMENTSOF(name##_table) && name##_table[i]) { \
                         s = strdup(name##_table[i]);                    \
                         if (!s)                                         \
                                 return -ENOMEM;                         \
diff --git a/shared/systemd/src/basic/string-util.c b/shared/systemd/src/basic/string-util.c
index 3d2feb18..9f15caca 100644
--- a/shared/systemd/src/basic/string-util.c
+++ b/shared/systemd/src/basic/string-util.c
@@ -115,7 +115,7 @@ static size_t strcspn_escaped(const char *s, const char *reject) {
         bool escaped = false;
         int n;
 
-        for (n=0; s[n]; n++) {
+        for (n = 0; s[n] != '\0'; n++) {
                 if (escaped)
                         escaped = false;
                 else if (s[n] == '\\')
@@ -124,50 +124,62 @@ static size_t strcspn_escaped(const char *s, const char *reject) {
                         break;
         }
 
-        /* if s ends in \, return index of previous char */
-        return n - escaped;
+        return n;
 }
 
 /* Split a string into words. */
-const char* split(const char **state, size_t *l, const char *separator, SplitFlags flags) {
+const char* split(
+                const char **state,
+                size_t *l,
+                const char *separator,
+                SplitFlags flags) {
+
         const char *current;
 
+        assert(state);
+        assert(l);
+
+        if (!separator)
+                separator = WHITESPACE;
+
         current = *state;
 
-        if (!*current) {
-                assert(**state == '\0');
+        if (*current == '\0') /* already at the end? */
                 return NULL;
-        }
 
-        current += strspn(current, separator);
-        if (!*current) {
+        current += strspn(current, separator); /* skip leading separators */
+        if (*current == '\0') { /* at the end now? */
                 *state = current;
                 return NULL;
         }
 
-        if (flags & SPLIT_QUOTES && strchr("\'\"", *current)) {
-                char quotechars[2] = {*current, '\0'};
-
-                *l = strcspn_escaped(current + 1, quotechars);
-                if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] ||
-                    (current[*l + 2] && !strchr(separator, current[*l + 2]))) {
-                        /* right quote missing or garbage at the end */
-                        if (flags & SPLIT_RELAX) {
-                                *state = current + *l + 1 + (current[*l + 1] != '\0');
-                                return current + 1;
+        if (FLAGS_SET(flags, SPLIT_QUOTES)) {
+
+                if (strchr(QUOTES, *current)) {
+                        /* We are looking at a quote */
+                        *l = strcspn_escaped(current + 1, CHAR_TO_STR(*current));
+                        if (current[*l + 1] != *current ||
+                            (current[*l + 2] != 0 && !strchr(separator, current[*l + 2]))) {
+                                /* right quote missing or garbage at the end */
+                                if (FLAGS_SET(flags, SPLIT_RELAX)) {
+                                        *state = current + *l + 1 + (current[*l + 1] != '\0');
+                                        return current + 1;
+                                }
+                                *state = current;
+                                return NULL;
                         }
-                        *state = current;
-                        return NULL;
-                }
-                *state = current++ + *l + 2;
-        } else if (flags & SPLIT_QUOTES) {
-                *l = strcspn_escaped(current, separator);
-                if (current[*l] && !strchr(separator, current[*l]) && !(flags & SPLIT_RELAX)) {
-                        /* unfinished escape */
-                        *state = current;
-                        return NULL;
+                        *state = current++ + *l + 2;
+
+                } else {
+                        /* We are looking at a something that is not a quote */
+                        *l = strcspn_escaped(current, separator);
+                        if (current[*l] && !strchr(separator, current[*l]) && !FLAGS_SET(flags, SPLIT_RELAX)) {
+                                /* unfinished escape */
+                                *state = current;
+                                return NULL;
+                        }
+                        *state = current + *l;
                 }
-                *state = current + *l;
         } else {
                 *l = strcspn(current, separator);
                 *state = current + *l;
@@ -1062,6 +1074,8 @@ bool string_is_safe(const char *p) {
         if (!p)
                 return false;
 
+        /* Checks if the specified string contains no quotes or control characters */
+
         for (t = p; *t; t++) {
                 if (*t > 0 && *t < ' ') /* no control characters */
                         return false;
@@ -1072,3 +1086,133 @@ bool string_is_safe(const char *p) {
 
         return true;
 }
+
+#if 0 /* NM_IGNORED */
+char* string_erase(char *x) {
+        if (!x)
+                return NULL;
+
+        /* A delicious drop of snake-oil! To be called on memory where we stored passphrases or so, after we
+         * used them. */
+        explicit_bzero_safe(x, strlen(x));
+        return x;
+}
+
+int string_truncate_lines(const char *s, size_t n_lines, char **ret) {
+        const char *p = s, *e = s;
+        bool truncation_applied = false;
+        char *copy;
+        size_t n = 0;
+
+        assert(s);
+
+        /* Truncate after the specified number of lines. Returns > 0 if a truncation was applied or == 0 if
+         * there were fewer lines in the string anyway. Trailing newlines on input are ignored, and not
+         * generated either. */
+
+        for (;;) {
+                size_t k;
+
+                k = strcspn(p, "\n");
+
+                if (p[k] == 0) {
+                        if (k == 0) /* final empty line */
+                                break;
+
+                        if (n >= n_lines) /* above threshold */
+                                break;
+
+                        e = p + k; /* last line to include */
+                        break;
+                }
+
+                assert(p[k] == '\n');
+
+                if (n >= n_lines)
+                        break;
+
+                if (k > 0)
+                        e = p + k;
+
+                p += k + 1;
+                n++;
+        }
+
+        /* e points after the last character we want to keep */
+        if (isempty(e))
+                copy = strdup(s);
+        else {
+                if (!in_charset(e, "\n")) /* We only consider things truncated if we remove something that
+                                           * isn't a new-line or a series of them */
+                        truncation_applied = true;
+
+                copy = strndup(s, e - s);
+        }
+        if (!copy)
+                return -ENOMEM;
+
+        *ret = copy;
+        return truncation_applied;
+}
+
+int string_extract_line(const char *s, size_t i, char **ret) {
+        const char *p = s;
+        size_t c = 0;
+
+        /* Extract the i'nth line from the specified string. Returns > 0 if there are more lines after that,
+         * and == 0 if we are looking at the last line or already beyond the last line. As special
+         * optimization, if the first line is requested and the string only consists of one line we return
+         * NULL, indicating the input string should be used as is, and avoid a memory allocation for a very
+         * common case. */
+
+        for (;;) {
+                const char *q;
+
+                q = strchr(p, '\n');
+                if (i == c) {
+                        /* The line we are looking for! */
+
+                        if (q) {
+                                char *m;
+
+                                m = strndup(p, q - p);
+                                if (!m)
+                                        return -ENOMEM;
+
+                                *ret = m;
+                                return !isempty(q + 1); /* more coming? */
+                        } else {
+                                if (p == s)
+                                        *ret = NULL; /* Just use the input string */
+                                else {
+                                        char *m;
+
+                                        m = strdup(p);
+                                        if (!m)
+                                                return -ENOMEM;
+
+                                        *ret = m;
+                                }
+
+                                return 0; /* The end */
+                        }
+                }
+
+                if (!q) {
+                        char *m;
+
+                        /* No more lines, return empty line */
+
+                        m = strdup("");
+                        if (!m)
+                                return -ENOMEM;
+
+                        *ret = m;
+                        return 0; /* The end */
+                }
+
+                p = q + 1;
+                c++;
+        }
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/string-util.h b/shared/systemd/src/basic/string-util.h
index 04cc82b3..2a344b99 100644
--- a/shared/systemd/src/basic/string-util.h
+++ b/shared/systemd/src/basic/string-util.h
@@ -112,8 +112,10 @@ typedef enum SplitFlags {
         SPLIT_RELAX                      = 0x01 << 1,
 } SplitFlags;
 
+/* Smelly. Do not use this anymore. Use extract_first_word() instead! */
 const char* split(const char **state, size_t *l, const char *separator, SplitFlags flags);
 
+/* Similar, don't use this anymore */
 #define FOREACH_WORD(word, length, s, state)                            \
         _FOREACH_WORD(word, length, s, WHITESPACE, 0, state)
 
@@ -278,3 +280,8 @@ static inline char* str_realloc(char **p) {
 
         return (*p = t);
 }
+
+char* string_erase(char *x);
+
+int string_truncate_lines(const char *s, size_t n_lines, char **ret);
+int string_extract_line(const char *s, size_t i, char **ret);
diff --git a/shared/systemd/src/basic/strv.c b/shared/systemd/src/basic/strv.c
index aa467132..be1c8325 100644
--- a/shared/systemd/src/basic/strv.c
+++ b/shared/systemd/src/basic/strv.c
@@ -18,8 +18,8 @@
 #include "string-util.h"
 #include "strv.h"
 
-char *strv_find(char **l, const char *name) {
-        char **i;
+char *strv_find(char * const *l, const char *name) {
+        char * const *i;
 
         assert(name);
 
@@ -30,8 +30,8 @@ char *strv_find(char **l, const char *name) {
         return NULL;
 }
 
-char *strv_find_prefix(char **l, const char *name) {
-        char **i;
+char *strv_find_prefix(char * const *l, const char *name) {
+        char * const *i;
 
         assert(name);
 
@@ -42,8 +42,8 @@ char *strv_find_prefix(char **l, const char *name) {
         return NULL;
 }
 
-char *strv_find_startswith(char **l, const char *name) {
-        char **i, *e;
+char *strv_find_startswith(char * const *l, const char *name) {
+        char * const *i, *e;
 
         assert(name);
 
@@ -59,20 +59,15 @@ char *strv_find_startswith(char **l, const char *name) {
         return NULL;
 }
 
-void strv_clear(char **l) {
+char **strv_free(char **l) {
         char **k;
 
         if (!l)
-                return;
+                return NULL;
 
         for (k = l; *k; k++)
                 free(*k);
 
-        *l = NULL;
-}
-
-char **strv_free(char **l) {
-        strv_clear(l);
         return mfree(l);
 }
 
@@ -183,8 +178,8 @@ char **strv_new_internal(const char *x, ...) {
         return r;
 }
 
-int strv_extend_strv(char ***a, char **b, bool filter_duplicates) {
-        char **s, **t;
+int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates) {
+        char * const *s, **t;
         size_t p, q, i = 0, j;
 
         assert(a);
@@ -195,7 +190,10 @@ int strv_extend_strv(char ***a, char **b, bool filter_duplicates) {
         p = strv_length(*a);
         q = strv_length(b);
 
-        t = reallocarray(*a, p + q + 1, sizeof(char *));
+        if (p >= SIZE_MAX - q)
+                return -ENOMEM;
+
+        t = reallocarray(*a, GREEDY_ALLOC_ROUND_UP(p + q + 1), sizeof(char *));
         if (!t)
                 return -ENOMEM;
 
@@ -228,9 +226,9 @@ rollback:
 }
 
 #if 0 /* NM_IGNORED */
-int strv_extend_strv_concat(char ***a, char **b, const char *suffix) {
+int strv_extend_strv_concat(char ***a, char * const *b, const char *suffix) {
+        char * const *s;
         int r;
-        char **s;
 
         STRV_FOREACH(s, b) {
                 char *v;
@@ -349,9 +347,9 @@ int strv_split_extract(char ***t, const char *s, const char *separators, Extract
 }
 #endif /* NM_IGNORED */
 
-char *strv_join_prefix(char **l, const char *separator, const char *prefix) {
+char *strv_join_prefix(char * const *l, const char *separator, const char *prefix) {
+        char * const *s;
         char *r, *e;
-        char **s;
         size_t n, k, m;
 
         if (!separator)
@@ -389,19 +387,18 @@ char *strv_join_prefix(char **l, const char *separator, const char *prefix) {
 
 int strv_push(char ***l, char *value) {
         char **c;
-        size_t n, m;
+        size_t n;
 
         if (!value)
                 return 0;
 
         n = strv_length(*l);
 
-        /* Increase and check for overflow */
-        m = n + 2;
-        if (m < n)
+        /* Check for overflow */
+        if (n > SIZE_MAX-2)
                 return -ENOMEM;
 
-        c = reallocarray(*l, m, sizeof(char*));
+        c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(n + 2), sizeof(char*));
         if (!c)
                 return -ENOMEM;
 
@@ -414,19 +411,19 @@ int strv_push(char ***l, char *value) {
 
 int strv_push_pair(char ***l, char *a, char *b) {
         char **c;
-        size_t n, m;
+        size_t n;
 
         if (!a && !b)
                 return 0;
 
         n = strv_length(*l);
 
-        /* increase and check for overflow */
-        m = n + !!a + !!b + 1;
-        if (m < n)
+        /* Check for overflow */
+        if (n > SIZE_MAX-3)
                 return -ENOMEM;
 
-        c = reallocarray(*l, m, sizeof(char*));
+        /* increase and check for overflow */
+        c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(n + !!a + !!b + 1), sizeof(char*));
         if (!c)
                 return -ENOMEM;
 
@@ -566,8 +563,8 @@ char **strv_uniq(char **l) {
         return l;
 }
 
-bool strv_is_uniq(char **l) {
-        char **i;
+bool strv_is_uniq(char * const *l) {
+        char * const *i;
 
         STRV_FOREACH(i, l)
                 if (strv_find(i+1, *i))
@@ -672,7 +669,7 @@ char **strv_split_nulstr(const char *s) {
 }
 #endif /* NM_IGNORED */
 
-int strv_make_nulstr(char **l, char **p, size_t *q) {
+int strv_make_nulstr(char * const *l, char **ret, size_t *ret_size) {
         /* A valid nulstr with two NULs at the end will be created, but
          * q will be the length without the two trailing NULs. Thus the output
          * string is a valid nulstr and can be iterated over using NULSTR_FOREACH,
@@ -682,10 +679,10 @@ int strv_make_nulstr(char **l, char **p, size_t *q) {
 
         size_t n_allocated = 0, n = 0;
         _cleanup_free_ char *m = NULL;
-        char **i;
+        char * const *i;
 
-        assert(p);
-        assert(q);
+        assert(ret);
+        assert(ret_size);
 
         STRV_FOREACH(i, l) {
                 size_t z;
@@ -709,16 +706,16 @@ int strv_make_nulstr(char **l, char **p, size_t *q) {
                 m[n] = '\0';
 
         assert(n > 0);
-        *p = m;
-        *q = n - 1;
+        *ret = m;
+        *ret_size = n - 1;
 
         m = NULL;
 
         return 0;
 }
 
-bool strv_overlap(char **a, char **b) {
-        char **i;
+bool strv_overlap(char * const *a, char * const *b) {
+        char * const *i;
 
         STRV_FOREACH(i, a)
                 if (strv_contains(b, *i))
@@ -738,23 +735,30 @@ char **strv_sort(char **l) {
 }
 #endif /* NM_IGNORED */
 
-bool strv_equal(char **a, char **b) {
+int strv_compare(char * const *a, char * const *b) {
+        int r;
 
-        if (strv_isempty(a))
-                return strv_isempty(b);
+        if (strv_isempty(a)) {
+                if (strv_isempty(b))
+                        return 0;
+                else
+                        return -1;
+        }
 
         if (strv_isempty(b))
-                return false;
+                return 1;
 
-        for ( ; *a || *b; ++a, ++b)
-                if (!streq_ptr(*a, *b))
-                        return false;
+        for ( ; *a || *b; ++a, ++b) {
+                r = strcmp_ptr(*a, *b);
+                if (r != 0)
+                        return r;
+        }
 
-        return true;
+        return 0;
 }
 
-void strv_print(char **l) {
-        char **s;
+void strv_print(char * const *l) {
+        char * const *s;
 
         STRV_FOREACH(s, l)
                 puts(*s);
@@ -808,12 +812,13 @@ char **strv_shell_escape(char **l, const char *bad) {
         return l;
 }
 
-bool strv_fnmatch(char* const* patterns, const char *s, int flags) {
-        char* const* p;
-
-        STRV_FOREACH(p, patterns)
-                if (fnmatch(*p, s, flags) == 0)
+bool strv_fnmatch_full(char* const* patterns, const char *s, int flags, size_t *matched_pos) {
+        for (size_t i = 0; patterns && patterns[i]; i++)
+                if (fnmatch(patterns[i], s, flags) == 0) {
+                        if (matched_pos)
+                                *matched_pos = i;
                         return true;
+                }
 
         return false;
 }
@@ -856,8 +861,10 @@ int strv_extend_n(char ***l, const char *value, size_t n) {
         /* Adds the value n times to l */
 
         k = strv_length(*l);
+        if (n >= SIZE_MAX - k)
+                return -ENOMEM;
 
-        nl = reallocarray(*l, k + n + 1, sizeof(char *));
+        nl = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(k + n + 1), sizeof(char *));
         if (!nl)
                 return -ENOMEM;
 
@@ -880,9 +887,9 @@ rollback:
         return -ENOMEM;
 }
 
-int fputstrv(FILE *f, char **l, const char *separator, bool *space) {
+int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) {
         bool b = false;
-        char **s;
+        char * const *s;
         int r;
 
         /* Like fputs(), but for strv, and with a less stupid argument order */
diff --git a/shared/systemd/src/basic/strv.h b/shared/systemd/src/basic/strv.h
index fbfa96a5..dd3323c2 100644
--- a/shared/systemd/src/basic/strv.h
+++ b/shared/systemd/src/basic/strv.h
@@ -13,9 +13,9 @@
 #include "macro.h"
 #include "string-util.h"
 
-char *strv_find(char **l, const char *name) _pure_;
-char *strv_find_prefix(char **l, const char *name) _pure_;
-char *strv_find_startswith(char **l, const char *name) _pure_;
+char *strv_find(char * const *l, const char *name) _pure_;
+char *strv_find_prefix(char * const *l, const char *name) _pure_;
+char *strv_find_startswith(char * const *l, const char *name) _pure_;
 
 char **strv_free(char **l);
 DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free);
@@ -25,13 +25,11 @@ char **strv_free_erase(char **l);
 DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase);
 #define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep)
 
-void strv_clear(char **l);
-
 char **strv_copy(char * const *l);
 size_t strv_length(char * const *l) _pure_;
 
-int strv_extend_strv(char ***a, char **b, bool filter_duplicates);
-int strv_extend_strv_concat(char ***a, char **b, const char *suffix);
+int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates);
+int strv_extend_strv_concat(char ***a, char * const *b, const char *suffix);
 int strv_extend(char ***l, const char *value);
 int strv_extendf(char ***l, const char *format, ...) _printf_(2,0);
 int strv_extend_front(char ***l, const char *value);
@@ -49,9 +47,12 @@ int strv_consume_prepend(char ***l, char *value);
 
 char **strv_remove(char **l, const char *s);
 char **strv_uniq(char **l);
-bool strv_is_uniq(char **l);
+bool strv_is_uniq(char * const *l);
 
-bool strv_equal(char **a, char **b);
+int strv_compare(char * const *a, char * const *b);
+static inline bool strv_equal(char * const *a, char * const *b) {
+        return strv_compare(a, b) == 0;
+}
 
 #define strv_contains(l, s) (!!strv_find((l), (s)))
 
@@ -77,16 +78,26 @@ char **strv_split_newlines(const char *s);
 
 int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags);
 
-char *strv_join_prefix(char **l, const char *separator, const char *prefix);
-static inline char *strv_join(char **l, const char *separator) {
+char *strv_join_prefix(char * const *l, const char *separator, const char *prefix);
+static inline char *strv_join(char * const *l, const char *separator) {
         return strv_join_prefix(l, separator, NULL);
 }
 
 char **strv_parse_nulstr(const char *s, size_t l);
 char **strv_split_nulstr(const char *s);
-int strv_make_nulstr(char **l, char **p, size_t *n);
+int strv_make_nulstr(char * const *l, char **p, size_t *n);
+
+static inline int strv_from_nulstr(char ***a, const char *nulstr) {
+        char **t;
 
-bool strv_overlap(char **a, char **b) _pure_;
+        t = strv_split_nulstr(nulstr);
+        if (!t)
+                return -ENOMEM;
+        *a = t;
+        return 0;
+}
+
+bool strv_overlap(char * const *a, char * const *b) _pure_;
 
 #define STRV_FOREACH(s, l)                      \
         for ((s) = (l); (s) && *(s); (s)++)
@@ -103,7 +114,7 @@ bool strv_overlap(char **a, char **b) _pure_;
         for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1))
 
 char **strv_sort(char **l);
-void strv_print(char **l);
+void strv_print(char * const *l);
 
 #define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL }))
 
@@ -177,12 +188,15 @@ void strv_print(char **l);
 char **strv_reverse(char **l);
 char **strv_shell_escape(char **l, const char *bad);
 
-bool strv_fnmatch(char* const* patterns, const char *s, int flags);
+bool strv_fnmatch_full(char* const* patterns, const char *s, int flags, size_t *matched_pos);
+static inline bool strv_fnmatch(char* const* patterns, const char *s) {
+        return strv_fnmatch_full(patterns, s, 0, NULL);
+}
 
 static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, int flags) {
         assert(s);
         return strv_isempty(patterns) ||
-               strv_fnmatch(patterns, s, flags);
+               strv_fnmatch_full(patterns, s, flags, NULL);
 }
 
 char ***strv_free_free(char ***l);
@@ -192,7 +206,7 @@ char **strv_skip(char **l, size_t n);
 
 int strv_extend_n(char ***l, const char *value, size_t n);
 
-int fputstrv(FILE *f, char **l, const char *separator, bool *space);
+int fputstrv(FILE *f, char * const *l, const char *separator, bool *space);
 
 #define strv_free_and_replace(a, b)             \
         ({                                      \
diff --git a/shared/systemd/src/basic/time-util.c b/shared/systemd/src/basic/time-util.c
index 4411127a..71af4f21 100644
--- a/shared/systemd/src/basic/time-util.c
+++ b/shared/systemd/src/basic/time-util.c
@@ -1507,9 +1507,30 @@ int time_change_fd(void) {
         if (fd < 0)
                 return -errno;
 
-        if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0)
-                return -errno;
+        if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) >= 0)
+                return TAKE_FD(fd);
+
+        /* So apparently there are systems where time_t is 64bit, but the kernel actually doesn't support
+         * 64bit time_t. In that case configuring a timer to TIME_T_MAX will fail with EOPNOTSUPP or a
+         * similar error. If that's the case let's try with INT32_MAX instead, maybe that works. It's a bit
+         * of a black magic thing though, but what can we do?
+         *
+         * We don't want this code on x86-64, hence let's conditionalize this for systems with 64bit time_t
+         * but where "long" is shorter than 64bit, i.e. 32bit archs.
+         *
+         * See: https://github.com/systemd/systemd/issues/14362 */
+
+#if SIZEOF_TIME_T == 8 && ULONG_MAX < UINT64_MAX
+        if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EOVERFLOW) {
+                static const struct itimerspec its32 = {
+                        .it_value.tv_sec = INT32_MAX,
+                };
+
+                if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its32, NULL) >= 0)
+                        return TAKE_FD(fd);
+        }
+#endif
 
-        return TAKE_FD(fd);
+        return -errno;
 }
 #endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/tmpfile-util.c b/shared/systemd/src/basic/tmpfile-util.c
index d8a689e0..8bbd12b0 100644
--- a/shared/systemd/src/basic/tmpfile-util.c
+++ b/shared/systemd/src/basic/tmpfile-util.c
@@ -21,50 +21,58 @@
 #include "tmpfile-util.h"
 #include "umask-util.h"
 
-int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
-        FILE *f;
-        char *t;
-        int r, fd;
+int fopen_temporary(const char *path, FILE **ret_f, char **ret_temp_path) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *t = NULL;
+        _cleanup_close_ int fd = -1;
+        int r;
 
-        assert(path);
-        assert(_f);
-        assert(_temp_path);
+        if (path) {
+                r = tempfn_xxxxxx(path, NULL, &t);
+                if (r < 0)
+                        return r;
+        } else {
+                const char *d;
 
-        r = tempfn_xxxxxx(path, NULL, &t);
-        if (r < 0)
-                return r;
+                r = tmp_dir(&d);
+                if (r < 0)
+                        return r;
+
+                t = path_join(d, "XXXXXX");
+                if (!t)
+                        return -ENOMEM;
+        }
 
         fd = mkostemp_safe(t);
-        if (fd < 0) {
-                free(t);
+        if (fd < 0)
                 return -errno;
-        }
 
         /* This assumes that returned FILE object is short-lived and used within the same single-threaded
          * context and never shared externally, hence locking is not necessary. */
 
-        r = fdopen_unlocked(fd, "w", &f);
+        r = take_fdopen_unlocked(&fd, "w", &f);
         if (r < 0) {
-                unlink(t);
-                free(t);
-                safe_close(fd);
+                (void) unlink(t);
                 return r;
         }
 
-        *_f = f;
-        *_temp_path = t;
+        if (ret_f)
+                *ret_f = TAKE_PTR(f);
+
+        if (ret_temp_path)
+                *ret_temp_path = TAKE_PTR(t);
 
         return 0;
 }
 
 /* This is much like mkostemp() but is subject to umask(). */
 int mkostemp_safe(char *pattern) {
-        _unused_ _cleanup_umask_ mode_t u = umask(0077);
-        int fd;
+        int fd = -1; /* avoid false maybe-uninitialized warning */
 
         assert(pattern);
 
-        fd = mkostemp(pattern, O_CLOEXEC);
+        RUN_WITH_UMASK(0077)
+                fd = mkostemp(pattern, O_CLOEXEC);
         if (fd < 0)
                 return -errno;
 
@@ -73,18 +81,16 @@ int mkostemp_safe(char *pattern) {
 
 #if 0 /* NM_IGNORED */
 int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) {
-        int fd;
+        _cleanup_close_ int fd = -1;
         FILE *f;
 
         fd = mkostemp_safe(pattern);
         if (fd < 0)
                 return fd;
 
-        f = fdopen(fd, mode);
-        if (!f) {
-                safe_close(fd);
+        f = take_fdopen(&fd, mode);
+        if (!f)
                 return -errno;
-        }
 
         *ret_f = f;
         return 0;
diff --git a/shared/systemd/src/shared/web-util.c b/shared/systemd/src/shared/web-util.c
new file mode 100644
index 00000000..f5a5362b
--- /dev/null
+++ b/shared/systemd/src/shared/web-util.c
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <stdbool.h>
+
+#include "string-util.h"
+#include "strv.h"
+#include "utf8.h"
+#include "web-util.h"
+
+#if 0 /* NM_IGNORED */
+bool http_etag_is_valid(const char *etag) {
+        if (isempty(etag))
+                return false;
+
+        if (!endswith(etag, "\""))
+                return false;
+
+        if (!STARTSWITH_SET(etag, "\"", "W/\""))
+                return false;
+
+        return true;
+}
+#endif /* NM_IGNORED */
+
+bool http_url_is_valid(const char *url) {
+        const char *p;
+
+        if (isempty(url))
+                return false;
+
+        p = STARTSWITH_SET(url, "http://", "https://");
+        if (!p)
+                return false;
+
+        if (isempty(p))
+                return false;
+
+        return ascii_is_valid(p);
+}
+
+#if 0 /* NM_IGNORED */
+bool documentation_url_is_valid(const char *url) {
+        const char *p;
+
+        if (isempty(url))
+                return false;
+
+        if (http_url_is_valid(url))
+                return true;
+
+        p = STARTSWITH_SET(url, "file:/", "info:", "man:");
+        if (isempty(p))
+                return false;
+
+        return ascii_is_valid(p);
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/shared/web-util.h b/shared/systemd/src/shared/web-util.h
new file mode 100644
index 00000000..c9e67e5c
--- /dev/null
+++ b/shared/systemd/src/shared/web-util.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+
+#include "macro.h"
+
+bool http_url_is_valid(const char *url) _pure_;
+
+bool documentation_url_is_valid(const char *url) _pure_;
+
+bool http_etag_is_valid(const char *etag);