summary refs log tree commit diff
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/README.md108
-rw-r--r--shared/c-list/src/c-list.h4
-rw-r--r--shared/c-stdaux/src/c-stdaux.h6
-rw-r--r--shared/meson.build359
-rw-r--r--shared/n-dhcp4/src/util/packet.c2
-rw-r--r--shared/nm-base/nm-base.h210
-rw-r--r--shared/nm-base/nm-ethtool-base.c288
-rw-r--r--shared/nm-base/nm-ethtool-base.h37
-rw-r--r--shared/nm-base/nm-ethtool-utils-base.h107
-rw-r--r--shared/nm-default.h302
-rw-r--r--shared/nm-glib-aux/nm-c-list.h7
-rw-r--r--shared/nm-glib-aux/nm-dbus-aux.c4
-rw-r--r--shared/nm-glib-aux/nm-dbus-aux.h2
-rw-r--r--shared/nm-glib-aux/nm-dedup-multi.c4
-rw-r--r--shared/nm-glib-aux/nm-dedup-multi.h2
-rw-r--r--shared/nm-glib-aux/nm-default-glib-i18n-lib.h21
-rw-r--r--shared/nm-glib-aux/nm-default-glib-i18n-prog.h21
-rw-r--r--shared/nm-glib-aux/nm-default-glib.h75
-rw-r--r--shared/nm-glib-aux/nm-enum-utils.c58
-rw-r--r--shared/nm-glib-aux/nm-enum-utils.h2
-rw-r--r--shared/nm-glib-aux/nm-errno.c6
-rw-r--r--shared/nm-glib-aux/nm-errno.h2
-rw-r--r--shared/nm-glib-aux/nm-gassert-patch.h76
-rw-r--r--shared/nm-glib-aux/nm-glib.h4
-rw-r--r--shared/nm-glib-aux/nm-hash-utils.c4
-rw-r--r--shared/nm-glib-aux/nm-hash-utils.h10
-rw-r--r--shared/nm-glib-aux/nm-io-utils.c4
-rw-r--r--shared/nm-glib-aux/nm-io-utils.h2
-rw-r--r--shared/nm-glib-aux/nm-jansson.h2
-rw-r--r--shared/nm-glib-aux/nm-json-aux.c14
-rw-r--r--shared/nm-glib-aux/nm-json-aux.h2
-rw-r--r--shared/nm-glib-aux/nm-keyfile-aux.c4
-rw-r--r--shared/nm-glib-aux/nm-keyfile-aux.h2
-rw-r--r--shared/nm-glib-aux/nm-logging-base.c4
-rw-r--r--shared/nm-glib-aux/nm-logging-base.h2
-rw-r--r--shared/nm-glib-aux/nm-logging-fwd.h12
-rw-r--r--shared/nm-glib-aux/nm-macros-internal.h32
-rw-r--r--shared/nm-glib-aux/nm-obj.h2
-rw-r--r--shared/nm-glib-aux/nm-random-utils.c4
-rw-r--r--shared/nm-glib-aux/nm-random-utils.h2
-rw-r--r--shared/nm-glib-aux/nm-ref-string.c21
-rw-r--r--shared/nm-glib-aux/nm-ref-string.h2
-rw-r--r--shared/nm-glib-aux/nm-secret-utils.c4
-rw-r--r--shared/nm-glib-aux/nm-secret-utils.h2
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.c316
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.h287
-rw-r--r--shared/nm-glib-aux/nm-str-buf.h61
-rw-r--r--shared/nm-glib-aux/nm-time-utils.c4
-rw-r--r--shared/nm-glib-aux/nm-time-utils.h2
-rw-r--r--shared/nm-glib-aux/nm-value-type.h2
-rw-r--r--shared/nm-glib-aux/tests/meson.build12
-rw-r--r--shared/nm-glib-aux/tests/test-json-aux.c6
-rw-r--r--shared/nm-glib-aux/tests/test-shared-general.c78
-rw-r--r--shared/nm-log-core/nm-logging.c1035
-rw-r--r--shared/nm-log-core/nm-logging.h189
-rw-r--r--shared/nm-meta-setting.c592
-rw-r--r--shared/nm-meta-setting.h209
-rw-r--r--shared/nm-platform/nm-netlink.c1518
-rw-r--r--shared/nm-platform/nm-netlink.h616
-rw-r--r--shared/nm-platform/nm-platform-utils.c1807
-rw-r--r--shared/nm-platform/nm-platform-utils.h73
-rw-r--r--shared/nm-platform/nmp-base.h94
-rw-r--r--shared/nm-platform/nmp-netns.c766
-rw-r--r--shared/nm-platform/nmp-netns.h56
-rw-r--r--shared/nm-platform/tests/meson.build20
-rw-r--r--shared/nm-platform/tests/test-nm-platform.c116
-rw-r--r--shared/nm-std-aux/c-list-util.c7
-rw-r--r--shared/nm-std-aux/c-list-util.h13
-rw-r--r--shared/nm-std-aux/nm-dbus-compat.h2
-rw-r--r--shared/nm-std-aux/nm-default-std.h102
-rw-r--r--shared/nm-std-aux/nm-networkmanager-compilation.h54
-rw-r--r--shared/nm-std-aux/nm-std-aux.h50
-rw-r--r--shared/nm-std-aux/nm-std-utils.c25
-rw-r--r--shared/nm-std-aux/nm-std-utils.h2
-rw-r--r--shared/nm-std-aux/unaligned.h146
-rw-r--r--shared/nm-test-libnm-utils.h2
-rw-r--r--shared/nm-test-utils-impl.c4
-rw-r--r--shared/nm-udev-aux/nm-udev-utils.c8
-rw-r--r--shared/nm-udev-aux/nm-udev-utils.h4
-rw-r--r--shared/nm-utils/nm-compat.c4
-rw-r--r--shared/nm-utils/nm-compat.h2
-rw-r--r--shared/nm-utils/nm-test-utils.h81
-rw-r--r--shared/nm-utils/nm-vpn-editor-plugin-call.h2
-rw-r--r--shared/nm-utils/nm-vpn-plugin-macros.h2
-rw-r--r--shared/nm-utils/nm-vpn-plugin-utils.c4
-rw-r--r--shared/nm-utils/nm-vpn-plugin-utils.h2
-rw-r--r--shared/nm-version-macros.h90
-rw-r--r--shared/nm-version-macros.h.in90
-rw-r--r--shared/systemd/nm-default-systemd-shared.h18
-rw-r--r--shared/systemd/nm-logging-stub.c4
-rw-r--r--shared/systemd/nm-sd-utils-shared.c26
-rw-r--r--shared/systemd/nm-sd-utils-shared.h4
-rw-r--r--shared/systemd/sd-adapt-shared/idn-util.h3
-rw-r--r--shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h23
-rw-r--r--shared/systemd/src/basic/alloc-util.c2
-rw-r--r--shared/systemd/src/basic/alloc-util.h14
-rw-r--r--shared/systemd/src/basic/async.h8
-rw-r--r--shared/systemd/src/basic/cgroup-util.h15
-rw-r--r--shared/systemd/src/basic/env-file.c2
-rw-r--r--shared/systemd/src/basic/env-file.h2
-rw-r--r--shared/systemd/src/basic/env-util.c30
-rw-r--r--shared/systemd/src/basic/env-util.h5
-rw-r--r--shared/systemd/src/basic/errno-util.h10
-rw-r--r--shared/systemd/src/basic/escape.c2
-rw-r--r--shared/systemd/src/basic/escape.h4
-rw-r--r--shared/systemd/src/basic/ether-addr-util.c17
-rw-r--r--shared/systemd/src/basic/ether-addr-util.h26
-rw-r--r--shared/systemd/src/basic/extract-word.c2
-rw-r--r--shared/systemd/src/basic/extract-word.h2
-rw-r--r--shared/systemd/src/basic/fd-util.c88
-rw-r--r--shared/systemd/src/basic/fd-util.h2
-rw-r--r--shared/systemd/src/basic/fileio.c93
-rw-r--r--shared/systemd/src/basic/fileio.h39
-rw-r--r--shared/systemd/src/basic/format-util.c2
-rw-r--r--shared/systemd/src/basic/format-util.h7
-rw-r--r--shared/systemd/src/basic/fs-util.c87
-rw-r--r--shared/systemd/src/basic/fs-util.h4
-rw-r--r--shared/systemd/src/basic/hash-funcs.c15
-rw-r--r--shared/systemd/src/basic/hash-funcs.h4
-rw-r--r--shared/systemd/src/basic/hashmap.c64
-rw-r--r--shared/systemd/src/basic/hashmap.h7
-rw-r--r--shared/systemd/src/basic/hexdecoct.c2
-rw-r--r--shared/systemd/src/basic/hexdecoct.h2
-rw-r--r--shared/systemd/src/basic/hostname-util.c173
-rw-r--r--shared/systemd/src/basic/hostname-util.h26
-rw-r--r--shared/systemd/src/basic/in-addr-util.c2
-rw-r--r--shared/systemd/src/basic/in-addr-util.h2
-rw-r--r--shared/systemd/src/basic/io-util.c6
-rw-r--r--shared/systemd/src/basic/io-util.h2
-rw-r--r--shared/systemd/src/basic/list.h2
-rw-r--r--shared/systemd/src/basic/log.h39
-rw-r--r--shared/systemd/src/basic/macro.h30
-rw-r--r--shared/systemd/src/basic/memory-util.c2
-rw-r--r--shared/systemd/src/basic/memory-util.h2
-rw-r--r--shared/systemd/src/basic/mempool.c2
-rw-r--r--shared/systemd/src/basic/mempool.h2
-rw-r--r--shared/systemd/src/basic/missing_fcntl.h2
-rw-r--r--shared/systemd/src/basic/missing_random.h2
-rw-r--r--shared/systemd/src/basic/missing_socket.h10
-rw-r--r--shared/systemd/src/basic/missing_stat.h2
-rw-r--r--shared/systemd/src/basic/missing_syscall.h495
-rw-r--r--shared/systemd/src/basic/missing_type.h2
-rw-r--r--shared/systemd/src/basic/parse-util.c44
-rw-r--r--shared/systemd/src/basic/parse-util.h24
-rw-r--r--shared/systemd/src/basic/path-util.c58
-rw-r--r--shared/systemd/src/basic/path-util.h10
-rw-r--r--shared/systemd/src/basic/prioq.c2
-rw-r--r--shared/systemd/src/basic/prioq.h2
-rw-r--r--shared/systemd/src/basic/process-util.c2
-rw-r--r--shared/systemd/src/basic/process-util.h2
-rw-r--r--shared/systemd/src/basic/random-util.c19
-rw-r--r--shared/systemd/src/basic/random-util.h2
-rw-r--r--shared/systemd/src/basic/ratelimit.c40
-rw-r--r--shared/systemd/src/basic/ratelimit.h24
-rw-r--r--shared/systemd/src/basic/set.h14
-rw-r--r--shared/systemd/src/basic/signal-util.c13
-rw-r--r--shared/systemd/src/basic/signal-util.h2
-rw-r--r--shared/systemd/src/basic/siphash24.h14
-rw-r--r--shared/systemd/src/basic/socket-util.c117
-rw-r--r--shared/systemd/src/basic/socket-util.h46
-rw-r--r--shared/systemd/src/basic/sort-util.h6
-rw-r--r--shared/systemd/src/basic/stat-util.c12
-rw-r--r--shared/systemd/src/basic/stat-util.h2
-rw-r--r--shared/systemd/src/basic/stdio-util.h4
-rw-r--r--shared/systemd/src/basic/string-table.c2
-rw-r--r--shared/systemd/src/basic/string-table.h9
-rw-r--r--shared/systemd/src/basic/string-util.c77
-rw-r--r--shared/systemd/src/basic/string-util.h17
-rw-r--r--shared/systemd/src/basic/strv.c57
-rw-r--r--shared/systemd/src/basic/strv.h5
-rw-r--r--shared/systemd/src/basic/strxcpyx.c2
-rw-r--r--shared/systemd/src/basic/strxcpyx.h2
-rw-r--r--shared/systemd/src/basic/time-util.c4
-rw-r--r--shared/systemd/src/basic/time-util.h10
-rw-r--r--shared/systemd/src/basic/tmpfile-util.c2
-rw-r--r--shared/systemd/src/basic/tmpfile-util.h2
-rw-r--r--shared/systemd/src/basic/umask-util.h2
-rw-r--r--shared/systemd/src/basic/utf8.c2
-rw-r--r--shared/systemd/src/basic/utf8.h2
-rw-r--r--shared/systemd/src/basic/util.c8
-rw-r--r--shared/systemd/src/basic/util.h2
-rw-r--r--shared/systemd/src/shared/dns-domain.c90
-rw-r--r--shared/systemd/src/shared/dns-domain.h2
-rw-r--r--shared/systemd/src/shared/log-link.h45
-rw-r--r--shared/systemd/src/shared/web-util.c2
-rw-r--r--shared/systemd/src/shared/web-util.h2
186 files changed, 10013 insertions, 2623 deletions
diff --git a/shared/README.md b/shared/README.md
new file mode 100644
index 00000000..ed950fce
--- /dev/null
+++ b/shared/README.md
@@ -0,0 +1,108 @@
+The "shared/" Directory
+=======================
+
+For NetworkManager we place helper/utility code under "shared/"
+in static libraries. The idea is to avoid code duplication but also
+provide high quality helper functions that simplify the higher layers.
+In NetworkManager there are complicated parts, for example "src/nm-manager.c"
+is huge. On the other hand, this helper code should be simple and easy
+to understand, so that we can build more complex code on top of it.
+
+As we statically link them into our binaries, they are all inherently
+internal API, that means they cannot be part of libnm's (libnm-core's) public API.
+It also means that their API/ABI is not stable.
+
+We don't care these libraries to be minimal and contain only symbols that are
+used by all users. Instead, we expect the linker to throw away unused symbols.
+We achieve this by having a symbol versioning file to hide internal symbols
+(which gives the linker a possibility to remove them if they are unused) and
+compiling with LTO or `"-Wl,--gc-sections"`. Let the tool solve this and not
+manual organization.
+
+Hence these libraries (and their content) are structured this way to satisfy
+the following questions:
+
+1) which dependencies (libraries) do they have? That determines which
+   other libraries can use it. For example:
+
+   - "shared/nm-std-aux" and "shared/nm-glib-aux" both provide general
+     purpose helpers, the difference is that the former has no dependency
+     on glib2 library. Both these libraries are a basic dependency for
+     many other parts of the code.
+
+   - "shared/nm-udev-aux" has a dependency on libudev, it thus cannot
+     be in "shared/nm-glib-aux".
+
+   - client code also has a glib2 dependency. That means it can link with
+     "shared/nm-std-aux" and "shared/nm-glib-aux", but must not link
+     with "shared/nm-udev-aux" (as it has no direct udev dependenct --
+     although clients get it indirectly because libnm already requires
+     it).
+
+2) what is their overall purpose? As said, we rely on the linker to
+   prune unused symbols. But in a few cases we avoid to merge different
+   code in the same library. For example:
+
+   - "shared/nm-glib-aux" and "shared/nm-base" both only have a
+     glib2 dependency. Hence, they could be merged. However we
+     don't do that because "shared/nm-base" is more about NetworkManager
+     specific code, while "shared/nm-glib-aux" is about general
+     purpose helpers.
+
+3) some of these libraries are forked from an upstream. They are kept
+   separate so that we can re-import future upstream versions.
+
+Detail
+======
+
+- `shared/c-list`
+- `shared/c-rbtree`
+- `shared/c-siphash`
+- `shared/c-stdoux`
+- `shared/n-acd`
+- `shared/n-dhcp4`
+
+   These are forked from upstream and imported with git-subtree. They
+   in general only have a libc dependency (or dependencies between each
+   other).
+
+- `shared/nm-std-aux`
+
+   This contains helper code with only a libc dependency.
+   Almost all C code depends on this library.
+
+- `shared/nm-glib-aux`
+
+   Like "shared/nm-std-aux" but also has a glib2 dependency.
+   Almost all glib2 code depends on this library.
+
+- `shared/nm-udev/aux`
+
+   Like "shared/nm-glib-aux" but also has a libudev dependency. It
+   has code related to libudev.
+
+- `shared/systemd`
+
+   These are forked from upstream systemd and imported with a script.
+   Under "shared/systemd/src" we try to keep the sources as close to
+   the original as possible. There is also some adapter code to make
+   it useable for us. It has a dependency on "shared/nm-glib-aux"
+   and will need a logging implementation for "shared/nm-glib-aux/nm-logging-fwd.h".
+
+- `shared/nm-base`
+
+   Depends on "shared/nm-glib-aux" and glib2 but it provides helper code
+   that more about NetworkManager specifc things.
+
+- `shared/nm-log-core`
+
+   This is the logging implementation as used by NetworkManager core ("src/").
+   It is also a dependency for "shared/nm-platform".
+
+- `shared/nm-platform`
+
+   Platform implementation. It depends on "shared/nm-log-core", "shared/nm-base"
+   and "shared/nm-glib-aux".
+
+- Other than that, there are still a few unorganized files/directories here.
+  These should be cleaned up.
diff --git a/shared/c-list/src/c-list.h b/shared/c-list/src/c-list.h
index 3d44e330..c92aa6f3 100644
--- a/shared/c-list/src/c-list.h
+++ b/shared/c-list/src/c-list.h
@@ -87,10 +87,12 @@ static inline _Bool c_list_is_linked(const CList *what) {
  * c_list_is_empty() - check whether a list is empty
  * @list:               list to check, or NULL
  *
+ * This is the same as !c_list_is_linked().
+ *
  * Return: True if @list is empty, false if not.
  */
 static inline _Bool c_list_is_empty(const CList *list) {
-        return !list || !c_list_is_linked(list);
+        return !c_list_is_linked(list);
 }
 
 /**
diff --git a/shared/c-stdaux/src/c-stdaux.h b/shared/c-stdaux/src/c-stdaux.h
index 08d155ce..1cdbbbcf 100644
--- a/shared/c-stdaux/src/c-stdaux.h
+++ b/shared/c-stdaux/src/c-stdaux.h
@@ -84,7 +84,10 @@ extern "C" {
  *
  * Return: Evaluates to @_expr.
  */
-#define C_EXPR_ASSERT(_expr, _assertion, _message)                      \
+#if defined(__COVERITY__) // Coverity cannot const-fold __builtin_choose_expr()
+#  define C_EXPR_ASSERT(_expr, _assertion, _message) (_expr)
+#else
+#  define C_EXPR_ASSERT(_expr, _assertion, _message)                    \
         /* indentation and line-split to get better diagnostics */      \
         (__builtin_choose_expr(                                         \
                 !!(1 + 0 * sizeof(                                      \
@@ -95,6 +98,7 @@ _Static_assert(_assertion, _message); \
                 (_expr),                                                \
                 ((void)0)                                               \
         ))
+#endif
 
 /**
  * C_STRINGIFY() - stringify a token, but evaluate it first
diff --git a/shared/meson.build b/shared/meson.build
index 0f46a00c..52eb6b9b 100644
--- a/shared/meson.build
+++ b/shared/meson.build
@@ -1,10 +1,16 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 shared_inc = include_directories('.')
 
-nm_default_dep = declare_dependency(include_directories: [top_inc, shared_inc])
-
-glib_nm_default_dep = declare_dependency(dependencies: [glib_dep, nm_default_dep])
+glib_nm_default_dep = declare_dependency(
+  include_directories: [
+    top_inc,
+    shared_inc,
+  ],
+  dependencies: [
+    glib_dep,
+  ],
+)
 
 libc_siphash = static_library(
   'c-siphash',
@@ -19,44 +25,37 @@ libc_rbtree = static_library(
   c_args: '-std=c11',
 )
 
-sources = files(
-  'n-acd/src/n-acd.c',
-  'n-acd/src/n-acd-probe.c',
-  'n-acd/src/util/timer.c',
-)
-
 if enable_ebpf
-  sources += files('n-acd/src/n-acd-bpf.c')
+  n_acd_bpf_source = 'n-acd/src/n-acd-bpf.c'
 else
-  sources += files('n-acd/src/n-acd-bpf-fallback.c')
+  n_acd_bpf_source = 'n-acd/src/n-acd-bpf-fallback.c'
 endif
 
-incs = include_directories(
-  'c-list/src',
-  'c-rbtree/src',
-  'c-siphash/src',
-  'c-stdaux/src',
-)
-
-c_flags = [
-  '-D_GNU_SOURCE',
-  '-DSO_ATTACH_BPF=50',
-  '-std=c11',
-  '-Wno-pointer-arith',
-  '-Wno-vla',
-]
-
-links = [
-  libc_rbtree,
-  libc_siphash,
-]
-
 libn_acd = static_library(
   'n-acd',
-  sources: sources,
-  include_directories: incs,
-  c_args: c_flags,
-  link_with: links,
+  sources: files(
+    'n-acd/src/n-acd.c',
+    'n-acd/src/n-acd-probe.c',
+    'n-acd/src/util/timer.c',
+    n_acd_bpf_source,
+  ),
+  include_directories: include_directories(
+    'c-list/src',
+    'c-rbtree/src',
+    'c-siphash/src',
+    'c-stdaux/src',
+  ),
+  c_args: [
+    '-D_GNU_SOURCE',
+    '-DSO_ATTACH_BPF=50',
+    '-std=c11',
+    '-Wno-pointer-arith',
+    '-Wno-vla',
+  ],
+  link_with: [
+    libc_rbtree,
+    libc_siphash,
+  ],
 )
 
 libn_acd_dep = declare_dependency(
@@ -64,35 +63,29 @@ libn_acd_dep = declare_dependency(
   link_with: libn_acd,
 )
 
-sources = files(
-  'n-dhcp4/src/n-dhcp4-c-connection.c',
-  'n-dhcp4/src/n-dhcp4-c-lease.c',
-  'n-dhcp4/src/n-dhcp4-client.c',
-  'n-dhcp4/src/n-dhcp4-c-probe.c',
-  'n-dhcp4/src/n-dhcp4-incoming.c',
-  'n-dhcp4/src/n-dhcp4-outgoing.c',
-  'n-dhcp4/src/n-dhcp4-socket.c',
-  'n-dhcp4/src/util/packet.c',
-  'n-dhcp4/src/util/socket.c',
-)
-
-incs = include_directories(
-  'c-list/src',
-  'c-siphash/src',
-  'c-stdaux/src',
-)
-
-c_flags = [
-  '-D_GNU_SOURCE',
-  '-Wno-declaration-after-statement',
-  '-Wno-pointer-arith',
-]
-
 libn_dhcp4 = static_library(
   'n-dhcp4',
-  sources: sources,
-  c_args: c_flags,
-  include_directories: incs,
+  sources: files(
+    'n-dhcp4/src/n-dhcp4-c-connection.c',
+    'n-dhcp4/src/n-dhcp4-c-lease.c',
+    'n-dhcp4/src/n-dhcp4-client.c',
+    'n-dhcp4/src/n-dhcp4-c-probe.c',
+    'n-dhcp4/src/n-dhcp4-incoming.c',
+    'n-dhcp4/src/n-dhcp4-outgoing.c',
+    'n-dhcp4/src/n-dhcp4-socket.c',
+    'n-dhcp4/src/util/packet.c',
+    'n-dhcp4/src/util/socket.c',
+  ),
+  c_args: [
+    '-D_GNU_SOURCE',
+    '-Wno-declaration-after-statement',
+    '-Wno-pointer-arith',
+  ],
+  include_directories: include_directories(
+    'c-list/src',
+    'c-siphash/src',
+    'c-stdaux/src',
+  ),
   link_with: libc_siphash,
 )
 
@@ -101,14 +94,6 @@ libn_dhcp4_dep = declare_dependency(
   link_with: libn_dhcp4,
 )
 
-nm_version_macro_header = configure_file(
-  input: 'nm-version-macros.h.in',
-  output: '@BASENAME@',
-  configuration: data_conf,
-)
-
-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')
@@ -121,52 +106,43 @@ libnm_std_aux = static_library(
   ],
   include_directories: top_inc,
   c_args: [
-    '-DG_LOG_DOMAIN="@0@"'.format(libnm_name),
-    '-DNETWORKMANAGER_COMPILATION=0',
+    '-DG_LOG_DOMAIN="libnm"',
   ],
 )
 
-sources = files(
-  'nm-glib-aux/nm-dbus-aux.c',
-  'nm-glib-aux/nm-dedup-multi.c',
-  'nm-glib-aux/nm-enum-utils.c',
-  'nm-glib-aux/nm-errno.c',
-  'nm-glib-aux/nm-hash-utils.c',
-  'nm-glib-aux/nm-io-utils.c',
-  'nm-glib-aux/nm-json-aux.c',
-  'nm-glib-aux/nm-keyfile-aux.c',
-  'nm-glib-aux/nm-logging-base.c',
-  'nm-glib-aux/nm-random-utils.c',
-  'nm-glib-aux/nm-ref-string.c',
-  'nm-glib-aux/nm-secret-utils.c',
-  'nm-glib-aux/nm-shared-utils.c',
-  'nm-glib-aux/nm-time-utils.c',
-)
-
-c_flags = [
-  '-DG_LOG_DOMAIN="@0@"'.format(libnm_name),
-  '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)',
-]
-
-links = [
-  libc_siphash,
-  libnm_std_aux,
-]
-
-libnm_utils_base = static_library(
-  'nm-utils-base',
-  sources: sources,
+libnm_glib_aux = static_library(
+  'nm-glib-aux',
+  sources: files(
+    'nm-glib-aux/nm-dbus-aux.c',
+    'nm-glib-aux/nm-dedup-multi.c',
+    'nm-glib-aux/nm-enum-utils.c',
+    'nm-glib-aux/nm-errno.c',
+    'nm-glib-aux/nm-hash-utils.c',
+    'nm-glib-aux/nm-io-utils.c',
+    'nm-glib-aux/nm-json-aux.c',
+    'nm-glib-aux/nm-keyfile-aux.c',
+    'nm-glib-aux/nm-logging-base.c',
+    'nm-glib-aux/nm-random-utils.c',
+    'nm-glib-aux/nm-ref-string.c',
+    'nm-glib-aux/nm-secret-utils.c',
+    'nm-glib-aux/nm-shared-utils.c',
+    'nm-glib-aux/nm-time-utils.c',
+  ),
   dependencies: glib_nm_default_dep,
-  c_args: c_flags,
-  link_with: links,
+  c_args: [
+    '-DG_LOG_DOMAIN="libnm"',
+  ],
+  link_with: [
+    libc_siphash,
+    libnm_std_aux,
+  ],
 )
 
-libnm_utils_base_dep = declare_dependency(
+libnm_glib_aux_dep = declare_dependency(
   dependencies: glib_nm_default_dep,
-  link_with: libnm_utils_base,
+  link_with: libnm_glib_aux,
 )
 
-
 libnm_udev_aux = static_library(
   'nm-udev-aux',
   sources: 'nm-udev-aux/nm-udev-utils.c',
@@ -174,7 +150,9 @@ libnm_udev_aux = static_library(
     glib_nm_default_dep,
     libudev_dep,
   ],
-  c_args: c_flags,
+  c_args: [
+    '-DG_LOG_DOMAIN="libnm"',
+  ],
 )
 
 libnm_udev_aux_dep = declare_dependency(
@@ -182,67 +160,125 @@ libnm_udev_aux_dep = declare_dependency(
   link_with: libnm_udev_aux,
 )
 
-sources = files(
-  'systemd/nm-sd-utils-shared.c',
-  'systemd/src/basic/alloc-util.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',
-  'systemd/src/basic/fileio.c',
-  'systemd/src/basic/format-util.c',
-  'systemd/src/basic/fs-util.c',
-  'systemd/src/basic/hash-funcs.c',
-  'systemd/src/basic/hashmap.c',
-  'systemd/src/basic/hexdecoct.c',
-  'systemd/src/basic/hostname-util.c',
-  'systemd/src/basic/in-addr-util.c',
-  'systemd/src/basic/io-util.c',
-  'systemd/src/basic/memory-util.c',
-  'systemd/src/basic/mempool.c',
-  'systemd/src/basic/parse-util.c',
-  'systemd/src/basic/path-util.c',
-  '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',
-  'systemd/src/basic/string-util.c',
-  'systemd/src/basic/strv.c',
-  'systemd/src/basic/strxcpyx.c',
-  'systemd/src/basic/time-util.c',
-  'systemd/src/basic/tmpfile-util.c',
-  'systemd/src/basic/utf8.c',
-  'systemd/src/basic/util.c',
-  'systemd/src/shared/dns-domain.c',
-  'systemd/src/shared/web-util.c',
+libnm_base = static_library(
+  'nm-base',
+  sources: files(
+    'nm-base/nm-ethtool-base.c',
+  ),
+  dependencies: libnm_glib_aux_dep,
+  c_args: [
+    '-DG_LOG_DOMAIN="libnm"',
+  ],
 )
 
-incs = include_directories(
-  'systemd/sd-adapt-shared',
-  'systemd/src/basic',
-  'systemd/src/shared',
+libnm_base_dep = declare_dependency(
+  include_directories: shared_inc,
+  dependencies: libnm_glib_aux_dep,
+  link_with: libnm_base,
 )
 
-c_flags = [
-  '-DG_LOG_DOMAIN="@0@"'.format(libnm_name),
-  '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED',
-]
+libnm_log_core = static_library(
+  'nm-log-core',
+  sources: 'nm-log-core/nm-logging.c',
+  dependencies: [
+    glib_nm_default_dep,
+    libsystemd_dep,
+  ],
+  c_args: [
+    '-DG_LOG_DOMAIN="NetworkManager"',
+  ],
+)
+
+libnm_log_core_dep = declare_dependency(
+  include_directories: shared_inc,
+  dependencies: [
+    libnm_glib_aux_dep,
+  ],
+  link_with: libnm_log_core,
+)
+
+libnm_platform = static_library(
+  'nm-platform',
+  sources: [
+    'nm-platform/nm-netlink.c',
+    'nm-platform/nm-platform-utils.c',
+    'nm-platform/nmp-netns.c',
+  ],
+  dependencies: [
+    glib_nm_default_dep,
+  ],
+  c_args: [
+    '-DG_LOG_DOMAIN="NetworkManager"',
+  ],
+)
+
+libnm_platform_dep = declare_dependency(
+  include_directories: shared_inc,
+  dependencies: [
+    libnm_glib_aux_dep,
+  ],
+  link_with: libnm_platform,
+)
 
 libnm_systemd_shared = static_library(
   'nm-systemd-shared',
-  sources: sources,
-  include_directories: incs,
+  sources: files(
+    'systemd/nm-sd-utils-shared.c',
+    'systemd/src/basic/alloc-util.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',
+    'systemd/src/basic/fileio.c',
+    'systemd/src/basic/format-util.c',
+    'systemd/src/basic/fs-util.c',
+    'systemd/src/basic/hash-funcs.c',
+    'systemd/src/basic/hashmap.c',
+    'systemd/src/basic/hexdecoct.c',
+    'systemd/src/basic/hostname-util.c',
+    'systemd/src/basic/in-addr-util.c',
+    'systemd/src/basic/io-util.c',
+    'systemd/src/basic/memory-util.c',
+    'systemd/src/basic/mempool.c',
+    'systemd/src/basic/parse-util.c',
+    'systemd/src/basic/path-util.c',
+    'systemd/src/basic/prioq.c',
+    'systemd/src/basic/process-util.c',
+    'systemd/src/basic/random-util.c',
+    'systemd/src/basic/ratelimit.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',
+    'systemd/src/basic/string-util.c',
+    'systemd/src/basic/strv.c',
+    'systemd/src/basic/strxcpyx.c',
+    'systemd/src/basic/time-util.c',
+    'systemd/src/basic/tmpfile-util.c',
+    'systemd/src/basic/utf8.c',
+    'systemd/src/basic/util.c',
+    'systemd/src/shared/dns-domain.c',
+    'systemd/src/shared/web-util.c',
+  ),
+  include_directories: include_directories(
+    'systemd/sd-adapt-shared',
+    'systemd/src/basic',
+    'systemd/src/shared',
+  ),
   dependencies: glib_nm_default_dep,
-  c_args: c_flags,
+  c_args: [
+    '-DG_LOG_DOMAIN="libnm"',
+  ],
 )
 
 libnm_systemd_shared_dep = declare_dependency(
-  include_directories: incs,
+  include_directories: include_directories(
+    'systemd/sd-adapt-shared',
+    'systemd/src/basic',
+    'systemd/src/shared',
+  ),
   dependencies: glib_dep,
   link_with: libnm_systemd_shared,
 )
@@ -251,9 +287,12 @@ libnm_systemd_logging_stub = static_library(
   'nm-systemd-logging-stub',
   sources: 'systemd/nm-logging-stub.c',
   dependencies: glib_nm_default_dep,
-  c_args: c_flags,
+  c_args: [
+    '-DG_LOG_DOMAIN="libnm"',
+  ],
 )
 
 if enable_tests
   subdir('nm-glib-aux/tests')
+  subdir('nm-platform/tests')
 endif
diff --git a/shared/n-dhcp4/src/util/packet.c b/shared/n-dhcp4/src/util/packet.c
index ef18b0b4..48f2c85e 100644
--- a/shared/n-dhcp4/src/util/packet.c
+++ b/shared/n-dhcp4/src/util/packet.c
@@ -223,7 +223,7 @@ int packet_sendto_udp(int sockfd,
 
         pktlen = sendmsg(sockfd, &msg, 0);
         if (pktlen < 0)
-                return -errno;
+                return -c_errno();
 
         /*
          * Kernel never truncates. Worst case, we get -EMSGSIZE. Kernel *might*
diff --git a/shared/nm-base/nm-base.h b/shared/nm-base/nm-base.h
new file mode 100644
index 00000000..105d1783
--- /dev/null
+++ b/shared/nm-base/nm-base.h
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_LIBNM_BASE_H__
+#define __NM_LIBNM_BASE_H__
+
+/*****************************************************************************/
+
+/* this must be the same as NM_UTILS_HWADDR_LEN_MAX from libnm. */
+#define _NM_UTILS_HWADDR_LEN_MAX 20
+
+/*****************************************************************************/
+
+typedef enum {
+    NM_ETHTOOL_ID_UNKNOWN = -1,
+
+    _NM_ETHTOOL_ID_FIRST = 0,
+
+    _NM_ETHTOOL_ID_COALESCE_FIRST      = _NM_ETHTOOL_ID_FIRST,
+    NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX = _NM_ETHTOOL_ID_COALESCE_FIRST,
+    NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX,
+    NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH,
+    NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW,
+    NM_ETHTOOL_ID_COALESCE_RX_FRAMES,
+    NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH,
+    NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ,
+    NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW,
+    NM_ETHTOOL_ID_COALESCE_RX_USECS,
+    NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH,
+    NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ,
+    NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW,
+    NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL,
+    NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS,
+    NM_ETHTOOL_ID_COALESCE_TX_FRAMES,
+    NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH,
+    NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ,
+    NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW,
+    NM_ETHTOOL_ID_COALESCE_TX_USECS,
+    NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH,
+    NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ,
+    NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW,
+    _NM_ETHTOOL_ID_COALESCE_LAST = NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW,
+
+    _NM_ETHTOOL_ID_FEATURE_FIRST         = _NM_ETHTOOL_ID_COALESCE_LAST + 1,
+    NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD = _NM_ETHTOOL_ID_FEATURE_FIRST,
+    NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_FCOE_MTU,
+    NM_ETHTOOL_ID_FEATURE_GRO,
+    NM_ETHTOOL_ID_FEATURE_GSO,
+    NM_ETHTOOL_ID_FEATURE_HIGHDMA,
+    NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_LOOPBACK,
+    NM_ETHTOOL_ID_FEATURE_LRO,
+    NM_ETHTOOL_ID_FEATURE_MACSEC_HW_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_NTUPLE,
+    NM_ETHTOOL_ID_FEATURE_RX,
+    NM_ETHTOOL_ID_FEATURE_RXHASH,
+    NM_ETHTOOL_ID_FEATURE_RXVLAN,
+    NM_ETHTOOL_ID_FEATURE_RX_ALL,
+    NM_ETHTOOL_ID_FEATURE_RX_FCS,
+    NM_ETHTOOL_ID_FEATURE_RX_GRO_HW,
+    NM_ETHTOOL_ID_FEATURE_RX_GRO_LIST,
+    NM_ETHTOOL_ID_FEATURE_RX_UDP_GRO_FORWARDING,
+    NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER,
+    NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER,
+    NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE,
+    NM_ETHTOOL_ID_FEATURE_SG,
+    NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD,
+    NM_ETHTOOL_ID_FEATURE_TLS_HW_RX_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_TSO,
+    NM_ETHTOOL_ID_FEATURE_TX,
+    NM_ETHTOOL_ID_FEATURE_TXVLAN,
+    NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC,
+    NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4,
+    NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6,
+    NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC,
+    NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP,
+    NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_GSO_LIST,
+    NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL,
+    NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST,
+    NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY,
+    NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER,
+    NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST,
+    NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT,
+    _NM_ETHTOOL_ID_FEATURE_LAST = NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT,
+
+    _NM_ETHTOOL_ID_RING_FIRST = _NM_ETHTOOL_ID_FEATURE_LAST + 1,
+    NM_ETHTOOL_ID_RING_RX     = _NM_ETHTOOL_ID_RING_FIRST,
+    NM_ETHTOOL_ID_RING_RX_JUMBO,
+    NM_ETHTOOL_ID_RING_RX_MINI,
+    NM_ETHTOOL_ID_RING_TX,
+    _NM_ETHTOOL_ID_RING_LAST = NM_ETHTOOL_ID_RING_TX,
+
+    _NM_ETHTOOL_ID_LAST = _NM_ETHTOOL_ID_RING_LAST,
+
+    _NM_ETHTOOL_ID_COALESCE_NUM =
+        (_NM_ETHTOOL_ID_COALESCE_LAST - _NM_ETHTOOL_ID_COALESCE_FIRST + 1),
+    _NM_ETHTOOL_ID_FEATURE_NUM = (_NM_ETHTOOL_ID_FEATURE_LAST - _NM_ETHTOOL_ID_FEATURE_FIRST + 1),
+    _NM_ETHTOOL_ID_RING_NUM    = (_NM_ETHTOOL_ID_RING_LAST - _NM_ETHTOOL_ID_RING_FIRST + 1),
+    _NM_ETHTOOL_ID_NUM         = (_NM_ETHTOOL_ID_LAST - _NM_ETHTOOL_ID_FIRST + 1),
+} NMEthtoolID;
+
+#define _NM_ETHTOOL_ID_FEATURE_AS_IDX(ethtool_id)  ((ethtool_id) -_NM_ETHTOOL_ID_FEATURE_FIRST)
+#define _NM_ETHTOOL_ID_COALESCE_AS_IDX(ethtool_id) ((ethtool_id) -_NM_ETHTOOL_ID_COALESCE_FIRST)
+
+typedef enum {
+    NM_ETHTOOL_TYPE_UNKNOWN,
+    NM_ETHTOOL_TYPE_COALESCE,
+    NM_ETHTOOL_TYPE_FEATURE,
+    NM_ETHTOOL_TYPE_RING,
+} NMEthtoolType;
+
+/****************************************************************************/
+
+static inline gboolean
+nm_ethtool_id_is_feature(NMEthtoolID id)
+{
+    return id >= _NM_ETHTOOL_ID_FEATURE_FIRST && id <= _NM_ETHTOOL_ID_FEATURE_LAST;
+}
+
+static inline gboolean
+nm_ethtool_id_is_coalesce(NMEthtoolID id)
+{
+    return id >= _NM_ETHTOOL_ID_COALESCE_FIRST && id <= _NM_ETHTOOL_ID_COALESCE_LAST;
+}
+
+static inline gboolean
+nm_ethtool_id_is_ring(NMEthtoolID id)
+{
+    return id >= _NM_ETHTOOL_ID_RING_FIRST && id <= _NM_ETHTOOL_ID_RING_LAST;
+}
+
+/*****************************************************************************/
+
+typedef enum {
+    _NM_SETTING_WIRED_WAKE_ON_LAN_NONE      = 0,
+    _NM_SETTING_WIRED_WAKE_ON_LAN_PHY       = 0x2,
+    _NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST   = 0x4,
+    _NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST = 0x8,
+    _NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST = 0x10,
+    _NM_SETTING_WIRED_WAKE_ON_LAN_ARP       = 0x20,
+    _NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC     = 0x40,
+
+    _NM_SETTING_WIRED_WAKE_ON_LAN_ALL = 0x7E,
+
+    _NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT         = 0x1,
+    _NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE          = 0x8000,
+    _NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS = 0x8001,
+} _NMSettingWiredWakeOnLan;
+
+/*****************************************************************************/
+
+typedef enum {
+    /* In priority order; higher number == higher priority */
+
+    NM_IP_CONFIG_SOURCE_UNKNOWN = 0,
+
+    /* for routes, the source is mapped to the uint8 field rtm_protocol.
+     * Reserve the range [1,0x100] for native RTPROT values. */
+
+    NM_IP_CONFIG_SOURCE_RTPROT_UNSPEC   = 1 + 0,
+    NM_IP_CONFIG_SOURCE_RTPROT_REDIRECT = 1 + 1,
+    NM_IP_CONFIG_SOURCE_RTPROT_KERNEL   = 1 + 2,
+    NM_IP_CONFIG_SOURCE_RTPROT_BOOT     = 1 + 3,
+    NM_IP_CONFIG_SOURCE_RTPROT_STATIC   = 1 + 4,
+    NM_IP_CONFIG_SOURCE_RTPROT_RA       = 1 + 9,
+    NM_IP_CONFIG_SOURCE_RTPROT_DHCP     = 1 + 16,
+    _NM_IP_CONFIG_SOURCE_RTPROT_LAST    = 1 + 0xFF,
+
+    NM_IP_CONFIG_SOURCE_KERNEL,
+    NM_IP_CONFIG_SOURCE_SHARED,
+    NM_IP_CONFIG_SOURCE_IP4LL,
+    NM_IP_CONFIG_SOURCE_IP6LL,
+    NM_IP_CONFIG_SOURCE_PPP,
+    NM_IP_CONFIG_SOURCE_WWAN,
+    NM_IP_CONFIG_SOURCE_VPN,
+    NM_IP_CONFIG_SOURCE_DHCP,
+    NM_IP_CONFIG_SOURCE_NDISC,
+    NM_IP_CONFIG_SOURCE_USER,
+} NMIPConfigSource;
+
+static inline gboolean
+NM_IS_IP_CONFIG_SOURCE_RTPROT(NMIPConfigSource source)
+{
+    return source > NM_IP_CONFIG_SOURCE_UNKNOWN && source <= _NM_IP_CONFIG_SOURCE_RTPROT_LAST;
+}
+
+/****************************************************************************/
+
+#endif /* __NM_LIBNM_BASE_H__ */
diff --git a/shared/nm-base/nm-ethtool-base.c b/shared/nm-base/nm-ethtool-base.c
new file mode 100644
index 00000000..52ff2871
--- /dev/null
+++ b/shared/nm-base/nm-ethtool-base.c
@@ -0,0 +1,288 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
+
+#include "nm-ethtool-base.h"
+
+#include "nm-ethtool-utils-base.h"
+
+/*****************************************************************************/
+
+#define ETHT_DATA(xname)                                \
+    [NM_ETHTOOL_ID_##xname] = (&((const NMEthtoolData){ \
+        .optname = NM_ETHTOOL_OPTNAME_##xname,          \
+        .id      = NM_ETHTOOL_ID_##xname,               \
+    }))
+
+const NMEthtoolData *const nm_ethtool_data[_NM_ETHTOOL_ID_NUM + 1] = {
+    /* indexed by NMEthtoolID */
+    ETHT_DATA(COALESCE_ADAPTIVE_RX),
+    ETHT_DATA(COALESCE_ADAPTIVE_TX),
+    ETHT_DATA(COALESCE_PKT_RATE_HIGH),
+    ETHT_DATA(COALESCE_PKT_RATE_LOW),
+    ETHT_DATA(COALESCE_RX_FRAMES),
+    ETHT_DATA(COALESCE_RX_FRAMES_HIGH),
+    ETHT_DATA(COALESCE_RX_FRAMES_IRQ),
+    ETHT_DATA(COALESCE_RX_FRAMES_LOW),
+    ETHT_DATA(COALESCE_RX_USECS),
+    ETHT_DATA(COALESCE_RX_USECS_HIGH),
+    ETHT_DATA(COALESCE_RX_USECS_IRQ),
+    ETHT_DATA(COALESCE_RX_USECS_LOW),
+    ETHT_DATA(COALESCE_SAMPLE_INTERVAL),
+    ETHT_DATA(COALESCE_STATS_BLOCK_USECS),
+    ETHT_DATA(COALESCE_TX_FRAMES),
+    ETHT_DATA(COALESCE_TX_FRAMES_HIGH),
+    ETHT_DATA(COALESCE_TX_FRAMES_IRQ),
+    ETHT_DATA(COALESCE_TX_FRAMES_LOW),
+    ETHT_DATA(COALESCE_TX_USECS),
+    ETHT_DATA(COALESCE_TX_USECS_HIGH),
+    ETHT_DATA(COALESCE_TX_USECS_IRQ),
+    ETHT_DATA(COALESCE_TX_USECS_LOW),
+    ETHT_DATA(FEATURE_ESP_HW_OFFLOAD),
+    ETHT_DATA(FEATURE_ESP_TX_CSUM_HW_OFFLOAD),
+    ETHT_DATA(FEATURE_FCOE_MTU),
+    ETHT_DATA(FEATURE_GRO),
+    ETHT_DATA(FEATURE_GSO),
+    ETHT_DATA(FEATURE_HIGHDMA),
+    ETHT_DATA(FEATURE_HW_TC_OFFLOAD),
+    ETHT_DATA(FEATURE_L2_FWD_OFFLOAD),
+    ETHT_DATA(FEATURE_LOOPBACK),
+    ETHT_DATA(FEATURE_LRO),
+    ETHT_DATA(FEATURE_MACSEC_HW_OFFLOAD),
+    ETHT_DATA(FEATURE_NTUPLE),
+    ETHT_DATA(FEATURE_RX),
+    ETHT_DATA(FEATURE_RXHASH),
+    ETHT_DATA(FEATURE_RXVLAN),
+    ETHT_DATA(FEATURE_RX_ALL),
+    ETHT_DATA(FEATURE_RX_FCS),
+    ETHT_DATA(FEATURE_RX_GRO_HW),
+    ETHT_DATA(FEATURE_RX_GRO_LIST),
+    ETHT_DATA(FEATURE_RX_UDP_GRO_FORWARDING),
+    ETHT_DATA(FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD),
+    ETHT_DATA(FEATURE_RX_VLAN_FILTER),
+    ETHT_DATA(FEATURE_RX_VLAN_STAG_FILTER),
+    ETHT_DATA(FEATURE_RX_VLAN_STAG_HW_PARSE),
+    ETHT_DATA(FEATURE_SG),
+    ETHT_DATA(FEATURE_TLS_HW_RECORD),
+    ETHT_DATA(FEATURE_TLS_HW_RX_OFFLOAD),
+    ETHT_DATA(FEATURE_TLS_HW_TX_OFFLOAD),
+    ETHT_DATA(FEATURE_TSO),
+    ETHT_DATA(FEATURE_TX),
+    ETHT_DATA(FEATURE_TXVLAN),
+    ETHT_DATA(FEATURE_TX_CHECKSUM_FCOE_CRC),
+    ETHT_DATA(FEATURE_TX_CHECKSUM_IPV4),
+    ETHT_DATA(FEATURE_TX_CHECKSUM_IPV6),
+    ETHT_DATA(FEATURE_TX_CHECKSUM_IP_GENERIC),
+    ETHT_DATA(FEATURE_TX_CHECKSUM_SCTP),
+    ETHT_DATA(FEATURE_TX_ESP_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_FCOE_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_GRE_CSUM_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_GRE_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_GSO_LIST),
+    ETHT_DATA(FEATURE_TX_GSO_PARTIAL),
+    ETHT_DATA(FEATURE_TX_GSO_ROBUST),
+    ETHT_DATA(FEATURE_TX_IPXIP4_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_IPXIP6_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_NOCACHE_COPY),
+    ETHT_DATA(FEATURE_TX_SCATTER_GATHER),
+    ETHT_DATA(FEATURE_TX_SCATTER_GATHER_FRAGLIST),
+    ETHT_DATA(FEATURE_TX_SCTP_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_TCP6_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_TCP_ECN_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_TCP_MANGLEID_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_TCP_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_UDP_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_UDP_TNL_SEGMENTATION),
+    ETHT_DATA(FEATURE_TX_VLAN_STAG_HW_INSERT),
+    ETHT_DATA(RING_RX),
+    ETHT_DATA(RING_RX_JUMBO),
+    ETHT_DATA(RING_RX_MINI),
+    ETHT_DATA(RING_TX),
+    [_NM_ETHTOOL_ID_NUM] = NULL,
+};
+
+static const guint8 _by_name[_NM_ETHTOOL_ID_NUM] = {
+    /* sorted by optname. */
+    NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX,
+    NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX,
+    NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH,
+    NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW,
+    NM_ETHTOOL_ID_COALESCE_RX_FRAMES,
+    NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH,
+    NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ,
+    NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW,
+    NM_ETHTOOL_ID_COALESCE_RX_USECS,
+    NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH,
+    NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ,
+    NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW,
+    NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL,
+    NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS,
+    NM_ETHTOOL_ID_COALESCE_TX_FRAMES,
+    NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH,
+    NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ,
+    NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW,
+    NM_ETHTOOL_ID_COALESCE_TX_USECS,
+    NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH,
+    NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ,
+    NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW,
+    NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_FCOE_MTU,
+    NM_ETHTOOL_ID_FEATURE_GRO,
+    NM_ETHTOOL_ID_FEATURE_GSO,
+    NM_ETHTOOL_ID_FEATURE_HIGHDMA,
+    NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_LOOPBACK,
+    NM_ETHTOOL_ID_FEATURE_LRO,
+    NM_ETHTOOL_ID_FEATURE_MACSEC_HW_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_NTUPLE,
+    NM_ETHTOOL_ID_FEATURE_RX,
+    NM_ETHTOOL_ID_FEATURE_RX_ALL,
+    NM_ETHTOOL_ID_FEATURE_RX_FCS,
+    NM_ETHTOOL_ID_FEATURE_RX_GRO_HW,
+    NM_ETHTOOL_ID_FEATURE_RX_GRO_LIST,
+    NM_ETHTOOL_ID_FEATURE_RX_UDP_GRO_FORWARDING,
+    NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER,
+    NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER,
+    NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE,
+    NM_ETHTOOL_ID_FEATURE_RXHASH,
+    NM_ETHTOOL_ID_FEATURE_RXVLAN,
+    NM_ETHTOOL_ID_FEATURE_SG,
+    NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD,
+    NM_ETHTOOL_ID_FEATURE_TLS_HW_RX_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD,
+    NM_ETHTOOL_ID_FEATURE_TSO,
+    NM_ETHTOOL_ID_FEATURE_TX,
+    NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC,
+    NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC,
+    NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4,
+    NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6,
+    NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP,
+    NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_GSO_LIST,
+    NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL,
+    NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST,
+    NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY,
+    NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER,
+    NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST,
+    NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION,
+    NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT,
+    NM_ETHTOOL_ID_FEATURE_TXVLAN,
+    NM_ETHTOOL_ID_RING_RX,
+    NM_ETHTOOL_ID_RING_RX_JUMBO,
+    NM_ETHTOOL_ID_RING_RX_MINI,
+    NM_ETHTOOL_ID_RING_TX,
+};
+
+/*****************************************************************************/
+
+static void
+_ASSERT_data(void)
+{
+    int i;
+
+    if (!NM_MORE_ASSERT_ONCE(10))
+        return;
+
+    G_STATIC_ASSERT_EXPR(_NM_ETHTOOL_ID_FIRST == 0);
+    G_STATIC_ASSERT_EXPR(_NM_ETHTOOL_ID_LAST == _NM_ETHTOOL_ID_NUM - 1);
+    G_STATIC_ASSERT_EXPR(_NM_ETHTOOL_ID_NUM > 0);
+
+    nm_assert(NM_PTRARRAY_LEN(nm_ethtool_data) == _NM_ETHTOOL_ID_NUM);
+    nm_assert(G_N_ELEMENTS(_by_name) == _NM_ETHTOOL_ID_NUM);
+    nm_assert(G_N_ELEMENTS(nm_ethtool_data) == _NM_ETHTOOL_ID_NUM + 1);
+
+    for (i = 0; i < _NM_ETHTOOL_ID_NUM; i++) {
+        const NMEthtoolData *d = nm_ethtool_data[i];
+
+        nm_assert(d);
+        nm_assert(d->id == (NMEthtoolID) i);
+        nm_assert(d->optname && d->optname[0]);
+    }
+
+    for (i = 0; i < _NM_ETHTOOL_ID_NUM; i++) {
+        NMEthtoolID          id = _by_name[i];
+        const NMEthtoolData *d;
+
+        nm_assert(id >= 0);
+        nm_assert(id < _NM_ETHTOOL_ID_NUM);
+
+        d = nm_ethtool_data[id];
+        if (i > 0) {
+            /* since we assert that all optnames are sorted strictly monotonically increasing,
+             * it also follows that there are no duplicates in the _by_name.
+             * It also follows, that all names in nm_ethtool_data are unique. */
+            if (strcmp(nm_ethtool_data[_by_name[i - 1]]->optname, d->optname) >= 0) {
+                g_error("nm_ethtool_data is not sorted asciibetically: %u/%s should be after %u/%s",
+                        i - 1,
+                        nm_ethtool_data[_by_name[i - 1]]->optname,
+                        i,
+                        d->optname);
+            }
+        }
+    }
+}
+
+static int
+_by_name_cmp(gconstpointer a, gconstpointer b, gpointer user_data)
+{
+    const guint8 *p_id    = a;
+    const char *  optname = b;
+
+    nm_assert(p_id && p_id >= _by_name && p_id <= &_by_name[_NM_ETHTOOL_ID_NUM]);
+    nm_assert(*p_id < _NM_ETHTOOL_ID_NUM);
+
+    return strcmp(nm_ethtool_data[*p_id]->optname, optname);
+}
+
+const NMEthtoolData *
+nm_ethtool_data_get_by_optname(const char *optname)
+{
+    gssize idx;
+
+    if (!optname)
+        return NULL;
+
+    _ASSERT_data();
+
+    idx = nm_utils_array_find_binary_search((gconstpointer *) _by_name,
+                                            sizeof(_by_name[0]),
+                                            _NM_ETHTOOL_ID_NUM,
+                                            optname,
+                                            _by_name_cmp,
+                                            NULL);
+    return (idx < 0) ? NULL : nm_ethtool_data[_by_name[idx]];
+}
+
+NMEthtoolType
+nm_ethtool_id_to_type(NMEthtoolID id)
+{
+    if (nm_ethtool_id_is_coalesce(id))
+        return NM_ETHTOOL_TYPE_COALESCE;
+    if (nm_ethtool_id_is_feature(id))
+        return NM_ETHTOOL_TYPE_FEATURE;
+    if (nm_ethtool_id_is_ring(id))
+        return NM_ETHTOOL_TYPE_RING;
+
+    return NM_ETHTOOL_TYPE_UNKNOWN;
+}
diff --git a/shared/nm-base/nm-ethtool-base.h b/shared/nm-base/nm-ethtool-base.h
new file mode 100644
index 00000000..be90ce75
--- /dev/null
+++ b/shared/nm-base/nm-ethtool-base.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_ETHTOOL_BASE_H__
+#define __NM_ETHTOOL_BASE_H__
+
+#include "nm-base/nm-base.h"
+
+/*****************************************************************************/
+
+typedef struct {
+    const char *optname;
+    NMEthtoolID id;
+} NMEthtoolData;
+
+extern const NMEthtoolData *const nm_ethtool_data[_NM_ETHTOOL_ID_NUM + 1];
+
+const NMEthtoolData *nm_ethtool_data_get_by_optname(const char *optname);
+
+NMEthtoolType nm_ethtool_id_to_type(NMEthtoolID id);
+
+/****************************************************************************/
+
+static inline NMEthtoolID
+nm_ethtool_id_get_by_name(const char *optname)
+{
+    const NMEthtoolData *d;
+
+    d = nm_ethtool_data_get_by_optname(optname);
+    return d ? d->id : NM_ETHTOOL_ID_UNKNOWN;
+}
+
+/****************************************************************************/
+
+#endif /* __NM_ETHTOOL_BASE_H__ */
diff --git a/shared/nm-base/nm-ethtool-utils-base.h b/shared/nm-base/nm-ethtool-utils-base.h
new file mode 100644
index 00000000..d422724b
--- /dev/null
+++ b/shared/nm-base/nm-ethtool-utils-base.h
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_ETHTOOL_UTILS_H__
+#define __NM_ETHTOOL_UTILS_H__
+
+G_BEGIN_DECLS
+
+/*****************************************************************************/
+
+#define NM_ETHTOOL_OPTNAME_FEATURE_ESP_HW_OFFLOAD             "feature-esp-hw-offload"
+#define NM_ETHTOOL_OPTNAME_FEATURE_ESP_TX_CSUM_HW_OFFLOAD     "feature-esp-tx-csum-hw-offload"
+#define NM_ETHTOOL_OPTNAME_FEATURE_FCOE_MTU                   "feature-fcoe-mtu"
+#define NM_ETHTOOL_OPTNAME_FEATURE_GRO                        "feature-gro"
+#define NM_ETHTOOL_OPTNAME_FEATURE_GSO                        "feature-gso"
+#define NM_ETHTOOL_OPTNAME_FEATURE_HIGHDMA                    "feature-highdma"
+#define NM_ETHTOOL_OPTNAME_FEATURE_HW_TC_OFFLOAD              "feature-hw-tc-offload"
+#define NM_ETHTOOL_OPTNAME_FEATURE_L2_FWD_OFFLOAD             "feature-l2-fwd-offload"
+#define NM_ETHTOOL_OPTNAME_FEATURE_LOOPBACK                   "feature-loopback"
+#define NM_ETHTOOL_OPTNAME_FEATURE_LRO                        "feature-lro"
+#define NM_ETHTOOL_OPTNAME_FEATURE_MACSEC_HW_OFFLOAD          "feature-macsec-hw-offload"
+#define NM_ETHTOOL_OPTNAME_FEATURE_NTUPLE                     "feature-ntuple"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RX                         "feature-rx"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RXHASH                     "feature-rxhash"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RXVLAN                     "feature-rxvlan"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RX_ALL                     "feature-rx-all"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RX_FCS                     "feature-rx-fcs"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RX_GRO_HW                  "feature-rx-gro-hw"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RX_GRO_LIST                "feature-rx-gro-list"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RX_UDP_GRO_FORWARDING      "feature-rx-udp-gro-forwarding"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD "feature-rx-udp_tunnel-port-offload"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RX_VLAN_FILTER             "feature-rx-vlan-filter"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RX_VLAN_STAG_FILTER        "feature-rx-vlan-stag-filter"
+#define NM_ETHTOOL_OPTNAME_FEATURE_RX_VLAN_STAG_HW_PARSE      "feature-rx-vlan-stag-hw-parse"
+#define NM_ETHTOOL_OPTNAME_FEATURE_SG                         "feature-sg"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TLS_HW_RECORD              "feature-tls-hw-record"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TLS_HW_RX_OFFLOAD          "feature-tls-hw-rx-offload"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TLS_HW_TX_OFFLOAD          "feature-tls-hw-tx-offload"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TSO                        "feature-tso"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX                         "feature-tx"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TXVLAN                     "feature-txvlan"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_CHECKSUM_FCOE_CRC       "feature-tx-checksum-fcoe-crc"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_CHECKSUM_IPV4           "feature-tx-checksum-ipv4"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_CHECKSUM_IPV6           "feature-tx-checksum-ipv6"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_CHECKSUM_IP_GENERIC     "feature-tx-checksum-ip-generic"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_CHECKSUM_SCTP           "feature-tx-checksum-sctp"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_ESP_SEGMENTATION        "feature-tx-esp-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_FCOE_SEGMENTATION       "feature-tx-fcoe-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_GRE_CSUM_SEGMENTATION   "feature-tx-gre-csum-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_GRE_SEGMENTATION        "feature-tx-gre-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_GSO_LIST                "feature-tx-gso-list"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_GSO_PARTIAL             "feature-tx-gso-partial"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_GSO_ROBUST              "feature-tx-gso-robust"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_IPXIP4_SEGMENTATION     "feature-tx-ipxip4-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_IPXIP6_SEGMENTATION     "feature-tx-ipxip6-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_NOCACHE_COPY            "feature-tx-nocache-copy"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_SCATTER_GATHER          "feature-tx-scatter-gather"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_SCATTER_GATHER_FRAGLIST "feature-tx-scatter-gather-fraglist"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_SCTP_SEGMENTATION       "feature-tx-sctp-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_TCP6_SEGMENTATION       "feature-tx-tcp6-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_TCP_ECN_SEGMENTATION    "feature-tx-tcp-ecn-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_TCP_MANGLEID_SEGMENTATION \
+    "feature-tx-tcp-mangleid-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_TCP_SEGMENTATION "feature-tx-tcp-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION \
+    "feature-tx-tunnel-remcsum-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_UDP_SEGMENTATION "feature-tx-udp-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION \
+    "feature-tx-udp_tnl-csum-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_UDP_TNL_SEGMENTATION "feature-tx-udp_tnl-segmentation"
+#define NM_ETHTOOL_OPTNAME_FEATURE_TX_VLAN_STAG_HW_INSERT  "feature-tx-vlan-stag-hw-insert"
+
+#define NM_ETHTOOL_OPTNAME_COALESCE_ADAPTIVE_RX       "coalesce-adaptive-rx"
+#define NM_ETHTOOL_OPTNAME_COALESCE_ADAPTIVE_TX       "coalesce-adaptive-tx"
+#define NM_ETHTOOL_OPTNAME_COALESCE_PKT_RATE_HIGH     "coalesce-pkt-rate-high"
+#define NM_ETHTOOL_OPTNAME_COALESCE_PKT_RATE_LOW      "coalesce-pkt-rate-low"
+#define NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES         "coalesce-rx-frames"
+#define NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES_HIGH    "coalesce-rx-frames-high"
+#define NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES_IRQ     "coalesce-rx-frames-irq"
+#define NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES_LOW     "coalesce-rx-frames-low"
+#define NM_ETHTOOL_OPTNAME_COALESCE_RX_USECS          "coalesce-rx-usecs"
+#define NM_ETHTOOL_OPTNAME_COALESCE_RX_USECS_HIGH     "coalesce-rx-usecs-high"
+#define NM_ETHTOOL_OPTNAME_COALESCE_RX_USECS_IRQ      "coalesce-rx-usecs-irq"
+#define NM_ETHTOOL_OPTNAME_COALESCE_RX_USECS_LOW      "coalesce-rx-usecs-low"
+#define NM_ETHTOOL_OPTNAME_COALESCE_SAMPLE_INTERVAL   "coalesce-sample-interval"
+#define NM_ETHTOOL_OPTNAME_COALESCE_STATS_BLOCK_USECS "coalesce-stats-block-usecs"
+#define NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES         "coalesce-tx-frames"
+#define NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES_HIGH    "coalesce-tx-frames-high"
+#define NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES_IRQ     "coalesce-tx-frames-irq"
+#define NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES_LOW     "coalesce-tx-frames-low"
+#define NM_ETHTOOL_OPTNAME_COALESCE_TX_USECS          "coalesce-tx-usecs"
+#define NM_ETHTOOL_OPTNAME_COALESCE_TX_USECS_HIGH     "coalesce-tx-usecs-high"
+#define NM_ETHTOOL_OPTNAME_COALESCE_TX_USECS_IRQ      "coalesce-tx-usecs-irq"
+#define NM_ETHTOOL_OPTNAME_COALESCE_TX_USECS_LOW      "coalesce-tx-usecs-low"
+
+#define NM_ETHTOOL_OPTNAME_RING_RX       "ring-rx"
+#define NM_ETHTOOL_OPTNAME_RING_RX_JUMBO "ring-rx-jumbo"
+#define NM_ETHTOOL_OPTNAME_RING_RX_MINI  "ring-rx-mini"
+#define NM_ETHTOOL_OPTNAME_RING_TX       "ring-tx"
+
+/*****************************************************************************/
+
+G_END_DECLS
+
+#endif /* __NM_ETHTOOL_UTILS_H__ */
diff --git a/shared/nm-default.h b/shared/nm-default.h
deleted file mode 100644
index b322f1d3..00000000
--- a/shared/nm-default.h
+++ /dev/null
@@ -1,302 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-/*
- * Copyright (C) 2015 Red Hat, Inc.
- */
-
-/* With autotools (and also meson), all our source files are expected to include <config.h>.
- * as very first header. Then they are expected to include "config-extra.h" and a small set
- * of headers that we always want to have included (depending on what sources we compile
- * (as determined by NETWORKMANAGER_COMPILATION define) that can be  "nm-glib-aux/nm-macros-internal.h"
- * and similar.
- *
- * To simplify that, all our source files are only expected to include "nm-default.h" as first,
- * and thereby rely on getting a basic set of headers already.
- */
-
-#ifndef __NM_DEFAULT_H__
-#define __NM_DEFAULT_H__
-
-#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB                (1 << 0)
-#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB       (1 << 1)
-#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG      (1 << 2)
-#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM               (1 << 3)
-#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE       (1 << 4)
-#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE          (1 << 5)
-#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL (1 << 6)
-#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE  (1 << 7)
-#define NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON              (1 << 10)
-#define NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD             (1 << 11)
-
-#define NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE             \
-    (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB             \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB      \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE         \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)
-
-#define NM_NETWORKMANAGER_COMPILATION_LIBNM                                                        \
-    (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB                                                   \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE                                            \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE                                               \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)
-
-#define NM_NETWORKMANAGER_COMPILATION_CLIENT             \
-    (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB         \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE)
-
-#define NM_NETWORKMANAGER_COMPILATION_DAEMON                  \
-    (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB              \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG      \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE          \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL \
-     | NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON)
-
-#define NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED \
-    (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB | NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD)
-
-#define NM_NETWORKMANAGER_COMPILATION_SYSTEMD \
-    (0 | NM_NETWORKMANAGER_COMPILATION_DAEMON | NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED)
-
-#define NM_NETWORKMANAGER_COMPILATION_GLIB (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB)
-
-#ifndef NETWORKMANAGER_COMPILATION
-    #error Define NETWORKMANAGER_COMPILATION accordingly
-#endif
-
-#ifndef G_LOG_DOMAIN
-    #if defined(NETWORKMANAGER_COMPILATION_TEST)
-        #define G_LOG_DOMAIN "test"
-    #elif NETWORKMANAGER_COMPILATION & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON
-        #define G_LOG_DOMAIN "NetworkManager"
-    #else
-        #error Need to define G_LOG_DOMAIN
-    #endif
-#endif
-
-/*****************************************************************************/
-
-/* always include these headers for our internal source files. */
-
-#ifndef ___CONFIG_H__
-    #define ___CONFIG_H__
-    #include <config.h>
-#endif
-
-#include "config-extra.h"
-
-/* for internal compilation we don't want the deprecation macros
- * to be in effect. Define the widest range of versions to effectively
- * disable deprecation checks */
-#define NM_VERSION_MIN_REQUIRED NM_VERSION_0_9_8
-
-#ifndef NM_MORE_ASSERTS
-    #define NM_MORE_ASSERTS 0
-#endif
-
-#if NM_MORE_ASSERTS == 0
-    /* The cast macros like NM_TYPE() are implemented via G_TYPE_CHECK_INSTANCE_CAST()
- * and _G_TYPE_CIC(). The latter, by default performs runtime checks of the type
- * by calling g_type_check_instance_cast().
- * This check has a certain overhead without being helpful.
- *
- * Example 1:
- *     static void foo (NMType *obj)
- *     {
- *         access_obj_without_check (obj);
- *     }
- *     foo ((NMType *) obj);
- *     // There is no runtime check and passing an invalid pointer
- *     // leads to a crash.
- *
- * Example 2:
- *     static void foo (NMType *obj)
- *     {
- *         access_obj_without_check (obj);
- *     }
- *     foo (NM_TYPE (obj));
- *     // There is a runtime check which prints a g_warning(), but that doesn't
- *     // avoid the crash as NM_TYPE() cannot do anything then passing on the
- *     // invalid pointer.
- *
- * Example 3:
- *     static void foo (NMType *obj)
- *     {
- *         g_return_if_fail (NM_IS_TYPE (obj));
- *         access_obj_without_check (obj);
- *     }
- *     foo ((NMType *) obj);
- *     // There is a runtime check which prints a g_critical() which also avoids
- *     // the crash. That is actually helpful to catch bugs and avoid crashes.
- *
- * Example 4:
- *     static void foo (NMType *obj)
- *     {
- *         g_return_if_fail (NM_IS_TYPE (obj));
- *         access_obj_without_check (obj);
- *     }
- *     foo (NM_TYPE (obj));
- *     // The runtime check is performed twice, with printing a g_warning() and
- *     // a g_critical() and avoiding the crash.
- *
- * Example 3 is how it should be done. Type checks in NM_TYPE() are pointless.
- * Disable them for our production builds.
- */
-    #ifndef G_DISABLE_CAST_CHECKS
-        #define G_DISABLE_CAST_CHECKS
-    #endif
-#endif
-
-#if NM_MORE_ASSERTS == 0
-    #ifndef G_DISABLE_CAST_CHECKS
-        /* Unless compiling with G_DISABLE_CAST_CHECKS, glib performs type checking
- * during G_VARIANT_TYPE() via g_variant_type_checked_(). This is not necessary
- * because commonly this cast is needed during something like
- *
- *   g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}"));
- *
- * Note that in if the variant type would be invalid, the check still
- * wouldn't make the buggy code magically work. Instead of passing a
- * bogus type string (bad), it would pass %NULL to g_variant_builder_init()
- * (also bad).
- *
- * Also, a function like g_variant_builder_init() already validates
- * the input type via something like
- *
- *   g_return_if_fail (g_variant_type_is_container (type));
- *
- * So, by having G_VARIANT_TYPE() also validate the type, we validate
- * twice, whereas the first validation is rather pointless because it
- * doesn't prevent the function to be called with invalid arguments.
- *
- * Just patch G_VARIANT_TYPE() to perform no check.
- */
-        #undef G_VARIANT_TYPE
-        #define G_VARIANT_TYPE(type_string) ((const GVariantType *) (type_string))
-    #endif
-#endif
-
-#include <stdlib.h>
-
-/*****************************************************************************/
-
-#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB
-
-    #include <glib.h>
-
-    #if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG
-        #if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB
-            #error Cannot define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG and NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB
-        #endif
-        #include <glib/gi18n.h>
-    #elif (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB
-        #include <glib/gi18n-lib.h>
-    #endif
-
-    /*****************************************************************************/
-
-    #if NM_MORE_ASSERTS == 0
-
-/* glib assertions (g_return_*(), g_assert*()) contain a textual representation
- * of the checked statement. This part of the assertion blows up the size of the
- * binary. Unless we compile a debug-build with NM_MORE_ASSERTS, drop these
- * parts. Note that the failed assertion still prints the file and line where the
- * assertion fails. That shall suffice. */
-
-static inline void
-_nm_g_return_if_fail_warning(const char *log_domain, const char *file, int line)
-{
-    char file_buf[256 + 15];
-
-    g_snprintf(file_buf, sizeof(file_buf), "((%s:%d))", file, line);
-    g_return_if_fail_warning(log_domain, file_buf, "<dropped>");
-}
-
-        #define g_return_if_fail_warning(log_domain, pretty_function, expression) \
-            _nm_g_return_if_fail_warning(log_domain, __FILE__, __LINE__)
-
-        #define g_assertion_message_expr(domain, file, line, func, expr) \
-            g_assertion_message_expr(domain,                             \
-                                     file,                               \
-                                     line,                               \
-                                     "<unknown-fcn>",                    \
-                                     (expr) ? "<dropped>" : NULL)
-
-        #undef g_return_val_if_reached
-        #define g_return_val_if_reached(val)                          \
-            G_STMT_START                                              \
-            {                                                         \
-                g_log(G_LOG_DOMAIN,                                   \
-                      G_LOG_LEVEL_CRITICAL,                           \
-                      "file %s: line %d (%s): should not be reached", \
-                      __FILE__,                                       \
-                      __LINE__,                                       \
-                      "<dropped>");                                   \
-                return (val);                                         \
-            }                                                         \
-            G_STMT_END
-
-        #undef g_return_if_reached
-        #define g_return_if_reached()                                 \
-            G_STMT_START                                              \
-            {                                                         \
-                g_log(G_LOG_DOMAIN,                                   \
-                      G_LOG_LEVEL_CRITICAL,                           \
-                      "file %s: line %d (%s): should not be reached", \
-                      __FILE__,                                       \
-                      __LINE__,                                       \
-                      "<dropped>");                                   \
-                return;                                               \
-            }                                                         \
-            G_STMT_END
-
-        #define NM_ASSERT_G_RETURN_EXPR(expr) "<dropped>"
-        #define NM_ASSERT_NO_MSG              1
-
-    #else
-
-        #define NM_ASSERT_G_RETURN_EXPR(expr) "" expr ""
-        #define NM_ASSERT_NO_MSG              0
-
-    #endif
-
-    /*****************************************************************************/
-
-    #include "nm-std-aux/nm-std-aux.h"
-    #include "nm-std-aux/nm-std-utils.h"
-    #include "nm-glib-aux/nm-macros-internal.h"
-    #include "nm-glib-aux/nm-shared-utils.h"
-    #include "nm-glib-aux/nm-errno.h"
-    #include "nm-glib-aux/nm-hash-utils.h"
-
-    /*****************************************************************************/
-
-    #if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE
-        #include "nm-version.h"
-    #endif
-
-    /*****************************************************************************/
-
-    #if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON
-        #include "nm-core-types.h"
-        #include "nm-types.h"
-        #include "nm-logging.h"
-    #endif
-
-    #if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE
-        #include "nm-libnm-utils.h"
-    #endif
-
-    #if ((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM) \
-        && !((NETWORKMANAGER_COMPILATION)                                        \
-             & (NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE                 \
-                | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL))
-        #include "NetworkManager.h"
-    #endif
-
-#endif /* NM_NETWORKMANAGER_COMPILATION_WITH_GLIB */
-
-/*****************************************************************************/
-
-#endif /* __NM_DEFAULT_H__ */
diff --git a/shared/nm-glib-aux/nm-c-list.h b/shared/nm-glib-aux/nm-c-list.h
index e19774dd..6dd3ac72 100644
--- a/shared/nm-glib-aux/nm-c-list.h
+++ b/shared/nm-glib-aux/nm-c-list.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
@@ -17,11 +17,6 @@
         _what &&c_list_contains(list, &_what->member); \
     })
 
-/* iterate over the list backwards. */
-#define nm_c_list_for_each_entry_prev(_iter, _list, _m)                                        \
-    for (_iter = c_list_entry((_list)->prev, __typeof__(*_iter), _m); &(_iter)->_m != (_list); \
-         _iter = c_list_entry((_iter)->_m.prev, __typeof__(*_iter), _m))
-
 /*****************************************************************************/
 
 typedef struct {
diff --git a/shared/nm-glib-aux/nm-dbus-aux.c b/shared/nm-glib-aux/nm-dbus-aux.c
index e47797a8..ec409ff1 100644
--- a/shared/nm-glib-aux/nm-dbus-aux.c
+++ b/shared/nm-glib-aux/nm-dbus-aux.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-dbus-aux.h"
 
diff --git a/shared/nm-glib-aux/nm-dbus-aux.h b/shared/nm-glib-aux/nm-dbus-aux.h
index 7b0b008d..4e3ae22d 100644
--- a/shared/nm-glib-aux/nm-dbus-aux.h
+++ b/shared/nm-glib-aux/nm-dbus-aux.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-dedup-multi.c b/shared/nm-glib-aux/nm-dedup-multi.c
index 4a16f850..99da3b35 100644
--- a/shared/nm-glib-aux/nm-dedup-multi.c
+++ b/shared/nm-glib-aux/nm-dedup-multi.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-dedup-multi.h"
 
diff --git a/shared/nm-glib-aux/nm-dedup-multi.h b/shared/nm-glib-aux/nm-dedup-multi.h
index 6941dc63..1c0761bf 100644
--- a/shared/nm-glib-aux/nm-dedup-multi.h
+++ b/shared/nm-glib-aux/nm-dedup-multi.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-default-glib-i18n-lib.h b/shared/nm-glib-aux/nm-default-glib-i18n-lib.h
new file mode 100644
index 00000000..9393d192
--- /dev/null
+++ b/shared/nm-glib-aux/nm-default-glib-i18n-lib.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEFAULT_GLIB_I18N_LIB_H__
+#define __NM_DEFAULT_GLIB_I18N_LIB_H__
+
+/*****************************************************************************/
+
+#define _NETWORKMANAGER_COMPILATION_GLIB_I18N_LIB
+
+#include "nm-glib-aux/nm-default-glib.h"
+
+#undef NETWORKMANAGER_COMPILATION
+#define NETWORKMANAGER_COMPILATION \
+    (NM_NETWORKMANAGER_COMPILATION_GLIB | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)
+
+/*****************************************************************************/
+
+#endif /* __NM_DEFAULT_GLIB_I18N_LIB_H__ */
diff --git a/shared/nm-glib-aux/nm-default-glib-i18n-prog.h b/shared/nm-glib-aux/nm-default-glib-i18n-prog.h
new file mode 100644
index 00000000..0abe807b
--- /dev/null
+++ b/shared/nm-glib-aux/nm-default-glib-i18n-prog.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEFAULT_GLIB_I18N_PROG_H__
+#define __NM_DEFAULT_GLIB_I18N_PROG_H__
+
+/*****************************************************************************/
+
+#define _NETWORKMANAGER_COMPILATION_GLIB_I18N_PROG
+
+#include "nm-glib-aux/nm-default-glib.h"
+
+#undef NETWORKMANAGER_COMPILATION
+#define NETWORKMANAGER_COMPILATION \
+    (NM_NETWORKMANAGER_COMPILATION_GLIB | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG)
+
+/*****************************************************************************/
+
+#endif /* __NM_DEFAULT_GLIB_I18N_PROG_H__ */
diff --git a/shared/nm-glib-aux/nm-default-glib.h b/shared/nm-glib-aux/nm-default-glib.h
new file mode 100644
index 00000000..34b23f77
--- /dev/null
+++ b/shared/nm-glib-aux/nm-default-glib.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEFAULT_GLIB_H__
+#define __NM_DEFAULT_GLIB_H__
+
+/*****************************************************************************/
+
+#include "nm-std-aux/nm-default-std.h"
+
+#undef NETWORKMANAGER_COMPILATION
+#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_WITH_GLIB
+
+/*****************************************************************************/
+
+#include <glib.h>
+
+#if defined(_NETWORKMANAGER_COMPILATION_GLIB_I18N_PROG)
+    #if defined(_NETWORKMANAGER_COMPILATION_GLIB_I18N_LIB)
+        #error Cannot define _NETWORKMANAGER_COMPILATION_GLIB_I18N_LIB and _NETWORKMANAGER_COMPILATION_GLIB_I18N_PROG together
+    #endif
+    #undef _NETWORKMANAGER_COMPILATION_GLIB_I18N_PROG
+    #include <glib/gi18n.h>
+#elif defined(_NETWORKMANAGER_COMPILATION_GLIB_I18N_LIB)
+    #undef _NETWORKMANAGER_COMPILATION_GLIB_I18N_LIB
+    #include <glib/gi18n-lib.h>
+#endif
+
+/*****************************************************************************/
+
+#if NM_MORE_ASSERTS == 0
+    #ifndef G_DISABLE_CAST_CHECKS
+        /* Unless compiling with G_DISABLE_CAST_CHECKS, glib performs type checking
+         * during G_VARIANT_TYPE() via g_variant_type_checked_(). This is not necessary
+         * because commonly this cast is needed during something like
+         *
+         *   g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}"));
+         *
+         * Note that in if the variant type would be invalid, the check still
+         * wouldn't make the buggy code magically work. Instead of passing a
+         * bogus type string (bad), it would pass %NULL to g_variant_builder_init()
+         * (also bad).
+         *
+         * Also, a function like g_variant_builder_init() already validates
+         * the input type via something like
+         *
+         *   g_return_if_fail (g_variant_type_is_container (type));
+         *
+         * So, by having G_VARIANT_TYPE() also validate the type, we validate
+         * twice, whereas the first validation is rather pointless because it
+         * doesn't prevent the function to be called with invalid arguments.
+         *
+         * Just patch G_VARIANT_TYPE() to perform no check.
+         */
+        #undef G_VARIANT_TYPE
+        #define G_VARIANT_TYPE(type_string) ((const GVariantType *) (type_string))
+    #endif
+#endif
+
+/*****************************************************************************/
+
+#include "nm-gassert-patch.h"
+
+#include "nm-std-aux/nm-std-aux.h"
+#include "nm-std-aux/nm-std-utils.h"
+#include "nm-glib-aux/nm-macros-internal.h"
+#include "nm-glib-aux/nm-shared-utils.h"
+#include "nm-glib-aux/nm-errno.h"
+#include "nm-glib-aux/nm-hash-utils.h"
+
+/*****************************************************************************/
+
+#endif /* __NM_DEFAULT_GLIB_H__ */
diff --git a/shared/nm-glib-aux/nm-enum-utils.c b/shared/nm-glib-aux/nm-enum-utils.c
index 5292755d..b06f2bb2 100644
--- a/shared/nm-glib-aux/nm-enum-utils.c
+++ b/shared/nm-glib-aux/nm-enum-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-enum-utils.h"
 #include "nm-str-buf.h"
@@ -76,30 +76,34 @@ _ASSERT_enum_values_info(GType type, const NMUtilsEnumValueInfo *value_infos)
 }
 
 static gboolean
-_is_hex_string(const char *str)
+_is_hex_string(const char *str, gboolean allow_sign)
 {
+    if (allow_sign && str[0] == '-')
+        str++;
     return str[0] == '0' && str[1] == 'x' && str[2]
            && NM_STRCHAR_ALL(&str[2], ch, g_ascii_isxdigit(ch));
 }
 
 static gboolean
-_is_dec_string(const char *str)
+_is_dec_string(const char *str, gboolean allow_sign)
 {
+    if (allow_sign && str[0] == '-')
+        str++;
     return str[0] && NM_STRCHAR_ALL(&str[0], ch, g_ascii_isdigit(ch));
 }
 
 static gboolean
 _enum_is_valid_enum_nick(const char *str)
 {
-    return str[0] && !NM_STRCHAR_ANY(str, ch, g_ascii_isspace(ch)) && !_is_dec_string(str)
-           && !_is_hex_string(str);
+    return str[0] && !NM_STRCHAR_ANY(str, ch, g_ascii_isspace(ch)) && !_is_dec_string(str, TRUE)
+           && !_is_hex_string(str, TRUE);
 }
 
 static gboolean
 _enum_is_valid_flags_nick(const char *str)
 {
-    return str[0] && !NM_STRCHAR_ANY(str, ch, IS_FLAGS_SEPARATOR(ch)) && !_is_dec_string(str)
-           && !_is_hex_string(str);
+    return str[0] && !NM_STRCHAR_ANY(str, ch, IS_FLAGS_SEPARATOR(ch)) && !_is_dec_string(str, FALSE)
+           && !_is_hex_string(str, FALSE);
 }
 
 char *
@@ -212,8 +216,7 @@ _nm_utils_enum_from_str_full(GType                       type,
 
     _ASSERT_enum_values_info(type, value_infos);
 
-    str_clone = strdup(str);
-    s         = nm_str_skip_leading_spaces(str_clone);
+    s = nm_strdup_maybe_a(300, nm_str_skip_leading_spaces(str), &str_clone);
     g_strchomp(s);
 
     klass = g_type_class_ref(type);
@@ -221,16 +224,31 @@ _nm_utils_enum_from_str_full(GType                       type,
     if (G_IS_ENUM_CLASS(klass)) {
         GEnumValue *enum_value;
 
+        G_STATIC_ASSERT(G_MAXINT < G_MAXINT64);
+        G_STATIC_ASSERT(G_MININT > G_MININT64);
+
         if (s[0]) {
-            if (_is_hex_string(s)) {
-                v64 = _nm_utils_ascii_str_to_int64(s, 16, 0, G_MAXUINT, -1);
-                if (v64 != -1) {
-                    value = (int) v64;
-                    ret   = TRUE;
+            if (_is_hex_string(s, TRUE)) {
+                if (s[0] == '-') {
+                    v64 = _nm_utils_ascii_str_to_int64(&s[3],
+                                                       16,
+                                                       -((gint64) G_MAXINT),
+                                                       -((gint64) G_MININT),
+                                                       G_MAXINT64);
+                    if (v64 != G_MAXINT64) {
+                        value = (int) (-v64);
+                        ret   = TRUE;
+                    }
+                } else {
+                    v64 = _nm_utils_ascii_str_to_int64(&s[2], 16, G_MININT, G_MAXINT, G_MAXINT64);
+                    if (v64 != G_MAXINT64) {
+                        value = (int) v64;
+                        ret   = TRUE;
+                    }
                 }
-            } else if (_is_dec_string(s)) {
-                v64 = _nm_utils_ascii_str_to_int64(s, 10, 0, G_MAXUINT, -1);
-                if (v64 != -1) {
+            } else if (_is_dec_string(s, TRUE)) {
+                v64 = _nm_utils_ascii_str_to_int64(s, 10, G_MININT, G_MAXINT, G_MAXINT64);
+                if (v64 != G_MAXINT64) {
                     value = (int) v64;
                     ret   = TRUE;
                 }
@@ -259,14 +277,14 @@ _nm_utils_enum_from_str_full(GType                       type,
             }
 
             if (s[0]) {
-                if (_is_hex_string(s)) {
+                if (_is_hex_string(s, FALSE)) {
                     v64 = _nm_utils_ascii_str_to_int64(&s[2], 16, 0, G_MAXUINT, -1);
                     if (v64 == -1) {
                         ret = FALSE;
                         break;
                     }
                     uvalue |= (unsigned) v64;
-                } else if (_is_dec_string(s)) {
+                } else if (_is_dec_string(s, FALSE)) {
                     v64 = _nm_utils_ascii_str_to_int64(s, 10, 0, G_MAXUINT, -1);
                     if (v64 == -1) {
                         ret = FALSE;
diff --git a/shared/nm-glib-aux/nm-enum-utils.h b/shared/nm-glib-aux/nm-enum-utils.h
index 38acf8fd..89be54e7 100644
--- a/shared/nm-glib-aux/nm-enum-utils.h
+++ b/shared/nm-glib-aux/nm-enum-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-errno.c b/shared/nm-glib-aux/nm-errno.c
index f7a7685d..668606ca 100644
--- a/shared/nm-glib-aux/nm-errno.c
+++ b/shared/nm-glib-aux/nm-errno.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-errno.h"
 
@@ -106,7 +106,7 @@ nm_strerror_native_r(int errsv, char *buf, gsize buf_size)
     nm_assert(buf);
     nm_assert(buf_size > 0);
 
-#if (_POSIX_C_SOURCE >= 200112L) && !_GNU_SOURCE
+#if (!defined(__GLIBC__) && !defined(__UCLIBC__)) || ((_POSIX_C_SOURCE >= 200112L) && !_GNU_SOURCE)
     /* XSI-compliant */
     {
         int errno_saved = errno;
diff --git a/shared/nm-glib-aux/nm-errno.h b/shared/nm-glib-aux/nm-errno.h
index bf7189c0..62c8379f 100644
--- a/shared/nm-glib-aux/nm-errno.h
+++ b/shared/nm-glib-aux/nm-errno.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-gassert-patch.h b/shared/nm-glib-aux/nm-gassert-patch.h
new file mode 100644
index 00000000..bac8697c
--- /dev/null
+++ b/shared/nm-glib-aux/nm-gassert-patch.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_GASSERT_PATCH_H__
+#define __NM_GASSERT_PATCH_H__
+
+/*****************************************************************************/
+
+#if NM_MORE_ASSERTS == 0
+
+/* glib assertions (g_return_*(), g_assert*()) contain a textual representation
+ * of the checked statement. This part of the assertion blows up the size of the
+ * binary. Unless we compile a debug-build with NM_MORE_ASSERTS, drop these
+ * parts. Note that the failed assertion still prints the file and line where the
+ * assertion fails. That shall suffice. */
+
+static inline void
+_nm_g_return_if_fail_warning(const char *log_domain, const char *file, int line)
+{
+    char file_buf[256 + 15];
+
+    g_snprintf(file_buf, sizeof(file_buf), "((%s:%d))", file, line);
+    g_return_if_fail_warning(log_domain, file_buf, "<dropped>");
+}
+
+    #define g_return_if_fail_warning(log_domain, pretty_function, expression) \
+        _nm_g_return_if_fail_warning(log_domain, __FILE__, __LINE__)
+
+    #define g_assertion_message_expr(domain, file, line, func, expr) \
+        g_assertion_message_expr(domain, file, line, "<unknown-fcn>", (expr) ? "<dropped>" : NULL)
+
+    #undef g_return_val_if_reached
+    #define g_return_val_if_reached(val)                          \
+        G_STMT_START                                              \
+        {                                                         \
+            g_log(G_LOG_DOMAIN,                                   \
+                  G_LOG_LEVEL_CRITICAL,                           \
+                  "file %s: line %d (%s): should not be reached", \
+                  __FILE__,                                       \
+                  __LINE__,                                       \
+                  "<dropped>");                                   \
+            return (val);                                         \
+        }                                                         \
+        G_STMT_END
+
+    #undef g_return_if_reached
+    #define g_return_if_reached()                                 \
+        G_STMT_START                                              \
+        {                                                         \
+            g_log(G_LOG_DOMAIN,                                   \
+                  G_LOG_LEVEL_CRITICAL,                           \
+                  "file %s: line %d (%s): should not be reached", \
+                  __FILE__,                                       \
+                  __LINE__,                                       \
+                  "<dropped>");                                   \
+            return;                                               \
+        }                                                         \
+        G_STMT_END
+#endif
+
+/*****************************************************************************/
+
+#if NM_MORE_ASSERTS == 0
+    #define NM_ASSERT_G_RETURN_EXPR(expr) "<dropped>"
+    #define NM_ASSERT_NO_MSG              1
+
+#else
+    #define NM_ASSERT_G_RETURN_EXPR(expr) "" expr ""
+    #define NM_ASSERT_NO_MSG              0
+#endif
+
+/*****************************************************************************/
+
+#endif /* __NM_GASSERT_PATCH_H__ */
diff --git a/shared/nm-glib-aux/nm-glib.h b/shared/nm-glib-aux/nm-glib.h
index 9794e1fb..befb8d90 100644
--- a/shared/nm-glib-aux/nm-glib.h
+++ b/shared/nm-glib-aux/nm-glib.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2008 - 2018 Red Hat, Inc.
  */
@@ -683,7 +683,7 @@ g_hash_table_steal_extended(GHashTable *  hash_table,
             gpointer *_stolen_value = (stolen_value);                                        \
                                                                                              \
             /* we cannot allow NULL arguments, because then we would leak the values in
-         * the compat implementation. */      \
+             * the compat implementation. */      \
             g_assert(_stolen_key);                                                           \
             g_assert(_stolen_value);                                                         \
                                                                                              \
diff --git a/shared/nm-glib-aux/nm-hash-utils.c b/shared/nm-glib-aux/nm-hash-utils.c
index 8a1c87fd..29349b1d 100644
--- a/shared/nm-glib-aux/nm-hash-utils.c
+++ b/shared/nm-glib-aux/nm-hash-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-hash-utils.h"
 
diff --git a/shared/nm-glib-aux/nm-hash-utils.h b/shared/nm-glib-aux/nm-hash-utils.h
index 1cfdcaf6..a7b8677b 100644
--- a/shared/nm-glib-aux/nm-hash-utils.h
+++ b/shared/nm-glib-aux/nm-hash-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
@@ -416,6 +416,14 @@ nm_hash_obfuscate_ptr(guint static_seed, gconstpointer val)
  * values in a global context. */
 #define NM_HASH_OBFUSCATE_PTR(ptr) (nm_hash_obfuscate_ptr(1678382159u, ptr))
 
+#define NM_HASH_OBFUSCATE_PTR_STR(ptr, buf)                                                        \
+    ({                                                                                             \
+        gconstpointer _ptr = (ptr);                                                                \
+                                                                                                   \
+        _ptr ? nm_sprintf_buf(buf, "[" NM_HASH_OBFUSCATE_PTR_FMT "]", NM_HASH_OBFUSCATE_PTR(_ptr)) \
+             : "(null)";                                                                           \
+    })
+
 static inline const char *
 nm_hash_obfuscated_ptr_str(gconstpointer ptr, char buf[static 17])
 {
diff --git a/shared/nm-glib-aux/nm-io-utils.c b/shared/nm-glib-aux/nm-io-utils.c
index 32f92156..429591ad 100644
--- a/shared/nm-glib-aux/nm-io-utils.c
+++ b/shared/nm-glib-aux/nm-io-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-io-utils.h"
 
diff --git a/shared/nm-glib-aux/nm-io-utils.h b/shared/nm-glib-aux/nm-io-utils.h
index 81be7b60..8182f5cf 100644
--- a/shared/nm-glib-aux/nm-io-utils.h
+++ b/shared/nm-glib-aux/nm-io-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-jansson.h b/shared/nm-glib-aux/nm-jansson.h
index 9b7731a4..6173a7ac 100644
--- a/shared/nm-glib-aux/nm-jansson.h
+++ b/shared/nm-glib-aux/nm-jansson.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-json-aux.c b/shared/nm-glib-aux/nm-json-aux.c
index 4212e628..97ee606f 100644
--- a/shared/nm-glib-aux/nm-json-aux.c
+++ b/shared/nm-glib-aux/nm-json-aux.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 - 2019 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-json-aux.h"
 
@@ -11,6 +11,16 @@
 
 /*****************************************************************************/
 
+/* If RTLD_DEEPBIND isn't available just ignore it. This can cause problems
+ * with jansson, json-glib, and cjson symbols clashing (and as such crashing the
+ * program). But that needs to be fixed by the json libraries, and it is by adding
+ * symbol versioning in recent versions. */
+#ifndef RTLD_DEEPBIND
+    #define RTLD_DEEPBIND 0
+#endif
+
+/*****************************************************************************/
+
 static void
 _gstr_append_string_len(GString *gstr, const char *str, gsize len)
 {
diff --git a/shared/nm-glib-aux/nm-json-aux.h b/shared/nm-glib-aux/nm-json-aux.h
index 348f0f58..99759a8d 100644
--- a/shared/nm-glib-aux/nm-json-aux.h
+++ b/shared/nm-glib-aux/nm-json-aux.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 - 2019 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-keyfile-aux.c b/shared/nm-glib-aux/nm-keyfile-aux.c
index ae90fb0d..b5962712 100644
--- a/shared/nm-glib-aux/nm-keyfile-aux.c
+++ b/shared/nm-glib-aux/nm-keyfile-aux.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-keyfile-aux.h"
 
diff --git a/shared/nm-glib-aux/nm-keyfile-aux.h b/shared/nm-glib-aux/nm-keyfile-aux.h
index 198a2c53..72d2f418 100644
--- a/shared/nm-glib-aux/nm-keyfile-aux.h
+++ b/shared/nm-glib-aux/nm-keyfile-aux.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-logging-base.c b/shared/nm-glib-aux/nm-logging-base.c
index 229470f6..66b591b2 100644
--- a/shared/nm-glib-aux/nm-logging-base.c
+++ b/shared/nm-glib-aux/nm-logging-base.c
@@ -1,6 +1,6 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-logging-base.h"
 
diff --git a/shared/nm-glib-aux/nm-logging-base.h b/shared/nm-glib-aux/nm-logging-base.h
index eb71c2a4..d9ac03c7 100644
--- a/shared/nm-glib-aux/nm-logging-base.h
+++ b/shared/nm-glib-aux/nm-logging-base.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #ifndef __NM_LOGGING_BASE_H__
 #define __NM_LOGGING_BASE_H__
diff --git a/shared/nm-glib-aux/nm-logging-fwd.h b/shared/nm-glib-aux/nm-logging-fwd.h
index ee75b713..df0bb161 100644
--- a/shared/nm-glib-aux/nm-logging-fwd.h
+++ b/shared/nm-glib-aux/nm-logging-fwd.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2006 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
@@ -57,15 +57,11 @@ typedef enum { /*< skip >*/
                /* aliases: */
                LOGD_DHCP = LOGD_DHCP4 | LOGD_DHCP6,
                LOGD_IP   = LOGD_IP4 | LOGD_IP6,
-} NMLogDomain;
 
-static inline NMLogDomain
-LOGD_DHCP_from_addr_family(int addr_family)
-{
-    nm_assert_addr_family(addr_family);
+#define LOGD_DHCPX(is_ipv4) ((is_ipv4) ? LOGD_DHCP4 : LOGD_DHCP6)
+#define LOGD_IPX(is_ipv4)   ((is_ipv4) ? LOGD_IP4 : LOGD_IP6)
 
-    return addr_family == AF_INET6 ? LOGD_DHCP6 : LOGD_DHCP4;
-}
+} NMLogDomain;
 
 /* Log levels */
 typedef enum { /*< skip >*/
diff --git a/shared/nm-glib-aux/nm-macros-internal.h b/shared/nm-glib-aux/nm-macros-internal.h
index d495dd8e..113a67a0 100644
--- a/shared/nm-glib-aux/nm-macros-internal.h
+++ b/shared/nm-glib-aux/nm-macros-internal.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2012 Colin Walters <walters@verbum.org>.
  * Copyright (C) 2014 Red Hat, Inc.
@@ -656,11 +656,11 @@ NM_G_ERROR_MSG(GError *error)
 
 #if _NM_CC_SUPPORT_GENERIC
     /* returns @value, if the type of @value matches @type.
- * This requires support for C11 _Generic(). If no support is
- * present, this returns @value directly.
- *
- * It's useful to check the let the compiler ensure that @value is
- * of a certain type. */
+     * This requires support for C11 _Generic(). If no support is
+     * present, this returns @value directly.
+     *
+     * It's useful to check the let the compiler ensure that @value is
+     * of a certain type. */
     #define _NM_ENSURE_TYPE(type, value) (_Generic((value), type : (value)))
     #define _NM_ENSURE_TYPE_CONST(type, value)              \
         (_Generic((value), const type                       \
@@ -770,7 +770,7 @@ NM_G_ERROR_MSG(GError *error)
     {                                    \
         return NM_CACHED_QUARK(string);  \
     }                                    \
-    struct _dummy_struct_for_trailing_semicolon
+    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
 /*****************************************************************************/
 
@@ -1038,6 +1038,14 @@ nm_g_object_unref(gpointer obj)
  */
 #define nm_clear_g_free(pp) nm_clear_pointer(pp, g_free)
 
+/* Our nm_clear_pointer() is more typesafe than g_clear_pointer() and
+ * should be preferred.
+ *
+ * For g_clear_object() that is not the case (because g_object_unref()
+ * anyway takes a void pointer). So using g_clear_object() is fine.
+ *
+ * Still have a nm_clear_g_object() because that returns a boolean
+ * indication whether anything was cleared. */
 #define nm_clear_g_object(pp) nm_clear_pointer(pp, g_object_unref)
 
 /**
@@ -1604,6 +1612,16 @@ _nm_strndup_a_step(char *s, const char *str, gsize len)
         _nm_strndup_a_step(_s_snd, _str_snd, _len_snd);            \
     })
 
+#define nm_strdup_maybe_a(alloca_maxlen, str, out_str_free)               \
+    ({                                                                    \
+        const char *const _str_snd = (str);                               \
+                                                                          \
+        (char *) nm_memdup_maybe_a(alloca_maxlen,                         \
+                                   _str_snd,                              \
+                                   _str_snd ? strlen(_str_snd) + 1u : 0u, \
+                                   out_str_free);                         \
+    })
+
 /*****************************************************************************/
 
 /* generic macro to convert an int to a (heap allocated) string.
diff --git a/shared/nm-glib-aux/nm-obj.h b/shared/nm-glib-aux/nm-obj.h
index 9bbe05a4..2062f661 100644
--- a/shared/nm-glib-aux/nm-obj.h
+++ b/shared/nm-glib-aux/nm-obj.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-random-utils.c b/shared/nm-glib-aux/nm-random-utils.c
index 0c8b2758..c95d368d 100644
--- a/shared/nm-glib-aux/nm-random-utils.c
+++ b/shared/nm-glib-aux/nm-random-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-random-utils.h"
 
diff --git a/shared/nm-glib-aux/nm-random-utils.h b/shared/nm-glib-aux/nm-random-utils.h
index 5c34d8ab..d0eae103 100644
--- a/shared/nm-glib-aux/nm-random-utils.h
+++ b/shared/nm-glib-aux/nm-random-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-ref-string.c b/shared/nm-glib-aux/nm-ref-string.c
index 526185f3..902f1c80 100644
--- a/shared/nm-glib-aux/nm-ref-string.c
+++ b/shared/nm-glib-aux/nm-ref-string.c
@@ -1,6 +1,6 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-ref-string.h"
 
@@ -168,24 +168,25 @@ void
 _nm_ref_string_unref_non_null(NMRefString *rstr)
 {
     RefString *const rstr0 = (RefString *) rstr;
+    int              r;
 
     _ASSERT(rstr0);
 
-    if (G_LIKELY(!g_atomic_int_dec_and_test(&rstr0->ref_count)))
+    /* fast-path: first try to decrement the ref-count without bringing it
+     * to zero. */
+    r = rstr0->ref_count;
+    if (G_LIKELY(r > 1 && g_atomic_int_compare_and_exchange(&rstr0->ref_count, r, r - 1)))
         return;
 
+    /* We apparently are about to return the last reference. Take a lock. */
+
     G_LOCK(gl_lock);
 
-    /* in the fast-path above, we already decremented the ref-count to zero.
-     * We need recheck that the ref-count is still zero. */
+    nm_assert(g_hash_table_lookup(gl_hash, rstr0) == rstr0);
 
-    if (g_atomic_int_get(&rstr0->ref_count) == 0) {
+    if (G_LIKELY(g_atomic_int_dec_and_test(&rstr0->ref_count))) {
         if (!g_hash_table_remove(gl_hash, rstr0))
             nm_assert_not_reached();
-    } else {
-#if NM_MORE_ASSERTS > 5
-        nm_assert(g_hash_table_lookup(gl_hash, rstr0) == rstr0);
-#endif
     }
 
     G_UNLOCK(gl_lock);
diff --git a/shared/nm-glib-aux/nm-ref-string.h b/shared/nm-glib-aux/nm-ref-string.h
index fe65d6a3..97c263b0 100644
--- a/shared/nm-glib-aux/nm-ref-string.h
+++ b/shared/nm-glib-aux/nm-ref-string.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #ifndef __NM_REF_STRING_H__
 #define __NM_REF_STRING_H__
diff --git a/shared/nm-glib-aux/nm-secret-utils.c b/shared/nm-glib-aux/nm-secret-utils.c
index 3a488673..8188b503 100644
--- a/shared/nm-glib-aux/nm-secret-utils.c
+++ b/shared/nm-glib-aux/nm-secret-utils.c
@@ -1,10 +1,10 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  * Copyright (C) 2015 - 2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-secret-utils.h"
 
diff --git a/shared/nm-glib-aux/nm-secret-utils.h b/shared/nm-glib-aux/nm-secret-utils.h
index 2d342e59..ac279635 100644
--- a/shared/nm-glib-aux/nm-secret-utils.h
+++ b/shared/nm-glib-aux/nm-secret-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c
index 7f766871..3215a33b 100644
--- a/shared/nm-glib-aux/nm-shared-utils.c
+++ b/shared/nm-glib-aux/nm-shared-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-shared-utils.h"
 
@@ -19,6 +19,9 @@
 #include "nm-errno.h"
 #include "nm-str-buf.h"
 
+G_STATIC_ASSERT(sizeof(NMEtherAddr) == 6);
+G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) == 1);
+
 G_STATIC_ASSERT(sizeof(NMUtilsNamedEntry) == sizeof(const char *));
 G_STATIC_ASSERT(G_STRUCT_OFFSET(NMUtilsNamedValue, value_ptr) == sizeof(const char *));
 
@@ -361,6 +364,21 @@ again:
     return b;
 }
 
+GBytes *
+nm_g_bytes_new_from_str(const char *str)
+{
+    gsize l;
+
+    if (!str)
+        return NULL;
+
+    /* the returned array is guaranteed to have a trailing '\0'
+     * character *after* the length. */
+
+    l = strlen(str);
+    return g_bytes_new_take(nm_memdup(str, l + 1u), l);
+}
+
 /**
  * nm_utils_gbytes_equals:
  * @bytes: (allow-none): a #GBytes array to compare. Note that
@@ -434,6 +452,24 @@ nm_g_variant_singleton_u_0(void)
 
 /*****************************************************************************/
 
+GHashTable *
+nm_utils_strdict_clone(GHashTable *src)
+{
+    GHashTable *   dst;
+    GHashTableIter iter;
+    const char *   key;
+    const char *   val;
+
+    if (!src)
+        return NULL;
+
+    dst = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free);
+    g_hash_table_iter_init(&iter, src);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val))
+        g_hash_table_insert(dst, g_strdup(key), g_strdup(val));
+    return dst;
+}
+
 /* Convert a hash table with "char *" keys and values to an "a{ss}" GVariant.
  * The keys will be sorted asciibetically.
  * Returns a floating reference.
@@ -750,27 +786,27 @@ nm_utils_ip6_address_same_prefix_cmp(const struct in6_addr *addr_a,
     return 0;
 }
 
-/**
- * _nm_utils_ip4_get_default_prefix:
- * @ip: an IPv4 address (in network byte order)
- *
- * When the Internet was originally set up, various ranges of IP addresses were
- * segmented into three network classes: A, B, and C.  This function will return
- * a prefix that is associated with the IP address specified defining where it
- * falls in the predefined classes.
- *
- * Returns: the default class prefix for the given IP
- **/
-/* The function is originally from ipcalc.c of Red Hat's initscripts. */
+/*****************************************************************************/
+
 guint32
-_nm_utils_ip4_get_default_prefix(guint32 ip)
+_nm_utils_ip4_get_default_prefix0(in_addr_t ip)
 {
-    if (((ntohl(ip) & 0xFF000000) >> 24) <= 127)
-        return 8; /* Class A - 255.0.0.0 */
-    else if (((ntohl(ip) & 0xFF000000) >> 24) <= 191)
-        return 16; /* Class B - 255.255.0.0 */
+    /* The function is originally from ipcalc.c of Red Hat's initscripts. */
+    switch (ntohl(ip) >> 24) {
+    case 0 ... 127:
+        return 8; /* Class A */
+    case 128 ... 191:
+        return 16; /* Class B */
+    case 192 ... 223:
+        return 24; /* Class C */
+    }
+    return 0;
+}
 
-    return 24; /* Class C - 255.255.255.0 */
+guint32
+_nm_utils_ip4_get_default_prefix(in_addr_t ip)
+{
+    return _nm_utils_ip4_get_default_prefix0(ip) ?: 24;
 }
 
 gboolean
@@ -2231,6 +2267,8 @@ _nm_utils_ascii_str_to_bool(const char *str, int default_value)
 
 /*****************************************************************************/
 
+NM_CACHED_QUARK_FCN("nm-manager-error-quark", nm_manager_error_quark);
+
 NM_CACHED_QUARK_FCN("nm-utils-error-quark", nm_utils_error_quark);
 
 void
@@ -3066,6 +3104,18 @@ nm_utils_fd_read_loop_exact(int fd, void *buf, size_t nbytes, bool do_poll)
 
 /*****************************************************************************/
 
+void
+nm_utils_named_value_clear_with_g_free(NMUtilsNamedValue *val)
+{
+    if (val) {
+        gs_free gpointer x_name  = NULL;
+        gs_free gpointer x_value = NULL;
+
+        x_name  = (gpointer) g_steal_pointer(&val->name);
+        x_value = g_steal_pointer(&val->value_ptr);
+    }
+}
+
 G_STATIC_ASSERT(G_STRUCT_OFFSET(NMUtilsNamedValue, name) == 0);
 
 NMUtilsNamedValue *
@@ -3262,6 +3312,62 @@ nm_utils_hash_values_to_array(GHashTable *     hash,
     return arr;
 }
 
+/*****************************************************************************/
+
+/**
+ * nm_utils_hashtable_equal:
+ * @a: one #GHashTable
+ * @b: other #GHashTable
+ * @treat_null_as_empty: if %TRUE, when either @a or @b is %NULL, it is
+ *   treated like an empty hash. It means, a %NULL hash will compare equal
+ *   to an empty hash.
+ * @equal_func: the equality function, for comparing the values.
+ *   If %NULL, the values are not compared. In that case, the function
+ *   only checks, if both dictionaries have the same keys -- according
+ *   to @b's key equality function.
+ *   Note that the values of @a will be passed as first argument
+ *   to @equal_func.
+ *
+ * Compares two hash tables, whether they have equal content.
+ * This only makes sense, if @a and @b have the same key types and
+ * the same key compare-function.
+ *
+ * Returns: %TRUE, if both dictionaries have the same content.
+ */
+gboolean
+nm_utils_hashtable_equal(const GHashTable *a,
+                         const GHashTable *b,
+                         gboolean          treat_null_as_empty,
+                         GEqualFunc        equal_func)
+{
+    guint          n;
+    GHashTableIter iter;
+    gconstpointer  key, v_a, v_b;
+
+    if (a == b)
+        return TRUE;
+    if (!treat_null_as_empty) {
+        if (!a || !b)
+            return FALSE;
+    }
+
+    n = a ? g_hash_table_size((GHashTable *) a) : 0;
+    if (n != (b ? g_hash_table_size((GHashTable *) b) : 0))
+        return FALSE;
+
+    if (n > 0) {
+        g_hash_table_iter_init(&iter, (GHashTable *) a);
+        while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &v_a)) {
+            if (!g_hash_table_lookup_extended((GHashTable *) b, key, NULL, (gpointer *) &v_b))
+                return FALSE;
+            if (equal_func && !equal_func(v_a, v_b))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
 static gboolean
 _utils_hashtable_equal(GHashTable *     hash_a,
                        GHashTable *     hash_b,
@@ -3301,7 +3407,7 @@ _utils_hashtable_equal(GHashTable *     hash_a,
 }
 
 /**
- * nm_utils_hashtable_equal:
+ * nm_utils_hashtable_cmp_equal:
  * @a: (allow-none): the hash table or %NULL
  * @b: (allow-none): the other hash table or %NULL
  * @cmp_values: (allow-none): if %NULL, only the keys
@@ -3316,10 +3422,10 @@ _utils_hashtable_equal(GHashTable *     hash_a,
  *   @cmp_values is given) all values are the same.
  */
 gboolean
-nm_utils_hashtable_equal(const GHashTable *a,
-                         const GHashTable *b,
-                         GCompareDataFunc  cmp_values,
-                         gpointer          user_data)
+nm_utils_hashtable_cmp_equal(const GHashTable *a,
+                             const GHashTable *b,
+                             GCompareDataFunc  cmp_values,
+                             gpointer          user_data)
 {
     GHashTable *hash_a = (GHashTable *) a;
     GHashTable *hash_b = (GHashTable *) b;
@@ -3374,7 +3480,7 @@ _hashtable_cmp_func(gconstpointer a, gconstpointer b, gpointer user_data)
  * @a: (allow-none): the hash to compare. May be %NULL.
  * @b: (allow-none): the other hash to compare. May be %NULL.
  * @do_fast_precheck: if %TRUE, assume that the hashes are equal
- *   and that it is worth calling nm_utils_hashtable_equal() first.
+ *   and that it is worth calling nm_utils_hashtable_cmp_equal() first.
  *   That requires, that both hashes have the same equals function
  *   which is compatible with the @cmp_keys function.
  * @cmp_keys: the compare function for keys. Usually, the hash/equal function
@@ -3827,62 +3933,6 @@ nm_utils_array_find_binary_search(gconstpointer    list,
 /*****************************************************************************/
 
 /**
- * nm_utils_hash_table_equal:
- * @a: one #GHashTable
- * @b: other #GHashTable
- * @treat_null_as_empty: if %TRUE, when either @a or @b is %NULL, it is
- *   treated like an empty hash. It means, a %NULL hash will compare equal
- *   to an empty hash.
- * @equal_func: the equality function, for comparing the values.
- *   If %NULL, the values are not compared. In that case, the function
- *   only checks, if both dictionaries have the same keys -- according
- *   to @b's key equality function.
- *   Note that the values of @a will be passed as first argument
- *   to @equal_func.
- *
- * Compares two hash tables, whether they have equal content.
- * This only makes sense, if @a and @b have the same key types and
- * the same key compare-function.
- *
- * Returns: %TRUE, if both dictionaries have the same content.
- */
-gboolean
-nm_utils_hash_table_equal(const GHashTable *        a,
-                          const GHashTable *        b,
-                          gboolean                  treat_null_as_empty,
-                          NMUtilsHashTableEqualFunc equal_func)
-{
-    guint          n;
-    GHashTableIter iter;
-    gconstpointer  key, v_a, v_b;
-
-    if (a == b)
-        return TRUE;
-    if (!treat_null_as_empty) {
-        if (!a || !b)
-            return FALSE;
-    }
-
-    n = a ? g_hash_table_size((GHashTable *) a) : 0;
-    if (n != (b ? g_hash_table_size((GHashTable *) b) : 0))
-        return FALSE;
-
-    if (n > 0) {
-        g_hash_table_iter_init(&iter, (GHashTable *) a);
-        while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &v_a)) {
-            if (!g_hash_table_lookup_extended((GHashTable *) b, key, NULL, (gpointer *) &v_b))
-                return FALSE;
-            if (equal_func && !equal_func(v_a, v_b))
-                return FALSE;
-        }
-    }
-
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-/**
  * nm_utils_get_start_time_for_pid:
  * @pid: the process identifier
  * @out_state: return the state character, like R, S, Z. See `man 5 proc`.
@@ -4749,7 +4799,7 @@ typedef struct {
     GMainContext *context;
     GHashTable *  fds;
     GPollFD *     fds_arr;
-    int           fds_len;
+    guint         fds_len;
     int           max_priority;
     bool          acquired : 1;
 } CtxIntegSource;
@@ -4792,13 +4842,15 @@ _ctx_integ_source_prepare(GSource *source, int *out_timeout)
     int             max_priority;
     int             timeout = -1;
     gboolean        any_ready;
-    int             fds_allocated;
-    int             fds_len_old;
-    gs_free GPollFD *fds_arr_old = NULL;
-    GHashTableIter   h_iter;
-    PollData *       poll_data;
-    gboolean         fds_changed;
-    int              i;
+    GHashTableIter  h_iter;
+    PollData *      poll_data;
+    gboolean        fds_changed;
+    GPollFD         new_fds_stack[300u / sizeof(GPollFD)];
+    gs_free GPollFD *new_fds_heap = NULL;
+    GPollFD *        new_fds;
+    guint            new_fds_len;
+    guint            new_fds_alloc;
+    guint            i;
 
     _CTX_LOG("prepare...");
 
@@ -4806,30 +4858,44 @@ _ctx_integ_source_prepare(GSource *source, int *out_timeout)
 
     any_ready = g_main_context_prepare(ctx_src->context, &max_priority);
 
-    fds_arr_old = g_steal_pointer(&ctx_src->fds_arr);
-    fds_len_old = ctx_src->fds_len;
+    new_fds_alloc = NM_MAX(G_N_ELEMENTS(new_fds_stack), ctx_src->fds_len);
 
-    fds_allocated    = NM_MAX(1, fds_len_old); /* there is at least the wakeup's FD */
-    ctx_src->fds_arr = g_new(GPollFD, fds_allocated);
+    if (new_fds_alloc > G_N_ELEMENTS(new_fds_stack)) {
+        new_fds_heap = g_new(GPollFD, new_fds_alloc);
+        new_fds      = new_fds_heap;
+    } else
+        new_fds = new_fds_stack;
 
-    while ((ctx_src->fds_len = g_main_context_query(ctx_src->context,
-                                                    max_priority,
-                                                    &timeout,
-                                                    ctx_src->fds_arr,
-                                                    fds_allocated))
-           > fds_allocated) {
-        fds_allocated = ctx_src->fds_len;
-        g_free(ctx_src->fds_arr);
-        ctx_src->fds_arr = g_new(GPollFD, fds_allocated);
+    for (;;) {
+        int l;
+
+        nm_assert(new_fds_alloc <= (guint) G_MAXINT);
+
+        l = g_main_context_query(ctx_src->context,
+                                 max_priority,
+                                 &timeout,
+                                 new_fds,
+                                 (int) new_fds_alloc);
+        nm_assert(l >= 0);
+
+        new_fds_len = (guint) l;
+
+        if (G_LIKELY(new_fds_len <= new_fds_alloc))
+            break;
+
+        new_fds_alloc = new_fds_len;
+        g_free(new_fds_heap);
+        new_fds_heap = g_new(GPollFD, new_fds_alloc);
+        new_fds      = new_fds_heap;
     }
 
     fds_changed = FALSE;
-    if (fds_len_old != ctx_src->fds_len)
+    if (new_fds_len != ctx_src->fds_len)
         fds_changed = TRUE;
     else {
-        for (i = 0; i < ctx_src->fds_len; i++) {
-            if (fds_arr_old[i].fd != ctx_src->fds_arr[i].fd
-                || fds_arr_old[i].events != ctx_src->fds_arr[i].events) {
+        for (i = 0; i < new_fds_len; i++) {
+            if (new_fds[i].fd != ctx_src->fds_arr[i].fd
+                || new_fds[i].events != ctx_src->fds_arr[i].events) {
                 fds_changed = TRUE;
                 break;
             }
@@ -4837,6 +4903,13 @@ _ctx_integ_source_prepare(GSource *source, int *out_timeout)
     }
 
     if (G_UNLIKELY(fds_changed)) {
+        g_free(ctx_src->fds_arr);
+        ctx_src->fds_len = new_fds_len;
+        if (G_LIKELY(new_fds == new_fds_stack) || new_fds_alloc != new_fds_len)
+            ctx_src->fds_arr = nm_memdup(new_fds, sizeof(*new_fds) * new_fds_len);
+        else
+            ctx_src->fds_arr = g_steal_pointer(&new_fds_heap);
+
         g_hash_table_iter_init(&h_iter, ctx_src->fds);
         while (g_hash_table_iter_next(&h_iter, (gpointer *) &poll_data, NULL))
             poll_data->stale = TRUE;
@@ -4971,10 +5044,12 @@ _ctx_integ_source_check(GSource *source)
             ctx_src->fds_arr[poll_data->idx.one].revents = revents;
     }
 
+    nm_assert(ctx_src->fds_len <= (guint) G_MAXINT);
+
     some_ready = g_main_context_check(ctx_src->context,
                                       ctx_src->max_priority,
                                       ctx_src->fds_arr,
-                                      ctx_src->fds_len);
+                                      (int) ctx_src->fds_len);
 
     _CTX_LOG("check (some-ready=%d)...", some_ready);
 
@@ -5088,6 +5163,31 @@ nm_utils_g_main_context_create_integrate_source(GMainContext *inner_context)
     return &ctx_src->source;
 }
 
+/*****************************************************************************/
+
+void
+nm_utils_ifname_cpy(char *dst, const char *name)
+{
+    int i;
+
+    g_return_if_fail(dst);
+    g_return_if_fail(name && name[0]);
+
+    nm_assert(nm_utils_ifname_valid_kernel(name, NULL));
+
+    /* ensures NUL padding of the entire IFNAMSIZ buffer. */
+
+    for (i = 0; i < (int) IFNAMSIZ && name[i] != '\0'; i++)
+        dst[i] = name[i];
+
+    nm_assert(name[i] == '\0');
+
+    for (; i < (int) IFNAMSIZ; i++)
+        dst[i] = '\0';
+}
+
+/*****************************************************************************/
+
 gboolean
 nm_utils_ifname_valid_kernel(const char *name, GError **error)
 {
diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h
index 23884a37..7d330458 100644
--- a/shared/nm-glib-aux/nm-shared-utils.h
+++ b/shared/nm-glib-aux/nm-shared-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
@@ -8,6 +8,18 @@
 
 #include <netinet/in.h>
 
+/*****************************************************************************/
+
+/* An optional boolean (like NMTernary, with identical numerical
+ * enum values). Note that this enum type is _nm_packed! */
+typedef enum _nm_packed {
+    NM_OPTION_BOOL_DEFAULT = -1,
+    NM_OPTION_BOOL_FALSE   = 0,
+    NM_OPTION_BOOL_TRUE    = 1,
+} NMOptionBool;
+
+/*****************************************************************************/
+
 static inline gboolean
 nm_is_ascii(char ch)
 {
@@ -88,14 +100,25 @@ typedef struct {
 } NMEtherAddr;
 
 #define NM_ETHER_ADDR_FORMAT_STR "%02X:%02X:%02X:%02X:%02X:%02X"
-#define NM_ETHER_ADDR_FORMAT_VAL(x)                                            \
-    (x).ether_addr_octet[0], (x).ether_addr_octet[1], (x).ether_addr_octet[2], \
-        (x).ether_addr_octet[3], (x).ether_addr_octet[4], (x).ether_addr_octet[5]
-#define NM_ETHER_ADDR_INIT(...)            \
-    {                                      \
-        .ether_addr_octet = {__VA_ARGS__}, \
+
+#define NM_ETHER_ADDR_FORMAT_VAL(x)                                               \
+    (x)->ether_addr_octet[0], (x)->ether_addr_octet[1], (x)->ether_addr_octet[2], \
+        (x)->ether_addr_octet[3], (x)->ether_addr_octet[4], (x)->ether_addr_octet[5]
+
+#define _NM_ETHER_ADDR_INIT(a0, a1, a2, a3, a4, a5) \
+    {                                               \
+        .ether_addr_octet = {                       \
+            (a0),                                   \
+            (a1),                                   \
+            (a2),                                   \
+            (a3),                                   \
+            (a4),                                   \
+            (a5),                                   \
+        },                                          \
     }
 
+#define NM_ETHER_ADDR_INIT(...) ((NMEtherAddr) _NM_ETHER_ADDR_INIT(__VA_ARGS__))
+
 static inline int
 nm_ether_addr_cmp(const NMEtherAddr *a, const NMEtherAddr *b)
 {
@@ -308,6 +331,8 @@ nm_utils_is_separator(const char c)
 
 GBytes *nm_gbytes_get_empty(void);
 
+GBytes *nm_g_bytes_new_from_str(const char *str);
+
 static inline gboolean
 nm_gbytes_equal0(GBytes *a, GBytes *b)
 {
@@ -318,6 +343,8 @@ gboolean nm_utils_gbytes_equal_mem(GBytes *bytes, gconstpointer mem_data, gsize
 
 GVariant *nm_utils_gbytes_to_variant_ay(GBytes *bytes);
 
+GHashTable *nm_utils_strdict_clone(GHashTable *src);
+
 GVariant *nm_utils_strdict_to_variant_ass(GHashTable *strdict);
 GVariant *nm_utils_strdict_to_variant_asv(GHashTable *strdict);
 
@@ -672,7 +699,8 @@ nm_utils_escaped_tokens_options_escape_val(const char *val, char **out_to_free)
 /*****************************************************************************/
 
 guint32 _nm_utils_ip4_prefix_to_netmask(guint32 prefix);
-guint32 _nm_utils_ip4_get_default_prefix(guint32 ip);
+guint32 _nm_utils_ip4_get_default_prefix0(in_addr_t ip);
+guint32 _nm_utils_ip4_get_default_prefix(in_addr_t ip);
 
 gconstpointer
 nm_utils_ipx_address_clear_host_address(int family, gpointer dst, gconstpointer src, guint8 plen);
@@ -758,8 +786,10 @@ typedef struct {
     {                                                                           \
         static const NMUtilsFlags2StrDesc descs[] = {__VA_ARGS__};              \
         G_STATIC_ASSERT(sizeof(flags_type) <= sizeof(unsigned));                \
+                                                                                \
         return nm_utils_flags2str(descs, G_N_ELEMENTS(descs), flags, buf, len); \
-    };
+    }                                                                           \
+    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
 const char *nm_utils_flags2str(const NMUtilsFlags2StrDesc *descs,
                                gsize                       n_descs,
@@ -796,7 +826,8 @@ case v:                             \
                 g_snprintf(buf, len, "(%" int_fmt ")", val);               \
         }                                                                  \
         return buf;                                                        \
-    }
+    }                                                                      \
+    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
 #define NM_UTILS_ENUM2STR_DEFINE(fcn_name, lookup_type, ...) \
     NM_UTILS_ENUM2STR_DEFINE_FULL(fcn_name, lookup_type, "d", __VA_ARGS__)
@@ -918,6 +949,8 @@ _nm_g_slice_free_fcn_define(1) _nm_g_slice_free_fcn_define(2) _nm_g_slice_free_f
  *   error reason. Depending on the usage, this might indicate a bug because
  *   usually the target object should stay alive as long as there are pending
  *   operations.
+ * @NM_UTILS_ERROR_NOT_READY: the failure is related to being currently
+ *   not ready to perform the operation.
  *
  * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE: used for a very particular
  *   purpose during nm_device_check_connection_compatible() to indicate that
@@ -938,6 +971,7 @@ typedef enum {
     NM_UTILS_ERROR_UNKNOWN = 0,         /*< nick=Unknown >*/
     NM_UTILS_ERROR_CANCELLED_DISPOSING, /*< nick=CancelledDisposing >*/
     NM_UTILS_ERROR_INVALID_ARGUMENT,    /*< nick=InvalidArgument >*/
+    NM_UTILS_ERROR_NOT_READY,           /*< nick=NotReady >*/
 
     /* the following codes have a special meaning and are exactly used for
      * nm_device_check_connection_compatible() and nm_device_check_connection_available().
@@ -961,6 +995,12 @@ typedef enum {
 #define NM_UTILS_ERROR (nm_utils_error_quark())
 GQuark nm_utils_error_quark(void);
 
+GQuark nm_manager_error_quark(void);
+#define _NM_MANAGER_ERROR (nm_manager_error_quark())
+
+#define _NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL  10
+#define _NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN 11
+
 void nm_utils_error_set_cancelled(GError **error, gboolean is_disposing, const char *instance_name);
 
 static inline GError *
@@ -1214,6 +1254,27 @@ nm_g_variant_is_of_type(GVariant *value, const GVariantType *type)
     return value && g_variant_is_of_type(value, type);
 }
 
+static inline GVariant *
+nm_g_variant_new_ay_inaddr(int addr_family, gconstpointer addr)
+{
+    return g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
+                                     addr ?: &nm_ip_addr_zero,
+                                     nm_utils_addr_family_to_size(addr_family),
+                                     1);
+}
+
+static inline GVariant *
+nm_g_variant_new_ay_in4addr(in_addr_t addr)
+{
+    return nm_g_variant_new_ay_inaddr(AF_INET, &addr);
+}
+
+static inline GVariant *
+nm_g_variant_new_ay_in6addr(const struct in6_addr *addr)
+{
+    return nm_g_variant_new_ay_inaddr(AF_INET6, addr);
+}
+
 static inline void
 nm_g_variant_builder_add_sv(GVariantBuilder *builder, const char *key, GVariant *val)
 {
@@ -1304,6 +1365,59 @@ nm_g_source_attach(GSource *source, GMainContext *context)
     return source;
 }
 
+static inline GSource *
+nm_g_idle_add_source(GSourceFunc func, gpointer user_data)
+{
+    /* G convenience function to attach a new timeout source to the default GMainContext.
+     * In that sense it's very similar to g_idle_add() except that it returns a
+     * reference to the new source.  */
+    return nm_g_source_attach(nm_g_idle_source_new(G_PRIORITY_DEFAULT, func, user_data, NULL),
+                              NULL);
+}
+
+static inline GSource *
+nm_g_timeout_add_source(guint timeout_msec, GSourceFunc func, gpointer user_data)
+{
+    /* G convenience function to attach a new timeout source to the default GMainContext.
+     * In that sense it's very similar to g_timeout_add() except that it returns a
+     * reference to the new source.  */
+    return nm_g_source_attach(
+        nm_g_timeout_source_new(timeout_msec, G_PRIORITY_DEFAULT, func, user_data, NULL),
+        NULL);
+}
+
+static inline GSource *
+nm_g_timeout_add_source_seconds(guint timeout_sec, GSourceFunc func, gpointer user_data)
+{
+    /* G convenience function to attach a new timeout source to the default GMainContext.
+     * In that sense it's very similar to g_timeout_add_seconds() except that it returns a
+     * reference to the new source.  */
+    return nm_g_source_attach(
+        nm_g_timeout_source_new_seconds(timeout_sec, G_PRIORITY_DEFAULT, func, user_data, NULL),
+        NULL);
+}
+
+static inline GSource *
+nm_g_timeout_add_source_approx(guint       timeout_msec,
+                               guint       timeout_sec_threshold,
+                               GSourceFunc func,
+                               gpointer    user_data)
+{
+    GSource *source;
+
+    /* If timeout_msec is larger or equal than a threshold, then we use g_timeout_source_new_seconds()
+     * instead. */
+    if (timeout_msec / 1000u >= timeout_sec_threshold)
+        source = nm_g_timeout_source_new_seconds(timeout_msec / 1000u,
+                                                 G_PRIORITY_DEFAULT,
+                                                 func,
+                                                 user_data,
+                                                 NULL);
+    else
+        source = nm_g_timeout_source_new(timeout_msec, G_PRIORITY_DEFAULT, func, user_data, NULL);
+    return nm_g_source_attach(source, NULL);
+}
+
 NM_AUTO_DEFINE_FCN0(GMainContext *, _nm_auto_unref_gmaincontext, g_main_context_unref);
 #define nm_auto_unref_gmaincontext nm_auto(_nm_auto_unref_gmaincontext)
 
@@ -1434,6 +1548,8 @@ void nm_utils_named_value_list_sort(NMUtilsNamedValue *arr,
                                     GCompareDataFunc   compare_func,
                                     gpointer           user_data);
 
+void nm_utils_named_value_clear_with_g_free(NMUtilsNamedValue *val);
+
 /*****************************************************************************/
 
 gpointer *nm_utils_hash_keys_to_array(GHashTable *     hash,
@@ -1457,13 +1573,18 @@ nm_utils_strdict_get_keys(const GHashTable *hash, gboolean sorted, guint *out_le
 
 gboolean nm_utils_hashtable_equal(const GHashTable *a,
                                   const GHashTable *b,
-                                  GCompareDataFunc  cmp_values,
-                                  gpointer          user_data);
+                                  gboolean          treat_null_as_empty,
+                                  GEqualFunc        equal_func);
+
+gboolean nm_utils_hashtable_cmp_equal(const GHashTable *a,
+                                      const GHashTable *b,
+                                      GCompareDataFunc  cmp_values,
+                                      gpointer          user_data);
 
 static inline gboolean
 nm_utils_hashtable_same_keys(const GHashTable *a, const GHashTable *b)
 {
-    return nm_utils_hashtable_equal(a, b, NULL, NULL);
+    return nm_utils_hashtable_cmp_equal(a, b, NULL, NULL);
 }
 
 int nm_utils_hashtable_cmp(const GHashTable *a,
@@ -1508,6 +1629,13 @@ nm_g_array_len(const GArray *arr)
     return arr ? arr->len : 0u;
 }
 
+static inline void
+nm_g_array_unref(GArray *arr)
+{
+    if (arr)
+        g_array_unref(arr);
+}
+
 #define nm_g_array_append_new(arr, type)   \
     ({                                     \
         GArray *const _arr = (arr);        \
@@ -1522,6 +1650,55 @@ nm_g_array_len(const GArray *arr)
 
 /*****************************************************************************/
 
+static inline GPtrArray *
+nm_g_ptr_array_ref(GPtrArray *arr)
+{
+    return arr ? g_ptr_array_ref(arr) : NULL;
+}
+
+static inline void
+nm_g_ptr_array_unref(GPtrArray *arr)
+{
+    if (arr)
+        g_ptr_array_unref(arr);
+}
+
+#define nm_g_ptr_array_set(pdst, val)                              \
+    ({                                                             \
+        GPtrArray **_pdst    = (pdst);                             \
+        GPtrArray * _val     = (val);                              \
+        gboolean    _changed = FALSE;                              \
+                                                                   \
+        nm_assert(_pdst);                                          \
+                                                                   \
+        if (*_pdst != _val) {                                      \
+            _nm_unused gs_unref_ptrarray GPtrArray *_old = *_pdst; \
+                                                                   \
+            *_pdst   = nm_g_ptr_array_ref(_val);                   \
+            _changed = TRUE;                                       \
+        }                                                          \
+        _changed;                                                  \
+    })
+
+#define nm_g_ptr_array_set_take(pdst, val)                         \
+    ({                                                             \
+        GPtrArray **_pdst    = (pdst);                             \
+        GPtrArray * _val     = (val);                              \
+        gboolean    _changed = FALSE;                              \
+                                                                   \
+        nm_assert(_pdst);                                          \
+                                                                   \
+        if (*_pdst != _val) {                                      \
+            _nm_unused gs_unref_ptrarray GPtrArray *_old = *_pdst; \
+                                                                   \
+            *_pdst   = _val;                                       \
+            _changed = TRUE;                                       \
+        } else {                                                   \
+            nm_g_ptr_array_unref(_val);                            \
+        }                                                          \
+        _changed;                                                  \
+    })
+
 static inline guint
 nm_g_ptr_array_len(const GPtrArray *arr)
 {
@@ -1571,6 +1748,19 @@ GPtrArray *_nm_g_ptr_array_copy(GPtrArray *    array,
 
 /*****************************************************************************/
 
+static inline GHashTable *
+nm_g_hash_table_ref(GHashTable *hash)
+{
+    return hash ? g_hash_table_ref(hash) : NULL;
+}
+
+static inline void
+nm_g_hash_table_unref(GHashTable *hash)
+{
+    if (hash)
+        g_hash_table_unref(hash);
+}
+
 static inline guint
 nm_g_hash_table_size(GHashTable *hash)
 {
@@ -1614,27 +1804,16 @@ gssize nm_utils_array_find_binary_search(gconstpointer    list,
 
 /*****************************************************************************/
 
-typedef gboolean (*NMUtilsHashTableEqualFunc)(gconstpointer a, gconstpointer b);
-
-gboolean nm_utils_hash_table_equal(const GHashTable *        a,
-                                   const GHashTable *        b,
-                                   gboolean                  treat_null_as_empty,
-                                   NMUtilsHashTableEqualFunc equal_func);
-
-/*****************************************************************************/
-
 void _nm_utils_strv_sort(const char **strv, gssize len);
 #define nm_utils_strv_sort(strv, len) _nm_utils_strv_sort(NM_CAST_STRV_MC(strv), len)
 
 int
 _nm_utils_strv_cmp_n(const char *const *strv1, gssize len1, const char *const *strv2, gssize len2);
 
-static inline gboolean
-_nm_utils_strv_equal(char **strv1, char **strv2)
-{
-    return _nm_utils_strv_cmp_n((const char *const *) strv1, -1, (const char *const *) strv2, -1)
-           == 0;
-}
+#define nm_utils_strv_cmp_n(strv1, len1, strv2, len2) \
+    _nm_utils_strv_cmp_n(NM_CAST_STRV_CC(strv1), (len1), NM_CAST_STRV_CC(strv2), (len2))
+
+#define nm_utils_strv_equal(strv1, strv2) (nm_utils_strv_cmp_n((strv1), -1, (strv2), -1) == 0)
 
 /*****************************************************************************/
 
@@ -1851,14 +2030,14 @@ nm_strv_ptrarray_contains(const GPtrArray *strv, const char *str)
 static inline int
 nm_strv_ptrarray_cmp(const GPtrArray *a, const GPtrArray *b)
 {
-    /* _nm_utils_strv_cmp_n() will treat NULL and empty arrays the same.
+    /* nm_utils_strv_cmp_n() will treat NULL and empty arrays the same.
      * That means, an empty strv array can both be represented by NULL
      * and an array of length zero.
      * If you need to distinguish between these case, do that yourself. */
-    return _nm_utils_strv_cmp_n((const char *const *) nm_g_ptr_array_pdata(a),
-                                nm_g_ptr_array_len(a),
-                                (const char *const *) nm_g_ptr_array_pdata(b),
-                                nm_g_ptr_array_len(b));
+    return nm_utils_strv_cmp_n((const char *const *) nm_g_ptr_array_pdata(a),
+                               nm_g_ptr_array_len(a),
+                               (const char *const *) nm_g_ptr_array_pdata(b),
+                               nm_g_ptr_array_len(b));
 }
 
 /*****************************************************************************/
@@ -1985,6 +2164,24 @@ _nm_utils_hwaddr_aton(const char *asc, gpointer buffer, gsize buffer_length, gsi
                                     out_length);
 }
 
+static inline guint8 *
+_nm_utils_hwaddr_aton_exact(const char *asc, gpointer buffer, gsize buffer_length)
+{
+    g_return_val_if_fail(asc, NULL);
+    g_return_val_if_fail(buffer, NULL);
+    g_return_val_if_fail(buffer_length > 0, NULL);
+
+    return nm_utils_hexstr2bin_full(asc,
+                                    FALSE,
+                                    TRUE,
+                                    FALSE,
+                                    ":-",
+                                    buffer_length,
+                                    buffer,
+                                    buffer_length,
+                                    NULL);
+}
+
 static inline const char *
 _nm_utils_hwaddr_ntoa(gconstpointer addr,
                       gsize         addr_len,
@@ -2060,7 +2257,8 @@ _nm_utils_hwaddr_ntoa(gconstpointer addr,
         {                                                                                      \
             unknown_val_cmd;                                                                   \
         }                                                                                      \
-    }
+    }                                                                                          \
+    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
 #define NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE(fcn_name,        \
                                                    result_type,     \
@@ -2130,10 +2328,31 @@ nm_utils_strdup_reset(char **dst, const char *src)
     return TRUE;
 }
 
+static inline gboolean
+nm_utils_strdup_reset_take(char **dst, char *src)
+{
+    char *old;
+
+    nm_assert(dst);
+    nm_assert(src != *dst);
+
+    if (nm_streq0(*dst, src)) {
+        if (src)
+            g_free(src);
+        return FALSE;
+    }
+    old  = *dst;
+    *dst = src;
+    g_free(old);
+    return TRUE;
+}
+
 void nm_indirect_g_free(gpointer arg);
 
 /*****************************************************************************/
 
+void nm_utils_ifname_cpy(char *dst, const char *name);
+
 typedef enum {
     NMU_IFACE_ANY,
     NMU_IFACE_KERNEL,
diff --git a/shared/nm-glib-aux/nm-str-buf.h b/shared/nm-glib-aux/nm-str-buf.h
index 062630a5..cb0d3fb1 100644
--- a/shared/nm-glib-aux/nm-str-buf.h
+++ b/shared/nm-glib-aux/nm-str-buf.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #ifndef __NM_STR_BUF_H__
 #define __NM_STR_BUF_H__
@@ -31,7 +31,7 @@ typedef struct _NMStrBuf {
 /*****************************************************************************/
 
 static inline void
-_nm_str_buf_assert(NMStrBuf *strbuf)
+_nm_str_buf_assert(const NMStrBuf *strbuf)
 {
     nm_assert(strbuf);
     nm_assert((!!strbuf->_priv_str) == (strbuf->_priv_allocated > 0));
@@ -268,7 +268,34 @@ nm_str_buf_append_required_delimiter(NMStrBuf *strbuf, char delimiter)
 }
 
 static inline void
-nm_str_buf_reset(NMStrBuf *strbuf, const char *str)
+nm_str_buf_append_dirty(NMStrBuf *strbuf, gsize len)
+{
+    _nm_str_buf_assert(strbuf);
+
+    /* this append @len bytes to the buffer, but it does not
+     * initialize them! */
+    if (len > 0) {
+        nm_str_buf_maybe_expand(strbuf, len, FALSE);
+        strbuf->_priv_len += len;
+    }
+}
+
+static inline void
+nm_str_buf_append_c_len(NMStrBuf *strbuf, char ch, gsize len)
+{
+    _nm_str_buf_assert(strbuf);
+
+    if (len > 0) {
+        nm_str_buf_maybe_expand(strbuf, len, FALSE);
+        memset(&strbuf->_priv_str[strbuf->_priv_len], ch, len);
+        strbuf->_priv_len += len;
+    }
+}
+
+/*****************************************************************************/
+
+static inline NMStrBuf *
+nm_str_buf_reset(NMStrBuf *strbuf)
 {
     _nm_str_buf_assert(strbuf);
 
@@ -280,8 +307,7 @@ nm_str_buf_reset(NMStrBuf *strbuf, const char *str)
         strbuf->_priv_len = 0;
     }
 
-    if (str)
-        nm_str_buf_append(strbuf, str);
+    return strbuf;
 }
 
 /*****************************************************************************/
@@ -361,6 +387,31 @@ nm_str_buf_get_str_unsafe(NMStrBuf *strbuf)
     return strbuf->_priv_str;
 }
 
+static inline char *
+nm_str_buf_get_str_at_unsafe(NMStrBuf *strbuf, gsize index)
+{
+    _nm_str_buf_assert(strbuf);
+
+    /* it is acceptable to ask for a pointer at the end of the buffer -- even
+     * if there is no data there. The caller is anyway required to take care
+     * of the length (that's the "unsafe" part), and in that case, the length
+     * is merely zero. */
+    nm_assert(index <= strbuf->allocated);
+
+    if (!strbuf->_priv_str)
+        return NULL;
+
+    return &strbuf->_priv_str[index];
+}
+
+static inline char
+nm_str_buf_get_char(const NMStrBuf *strbuf, gsize index)
+{
+    _nm_str_buf_assert(strbuf);
+    nm_assert(index < strbuf->allocated);
+    return strbuf->_priv_str[index];
+}
+
 /**
  * nm_str_buf_finalize:
  * @strbuf: an initilized #NMStrBuf
diff --git a/shared/nm-glib-aux/nm-time-utils.c b/shared/nm-glib-aux/nm-time-utils.c
index 511363c3..df98176a 100644
--- a/shared/nm-glib-aux/nm-time-utils.c
+++ b/shared/nm-glib-aux/nm-time-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-time-utils.h"
 
diff --git a/shared/nm-glib-aux/nm-time-utils.h b/shared/nm-glib-aux/nm-time-utils.h
index 77aeae5b..3c3e935f 100644
--- a/shared/nm-glib-aux/nm-time-utils.h
+++ b/shared/nm-glib-aux/nm-time-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/nm-value-type.h b/shared/nm-glib-aux/nm-value-type.h
index c07f1104..f9edebdb 100644
--- a/shared/nm-glib-aux/nm-value-type.h
+++ b/shared/nm-glib-aux/nm-value-type.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2019 Red Hat, Inc.
  */
diff --git a/shared/nm-glib-aux/tests/meson.build b/shared/nm-glib-aux/tests/meson.build
index 8136eff9..464f4e7c 100644
--- a/shared/nm-glib-aux/tests/meson.build
+++ b/shared/nm-glib-aux/tests/meson.build
@@ -1,13 +1,12 @@
-# SPDX-License-Identifier: LGPL-2.1+
+# SPDX-License-Identifier: LGPL-2.1-or-later
 
 exe = executable(
   'test-shared-general',
   'test-shared-general.c',
   c_args: [
-    '-DNETWORKMANAGER_COMPILATION_TEST',
-    '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG)',
+    '-DG_LOG_DOMAIN="test"',
   ],
-  dependencies: libnm_utils_base_dep,
+  dependencies: libnm_glib_aux_dep,
   link_with: libnm_systemd_logging_stub,
 )
 
@@ -23,11 +22,10 @@ if jansson_dep.found()
     'test-json-aux',
     'test-json-aux.c',
     c_args: [
-      '-DNETWORKMANAGER_COMPILATION_TEST',
-      '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG)',
+      '-DG_LOG_DOMAIN="test"',
     ],
     dependencies: [
-      libnm_utils_base_dep,
+      libnm_glib_aux_dep,
       jansson_dep,
       dl_dep,
     ],
diff --git a/shared/nm-glib-aux/tests/test-json-aux.c b/shared/nm-glib-aux/tests/test-json-aux.c
index 5331c015..b07d673f 100644
--- a/shared/nm-glib-aux/tests/test-json-aux.c
+++ b/shared/nm-glib-aux/tests/test-json-aux.c
@@ -1,8 +1,6 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#define NM_TEST_UTILS_NO_LIBNM 1
-
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-prog.h"
 
 #include <jansson.h>
 
diff --git a/shared/nm-glib-aux/tests/test-shared-general.c b/shared/nm-glib-aux/tests/test-shared-general.c
index 84e0a6f0..f42c6fb1 100644
--- a/shared/nm-glib-aux/tests/test-shared-general.c
+++ b/shared/nm-glib-aux/tests/test-shared-general.c
@@ -1,11 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#define NM_TEST_UTILS_NO_LIBNM 1
-
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-prog.h"
 
 #include "nm-std-aux/unaligned.h"
 #include "nm-glib-aux/nm-random-utils.h"
@@ -364,8 +362,8 @@ test_strv_cmp(void)
         _strv_cmp_fuzz_input((a1), _l1, &_a1_free_shallow, &_a1_free_deep, &_a1, &_a1x);            \
         _strv_cmp_fuzz_input((a2), _l2, &_a2_free_shallow, &_a2_free_deep, &_a2, &_a2x);            \
                                                                                                     \
-        _c1 = _nm_utils_strv_cmp_n(_a1, _l1, _a2, _l2);                                             \
-        _c2 = _nm_utils_strv_cmp_n(_a2, _l2, _a1, _l1);                                             \
+        _c1 = nm_utils_strv_cmp_n(_a1, _l1, _a2, _l2);                                              \
+        _c2 = nm_utils_strv_cmp_n(_a2, _l2, _a1, _l1);                                              \
         if (equal) {                                                                                \
             g_assert_cmpint(_c1, ==, 0);                                                            \
             g_assert_cmpint(_c2, ==, 0);                                                            \
@@ -376,8 +374,8 @@ test_strv_cmp(void)
                                                                                                     \
         /* Compare with self. _strv_cmp_fuzz_input() randomly swapped the arguments (_a1 and _a1x).
          * Either way, the arrays must compare equal to their semantically equal alternative. */ \
-        g_assert_cmpint(_nm_utils_strv_cmp_n(_a1, _l1, _a1x, _l1), ==, 0);                          \
-        g_assert_cmpint(_nm_utils_strv_cmp_n(_a2, _l2, _a2x, _l2), ==, 0);                          \
+        g_assert_cmpint(nm_utils_strv_cmp_n(_a1, _l1, _a1x, _l1), ==, 0);                           \
+        g_assert_cmpint(nm_utils_strv_cmp_n(_a2, _l2, _a2x, _l2), ==, 0);                           \
                                                                                                     \
         _strv_cmp_free_deep(_a1_free_deep, _l1);                                                    \
         _strv_cmp_free_deep(_a2_free_deep, _l2);                                                    \
@@ -632,9 +630,10 @@ static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
     {"0", 0},
     {"1", 1},
     {"2", 2},
-    {"3", 3}, )
+    {"3", 3}, );
 
-    static void test_string_table_lookup(void)
+static void
+test_string_table_lookup(void)
 {
     const char *const args[] = {
         NULL,
@@ -762,25 +761,44 @@ test_nm_utils_get_next_realloc_size(void)
         }
 
         {
-            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);
+            const gsize requested = requested0;
+            gsize       reserved_true;
+            gsize       reserved_false;
+            bool        truncated_true  = FALSE;
+            bool        truncated_false = FALSE;
+
+            if (sizeof(gsize) > 4 && requested > SIZE_MAX / 2u - 24u) {
+                reserved_false  = G_MAXSSIZE;
+                truncated_false = TRUE;
+            } else
+                reserved_false = nm_utils_get_next_realloc_size(FALSE, requested);
+
+            if (sizeof(gsize) > 4 && requested > SIZE_MAX - 0x1000u - 24u) {
+                reserved_true  = G_MAXSSIZE;
+                truncated_true = TRUE;
+            } else
+                reserved_true = nm_utils_get_next_realloc_size(TRUE, 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 (!truncated_true)
+                g_assert_cmpuint(reserved_true, >=, requested);
+            if (!truncated_false)
+                g_assert_cmpuint(reserved_false, >=, requested);
+            if (!truncated_true && !truncated_false)
+                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);
+                if (!truncated_true)
+                    g_assert_cmpuint(reserved_true, ==, test_data[i].reserved_true);
+                if (!truncated_false)
+                    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 {
+            else if (!reserved_false) {
                 g_assert_cmpuint(reserved_false, <=, G_MAXSIZE - 24u);
                 if (reserved_false >= 40) {
                     const gsize _pow2 = reserved_false + 24u;
@@ -800,7 +818,7 @@ test_nm_utils_get_next_realloc_size(void)
             /* reserved_true is generally the next 4k border - 24. */
             if (reserved_true == G_MAXSIZE)
                 g_assert_cmpuint(requested, >, G_MAXSIZE - 0x1000u - 24u);
-            else {
+            else if (!truncated_true) {
                 g_assert_cmpuint(reserved_true, <=, G_MAXSIZE - 24u);
                 if (reserved_true > 8168u) {
                     const gsize page_border = reserved_true + 24u;
@@ -951,10 +969,10 @@ again:
         else
             g_assert(!data);
 
-        g_assert(_nm_utils_strv_cmp_n((const char *const *) strv->pdata,
-                                      strv->len,
-                                      (const char *const *) strv2->pdata,
-                                      strv2->len)
+        g_assert(nm_utils_strv_cmp_n((const char *const *) strv->pdata,
+                                     strv->len,
+                                     (const char *const *) strv2->pdata,
+                                     strv2->len)
                  == 0);
     }
 }
@@ -1050,7 +1068,7 @@ test_strv_dup_packed(void)
             g_assert(strv_cpy);
         g_assert(NM_PTRARRAY_LEN(strv_cpy) == strv_len);
         if (strv_cpy)
-            g_assert(_nm_utils_strv_equal((char **) strv_cpy, (char **) strv_src));
+            g_assert(nm_utils_strv_equal(strv_cpy, strv_src));
     }
 }
 
@@ -1189,8 +1207,8 @@ test_utils_hashtable_cmp(void)
             }
 
             g_assert(nm_utils_hashtable_same_keys(h1, h2));
-            g_assert(nm_utils_hashtable_equal(h1, h2, NULL, NULL));
-            g_assert(nm_utils_hashtable_equal(h1, h2, func_val_cmp, NULL));
+            g_assert(nm_utils_hashtable_cmp_equal(h1, h2, NULL, NULL));
+            g_assert(nm_utils_hashtable_cmp_equal(h1, h2, func_val_cmp, NULL));
             g_assert(nm_utils_hashtable_cmp(h1, h2, FALSE, func_key_cmp, NULL, NULL) == 0);
             g_assert(nm_utils_hashtable_cmp(h1, h2, TRUE, func_key_cmp, NULL, NULL) == 0);
             g_assert(nm_utils_hashtable_cmp(h1, h2, FALSE, func_key_cmp, func_val_cmp, NULL) == 0);
@@ -1220,16 +1238,16 @@ again:
 
             if (has_same_keys) {
                 g_assert(nm_utils_hashtable_same_keys(h1, h2));
-                g_assert(nm_utils_hashtable_equal(h1, h2, NULL, NULL));
+                g_assert(nm_utils_hashtable_cmp_equal(h1, h2, NULL, NULL));
                 g_assert(nm_utils_hashtable_cmp(h1, h2, FALSE, func_key_cmp, NULL, NULL) == 0);
                 g_assert(nm_utils_hashtable_cmp(h1, h2, TRUE, func_key_cmp, NULL, NULL) == 0);
             } else {
                 g_assert(!nm_utils_hashtable_same_keys(h1, h2));
-                g_assert(!nm_utils_hashtable_equal(h1, h2, NULL, NULL));
+                g_assert(!nm_utils_hashtable_cmp_equal(h1, h2, NULL, NULL));
                 g_assert(nm_utils_hashtable_cmp(h1, h2, FALSE, func_key_cmp, NULL, NULL) != 0);
                 g_assert(nm_utils_hashtable_cmp(h1, h2, TRUE, func_key_cmp, NULL, NULL) != 0);
             }
-            g_assert(!nm_utils_hashtable_equal(h1, h2, func_val_cmp, NULL));
+            g_assert(!nm_utils_hashtable_cmp_equal(h1, h2, func_val_cmp, NULL));
             g_assert(nm_utils_hashtable_cmp(h1, h2, FALSE, func_key_cmp, func_val_cmp, NULL) != 0);
             g_assert(nm_utils_hashtable_cmp(h1, h2, TRUE, func_key_cmp, func_val_cmp, NULL) != 0);
         }
diff --git a/shared/nm-log-core/nm-logging.c b/shared/nm-log-core/nm-logging.c
new file mode 100644
index 00000000..cf3c3a86
--- /dev/null
+++ b/shared/nm-log-core/nm-logging.c
@@ -0,0 +1,1035 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2006 - 2012 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
+
+#include "nm-logging.h"
+
+#include <dlfcn.h>
+#include <syslog.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <strings.h>
+
+#if SYSTEMD_JOURNAL
+    #define SD_JOURNAL_SUPPRESS_LOCATION
+    #include <systemd/sd-journal.h>
+#endif
+
+#include "nm-glib-aux/nm-logging-base.h"
+#include "nm-glib-aux/nm-time-utils.h"
+
+/*****************************************************************************/
+
+/* Notes about thread-safety:
+ *
+ * NetworkManager generally is single-threaded and uses a (GLib) mainloop.
+ * However, nm-logging is in parts thread-safe. That means:
+ *
+ * - functions that configure logging (nm_logging_init(), nm_logging_setup()) and
+ *   most other functions MUST be called only from the main-thread. These functions
+ *   are expected to be called infrequently, so they may or may not use a mutex
+ *   (but the overhead is negligible here).
+ *
+ * - functions that do the actual logging logging (nm_log(), nm_logging_enabled()) are
+ *   thread-safe and may be used from multiple threads.
+ *    - When called from the not-main-thread, @mt_require_locking must be set to %TRUE.
+ *      In this case, a Mutex will be used for accessing the global state.
+ *    - When called from the main-thread, they may optionally pass @mt_require_locking %FALSE.
+ *      This avoids extra locking and is in particular interesting for nm_logging_enabled(),
+ *      which is expected to be called frequently and from the main-thread.
+ *
+ * Note that the logging macros honor %NM_THREAD_SAFE_ON_MAIN_THREAD define, to automatically
+ * set @mt_require_locking. That means, by default %NM_THREAD_SAFE_ON_MAIN_THREAD is "1",
+ * and code that only runs on the main-thread (which is the majority), can get away
+ * without locking.
+ */
+
+/*****************************************************************************/
+
+G_STATIC_ASSERT(LOG_EMERG == 0);
+G_STATIC_ASSERT(LOG_ALERT == 1);
+G_STATIC_ASSERT(LOG_CRIT == 2);
+G_STATIC_ASSERT(LOG_ERR == 3);
+G_STATIC_ASSERT(LOG_WARNING == 4);
+G_STATIC_ASSERT(LOG_NOTICE == 5);
+G_STATIC_ASSERT(LOG_INFO == 6);
+G_STATIC_ASSERT(LOG_DEBUG == 7);
+
+/* We have more then 32 logging domains. Assert that it compiles to a 64 bit sized enum */
+G_STATIC_ASSERT(sizeof(NMLogDomain) >= sizeof(guint64));
+
+/* Combined domains */
+#define LOGD_ALL_STRING     "ALL"
+#define LOGD_DEFAULT_STRING "DEFAULT"
+#define LOGD_DHCP_STRING    "DHCP"
+#define LOGD_IP_STRING      "IP"
+
+/*****************************************************************************/
+
+typedef enum {
+    LOG_BACKEND_GLIB,
+    LOG_BACKEND_SYSLOG,
+    LOG_BACKEND_JOURNAL,
+} LogBackend;
+
+typedef struct {
+    NMLogDomain num;
+    const char *name;
+} LogDesc;
+
+typedef struct {
+    char *logging_domains_to_string;
+} GlobalMain;
+
+typedef struct {
+    NMLogLevel  log_level;
+    bool        uses_syslog : 1;
+    bool        init_pre_done : 1;
+    bool        init_done : 1;
+    bool        debug_stderr : 1;
+    const char *prefix;
+    const char *syslog_identifier;
+
+    /* before we setup syslog (during start), the backend defaults to GLIB, meaning:
+     * we use g_log() for all logging. At that point, the application is not yet supposed
+     * to do any logging and doing so indicates a bug.
+     *
+     * Afterwards, the backend is either SYSLOG or JOURNAL. From that point, also
+     * g_log() is redirected to this backend via a logging handler. */
+    LogBackend log_backend;
+} Global;
+
+/*****************************************************************************/
+
+G_LOCK_DEFINE_STATIC(log);
+
+/* This data must only be accessed from the main-thread (and as
+ * such does not need any lock). */
+static GlobalMain gl_main = {};
+
+static union {
+    /* a union with an immutable and a mutable alias for the Global.
+     * Since nm-logging must be thread-safe, we must take care at which
+     * places we only read value ("imm") and where we modify them ("mut"). */
+    Global       mut;
+    const Global imm;
+} gl = {
+    .imm =
+        {
+            /* nm_logging_setup ("INFO", LOGD_DEFAULT_STRING, NULL, NULL); */
+            .log_level         = LOGL_INFO,
+            .log_backend       = LOG_BACKEND_GLIB,
+            .syslog_identifier = "SYSLOG_IDENTIFIER=" G_LOG_DOMAIN,
+            .prefix            = "",
+        },
+};
+
+NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL] = {
+    /* nm_logging_setup ("INFO", LOGD_DEFAULT_STRING, NULL, NULL);
+     *
+     * Note: LOGD_VPN_PLUGIN is special and must be disabled for
+     * DEBUG and TRACE levels. */
+    [LOGL_INFO] = LOGD_DEFAULT,
+    [LOGL_WARN] = LOGD_DEFAULT,
+    [LOGL_ERR]  = LOGD_DEFAULT,
+};
+
+/*****************************************************************************/
+
+static const LogDesc domain_desc[] = {
+    {LOGD_PLATFORM, "PLATFORM"},
+    {LOGD_RFKILL, "RFKILL"},
+    {LOGD_ETHER, "ETHER"},
+    {LOGD_WIFI, "WIFI"},
+    {LOGD_BT, "BT"},
+    {LOGD_MB, "MB"},
+    {LOGD_DHCP4, "DHCP4"},
+    {LOGD_DHCP6, "DHCP6"},
+    {LOGD_PPP, "PPP"},
+    {LOGD_WIFI_SCAN, "WIFI_SCAN"},
+    {LOGD_IP4, "IP4"},
+    {LOGD_IP6, "IP6"},
+    {LOGD_AUTOIP4, "AUTOIP4"},
+    {LOGD_DNS, "DNS"},
+    {LOGD_VPN, "VPN"},
+    {LOGD_SHARING, "SHARING"},
+    {LOGD_SUPPLICANT, "SUPPLICANT"},
+    {LOGD_AGENTS, "AGENTS"},
+    {LOGD_SETTINGS, "SETTINGS"},
+    {LOGD_SUSPEND, "SUSPEND"},
+    {LOGD_CORE, "CORE"},
+    {LOGD_DEVICE, "DEVICE"},
+    {LOGD_OLPC, "OLPC"},
+    {LOGD_INFINIBAND, "INFINIBAND"},
+    {LOGD_FIREWALL, "FIREWALL"},
+    {LOGD_ADSL, "ADSL"},
+    {LOGD_BOND, "BOND"},
+    {LOGD_VLAN, "VLAN"},
+    {LOGD_BRIDGE, "BRIDGE"},
+    {LOGD_DBUS_PROPS, "DBUS_PROPS"},
+    {LOGD_TEAM, "TEAM"},
+    {LOGD_CONCHECK, "CONCHECK"},
+    {LOGD_DCB, "DCB"},
+    {LOGD_DISPATCH, "DISPATCH"},
+    {LOGD_AUDIT, "AUDIT"},
+    {LOGD_SYSTEMD, "SYSTEMD"},
+    {LOGD_VPN_PLUGIN, "VPN_PLUGIN"},
+    {LOGD_PROXY, "PROXY"},
+    {0},
+};
+
+/*****************************************************************************/
+
+static char *_domains_to_string(gboolean          include_level_override,
+                                NMLogLevel        log_level,
+                                const NMLogDomain log_state[static _LOGL_N_REAL]);
+
+/*****************************************************************************/
+
+static gboolean
+_syslog_identifier_valid_domain(const char *domain)
+{
+    char c;
+
+    if (!domain || !domain[0])
+        return FALSE;
+
+    /* we pass the syslog identifier as format string. No funny stuff. */
+
+    for (; (c = domain[0]); domain++) {
+        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
+            || NM_IN_SET(c, '-', '_'))
+            continue;
+        return FALSE;
+    }
+    return TRUE;
+}
+
+static gboolean
+_syslog_identifier_assert(const char *syslog_identifier)
+{
+    g_assert(syslog_identifier);
+    g_assert(g_str_has_prefix(syslog_identifier, "SYSLOG_IDENTIFIER="));
+    g_assert(_syslog_identifier_valid_domain(&syslog_identifier[NM_STRLEN("SYSLOG_IDENTIFIER=")]));
+    return TRUE;
+}
+
+static const char *
+syslog_identifier_domain(const char *syslog_identifier)
+{
+    nm_assert(_syslog_identifier_assert(syslog_identifier));
+    return &syslog_identifier[NM_STRLEN("SYSLOG_IDENTIFIER=")];
+}
+
+#if SYSTEMD_JOURNAL
+static const char *
+syslog_identifier_full(const char *syslog_identifier)
+{
+    nm_assert(_syslog_identifier_assert(syslog_identifier));
+    return &syslog_identifier[0];
+}
+#endif
+
+/*****************************************************************************/
+
+static gboolean
+match_log_level(const char *level, NMLogLevel *out_level, GError **error)
+{
+    if (_nm_log_parse_level(level, out_level))
+        return TRUE;
+
+    g_set_error(error,
+                _NM_MANAGER_ERROR,
+                _NM_MANAGER_ERROR_UNKNOWN_LOG_LEVEL,
+                _("Unknown log level '%s'"),
+                level);
+    return FALSE;
+}
+
+gboolean
+nm_logging_setup(const char *level, const char *domains, char **bad_domains, GError **error)
+{
+    GString *            unrecognized = NULL;
+    NMLogDomain          cur_log_state[_LOGL_N_REAL];
+    NMLogDomain          new_log_state[_LOGL_N_REAL];
+    NMLogLevel           cur_log_level;
+    NMLogLevel           new_log_level;
+    gs_free const char **domains_v = NULL;
+    gsize                i_d;
+    int                  i;
+    gboolean             had_platform_debug;
+    gs_free char *       domains_free = NULL;
+
+    NM_ASSERT_ON_MAIN_THREAD();
+
+    g_return_val_if_fail(!bad_domains || !*bad_domains, FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    cur_log_level = gl.imm.log_level;
+    memcpy(cur_log_state, _nm_logging_enabled_state, sizeof(cur_log_state));
+
+    new_log_level = cur_log_level;
+
+    if (!domains || !*domains) {
+        domains_free = _domains_to_string(FALSE, cur_log_level, cur_log_state);
+        domains      = domains_free;
+    }
+
+    for (i = 0; i < G_N_ELEMENTS(new_log_state); i++)
+        new_log_state[i] = 0;
+
+    if (level && *level) {
+        if (!match_log_level(level, &new_log_level, error))
+            return FALSE;
+        if (new_log_level == _LOGL_KEEP) {
+            new_log_level = cur_log_level;
+            for (i = 0; i < G_N_ELEMENTS(new_log_state); i++)
+                new_log_state[i] = cur_log_state[i];
+        }
+    }
+
+    domains_v = nm_utils_strsplit_set(domains, ", ");
+    for (i_d = 0; domains_v && domains_v[i_d]; i_d++) {
+        const char *   s = domains_v[i_d];
+        const char *   p;
+        const LogDesc *diter;
+        NMLogLevel     domain_log_level;
+        NMLogDomain    bits;
+
+        /* LOGD_VPN_PLUGIN is protected, that is, when setting ALL or DEFAULT,
+         * it does not enable the verbose levels DEBUG and TRACE, because that
+         * may expose sensitive data. */
+        NMLogDomain protect = LOGD_NONE;
+
+        p = strchr(s, ':');
+        if (p) {
+            *((char *) p) = '\0';
+            if (!match_log_level(p + 1, &domain_log_level, error))
+                return FALSE;
+        } else
+            domain_log_level = new_log_level;
+
+        bits = 0;
+
+        if (domains_free) {
+            /* The caller didn't provide any domains to set (`nmcli general logging level DEBUG`).
+             * We reset all domains that were previously set, but we still want to protect
+             * VPN_PLUGIN domain. */
+            protect = LOGD_VPN_PLUGIN;
+        }
+
+        /* Check for combined domains */
+        if (!g_ascii_strcasecmp(s, LOGD_ALL_STRING)) {
+            bits    = LOGD_ALL;
+            protect = LOGD_VPN_PLUGIN;
+        } else if (!g_ascii_strcasecmp(s, LOGD_DEFAULT_STRING)) {
+            bits    = LOGD_DEFAULT;
+            protect = LOGD_VPN_PLUGIN;
+        } else if (!g_ascii_strcasecmp(s, LOGD_DHCP_STRING))
+            bits = LOGD_DHCP;
+        else if (!g_ascii_strcasecmp(s, LOGD_IP_STRING))
+            bits = LOGD_IP;
+
+        /* Check for compatibility domains */
+        else if (!g_ascii_strcasecmp(s, "HW"))
+            bits = LOGD_PLATFORM;
+        else if (!g_ascii_strcasecmp(s, "WIMAX"))
+            continue;
+
+        else {
+            for (diter = &domain_desc[0]; diter->name; diter++) {
+                if (!g_ascii_strcasecmp(diter->name, s)) {
+                    bits = diter->num;
+                    break;
+                }
+            }
+
+            if (!bits) {
+                if (!bad_domains) {
+                    g_set_error(error,
+                                _NM_MANAGER_ERROR,
+                                _NM_MANAGER_ERROR_UNKNOWN_LOG_DOMAIN,
+                                _("Unknown log domain '%s'"),
+                                s);
+                    return FALSE;
+                }
+
+                if (unrecognized)
+                    g_string_append(unrecognized, ", ");
+                else
+                    unrecognized = g_string_new(NULL);
+                g_string_append(unrecognized, s);
+                continue;
+            }
+        }
+
+        if (domain_log_level == _LOGL_KEEP) {
+            for (i = 0; i < G_N_ELEMENTS(new_log_state); i++)
+                new_log_state[i] = (new_log_state[i] & ~bits) | (cur_log_state[i] & bits);
+        } else {
+            for (i = 0; i < G_N_ELEMENTS(new_log_state); i++) {
+                if (i < domain_log_level)
+                    new_log_state[i] &= ~bits;
+                else {
+                    new_log_state[i] |= bits;
+                    if ((protect & bits) && i < LOGL_INFO)
+                        new_log_state[i] &= ~protect;
+                }
+            }
+        }
+    }
+
+    nm_clear_g_free(&gl_main.logging_domains_to_string);
+
+    had_platform_debug = _nm_logging_enabled_lockfree(LOGL_DEBUG, LOGD_PLATFORM);
+
+    G_LOCK(log);
+
+    gl.mut.log_level = new_log_level;
+    for (i = 0; i < G_N_ELEMENTS(new_log_state); i++)
+        _nm_logging_enabled_state[i] = new_log_state[i];
+
+    G_UNLOCK(log);
+
+    if (had_platform_debug && !_nm_logging_enabled_lockfree(LOGL_DEBUG, LOGD_PLATFORM)) {
+        /* when debug logging is enabled, platform will cache all access to
+         * sysctl. When the user disables debug-logging, we want to clear that
+         * cache right away. */
+        _nm_logging_clear_platform_logging_cache();
+    }
+
+    if (unrecognized)
+        *bad_domains = g_string_free(unrecognized, FALSE);
+
+    return TRUE;
+}
+
+const char *
+nm_logging_level_to_string(void)
+{
+    NM_ASSERT_ON_MAIN_THREAD();
+
+    return level_desc[gl.imm.log_level].name;
+}
+
+const char *
+nm_logging_all_levels_to_string(void)
+{
+    static GString *str;
+
+    if (G_UNLIKELY(!str)) {
+        int i;
+
+        str = g_string_new(NULL);
+        for (i = 0; i < G_N_ELEMENTS(level_desc); i++) {
+            if (str->len)
+                g_string_append_c(str, ',');
+            g_string_append(str, level_desc[i].name);
+        }
+    }
+
+    return str->str;
+}
+
+const char *
+nm_logging_domains_to_string(void)
+{
+    NM_ASSERT_ON_MAIN_THREAD();
+
+    if (G_UNLIKELY(!gl_main.logging_domains_to_string)) {
+        gl_main.logging_domains_to_string =
+            _domains_to_string(TRUE, gl.imm.log_level, _nm_logging_enabled_state);
+    }
+
+    return gl_main.logging_domains_to_string;
+}
+
+static char *
+_domains_to_string(gboolean          include_level_override,
+                   NMLogLevel        log_level,
+                   const NMLogDomain log_state[static _LOGL_N_REAL])
+{
+    const LogDesc *diter;
+    GString *      str;
+    int            i;
+
+    /* We don't just return g_strdup() the logging domains that were set during
+     * nm_logging_setup(), because we want to expand "DEFAULT" and "ALL".
+     */
+
+    str = g_string_sized_new(75);
+    for (diter = &domain_desc[0]; diter->name; diter++) {
+        /* If it's set for any lower level, it will also be set for LOGL_ERR */
+        if (!(diter->num & log_state[LOGL_ERR]))
+            continue;
+
+        if (str->len)
+            g_string_append_c(str, ',');
+        g_string_append(str, diter->name);
+
+        if (!include_level_override)
+            continue;
+
+        /* Check if it's logging at a lower level than the default. */
+        for (i = 0; i < log_level; i++) {
+            if (diter->num & log_state[i]) {
+                g_string_append_printf(str, ":%s", level_desc[i].name);
+                break;
+            }
+        }
+        /* Check if it's logging at a higher level than the default. */
+        if (!(diter->num & log_state[log_level])) {
+            for (i = log_level + 1; i < _LOGL_N_REAL; i++) {
+                if (diter->num & log_state[i]) {
+                    g_string_append_printf(str, ":%s", level_desc[i].name);
+                    break;
+                }
+            }
+        }
+    }
+    return g_string_free(str, FALSE);
+}
+
+static char _all_logging_domains_to_str[273];
+
+const char *
+nm_logging_all_domains_to_string(void)
+{
+    static const char *volatile str = NULL;
+    const char *s;
+
+again:
+    s = g_atomic_pointer_get(&str);
+    if (G_UNLIKELY(!s)) {
+        static gsize   once = 0;
+        const LogDesc *diter;
+        gsize          buf_l;
+        char *         buf_p;
+
+        if (!g_once_init_enter(&once))
+            goto again;
+
+        buf_p = _all_logging_domains_to_str;
+        buf_l = sizeof(_all_logging_domains_to_str);
+
+        nm_utils_strbuf_append_str(&buf_p, &buf_l, LOGD_DEFAULT_STRING);
+        for (diter = &domain_desc[0]; diter->name; diter++) {
+            nm_utils_strbuf_append_c(&buf_p, &buf_l, ',');
+            nm_utils_strbuf_append_str(&buf_p, &buf_l, diter->name);
+            if (diter->num == LOGD_DHCP6)
+                nm_utils_strbuf_append_str(&buf_p, &buf_l, "," LOGD_DHCP_STRING);
+            else if (diter->num == LOGD_IP6)
+                nm_utils_strbuf_append_str(&buf_p, &buf_l, "," LOGD_IP_STRING);
+        }
+        nm_utils_strbuf_append_str(&buf_p, &buf_l, LOGD_ALL_STRING);
+
+        /* Did you modify the logging domains (or their names)? Adjust the size of
+         * _all_logging_domains_to_str buffer above to have the exact size. */
+        nm_assert(strlen(_all_logging_domains_to_str) == sizeof(_all_logging_domains_to_str) - 1);
+        nm_assert(buf_l == 1);
+
+        s = _all_logging_domains_to_str;
+        g_atomic_pointer_set(&str, s);
+        g_once_init_leave(&once, 1);
+    }
+
+    return s;
+}
+
+/**
+ * nm_logging_get_level:
+ * @domain: find the lowest enabled logging level for the
+ *   given domain. If this is a set of multiple
+ *   domains, the most verbose level will be returned.
+ *
+ * Returns: the lowest (most verbose) logging level for the
+ *   give @domain, or %_LOGL_OFF if it is disabled.
+ **/
+NMLogLevel
+nm_logging_get_level(NMLogDomain domain)
+{
+    NMLogLevel sl = _LOGL_OFF;
+
+    G_STATIC_ASSERT(LOGL_TRACE == 0);
+    while (sl > LOGL_TRACE && _nm_logging_enabled_lockfree(sl - 1, domain))
+        sl--;
+    return sl;
+}
+
+gboolean
+_nm_logging_enabled_locking(NMLogLevel level, NMLogDomain domain)
+{
+    gboolean v;
+
+    G_LOCK(log);
+    v = _nm_logging_enabled_lockfree(level, domain);
+    G_UNLOCK(log);
+    return v;
+}
+
+gboolean
+_nm_log_enabled_impl(gboolean mt_require_locking, NMLogLevel level, NMLogDomain domain)
+{
+    return nm_logging_enabled_mt(mt_require_locking, level, domain);
+}
+
+#if SYSTEMD_JOURNAL
+static void
+_iovec_set(struct iovec *iov, const void *str, gsize len)
+{
+    iov->iov_base = (void *) str;
+    iov->iov_len  = len;
+}
+
+static void
+_iovec_set_string(struct iovec *iov, const char *str)
+{
+    _iovec_set(iov, str, strlen(str));
+}
+
+    #define _iovec_set_string_literal(iov, str) _iovec_set((iov), "" str "", NM_STRLEN(str))
+
+_nm_printf(3, 4) static void _iovec_set_format(struct iovec *iov,
+                                               char **       iov_free,
+                                               const char *  format,
+                                               ...)
+{
+    va_list ap;
+    char *  str;
+
+    va_start(ap, format);
+    str = g_strdup_vprintf(format, ap);
+    va_end(ap);
+
+    _iovec_set_string(iov, str);
+    *iov_free = str;
+}
+
+    #define _iovec_set_format_a(iov, reserve_extra, format, ...)                   \
+        G_STMT_START                                                               \
+        {                                                                          \
+            const gsize _size = (reserve_extra) + (NM_STRLEN(format) + 3);         \
+            char *const _buf  = g_alloca(_size);                                   \
+            int         _len;                                                      \
+                                                                                   \
+            G_STATIC_ASSERT_EXPR((reserve_extra) + (NM_STRLEN(format) + 3) <= 96); \
+                                                                                   \
+            _len = g_snprintf(_buf, _size, "" format "", ##__VA_ARGS__);           \
+                                                                                   \
+            nm_assert(_len >= 0);                                                  \
+            nm_assert(_len < _size);                                               \
+            nm_assert(_len == strlen(_buf));                                       \
+                                                                                   \
+            _iovec_set((iov), _buf, _len);                                         \
+        }                                                                          \
+        G_STMT_END
+
+    #define _iovec_set_format_str_a(iov, max_str_len, format, str_arg)  \
+        G_STMT_START                                                    \
+        {                                                               \
+            const char *_str_arg = (str_arg);                           \
+                                                                        \
+            nm_assert(_str_arg &&strlen(_str_arg) < (max_str_len));     \
+            _iovec_set_format_a((iov), (max_str_len), format, str_arg); \
+        }                                                               \
+        G_STMT_END
+
+#endif
+
+void
+_nm_log_impl(const char *file,
+             guint       line,
+             const char *func,
+             gboolean    mt_require_locking,
+             NMLogLevel  level,
+             NMLogDomain domain,
+             int         error,
+             const char *ifname,
+             const char *conn_uuid,
+             const char *fmt,
+             ...)
+{
+    va_list            args;
+    char *             msg;
+    GTimeVal           tv;
+    int                errsv;
+    const NMLogDomain *cur_log_state;
+    NMLogDomain        cur_log_state_copy[_LOGL_N_REAL];
+    Global             g_copy;
+    const Global *     g;
+
+    if (G_UNLIKELY(mt_require_locking)) {
+        G_LOCK(log);
+        /* we evaluate logging-enabled under lock. There is still a race that
+         * we might log the message below *after* logging was disabled. That means,
+         * when disabling logging, we might still log messages. */
+        if (!_nm_logging_enabled_lockfree(level, domain)) {
+            G_UNLOCK(log);
+            return;
+        }
+        g_copy = gl.imm;
+        memcpy(cur_log_state_copy, _nm_logging_enabled_state, sizeof(cur_log_state_copy));
+        G_UNLOCK(log);
+        g             = &g_copy;
+        cur_log_state = cur_log_state_copy;
+    } else {
+        NM_ASSERT_ON_MAIN_THREAD();
+        if (!_nm_logging_enabled_lockfree(level, domain))
+            return;
+        g             = &gl.imm;
+        cur_log_state = _nm_logging_enabled_state;
+    }
+
+    (void) cur_log_state;
+
+    errsv = errno;
+
+    /* Make sure that %m maps to the specified error */
+    if (error != 0) {
+        if (error < 0)
+            error = -error;
+        errno = error;
+    }
+
+    va_start(args, fmt);
+    msg = g_strdup_vprintf(fmt, args);
+    va_end(args);
+
+#define MESSAGE_FMT "%s%-7s [%ld.%04ld] %s"
+#define MESSAGE_ARG(prefix, tv, msg) \
+    prefix, level_desc[level].level_str, (tv).tv_sec, ((tv).tv_usec / 100), (msg)
+
+    g_get_current_time(&tv);
+
+    if (g->debug_stderr)
+        g_printerr(MESSAGE_FMT "\n", MESSAGE_ARG(g->prefix, tv, msg));
+
+    switch (g->log_backend) {
+#if SYSTEMD_JOURNAL
+    case LOG_BACKEND_JOURNAL:
+    {
+        gint64         now, boottime;
+        struct iovec   iov_data[15];
+        struct iovec * iov = iov_data;
+        char *         iov_free_data[5];
+        char **        iov_free = iov_free_data;
+        const LogDesc *diter;
+        NMLogDomain    dom_all;
+        char  s_log_domains_buf[NM_STRLEN("NM_LOG_DOMAINS=") + sizeof(_all_logging_domains_to_str)];
+        char *s_log_domains;
+        gsize l_log_domains;
+
+        now      = nm_utils_get_monotonic_timestamp_nsec();
+        boottime = nm_utils_monotonic_timestamp_as_boottime(now, 1);
+
+        _iovec_set_format_a(iov++, 30, "PRIORITY=%d", level_desc[level].syslog_level);
+        _iovec_set_format(iov++,
+                          iov_free++,
+                          "MESSAGE=" MESSAGE_FMT,
+                          MESSAGE_ARG(g->prefix, tv, msg));
+        _iovec_set_string(iov++, syslog_identifier_full(g->syslog_identifier));
+        _iovec_set_format_a(iov++, 30, "SYSLOG_PID=%ld", (long) getpid());
+
+        dom_all       = domain;
+        s_log_domains = s_log_domains_buf;
+        l_log_domains = sizeof(s_log_domains_buf);
+
+        nm_utils_strbuf_append_str(&s_log_domains, &l_log_domains, "NM_LOG_DOMAINS=");
+        for (diter = &domain_desc[0]; dom_all != 0 && diter->name; diter++) {
+            if (!NM_FLAGS_ANY(dom_all, diter->num))
+                continue;
+            if (dom_all != domain)
+                nm_utils_strbuf_append_c(&s_log_domains, &l_log_domains, ',');
+            nm_utils_strbuf_append_str(&s_log_domains, &l_log_domains, diter->name);
+            dom_all &= ~diter->num;
+        }
+        nm_assert(l_log_domains > 0);
+        _iovec_set(iov++, s_log_domains_buf, s_log_domains - s_log_domains_buf);
+
+        G_STATIC_ASSERT_EXPR(LOG_FAC(LOG_DAEMON) == 3);
+        _iovec_set_string_literal(iov++, "SYSLOG_FACILITY=3");
+        _iovec_set_format_str_a(iov++, 15, "NM_LOG_LEVEL=%s", level_desc[level].name);
+        if (func)
+            _iovec_set_format(iov++, iov_free++, "CODE_FUNC=%s", func);
+        _iovec_set_format(iov++, iov_free++, "CODE_FILE=%s", file ?: "");
+        _iovec_set_format_a(iov++, 20, "CODE_LINE=%u", line);
+        _iovec_set_format_a(iov++,
+                            60,
+                            "TIMESTAMP_MONOTONIC=%lld.%06lld",
+                            (long long) (now / NM_UTILS_NSEC_PER_SEC),
+                            (long long) ((now % NM_UTILS_NSEC_PER_SEC) / 1000));
+        _iovec_set_format_a(iov++,
+                            60,
+                            "TIMESTAMP_BOOTTIME=%lld.%06lld",
+                            (long long) (boottime / NM_UTILS_NSEC_PER_SEC),
+                            (long long) ((boottime % NM_UTILS_NSEC_PER_SEC) / 1000));
+        if (error != 0)
+            _iovec_set_format_a(iov++, 30, "ERRNO=%d", error);
+        if (ifname)
+            _iovec_set_format(iov++, iov_free++, "NM_DEVICE=%s", ifname);
+        if (conn_uuid)
+            _iovec_set_format(iov++, iov_free++, "NM_CONNECTION=%s", conn_uuid);
+
+        nm_assert(iov <= &iov_data[G_N_ELEMENTS(iov_data)]);
+        nm_assert(iov_free <= &iov_free_data[G_N_ELEMENTS(iov_free_data)]);
+
+        sd_journal_sendv(iov_data, iov - iov_data);
+
+        for (; --iov_free >= iov_free_data;)
+            g_free(*iov_free);
+    } break;
+#endif
+    case LOG_BACKEND_SYSLOG:
+        syslog(level_desc[level].syslog_level, MESSAGE_FMT, MESSAGE_ARG(g->prefix, tv, msg));
+        break;
+    default:
+        g_log(syslog_identifier_domain(g->syslog_identifier),
+              level_desc[level].g_log_level,
+              MESSAGE_FMT,
+              MESSAGE_ARG(g->prefix, tv, msg));
+        break;
+    }
+
+    g_free(msg);
+
+    errno = errsv;
+}
+
+/*****************************************************************************/
+
+void
+_nm_utils_monotonic_timestamp_initialized(const struct timespec *tp,
+                                          gint64                 offset_sec,
+                                          gboolean               is_boottime)
+{
+    NM_ASSERT_ON_MAIN_THREAD();
+
+    if (_nm_logging_enabled_lockfree(LOGL_DEBUG, LOGD_CORE)) {
+        time_t    now = time(NULL);
+        struct tm tm;
+        char      s[255];
+
+        strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", localtime_r(&now, &tm));
+        nm_log_dbg(LOGD_CORE,
+                   "monotonic timestamp started counting 1.%09ld seconds ago with "
+                   "an offset of %lld.0 seconds to %s (local time is %s)",
+                   tp->tv_nsec,
+                   (long long) -offset_sec,
+                   is_boottime ? "CLOCK_BOOTTIME" : "CLOCK_MONOTONIC",
+                   s);
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_log_handler(const char *log_domain, GLogLevelFlags level, const char *message, gpointer ignored)
+{
+    int syslog_priority;
+
+    switch (level & G_LOG_LEVEL_MASK) {
+    case G_LOG_LEVEL_ERROR:
+        syslog_priority = LOG_CRIT;
+        break;
+    case G_LOG_LEVEL_CRITICAL:
+        syslog_priority = LOG_ERR;
+        break;
+    case G_LOG_LEVEL_WARNING:
+        syslog_priority = LOG_WARNING;
+        break;
+    case G_LOG_LEVEL_MESSAGE:
+        syslog_priority = LOG_NOTICE;
+        break;
+    case G_LOG_LEVEL_DEBUG:
+        syslog_priority = LOG_DEBUG;
+        break;
+    case G_LOG_LEVEL_INFO:
+    default:
+        syslog_priority = LOG_INFO;
+        break;
+    }
+
+    /* we don't need any locking here. The glib log handler gets only registered
+     * once during nm_logging_init() and the global data is not modified afterwards. */
+    nm_assert(gl.imm.init_done);
+
+    if (gl.imm.debug_stderr)
+        g_printerr("%s%s\n", gl.imm.prefix, message ?: "");
+
+    switch (gl.imm.log_backend) {
+#if SYSTEMD_JOURNAL
+    case LOG_BACKEND_JOURNAL:
+    {
+        gint64 now, boottime;
+
+        now      = nm_utils_get_monotonic_timestamp_nsec();
+        boottime = nm_utils_monotonic_timestamp_as_boottime(now, 1);
+
+        sd_journal_send("PRIORITY=%d",
+                        syslog_priority,
+                        "MESSAGE=%s%s",
+                        gl.imm.prefix,
+                        message ?: "",
+                        syslog_identifier_full(gl.imm.syslog_identifier),
+                        "SYSLOG_PID=%ld",
+                        (long) getpid(),
+                        "SYSLOG_FACILITY=3",
+                        "GLIB_DOMAIN=%s",
+                        log_domain ?: "",
+                        "GLIB_LEVEL=%d",
+                        (int) (level & G_LOG_LEVEL_MASK),
+                        "TIMESTAMP_MONOTONIC=%lld.%06lld",
+                        (long long) (now / NM_UTILS_NSEC_PER_SEC),
+                        (long long) ((now % NM_UTILS_NSEC_PER_SEC) / 1000),
+                        "TIMESTAMP_BOOTTIME=%lld.%06lld",
+                        (long long) (boottime / NM_UTILS_NSEC_PER_SEC),
+                        (long long) ((boottime % NM_UTILS_NSEC_PER_SEC) / 1000),
+                        NULL);
+    } break;
+#endif
+    default:
+        syslog(syslog_priority, "%s%s", gl.imm.prefix, message ?: "");
+        break;
+    }
+}
+
+gboolean
+nm_logging_syslog_enabled(void)
+{
+    NM_ASSERT_ON_MAIN_THREAD();
+
+    return gl.imm.uses_syslog;
+}
+
+void
+nm_logging_init_pre(const char *syslog_identifier, char *prefix_take)
+{
+    /* this function may be called zero or one times, and only
+     * - on the main thread
+     * - not after nm_logging_init(). */
+
+    NM_ASSERT_ON_MAIN_THREAD();
+
+    if (gl.imm.init_pre_done)
+        g_return_if_reached();
+
+    if (gl.imm.init_done)
+        g_return_if_reached();
+
+    if (!_syslog_identifier_valid_domain(syslog_identifier))
+        g_return_if_reached();
+
+    if (!prefix_take || !prefix_take[0])
+        g_return_if_reached();
+
+    G_LOCK(log);
+
+    gl.mut.init_pre_done = TRUE;
+
+    gl.mut.syslog_identifier = g_strdup_printf("SYSLOG_IDENTIFIER=%s", syslog_identifier);
+    nm_assert(_syslog_identifier_assert(gl.imm.syslog_identifier));
+
+    /* we pass the allocated string on and never free it. */
+    gl.mut.prefix = prefix_take;
+
+    G_UNLOCK(log);
+}
+
+void
+nm_logging_init(const char *logging_backend, gboolean debug)
+{
+    gboolean   fetch_monotonic_timestamp = FALSE;
+    gboolean   obsolete_debug_backend    = FALSE;
+    LogBackend x_log_backend;
+
+    /* this function may be called zero or one times, and only on the
+     * main thread. */
+
+    NM_ASSERT_ON_MAIN_THREAD();
+
+    nm_assert(NM_IN_STRSET("" NM_CONFIG_DEFAULT_LOGGING_BACKEND,
+                           NM_LOG_CONFIG_BACKEND_JOURNAL,
+                           NM_LOG_CONFIG_BACKEND_SYSLOG));
+
+    if (gl.imm.init_done)
+        g_return_if_reached();
+
+    if (!logging_backend)
+        logging_backend = "" NM_CONFIG_DEFAULT_LOGGING_BACKEND;
+
+    if (nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_DEBUG)) {
+        /* "debug" was wrongly documented as a valid logging backend. It makes no sense however,
+         * because printing to stderr only makes sense when not demonizing. Whether to daemonize
+         * is only controlled via command line arguments (--no-daemon, --debug) and not via the
+         * logging backend from configuration.
+         *
+         * Fall back to the default. */
+        logging_backend        = "" NM_CONFIG_DEFAULT_LOGGING_BACKEND;
+        obsolete_debug_backend = TRUE;
+    }
+
+    G_LOCK(log);
+
+#if SYSTEMD_JOURNAL
+    if (!nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_SYSLOG)) {
+        x_log_backend = LOG_BACKEND_JOURNAL;
+
+        /* We only log the monotonic-timestamp with structured logging (journal).
+         * Only in this case, fetch the timestamp. */
+        fetch_monotonic_timestamp = TRUE;
+    } else
+#endif
+    {
+        x_log_backend = LOG_BACKEND_SYSLOG;
+        openlog(syslog_identifier_domain(gl.imm.syslog_identifier), LOG_PID, LOG_DAEMON);
+    }
+
+    gl.mut.init_done    = TRUE;
+    gl.mut.log_backend  = x_log_backend;
+    gl.mut.uses_syslog  = TRUE;
+    gl.mut.debug_stderr = debug;
+
+    g_log_set_handler(syslog_identifier_domain(gl.imm.syslog_identifier),
+                      G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
+                      nm_log_handler,
+                      NULL);
+
+    G_UNLOCK(log);
+
+    if (fetch_monotonic_timestamp) {
+        /* ensure we read a monotonic timestamp. Reading the timestamp the first
+         * time causes a logging message. We don't want to do that during _nm_log_impl. */
+        nm_utils_get_monotonic_timestamp_nsec();
+    }
+
+    if (obsolete_debug_backend)
+        nm_log_dbg(LOGD_CORE,
+                   "config: ignore deprecated logging backend 'debug', fallback to '%s'",
+                   logging_backend);
+
+    if (nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_SYSLOG)) {
+        /* good */
+    } else if (nm_streq(logging_backend, NM_LOG_CONFIG_BACKEND_JOURNAL)) {
+#if !SYSTEMD_JOURNAL
+        nm_log_warn(LOGD_CORE,
+                    "config: logging backend 'journal' is not available, fallback to 'syslog'");
+#endif
+    } else {
+        nm_log_warn(LOGD_CORE,
+                    "config: invalid logging backend '%s', fallback to '%s'",
+                    logging_backend,
+#if SYSTEMD_JOURNAL
+                    NM_LOG_CONFIG_BACKEND_JOURNAL
+#else
+                    NM_LOG_CONFIG_BACKEND_SYSLOG
+#endif
+        );
+    }
+}
diff --git a/shared/nm-log-core/nm-logging.h b/shared/nm-log-core/nm-logging.h
new file mode 100644
index 00000000..d3143d39
--- /dev/null
+++ b/shared/nm-log-core/nm-logging.h
@@ -0,0 +1,189 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2006 - 2012 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_LOGGING_H__
+#define __NETWORKMANAGER_LOGGING_H__
+
+#ifdef __NM_TEST_UTILS_H__
+    #error nm-test-utils.h must be included as last header
+#endif
+
+#include "nm-glib-aux/nm-logging-fwd.h"
+
+#define NM_LOG_CONFIG_BACKEND_DEBUG   "debug"
+#define NM_LOG_CONFIG_BACKEND_SYSLOG  "syslog"
+#define NM_LOG_CONFIG_BACKEND_JOURNAL "journal"
+
+#define nm_log_err(domain, ...)   nm_log(LOGL_ERR, (domain), NULL, NULL, __VA_ARGS__)
+#define nm_log_warn(domain, ...)  nm_log(LOGL_WARN, (domain), NULL, NULL, __VA_ARGS__)
+#define nm_log_info(domain, ...)  nm_log(LOGL_INFO, (domain), NULL, NULL, __VA_ARGS__)
+#define nm_log_dbg(domain, ...)   nm_log(LOGL_DEBUG, (domain), NULL, NULL, __VA_ARGS__)
+#define nm_log_trace(domain, ...) nm_log(LOGL_TRACE, (domain), NULL, NULL, __VA_ARGS__)
+
+//#define _NM_LOG_FUNC G_STRFUNC
+#define _NM_LOG_FUNC NULL
+
+/* A wrapper for the _nm_log_impl() function that adds call site information.
+ * Contrary to nm_log(), it unconditionally calls the function without
+ * checking whether logging for the given level and domain is enabled. */
+#define _nm_log_mt(mt_require_locking, level, domain, error, ifname, con_uuid, ...) \
+    G_STMT_START                                                                    \
+    {                                                                               \
+        _nm_log_impl(__FILE__,                                                      \
+                     __LINE__,                                                      \
+                     _NM_LOG_FUNC,                                                  \
+                     (mt_require_locking),                                          \
+                     (level),                                                       \
+                     (domain),                                                      \
+                     (error),                                                       \
+                     (ifname),                                                      \
+                     (con_uuid),                                                    \
+                     ""__VA_ARGS__);                                                \
+    }                                                                               \
+    G_STMT_END
+
+#define _nm_log(level, domain, error, ifname, con_uuid, ...) \
+    _nm_log_mt(!(NM_THREAD_SAFE_ON_MAIN_THREAD),             \
+               level,                                        \
+               domain,                                       \
+               error,                                        \
+               ifname,                                       \
+               con_uuid,                                     \
+               __VA_ARGS__)
+
+/* nm_log() only evaluates its argument list after checking
+ * whether logging for the given level/domain is enabled.  */
+#define nm_log(level, domain, ifname, con_uuid, ...)                  \
+    G_STMT_START                                                      \
+    {                                                                 \
+        if (nm_logging_enabled((level), (domain))) {                  \
+            _nm_log(level, domain, 0, ifname, con_uuid, __VA_ARGS__); \
+        }                                                             \
+    }                                                                 \
+    G_STMT_END
+
+#define _nm_log_ptr(level, domain, ifname, con_uuid, self, prefix, ...)             \
+    nm_log((level),                                                                 \
+           (domain),                                                                \
+           (ifname),                                                                \
+           (con_uuid),                                                              \
+           "%s[" NM_HASH_OBFUSCATE_PTR_FMT "] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+           (prefix) ?: "",                                                          \
+           NM_HASH_OBFUSCATE_PTR(self) _NM_UTILS_MACRO_REST(__VA_ARGS__))
+
+static inline gboolean
+_nm_log_ptr_is_debug(NMLogLevel level)
+{
+    return level <= LOGL_DEBUG;
+}
+
+/* log a message for an object (with providing a generic @self pointer) */
+#define nm_log_ptr(level, domain, ifname, con_uuid, self, prefix, ...)                           \
+    G_STMT_START                                                                                 \
+    {                                                                                            \
+        if (_nm_log_ptr_is_debug(level)) {                                                       \
+            _nm_log_ptr((level), (domain), (ifname), (con_uuid), (self), (prefix), __VA_ARGS__); \
+        } else {                                                                                 \
+            const char *__prefix = (prefix);                                                     \
+                                                                                                 \
+            nm_log((level),                                                                      \
+                   (domain),                                                                     \
+                   (ifname),                                                                     \
+                   (con_uuid),                                                                   \
+                   "%s%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                                    \
+                   __prefix ?: "",                                                               \
+                   __prefix ? " " : "" _NM_UTILS_MACRO_REST(__VA_ARGS__));                       \
+        }                                                                                        \
+    }                                                                                            \
+    G_STMT_END
+
+#define _nm_log_obj(level, domain, ifname, con_uuid, self, prefix, ...) \
+    _nm_log_ptr((level), (domain), (ifname), (con_uuid), (self), prefix, __VA_ARGS__)
+
+/* log a message for an object (with providing a @self pointer to a GObject).
+ * Contrary to nm_log_ptr(), @self must be a GObject type (or %NULL).
+ * As of now, nm_log_obj() is identical to nm_log_ptr(), but we might change that */
+#define nm_log_obj(level, domain, ifname, con_uuid, self, prefix, ...) \
+    nm_log_ptr((level), (domain), (ifname), (con_uuid), (self), prefix, __VA_ARGS__)
+
+const char *nm_logging_level_to_string(void);
+const char *nm_logging_domains_to_string(void);
+
+/*****************************************************************************/
+
+extern NMLogDomain _nm_logging_enabled_state[_LOGL_N_REAL];
+
+static inline gboolean
+_nm_logging_enabled_lockfree(NMLogLevel level, NMLogDomain domain)
+{
+    nm_assert(((guint) level) < G_N_ELEMENTS(_nm_logging_enabled_state));
+    return (((guint) level) < G_N_ELEMENTS(_nm_logging_enabled_state))
+           && !!(_nm_logging_enabled_state[level] & domain);
+}
+
+gboolean _nm_logging_enabled_locking(NMLogLevel level, NMLogDomain domain);
+
+static inline gboolean
+nm_logging_enabled_mt(gboolean mt_require_locking, NMLogLevel level, NMLogDomain domain)
+{
+    if (mt_require_locking)
+        return _nm_logging_enabled_locking(level, domain);
+
+    NM_ASSERT_ON_MAIN_THREAD();
+    return _nm_logging_enabled_lockfree(level, domain);
+}
+
+#define nm_logging_enabled(level, domain) \
+    nm_logging_enabled_mt(!(NM_THREAD_SAFE_ON_MAIN_THREAD), level, domain)
+
+/*****************************************************************************/
+
+NMLogLevel nm_logging_get_level(NMLogDomain domain);
+
+const char *nm_logging_all_levels_to_string(void);
+const char *nm_logging_all_domains_to_string(void);
+
+gboolean
+nm_logging_setup(const char *level, const char *domains, char **bad_domains, GError **error);
+
+void nm_logging_init_pre(const char *syslog_identifier, char *prefix_take);
+
+void nm_logging_init(const char *logging_backend, gboolean debug);
+
+gboolean nm_logging_syslog_enabled(void);
+
+/*****************************************************************************/
+
+#define __NMLOG_DEFAULT(level, domain, prefix, ...)         \
+    G_STMT_START                                            \
+    {                                                       \
+        nm_log((level),                                     \
+               (domain),                                    \
+               NULL,                                        \
+               NULL,                                        \
+               "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),   \
+               (prefix) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    }                                                       \
+    G_STMT_END
+
+#define __NMLOG_DEFAULT_WITH_ADDR(level, domain, prefix, ...)                            \
+    G_STMT_START                                                                         \
+    {                                                                                    \
+        nm_log((level),                                                                  \
+               (domain),                                                                 \
+               NULL,                                                                     \
+               NULL,                                                                     \
+               "%s[" NM_HASH_OBFUSCATE_PTR_FMT "]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+               (prefix),                                                                 \
+               NM_HASH_OBFUSCATE_PTR(self) _NM_UTILS_MACRO_REST(__VA_ARGS__));           \
+    }                                                                                    \
+    G_STMT_END
+
+/*****************************************************************************/
+
+extern void _nm_logging_clear_platform_logging_cache(void);
+
+#endif /* __NETWORKMANAGER_LOGGING_H__ */
diff --git a/shared/nm-meta-setting.c b/shared/nm-meta-setting.c
deleted file mode 100644
index 51b90e6f..00000000
--- a/shared/nm-meta-setting.c
+++ /dev/null
@@ -1,592 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-/*
- * Copyright (C) 2017 - 2018 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-meta-setting.h"
-
-#include "nm-setting-6lowpan.h"
-#include "nm-setting-8021x.h"
-#include "nm-setting-adsl.h"
-#include "nm-setting-bluetooth.h"
-#include "nm-setting-bond.h"
-#include "nm-setting-bridge-port.h"
-#include "nm-setting-bridge.h"
-#include "nm-setting-cdma.h"
-#include "nm-setting-connection.h"
-#include "nm-setting-dcb.h"
-#include "nm-setting-dummy.h"
-#include "nm-setting-ethtool.h"
-#include "nm-setting-generic.h"
-#include "nm-setting-gsm.h"
-#include "nm-setting-infiniband.h"
-#include "nm-setting-ip-config.h"
-#include "nm-setting-ip-tunnel.h"
-#include "nm-setting-ip4-config.h"
-#include "nm-setting-ip6-config.h"
-#include "nm-setting-macsec.h"
-#include "nm-setting-macvlan.h"
-#include "nm-setting-match.h"
-#include "nm-setting-olpc-mesh.h"
-#include "nm-setting-ovs-bridge.h"
-#include "nm-setting-ovs-interface.h"
-#include "nm-setting-ovs-dpdk.h"
-#include "nm-setting-ovs-patch.h"
-#include "nm-setting-ovs-port.h"
-#include "nm-setting-ppp.h"
-#include "nm-setting-pppoe.h"
-#include "nm-setting-proxy.h"
-#include "nm-setting-serial.h"
-#include "nm-setting-tc-config.h"
-#include "nm-setting-team-port.h"
-#include "nm-setting-team.h"
-#include "nm-setting-tun.h"
-#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"
-#include "nm-setting-wired.h"
-#include "nm-setting-wireguard.h"
-#include "nm-setting-wireless-security.h"
-#include "nm-setting-wireless.h"
-#include "nm-setting-wpan.h"
-
-/*****************************************************************************/
-
-const NMSetting8021xSchemeVtable nm_setting_8021x_scheme_vtable[] = {
-
-#define _D(_scheme_type, ...) [(_scheme_type)] = {.scheme_type = (_scheme_type), __VA_ARGS__}
-
-    _D(NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN),
-
-    _D(NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT,
-       .setting_key   = NM_SETTING_802_1X_CA_CERT,
-       .scheme_func   = nm_setting_802_1x_get_ca_cert_scheme,
-       .format_func   = NULL,
-       .path_func     = nm_setting_802_1x_get_ca_cert_path,
-       .blob_func     = nm_setting_802_1x_get_ca_cert_blob,
-       .uri_func      = nm_setting_802_1x_get_ca_cert_uri,
-       .passwd_func   = nm_setting_802_1x_get_ca_cert_password,
-       .pwflag_func   = nm_setting_802_1x_get_ca_cert_password_flags,
-       .set_cert_func = nm_setting_802_1x_set_ca_cert,
-       .file_suffix   = "ca-cert", ),
-
-    _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT,
-       .setting_key   = NM_SETTING_802_1X_PHASE2_CA_CERT,
-       .scheme_func   = nm_setting_802_1x_get_phase2_ca_cert_scheme,
-       .format_func   = NULL,
-       .path_func     = nm_setting_802_1x_get_phase2_ca_cert_path,
-       .blob_func     = nm_setting_802_1x_get_phase2_ca_cert_blob,
-       .uri_func      = nm_setting_802_1x_get_phase2_ca_cert_uri,
-       .passwd_func   = nm_setting_802_1x_get_phase2_ca_cert_password,
-       .pwflag_func   = nm_setting_802_1x_get_phase2_ca_cert_password_flags,
-       .set_cert_func = nm_setting_802_1x_set_phase2_ca_cert,
-       .file_suffix   = "inner-ca-cert", ),
-
-    _D(NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT,
-       .setting_key   = NM_SETTING_802_1X_CLIENT_CERT,
-       .scheme_func   = nm_setting_802_1x_get_client_cert_scheme,
-       .format_func   = NULL,
-       .path_func     = nm_setting_802_1x_get_client_cert_path,
-       .blob_func     = nm_setting_802_1x_get_client_cert_blob,
-       .uri_func      = nm_setting_802_1x_get_client_cert_uri,
-       .passwd_func   = nm_setting_802_1x_get_client_cert_password,
-       .pwflag_func   = nm_setting_802_1x_get_client_cert_password_flags,
-       .set_cert_func = nm_setting_802_1x_set_client_cert,
-       .file_suffix   = "client-cert", ),
-
-    _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT,
-       .setting_key   = NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
-       .scheme_func   = nm_setting_802_1x_get_phase2_client_cert_scheme,
-       .format_func   = NULL,
-       .path_func     = nm_setting_802_1x_get_phase2_client_cert_path,
-       .blob_func     = nm_setting_802_1x_get_phase2_client_cert_blob,
-       .uri_func      = nm_setting_802_1x_get_phase2_client_cert_uri,
-       .passwd_func   = nm_setting_802_1x_get_phase2_client_cert_password,
-       .pwflag_func   = nm_setting_802_1x_get_phase2_client_cert_password_flags,
-       .set_cert_func = nm_setting_802_1x_set_phase2_client_cert,
-       .file_suffix   = "inner-client-cert", ),
-
-    _D(NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY,
-       .setting_key          = NM_SETTING_802_1X_PRIVATE_KEY,
-       .scheme_func          = nm_setting_802_1x_get_private_key_scheme,
-       .format_func          = nm_setting_802_1x_get_private_key_format,
-       .path_func            = nm_setting_802_1x_get_private_key_path,
-       .blob_func            = nm_setting_802_1x_get_private_key_blob,
-       .uri_func             = nm_setting_802_1x_get_private_key_uri,
-       .passwd_func          = nm_setting_802_1x_get_private_key_password,
-       .pwflag_func          = nm_setting_802_1x_get_private_key_password_flags,
-       .set_private_key_func = nm_setting_802_1x_set_private_key,
-       .file_suffix          = "private-key",
-       .is_secret            = TRUE, ),
-
-    _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY,
-       .setting_key          = NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
-       .scheme_func          = nm_setting_802_1x_get_phase2_private_key_scheme,
-       .format_func          = nm_setting_802_1x_get_phase2_private_key_format,
-       .path_func            = nm_setting_802_1x_get_phase2_private_key_path,
-       .blob_func            = nm_setting_802_1x_get_phase2_private_key_blob,
-       .uri_func             = nm_setting_802_1x_get_phase2_private_key_uri,
-       .passwd_func          = nm_setting_802_1x_get_phase2_private_key_password,
-       .pwflag_func          = nm_setting_802_1x_get_phase2_private_key_password_flags,
-       .set_private_key_func = nm_setting_802_1x_set_phase2_private_key,
-       .file_suffix          = "inner-private-key",
-       .is_secret            = TRUE, ),
-
-#undef _D
-};
-
-/*****************************************************************************/
-
-const NMMetaSettingInfo nm_meta_setting_infos[] = {
-    [NM_META_SETTING_TYPE_6LOWPAN] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_6LOWPAN,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_6LOWPAN_SETTING_NAME,
-            .get_setting_gtype = nm_setting_6lowpan_get_type,
-        },
-    [NM_META_SETTING_TYPE_802_1X] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_802_1X,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_AUX,
-            .setting_name      = NM_SETTING_802_1X_SETTING_NAME,
-            .get_setting_gtype = nm_setting_802_1x_get_type,
-        },
-    [NM_META_SETTING_TYPE_ADSL] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_ADSL,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_ADSL_SETTING_NAME,
-            .get_setting_gtype = nm_setting_adsl_get_type,
-        },
-    [NM_META_SETTING_TYPE_BLUETOOTH] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_BLUETOOTH,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_NON_BASE,
-            .setting_name      = NM_SETTING_BLUETOOTH_SETTING_NAME,
-            .get_setting_gtype = nm_setting_bluetooth_get_type,
-        },
-    [NM_META_SETTING_TYPE_BOND] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_BOND,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_BOND_SETTING_NAME,
-            .get_setting_gtype = nm_setting_bond_get_type,
-        },
-    [NM_META_SETTING_TYPE_BRIDGE] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_BRIDGE,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_BRIDGE_SETTING_NAME,
-            .get_setting_gtype = nm_setting_bridge_get_type,
-        },
-    [NM_META_SETTING_TYPE_BRIDGE_PORT] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_BRIDGE_PORT,
-            .setting_priority  = NM_SETTING_PRIORITY_AUX,
-            .setting_name      = NM_SETTING_BRIDGE_PORT_SETTING_NAME,
-            .get_setting_gtype = nm_setting_bridge_port_get_type,
-        },
-    [NM_META_SETTING_TYPE_CDMA] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_CDMA,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_CDMA_SETTING_NAME,
-            .get_setting_gtype = nm_setting_cdma_get_type,
-        },
-    [NM_META_SETTING_TYPE_CONNECTION] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_CONNECTION,
-            .setting_priority  = NM_SETTING_PRIORITY_CONNECTION,
-            .setting_name      = NM_SETTING_CONNECTION_SETTING_NAME,
-            .get_setting_gtype = nm_setting_connection_get_type,
-        },
-    [NM_META_SETTING_TYPE_DCB] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_DCB,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_AUX,
-            .setting_name      = NM_SETTING_DCB_SETTING_NAME,
-            .get_setting_gtype = nm_setting_dcb_get_type,
-        },
-    [NM_META_SETTING_TYPE_DUMMY] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_DUMMY,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_DUMMY_SETTING_NAME,
-            .get_setting_gtype = nm_setting_dummy_get_type,
-        },
-    [NM_META_SETTING_TYPE_ETHTOOL] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_ETHTOOL,
-            .setting_priority  = NM_SETTING_PRIORITY_AUX,
-            .setting_name      = NM_SETTING_ETHTOOL_SETTING_NAME,
-            .get_setting_gtype = nm_setting_ethtool_get_type,
-        },
-    [NM_META_SETTING_TYPE_GENERIC] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_GENERIC,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_GENERIC_SETTING_NAME,
-            .get_setting_gtype = nm_setting_generic_get_type,
-        },
-    [NM_META_SETTING_TYPE_GSM] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_GSM,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_GSM_SETTING_NAME,
-            .get_setting_gtype = nm_setting_gsm_get_type,
-        },
-    [NM_META_SETTING_TYPE_INFINIBAND] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_INFINIBAND,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_INFINIBAND_SETTING_NAME,
-            .get_setting_gtype = nm_setting_infiniband_get_type,
-        },
-    [NM_META_SETTING_TYPE_IP4_CONFIG] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_IP4_CONFIG,
-            .setting_priority  = NM_SETTING_PRIORITY_IP,
-            .setting_name      = NM_SETTING_IP4_CONFIG_SETTING_NAME,
-            .get_setting_gtype = nm_setting_ip4_config_get_type,
-        },
-    [NM_META_SETTING_TYPE_IP6_CONFIG] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_IP6_CONFIG,
-            .setting_priority  = NM_SETTING_PRIORITY_IP,
-            .setting_name      = NM_SETTING_IP6_CONFIG_SETTING_NAME,
-            .get_setting_gtype = nm_setting_ip6_config_get_type,
-        },
-    [NM_META_SETTING_TYPE_IP_TUNNEL] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_IP_TUNNEL,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_IP_TUNNEL_SETTING_NAME,
-            .get_setting_gtype = nm_setting_ip_tunnel_get_type,
-        },
-    [NM_META_SETTING_TYPE_MACSEC] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_MACSEC,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_MACSEC_SETTING_NAME,
-            .get_setting_gtype = nm_setting_macsec_get_type,
-        },
-    [NM_META_SETTING_TYPE_MACVLAN] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_MACVLAN,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_MACVLAN_SETTING_NAME,
-            .get_setting_gtype = nm_setting_macvlan_get_type,
-        },
-    [NM_META_SETTING_TYPE_MATCH] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_MATCH,
-            .setting_priority  = NM_SETTING_PRIORITY_AUX,
-            .setting_name      = NM_SETTING_MATCH_SETTING_NAME,
-            .get_setting_gtype = nm_setting_match_get_type,
-        },
-    [NM_META_SETTING_TYPE_OLPC_MESH] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_OLPC_MESH,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_OLPC_MESH_SETTING_NAME,
-            .get_setting_gtype = nm_setting_olpc_mesh_get_type,
-        },
-    [NM_META_SETTING_TYPE_OVS_BRIDGE] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_OVS_BRIDGE,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_OVS_BRIDGE_SETTING_NAME,
-            .get_setting_gtype = nm_setting_ovs_bridge_get_type,
-        },
-    [NM_META_SETTING_TYPE_OVS_DPDK] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_OVS_DPDK,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_OVS_DPDK_SETTING_NAME,
-            .get_setting_gtype = nm_setting_ovs_dpdk_get_type,
-        },
-    [NM_META_SETTING_TYPE_OVS_INTERFACE] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_OVS_INTERFACE,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_OVS_INTERFACE_SETTING_NAME,
-            .get_setting_gtype = nm_setting_ovs_interface_get_type,
-        },
-    [NM_META_SETTING_TYPE_OVS_PATCH] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_OVS_PATCH,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_OVS_PATCH_SETTING_NAME,
-            .get_setting_gtype = nm_setting_ovs_patch_get_type,
-        },
-    [NM_META_SETTING_TYPE_OVS_PORT] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_OVS_PORT,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_OVS_PORT_SETTING_NAME,
-            .get_setting_gtype = nm_setting_ovs_port_get_type,
-        },
-    [NM_META_SETTING_TYPE_PPPOE] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_PPPOE,
-            .setting_priority  = NM_SETTING_PRIORITY_AUX,
-            .setting_name      = NM_SETTING_PPPOE_SETTING_NAME,
-            .get_setting_gtype = nm_setting_pppoe_get_type,
-        },
-    [NM_META_SETTING_TYPE_PPP] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_PPP,
-            .setting_priority  = NM_SETTING_PRIORITY_AUX,
-            .setting_name      = NM_SETTING_PPP_SETTING_NAME,
-            .get_setting_gtype = nm_setting_ppp_get_type,
-        },
-    [NM_META_SETTING_TYPE_PROXY] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_PROXY,
-            .setting_priority  = NM_SETTING_PRIORITY_IP,
-            .setting_name      = NM_SETTING_PROXY_SETTING_NAME,
-            .get_setting_gtype = nm_setting_proxy_get_type,
-        },
-    [NM_META_SETTING_TYPE_SERIAL] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_SERIAL,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_AUX,
-            .setting_name      = NM_SETTING_SERIAL_SETTING_NAME,
-            .get_setting_gtype = nm_setting_serial_get_type,
-        },
-    [NM_META_SETTING_TYPE_SRIOV] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_SRIOV,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_AUX,
-            .setting_name      = NM_SETTING_SRIOV_SETTING_NAME,
-            .get_setting_gtype = nm_setting_sriov_get_type,
-        },
-    [NM_META_SETTING_TYPE_TC_CONFIG] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_TC_CONFIG,
-            .setting_priority  = NM_SETTING_PRIORITY_IP,
-            .setting_name      = NM_SETTING_TC_CONFIG_SETTING_NAME,
-            .get_setting_gtype = nm_setting_tc_config_get_type,
-        },
-    [NM_META_SETTING_TYPE_TEAM] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_TEAM,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_TEAM_SETTING_NAME,
-            .get_setting_gtype = nm_setting_team_get_type,
-        },
-    [NM_META_SETTING_TYPE_TEAM_PORT] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_TEAM_PORT,
-            .setting_priority  = NM_SETTING_PRIORITY_AUX,
-            .setting_name      = NM_SETTING_TEAM_PORT_SETTING_NAME,
-            .get_setting_gtype = nm_setting_team_port_get_type,
-        },
-    [NM_META_SETTING_TYPE_TUN] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_TUN,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_TUN_SETTING_NAME,
-            .get_setting_gtype = nm_setting_tun_get_type,
-        },
-    [NM_META_SETTING_TYPE_USER] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_USER,
-            .setting_priority  = NM_SETTING_PRIORITY_USER,
-            .setting_name      = NM_SETTING_USER_SETTING_NAME,
-            .get_setting_gtype = nm_setting_user_get_type,
-        },
-    [NM_META_SETTING_TYPE_VLAN] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_VLAN,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_VLAN_SETTING_NAME,
-            .get_setting_gtype = nm_setting_vlan_get_type,
-        },
-    [NM_META_SETTING_TYPE_VPN] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_VPN,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .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,
-            .setting_name      = NM_SETTING_VXLAN_SETTING_NAME,
-            .get_setting_gtype = nm_setting_vxlan_get_type,
-        },
-    [NM_META_SETTING_TYPE_WIFI_P2P] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_WIFI_P2P,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_WIFI_P2P_SETTING_NAME,
-            .get_setting_gtype = nm_setting_wifi_p2p_get_type,
-        },
-    [NM_META_SETTING_TYPE_WIMAX] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_WIMAX,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_WIMAX_SETTING_NAME,
-            .get_setting_gtype = nm_setting_wimax_get_type,
-        },
-    [NM_META_SETTING_TYPE_WIRED] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_WIRED,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_WIRED_SETTING_NAME,
-            .get_setting_gtype = nm_setting_wired_get_type,
-        },
-    [NM_META_SETTING_TYPE_WIREGUARD] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_WIREGUARD,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_WIREGUARD_SETTING_NAME,
-            .get_setting_gtype = nm_setting_wireguard_get_type,
-        },
-    [NM_META_SETTING_TYPE_WIRELESS] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_WIRELESS,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_WIRELESS_SETTING_NAME,
-            .get_setting_gtype = nm_setting_wireless_get_type,
-        },
-    [NM_META_SETTING_TYPE_WIRELESS_SECURITY] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_WIRELESS_SECURITY,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_AUX,
-            .setting_name      = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-            .get_setting_gtype = nm_setting_wireless_security_get_type,
-        },
-    [NM_META_SETTING_TYPE_WPAN] =
-        {
-            .meta_type         = NM_META_SETTING_TYPE_WPAN,
-            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
-            .setting_name      = NM_SETTING_WPAN_SETTING_NAME,
-            .get_setting_gtype = nm_setting_wpan_get_type,
-        },
-
-    [NM_META_SETTING_TYPE_UNKNOWN] =
-        {
-            .meta_type = NM_META_SETTING_TYPE_UNKNOWN,
-        },
-};
-
-const NMMetaSettingInfo *
-nm_meta_setting_infos_by_name(const char *name)
-{
-    gssize idx;
-
-#if NM_MORE_ASSERTS > 10
-    {
-        guint i, j;
-
-        for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) {
-            const NMMetaSettingInfo *setting_info = &nm_meta_setting_infos[i];
-
-            nm_assert(setting_info->meta_type == (NMMetaSettingType) i);
-            nm_assert(setting_info->setting_name);
-            nm_assert(setting_info->setting_name[0]);
-            nm_assert(setting_info->get_setting_gtype);
-            nm_assert(setting_info->setting_priority != NM_SETTING_PRIORITY_INVALID);
-            if (i > 0
-                && strcmp(nm_meta_setting_infos[i - 1].setting_name, setting_info->setting_name)
-                       >= 0) {
-                g_error("nm_meta_setting_infos[%u, \"%s\"] is wrongly sorted before "
-                        "nm_meta_setting_infos[%u, \"%s\"]. Rearange NMMetaSettingType enum",
-                        i - 1,
-                        nm_meta_setting_infos[i - 1].setting_name,
-                        i,
-                        setting_info->setting_name);
-            }
-            for (j = 0; j < i; j++) {
-                const NMMetaSettingInfo *s = &nm_meta_setting_infos[j];
-
-                nm_assert(setting_info->get_setting_gtype != s->get_setting_gtype);
-            }
-        }
-    }
-#endif
-
-    G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMMetaSettingInfo, setting_name) == 0);
-    idx = nm_utils_array_find_binary_search(nm_meta_setting_infos,
-                                            sizeof(NMMetaSettingInfo),
-                                            _NM_META_SETTING_TYPE_NUM,
-                                            &name,
-                                            nm_strcmp_p_with_data,
-                                            NULL);
-
-    return idx >= 0 ? &nm_meta_setting_infos[idx] : NULL;
-}
-
-const NMMetaSettingInfo *
-nm_meta_setting_infos_by_gtype(GType gtype)
-{
-#if ((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)
-    nm_auto_unref_gtypeclass GTypeClass *gtypeclass_unref = NULL;
-    GTypeClass *                         gtypeclass;
-    NMSettingClass *                     klass;
-
-    if (!g_type_is_a(gtype, NM_TYPE_SETTING))
-        goto out_none;
-
-    gtypeclass = g_type_class_peek(gtype);
-    if (!gtypeclass)
-        gtypeclass = gtypeclass_unref = g_type_class_ref(gtype);
-
-    nm_assert(NM_IS_SETTING_CLASS(gtypeclass));
-
-    klass = (NMSettingClass *) gtypeclass;
-
-    if (!klass->setting_info)
-        goto out_none;
-
-    nm_assert(klass->setting_info->get_setting_gtype);
-    nm_assert(klass->setting_info->get_setting_gtype() == gtype);
-
-    return klass->setting_info;
-
-out_none:
-
-    #if NM_MORE_ASSERTS > 10
-{
-    int i;
-
-    /* this might hint to a bug, but it would be expected for NM_TYPE_SETTING
-         * and NM_TYPE_SETTING_IP_CONFIG.
-         *
-         * Assert that we didn't lookup for a gtype, which we would expect to find.
-         * An assertion failure here, hints to a bug in nm_setting_*_class_init().
-         */
-    for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++)
-        nm_assert(nm_meta_setting_infos[i].get_setting_gtype() != gtype);
-}
-    #endif
-    return NULL;
-#else
-    guint i;
-
-    for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) {
-        if (nm_meta_setting_infos[i].get_setting_gtype() == gtype)
-            return &nm_meta_setting_infos[i];
-    }
-    return NULL;
-#endif
-}
-
-/*****************************************************************************/
diff --git a/shared/nm-meta-setting.h b/shared/nm-meta-setting.h
deleted file mode 100644
index dadf2f72..00000000
--- a/shared/nm-meta-setting.h
+++ /dev/null
@@ -1,209 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-/*
- * Copyright (C) 2017 - 2018 Red Hat, Inc.
- */
-
-#ifndef __NM_META_SETTING_H__
-#define __NM_META_SETTING_H__
-
-#include "nm-setting-8021x.h"
-
-/*****************************************************************************/
-
-/*
- * A setting's priority should roughly follow the OSI layer model, but it also
- * controls which settings get asked for secrets first.  Thus settings which
- * relate to things that must be working first, like hardware, should get a
- * higher priority than things which layer on top of the hardware.  For example,
- * the GSM/CDMA settings should provide secrets before the PPP setting does,
- * because a PIN is required to unlock the device before PPP can even start.
- * Even settings without secrets should be assigned the right priority.
- *
- * 0: reserved for invalid
- *
- * 1: reserved for the Connection setting
- *
- * 2,3: hardware-related settings like Ethernet, Wi-Fi, InfiniBand, Bridge, etc.
- * These priority 1 settings are also "base types", which means that at least
- * one of them is required for the connection to be valid, and their name is
- * valid in the 'type' property of the Connection setting.
- *
- * 4: hardware-related auxiliary settings that require a base setting to be
- * successful first, like Wi-Fi security, 802.1x, etc.
- *
- * 5: hardware-independent settings that are required before IP connectivity
- * can be established, like PPP, PPPoE, etc.
- *
- * 6: IP-level stuff
- *
- * 10: NMSettingUser
- */
-typedef enum { /*< skip >*/
-               NM_SETTING_PRIORITY_INVALID     = 0,
-               NM_SETTING_PRIORITY_CONNECTION  = 1,
-               NM_SETTING_PRIORITY_HW_BASE     = 2,
-               NM_SETTING_PRIORITY_HW_NON_BASE = 3,
-               NM_SETTING_PRIORITY_HW_AUX      = 4,
-               NM_SETTING_PRIORITY_AUX         = 5,
-               NM_SETTING_PRIORITY_IP          = 6,
-               NM_SETTING_PRIORITY_USER        = 10,
-} NMSettingPriority;
-
-/*****************************************************************************/
-
-typedef enum {
-    NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT,
-    NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT,
-    NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT,
-    NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT,
-    NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY,
-    NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY,
-
-    NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN,
-
-    _NM_SETTING_802_1X_SCHEME_TYPE_NUM = NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN,
-} NMSetting8021xSchemeType;
-
-typedef struct {
-    const char *setting_key;
-    NMSetting8021xCKScheme (*scheme_func)(NMSetting8021x *setting);
-    NMSetting8021xCKFormat (*format_func)(NMSetting8021x *setting);
-    const char *(*path_func)(NMSetting8021x *setting);
-    GBytes *(*blob_func)(NMSetting8021x *setting);
-    const char *(*uri_func)(NMSetting8021x *setting);
-    const char *(*passwd_func)(NMSetting8021x *setting);
-    NMSettingSecretFlags (*pwflag_func)(NMSetting8021x *setting);
-    gboolean (*set_cert_func)(NMSetting8021x *        setting,
-                              const char *            value,
-                              NMSetting8021xCKScheme  scheme,
-                              NMSetting8021xCKFormat *out_format,
-                              GError **               error);
-    gboolean (*set_private_key_func)(NMSetting8021x *        setting,
-                                     const char *            value,
-                                     const char *            password,
-                                     NMSetting8021xCKScheme  scheme,
-                                     NMSetting8021xCKFormat *out_format,
-                                     GError **               error);
-    const char *             file_suffix;
-    NMSetting8021xSchemeType scheme_type;
-    bool                     is_secret : 1;
-} NMSetting8021xSchemeVtable;
-
-extern const NMSetting8021xSchemeVtable
-    nm_setting_8021x_scheme_vtable[_NM_SETTING_802_1X_SCHEME_TYPE_NUM + 1];
-
-/*****************************************************************************/
-
-typedef enum {
-    /* the enum (and their numeric values) are internal API. Do not assign
-     * any meaning the numeric values, because they already have one:
-     *
-     * they are sorted in a way, that corresponds to the asciibetical sort
-     * order of the corresponding setting-name. */
-
-    NM_META_SETTING_TYPE_6LOWPAN,
-    NM_META_SETTING_TYPE_OLPC_MESH,
-    NM_META_SETTING_TYPE_WIRELESS,
-    NM_META_SETTING_TYPE_WIRELESS_SECURITY,
-    NM_META_SETTING_TYPE_802_1X,
-    NM_META_SETTING_TYPE_WIRED,
-    NM_META_SETTING_TYPE_ADSL,
-    NM_META_SETTING_TYPE_BLUETOOTH,
-    NM_META_SETTING_TYPE_BOND,
-    NM_META_SETTING_TYPE_BRIDGE,
-    NM_META_SETTING_TYPE_BRIDGE_PORT,
-    NM_META_SETTING_TYPE_CDMA,
-    NM_META_SETTING_TYPE_CONNECTION,
-    NM_META_SETTING_TYPE_DCB,
-    NM_META_SETTING_TYPE_DUMMY,
-    NM_META_SETTING_TYPE_ETHTOOL,
-    NM_META_SETTING_TYPE_GENERIC,
-    NM_META_SETTING_TYPE_GSM,
-    NM_META_SETTING_TYPE_INFINIBAND,
-    NM_META_SETTING_TYPE_IP_TUNNEL,
-    NM_META_SETTING_TYPE_IP4_CONFIG,
-    NM_META_SETTING_TYPE_IP6_CONFIG,
-    NM_META_SETTING_TYPE_MACSEC,
-    NM_META_SETTING_TYPE_MACVLAN,
-    NM_META_SETTING_TYPE_MATCH,
-    NM_META_SETTING_TYPE_OVS_BRIDGE,
-    NM_META_SETTING_TYPE_OVS_DPDK,
-    NM_META_SETTING_TYPE_OVS_INTERFACE,
-    NM_META_SETTING_TYPE_OVS_PATCH,
-    NM_META_SETTING_TYPE_OVS_PORT,
-    NM_META_SETTING_TYPE_PPP,
-    NM_META_SETTING_TYPE_PPPOE,
-    NM_META_SETTING_TYPE_PROXY,
-    NM_META_SETTING_TYPE_SERIAL,
-    NM_META_SETTING_TYPE_SRIOV,
-    NM_META_SETTING_TYPE_TC_CONFIG,
-    NM_META_SETTING_TYPE_TEAM,
-    NM_META_SETTING_TYPE_TEAM_PORT,
-    NM_META_SETTING_TYPE_TUN,
-    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,
-    NM_META_SETTING_TYPE_WIREGUARD,
-    NM_META_SETTING_TYPE_WPAN,
-
-    NM_META_SETTING_TYPE_UNKNOWN,
-
-    _NM_META_SETTING_TYPE_NUM = NM_META_SETTING_TYPE_UNKNOWN,
-} NMMetaSettingType;
-
-/* this header is statically linked with both libnm-core.la and libnmc.la.
- * Though, there is no stable API/ABI, so whenever on of these components
- * accesses NMMetaSettingInfo or NMMetaSettingType, it only has meaning
- * inside the same component.
- *
- * Note how NMSettingClass has field of type "struct _NMMetaSettingInfo".
- * It would be a serious bug, if libnmc tries to interpret this pointer
- * with the meaning of NMMetaSettingInfo. They might be different, because
- * libnm.so (libnm-core.la) might be a newer version than nmcli (libnmc.la).
- *
- * This define helps to ensure that we don't accidentally use the pointer
- * in different contexts. */
-#if ((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)
-    #define _NMMetaSettingInfoXX _NMMetaSettingInfo
-#else
-    #define _NMMetaSettingInfoXX _NMMetaSettingInfoCli
-#endif
-struct _NMMetaSettingInfoXX {
-    const char *setting_name;
-    GType (*get_setting_gtype)(void);
-    NMMetaSettingType meta_type;
-    NMSettingPriority setting_priority;
-};
-
-typedef struct _NMMetaSettingInfoXX NMMetaSettingInfo;
-
-/* note that we statically link nm-meta-setting.h both to libnm-core.la and
- * libnmc.la. That means, there are two versions of nm_meta_setting_infos
- * in nmcli. That is not easily avoidable, because at this point, we don't
- * want yet to making it public API.
- *
- * Eventually, this should become public API of libnm, and nmcli/libnmc.la
- * should use that version.
- *
- * Downsides of the current solution:
- *
- * - duplication of the array in nmcli.
- *
- * - there is no stable API/ABI. That means, when you have a NMMetaSettingInfo
- *   pointer, or a NMMetaSettingType value, the value can only be used within
- *   the current context (libnm-core.la or libnmc.la). In other words, libnmc.la
- *   (and nmcli) must never access a NMMetaSettingInfo/NMMetaSettingType value,
- *   that comes from libnm-core.la.
- */
-extern const NMMetaSettingInfo nm_meta_setting_infos[_NM_META_SETTING_TYPE_NUM + 1];
-
-const NMMetaSettingInfo *nm_meta_setting_infos_by_name(const char *name);
-const NMMetaSettingInfo *nm_meta_setting_infos_by_gtype(GType gtype);
-
-/*****************************************************************************/
-
-#endif /* __NM_META_SETTING_H__ */
diff --git a/shared/nm-platform/nm-netlink.c b/shared/nm-platform/nm-netlink.c
new file mode 100644
index 00000000..7a6d7e04
--- /dev/null
+++ b/shared/nm-platform/nm-netlink.c
@@ -0,0 +1,1518 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
+
+#include "nm-netlink.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+/*****************************************************************************/
+
+#ifndef SOL_NETLINK
+    #define SOL_NETLINK 270
+#endif
+
+/*****************************************************************************/
+
+#define NL_SOCK_PASSCRED     (1 << 1)
+#define NL_MSG_PEEK          (1 << 3)
+#define NL_MSG_PEEK_EXPLICIT (1 << 4)
+#define NL_NO_AUTO_ACK       (1 << 5)
+
+#ifndef NETLINK_EXT_ACK
+    #define NETLINK_EXT_ACK 11
+#endif
+
+struct nl_msg {
+    int                nm_protocol;
+    struct sockaddr_nl nm_src;
+    struct sockaddr_nl nm_dst;
+    struct ucred       nm_creds;
+    struct nlmsghdr *  nm_nlh;
+    size_t             nm_size;
+    bool               nm_creds_has : 1;
+};
+
+struct nl_sock {
+    struct sockaddr_nl s_local;
+    struct sockaddr_nl s_peer;
+    int                s_fd;
+    int                s_proto;
+    unsigned int       s_seq_next;
+    unsigned int       s_seq_expect;
+    int                s_flags;
+    size_t             s_bufsize;
+};
+
+/*****************************************************************************/
+
+NM_UTILS_ENUM2STR_DEFINE(nl_nlmsgtype2str,
+                         int,
+                         NM_UTILS_ENUM2STR(NLMSG_NOOP, "NOOP"),
+                         NM_UTILS_ENUM2STR(NLMSG_ERROR, "ERROR"),
+                         NM_UTILS_ENUM2STR(NLMSG_DONE, "DONE"),
+                         NM_UTILS_ENUM2STR(NLMSG_OVERRUN, "OVERRUN"), );
+
+NM_UTILS_FLAGS2STR_DEFINE(nl_nlmsg_flags2str,
+                          int,
+                          NM_UTILS_FLAGS2STR(NLM_F_REQUEST, "REQUEST"),
+                          NM_UTILS_FLAGS2STR(NLM_F_MULTI, "MULTI"),
+                          NM_UTILS_FLAGS2STR(NLM_F_ACK, "ACK"),
+                          NM_UTILS_FLAGS2STR(NLM_F_ECHO, "ECHO"),
+                          NM_UTILS_FLAGS2STR(NLM_F_ROOT, "ROOT"),
+                          NM_UTILS_FLAGS2STR(NLM_F_MATCH, "MATCH"),
+                          NM_UTILS_FLAGS2STR(NLM_F_ATOMIC, "ATOMIC"),
+                          NM_UTILS_FLAGS2STR(NLM_F_REPLACE, "REPLACE"),
+                          NM_UTILS_FLAGS2STR(NLM_F_EXCL, "EXCL"),
+                          NM_UTILS_FLAGS2STR(NLM_F_CREATE, "CREATE"),
+                          NM_UTILS_FLAGS2STR(NLM_F_APPEND, "APPEND"), );
+
+/*****************************************************************************/
+
+const char *
+nl_nlmsghdr_to_str(const struct nlmsghdr *hdr, char *buf, gsize len)
+{
+    const char *b;
+    const char *s;
+    guint       flags, flags_before;
+    const char *prefix;
+
+    if (!nm_utils_to_string_buffer_init_null(hdr, &buf, &len))
+        return buf;
+
+    b = buf;
+
+    switch (hdr->nlmsg_type) {
+    case RTM_GETLINK:
+        s = "RTM_GETLINK";
+        break;
+    case RTM_NEWLINK:
+        s = "RTM_NEWLINK";
+        break;
+    case RTM_DELLINK:
+        s = "RTM_DELLINK";
+        break;
+    case RTM_SETLINK:
+        s = "RTM_SETLINK";
+        break;
+    case RTM_GETADDR:
+        s = "RTM_GETADDR";
+        break;
+    case RTM_NEWADDR:
+        s = "RTM_NEWADDR";
+        break;
+    case RTM_DELADDR:
+        s = "RTM_DELADDR";
+        break;
+    case RTM_GETROUTE:
+        s = "RTM_GETROUTE";
+        break;
+    case RTM_NEWROUTE:
+        s = "RTM_NEWROUTE";
+        break;
+    case RTM_DELROUTE:
+        s = "RTM_DELROUTE";
+        break;
+    case RTM_GETRULE:
+        s = "RTM_GETRULE";
+        break;
+    case RTM_NEWRULE:
+        s = "RTM_NEWRULE";
+        break;
+    case RTM_DELRULE:
+        s = "RTM_DELRULE";
+        break;
+    case RTM_GETQDISC:
+        s = "RTM_GETQDISC";
+        break;
+    case RTM_NEWQDISC:
+        s = "RTM_NEWQDISC";
+        break;
+    case RTM_DELQDISC:
+        s = "RTM_DELQDISC";
+        break;
+    case RTM_GETTFILTER:
+        s = "RTM_GETTFILTER";
+        break;
+    case RTM_NEWTFILTER:
+        s = "RTM_NEWTFILTER";
+        break;
+    case RTM_DELTFILTER:
+        s = "RTM_DELTFILTER";
+        break;
+    case NLMSG_NOOP:
+        s = "NLMSG_NOOP";
+        break;
+    case NLMSG_ERROR:
+        s = "NLMSG_ERROR";
+        break;
+    case NLMSG_DONE:
+        s = "NLMSG_DONE";
+        break;
+    case NLMSG_OVERRUN:
+        s = "NLMSG_OVERRUN";
+        break;
+    default:
+        s = NULL;
+        break;
+    }
+
+    if (s)
+        nm_utils_strbuf_append_str(&buf, &len, s);
+    else
+        nm_utils_strbuf_append(&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type);
+
+    flags = hdr->nlmsg_flags;
+
+    if (!flags) {
+        nm_utils_strbuf_append_str(&buf, &len, ", flags 0");
+        goto flags_done;
+    }
+
+#define _F(f, n)                                                   \
+    G_STMT_START                                                   \
+    {                                                              \
+        if (NM_FLAGS_ALL(flags, f)) {                              \
+            flags &= ~(f);                                         \
+            nm_utils_strbuf_append(&buf, &len, "%s%s", prefix, n); \
+            if (!flags)                                            \
+                goto flags_done;                                   \
+            prefix = ",";                                          \
+        }                                                          \
+    }                                                              \
+    G_STMT_END
+
+    prefix       = ", flags ";
+    flags_before = flags;
+    _F(NLM_F_REQUEST, "request");
+    _F(NLM_F_MULTI, "multi");
+    _F(NLM_F_ACK, "ack");
+    _F(NLM_F_ECHO, "echo");
+    _F(NLM_F_DUMP_INTR, "dump_intr");
+    _F(0x20 /*NLM_F_DUMP_FILTERED*/, "dump_filtered");
+
+    if (flags_before != flags)
+        prefix = ";";
+
+    switch (hdr->nlmsg_type) {
+    case RTM_NEWLINK:
+    case RTM_NEWADDR:
+    case RTM_NEWROUTE:
+    case RTM_NEWQDISC:
+    case RTM_NEWTFILTER:
+        _F(NLM_F_REPLACE, "replace");
+        _F(NLM_F_EXCL, "excl");
+        _F(NLM_F_CREATE, "create");
+        _F(NLM_F_APPEND, "append");
+        break;
+    case RTM_GETLINK:
+    case RTM_GETADDR:
+    case RTM_GETROUTE:
+    case RTM_DELQDISC:
+    case RTM_DELTFILTER:
+        _F(NLM_F_DUMP, "dump");
+        _F(NLM_F_ROOT, "root");
+        _F(NLM_F_MATCH, "match");
+        _F(NLM_F_ATOMIC, "atomic");
+        break;
+    }
+
+#undef _F
+
+    if (flags_before != flags)
+        prefix = ";";
+    nm_utils_strbuf_append(&buf, &len, "%s0x%04x", prefix, flags);
+
+flags_done:
+
+    nm_utils_strbuf_append(&buf, &len, ", seq %u", (unsigned) hdr->nlmsg_seq);
+
+    return b;
+}
+
+/*****************************************************************************/
+
+struct nlmsghdr *
+nlmsg_hdr(struct nl_msg *n)
+{
+    return n->nm_nlh;
+}
+
+void *
+nlmsg_reserve(struct nl_msg *n, size_t len, int pad)
+{
+    char * buf       = (char *) n->nm_nlh;
+    size_t nlmsg_len = n->nm_nlh->nlmsg_len;
+    size_t tlen;
+
+    nm_assert(pad >= 0);
+
+    if (len > n->nm_size)
+        return NULL;
+
+    tlen = pad ? ((len + (pad - 1)) & ~(pad - 1)) : len;
+
+    if ((tlen + nlmsg_len) > n->nm_size)
+        return NULL;
+
+    buf += nlmsg_len;
+    n->nm_nlh->nlmsg_len += tlen;
+
+    if (tlen > len)
+        memset(buf + len, 0, tlen - len);
+
+    return buf;
+}
+
+/*****************************************************************************/
+
+struct nlattr *
+nla_reserve(struct nl_msg *msg, int attrtype, int attrlen)
+{
+    struct nlattr *nla;
+    int            tlen;
+
+    if (attrlen < 0)
+        return NULL;
+
+    tlen = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) + nla_total_size(attrlen);
+
+    if (tlen > msg->nm_size)
+        return NULL;
+
+    nla           = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
+    nla->nla_type = attrtype;
+    nla->nla_len  = nla_attr_size(attrlen);
+
+    if (attrlen)
+        memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen));
+    msg->nm_nlh->nlmsg_len = tlen;
+
+    return nla;
+}
+
+/*****************************************************************************/
+
+struct nl_msg *
+nlmsg_alloc_size(size_t len)
+{
+    struct nl_msg *nm;
+
+    if (len < sizeof(struct nlmsghdr))
+        len = sizeof(struct nlmsghdr);
+
+    nm  = g_slice_new(struct nl_msg);
+    *nm = (struct nl_msg){
+        .nm_protocol = -1,
+        .nm_size     = len,
+        .nm_nlh      = g_malloc0(len),
+    };
+    nm->nm_nlh->nlmsg_len = nlmsg_total_size(0);
+    return nm;
+}
+
+/**
+ * Allocate a new netlink message with the default maximum payload size.
+ *
+ * Allocates a new netlink message without any further payload. The
+ * maximum payload size defaults to PAGESIZE or as otherwise specified
+ * with nlmsg_set_default_size().
+ *
+ * @return Newly allocated netlink message or NULL.
+ */
+struct nl_msg *
+nlmsg_alloc(void)
+{
+    return nlmsg_alloc_size(nm_utils_getpagesize());
+}
+
+struct nl_msg *
+nlmsg_alloc_convert(struct nlmsghdr *hdr)
+{
+    struct nl_msg *nm;
+
+    nm = nlmsg_alloc_size(NLMSG_ALIGN(hdr->nlmsg_len));
+    memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len);
+    return nm;
+}
+
+struct nl_msg *
+nlmsg_alloc_simple(int nlmsgtype, int flags)
+{
+    struct nl_msg *nm;
+    struct nlmsghdr *new;
+
+    nm               = nlmsg_alloc();
+    new              = nm->nm_nlh;
+    new->nlmsg_type  = nlmsgtype;
+    new->nlmsg_flags = flags;
+    return nm;
+}
+
+void
+nlmsg_free(struct nl_msg *msg)
+{
+    if (!msg)
+        return;
+
+    g_free(msg->nm_nlh);
+    g_slice_free(struct nl_msg, msg);
+}
+
+/*****************************************************************************/
+
+int
+nlmsg_append(struct nl_msg *n, const void *data, size_t len, int pad)
+{
+    void *tmp;
+
+    nm_assert(n);
+    nm_assert(data);
+    nm_assert(len > 0);
+    nm_assert(pad >= 0);
+
+    tmp = nlmsg_reserve(n, len, pad);
+    if (tmp == NULL)
+        return -ENOMEM;
+
+    memcpy(tmp, data, len);
+    return 0;
+}
+
+/*****************************************************************************/
+
+int
+nlmsg_parse(struct nlmsghdr *        nlh,
+            int                      hdrlen,
+            struct nlattr *          tb[],
+            int                      maxtype,
+            const struct nla_policy *policy)
+{
+    if (!nlmsg_valid_hdr(nlh, hdrlen))
+        return -NME_NL_MSG_TOOSHORT;
+
+    return nla_parse(tb, maxtype, nlmsg_attrdata(nlh, hdrlen), nlmsg_attrlen(nlh, hdrlen), policy);
+}
+
+struct nlmsghdr *
+nlmsg_put(struct nl_msg *n, uint32_t pid, uint32_t seq, int type, int payload, int flags)
+{
+    struct nlmsghdr *nlh;
+
+    if (n->nm_nlh->nlmsg_len < NLMSG_HDRLEN)
+        g_return_val_if_reached(NULL);
+
+    nlh              = (struct nlmsghdr *) n->nm_nlh;
+    nlh->nlmsg_type  = type;
+    nlh->nlmsg_flags = flags;
+    nlh->nlmsg_pid   = pid;
+    nlh->nlmsg_seq   = seq;
+
+    if (payload > 0 && nlmsg_reserve(n, payload, NLMSG_ALIGNTO) == NULL)
+        return NULL;
+
+    return nlh;
+}
+
+size_t
+nla_strlcpy(char *dst, const struct nlattr *nla, size_t dstsize)
+{
+    const char *src;
+    size_t      srclen;
+    size_t      len;
+
+    /* - Always writes @dstsize bytes to @dst
+     * - Copies the first non-NUL characters to @dst.
+     *   Any characters after the first NUL bytes in @nla are ignored.
+     * - If the string @nla is longer than @dstsize, the string
+     *   gets truncated. @dst will always be NUL terminated. */
+
+    if (G_UNLIKELY(dstsize <= 1)) {
+        if (dstsize == 1)
+            dst[0] = '\0';
+        if (nla && (srclen = nla_len(nla)) > 0)
+            return strnlen(nla_data(nla), srclen);
+        return 0;
+    }
+
+    nm_assert(dst);
+
+    if (nla) {
+        srclen = nla_len(nla);
+        if (srclen > 0) {
+            src    = nla_data(nla);
+            srclen = strnlen(src, srclen);
+            if (srclen > 0) {
+                len = NM_MIN(dstsize - 1, srclen);
+                memcpy(dst, src, len);
+                memset(&dst[len], 0, dstsize - len);
+                return srclen;
+            }
+        }
+    }
+
+    memset(dst, 0, dstsize);
+    return 0;
+}
+
+size_t
+nla_memcpy(void *dst, const struct nlattr *nla, size_t dstsize)
+{
+    size_t len;
+    int    srclen;
+
+    if (!nla)
+        return 0;
+
+    srclen = nla_len(nla);
+
+    if (srclen <= 0) {
+        nm_assert(srclen == 0);
+        return 0;
+    }
+
+    len = NM_MIN((size_t) srclen, dstsize);
+    if (len > 0) {
+        /* there is a crucial difference between nla_strlcpy() and nla_memcpy().
+         * The former always write @dstsize bytes (akin to strncpy()), here, we only
+         * write the bytes that we actually have (leaving the remainder undefined). */
+        memcpy(dst, nla_data(nla), len);
+    }
+
+    return srclen;
+}
+
+int
+nla_put(struct nl_msg *msg, int attrtype, int datalen, const void *data)
+{
+    struct nlattr *nla;
+
+    nla = nla_reserve(msg, attrtype, datalen);
+    if (!nla) {
+        if (datalen < 0)
+            g_return_val_if_reached(-NME_BUG);
+
+        return -ENOMEM;
+    }
+
+    if (datalen > 0)
+        memcpy(nla_data(nla), data, datalen);
+
+    return 0;
+}
+
+struct nlattr *
+nla_find(const struct nlattr *head, int len, int attrtype)
+{
+    const struct nlattr *nla;
+    int                  rem;
+
+    nla_for_each_attr (nla, head, len, rem) {
+        if (nla_type(nla) == attrtype)
+            return (struct nlattr *) nla;
+    }
+
+    return NULL;
+}
+
+void
+nla_nest_cancel(struct nl_msg *msg, const struct nlattr *attr)
+{
+    ssize_t len;
+
+    len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) attr;
+    if (len < 0)
+        g_return_if_reached();
+    else if (len > 0) {
+        msg->nm_nlh->nlmsg_len -= len;
+        memset(nlmsg_tail(msg->nm_nlh), 0, len);
+    }
+}
+
+struct nlattr *
+nla_nest_start(struct nl_msg *msg, int attrtype)
+{
+    struct nlattr *start = (struct nlattr *) nlmsg_tail(msg->nm_nlh);
+
+    if (nla_put(msg, NLA_F_NESTED | attrtype, 0, NULL) < 0)
+        return NULL;
+
+    return start;
+}
+
+static int
+_nest_end(struct nl_msg *msg, struct nlattr *start, int keep_empty)
+{
+    size_t pad, len;
+
+    len = (char *) nlmsg_tail(msg->nm_nlh) - (char *) start;
+
+    if (len > USHRT_MAX || (!keep_empty && len == NLA_HDRLEN)) {
+        /*
+         * Max nlattr size exceeded or empty nested attribute, trim the
+         * attribute header again
+         */
+        nla_nest_cancel(msg, start);
+
+        /* Return error only if nlattr size was exceeded */
+        return (len == NLA_HDRLEN) ? 0 : -NME_NL_ATTRSIZE;
+    }
+
+    start->nla_len = len;
+
+    pad = NLMSG_ALIGN(msg->nm_nlh->nlmsg_len) - msg->nm_nlh->nlmsg_len;
+    if (pad > 0) {
+        /*
+         * Data inside attribute does not end at a alignment boundary.
+         * Pad accordingly and account for the additional space in
+         * the message. nlmsg_reserve() may never fail in this situation,
+         * the allocate message buffer must be a multiple of NLMSG_ALIGNTO.
+         */
+        if (!nlmsg_reserve(msg, pad, 0))
+            g_return_val_if_reached(-NME_BUG);
+    }
+
+    return 0;
+}
+
+int
+nla_nest_end(struct nl_msg *msg, struct nlattr *start)
+{
+    return _nest_end(msg, start, 0);
+}
+
+static const uint16_t nla_attr_minlen[NLA_TYPE_MAX + 1] = {
+    [NLA_U8]     = sizeof(uint8_t),
+    [NLA_U16]    = sizeof(uint16_t),
+    [NLA_U32]    = sizeof(uint32_t),
+    [NLA_U64]    = sizeof(uint64_t),
+    [NLA_STRING] = 1,
+    [NLA_FLAG]   = 0,
+};
+
+static int
+validate_nla(const struct nlattr *nla, int maxtype, const struct nla_policy *policy)
+{
+    const struct nla_policy *pt;
+    unsigned int             minlen = 0;
+    int                      type   = nla_type(nla);
+
+    if (type < 0 || type > maxtype)
+        return 0;
+
+    pt = &policy[type];
+
+    if (pt->type > NLA_TYPE_MAX)
+        g_return_val_if_reached(-NME_BUG);
+
+    if (pt->minlen)
+        minlen = pt->minlen;
+    else if (pt->type != NLA_UNSPEC)
+        minlen = nla_attr_minlen[pt->type];
+
+    if (nla_len(nla) < minlen)
+        return -NME_UNSPEC;
+
+    if (pt->maxlen && nla_len(nla) > pt->maxlen)
+        return -NME_UNSPEC;
+
+    if (pt->type == NLA_STRING) {
+        const char *data;
+
+        nm_assert(minlen > 0);
+
+        data = nla_data(nla);
+        if (data[nla_len(nla) - 1] != '\0')
+            return -NME_UNSPEC;
+    }
+
+    return 0;
+}
+
+int
+nla_parse(struct nlattr *          tb[],
+          int                      maxtype,
+          struct nlattr *          head,
+          int                      len,
+          const struct nla_policy *policy)
+{
+    struct nlattr *nla;
+    int            rem, nmerr;
+
+    memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1));
+
+    nla_for_each_attr (nla, head, len, rem) {
+        int type = nla_type(nla);
+
+        if (type > maxtype)
+            continue;
+
+        if (policy) {
+            nmerr = validate_nla(nla, maxtype, policy);
+            if (nmerr < 0)
+                return nmerr;
+        }
+
+        tb[type] = nla;
+    }
+
+    return 0;
+}
+
+/*****************************************************************************/
+
+int
+nlmsg_get_proto(struct nl_msg *msg)
+{
+    return msg->nm_protocol;
+}
+
+void
+nlmsg_set_proto(struct nl_msg *msg, int protocol)
+{
+    msg->nm_protocol = protocol;
+}
+
+void
+nlmsg_set_src(struct nl_msg *msg, struct sockaddr_nl *addr)
+{
+    memcpy(&msg->nm_src, addr, sizeof(*addr));
+}
+
+struct ucred *
+nlmsg_get_creds(struct nl_msg *msg)
+{
+    if (msg->nm_creds_has)
+        return &msg->nm_creds;
+    return NULL;
+}
+
+void
+nlmsg_set_creds(struct nl_msg *msg, struct ucred *creds)
+{
+    if (creds) {
+        memcpy(&msg->nm_creds, creds, sizeof(*creds));
+        msg->nm_creds_has = TRUE;
+    } else
+        msg->nm_creds_has = FALSE;
+}
+
+/*****************************************************************************/
+
+void *
+genlmsg_put(struct nl_msg *msg,
+            uint32_t       port,
+            uint32_t       seq,
+            int            family,
+            int            hdrlen,
+            int            flags,
+            uint8_t        cmd,
+            uint8_t        version)
+{
+    struct nlmsghdr * nlh;
+    struct genlmsghdr hdr = {
+        .cmd     = cmd,
+        .version = version,
+    };
+
+    nlh = nlmsg_put(msg, port, seq, family, GENL_HDRLEN + hdrlen, flags);
+    if (nlh == NULL)
+        return NULL;
+
+    memcpy(nlmsg_data(nlh), &hdr, sizeof(hdr));
+
+    return (char *) nlmsg_data(nlh) + GENL_HDRLEN;
+}
+
+void *
+genlmsg_data(const struct genlmsghdr *gnlh)
+{
+    return ((unsigned char *) gnlh + GENL_HDRLEN);
+}
+
+void *
+genlmsg_user_hdr(const struct genlmsghdr *gnlh)
+{
+    return genlmsg_data(gnlh);
+}
+
+struct genlmsghdr *
+genlmsg_hdr(struct nlmsghdr *nlh)
+{
+    return nlmsg_data(nlh);
+}
+
+void *
+genlmsg_user_data(const struct genlmsghdr *gnlh, const int hdrlen)
+{
+    return (char *) genlmsg_user_hdr(gnlh) + NLMSG_ALIGN(hdrlen);
+}
+
+struct nlattr *
+genlmsg_attrdata(const struct genlmsghdr *gnlh, int hdrlen)
+{
+    return genlmsg_user_data(gnlh, hdrlen);
+}
+
+int
+genlmsg_len(const struct genlmsghdr *gnlh)
+{
+    const struct nlmsghdr *nlh;
+
+    nlh = (const struct nlmsghdr *) ((const unsigned char *) gnlh - NLMSG_HDRLEN);
+    return (nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN);
+}
+
+int
+genlmsg_attrlen(const struct genlmsghdr *gnlh, int hdrlen)
+{
+    return genlmsg_len(gnlh) - NLMSG_ALIGN(hdrlen);
+}
+
+int
+genlmsg_valid_hdr(struct nlmsghdr *nlh, int hdrlen)
+{
+    struct genlmsghdr *ghdr;
+
+    if (!nlmsg_valid_hdr(nlh, GENL_HDRLEN))
+        return 0;
+
+    ghdr = nlmsg_data(nlh);
+    if (genlmsg_len(ghdr) < NLMSG_ALIGN(hdrlen))
+        return 0;
+
+    return 1;
+}
+
+int
+genlmsg_parse(struct nlmsghdr *        nlh,
+              int                      hdrlen,
+              struct nlattr *          tb[],
+              int                      maxtype,
+              const struct nla_policy *policy)
+{
+    struct genlmsghdr *ghdr;
+
+    if (!genlmsg_valid_hdr(nlh, hdrlen))
+        return -NME_NL_MSG_TOOSHORT;
+
+    ghdr = nlmsg_data(nlh);
+    return nla_parse(tb,
+                     maxtype,
+                     genlmsg_attrdata(ghdr, hdrlen),
+                     genlmsg_attrlen(ghdr, hdrlen),
+                     policy);
+}
+
+static int
+_genl_parse_getfamily(struct nl_msg *msg, void *arg)
+{
+    static const struct nla_policy ctrl_policy[] = {
+        [CTRL_ATTR_FAMILY_ID]    = {.type = NLA_U16},
+        [CTRL_ATTR_FAMILY_NAME]  = {.type = NLA_STRING, .maxlen = GENL_NAMSIZ},
+        [CTRL_ATTR_VERSION]      = {.type = NLA_U32},
+        [CTRL_ATTR_HDRSIZE]      = {.type = NLA_U32},
+        [CTRL_ATTR_MAXATTR]      = {.type = NLA_U32},
+        [CTRL_ATTR_OPS]          = {.type = NLA_NESTED},
+        [CTRL_ATTR_MCAST_GROUPS] = {.type = NLA_NESTED},
+    };
+    struct nlattr *  tb[G_N_ELEMENTS(ctrl_policy)];
+    struct nlmsghdr *nlh           = nlmsg_hdr(msg);
+    gint32 *         response_data = arg;
+
+    if (genlmsg_parse_arr(nlh, 0, tb, ctrl_policy) < 0)
+        return NL_SKIP;
+
+    if (tb[CTRL_ATTR_FAMILY_ID])
+        *response_data = nla_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+
+    return NL_STOP;
+}
+
+int
+genl_ctrl_resolve(struct nl_sock *sk, const char *name)
+{
+    nm_auto_nlmsg struct nl_msg *msg = NULL;
+    int                          nmerr;
+    gint32                       response_data = -1;
+    const struct nl_cb           cb            = {
+        .valid_cb  = _genl_parse_getfamily,
+        .valid_arg = &response_data,
+    };
+
+    msg = nlmsg_alloc();
+
+    if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL, 0, 0, CTRL_CMD_GETFAMILY, 1))
+        return -ENOMEM;
+
+    nmerr = nla_put_string(msg, CTRL_ATTR_FAMILY_NAME, name);
+    if (nmerr < 0)
+        return nmerr;
+
+    nmerr = nl_send_auto(sk, msg);
+    if (nmerr < 0)
+        return nmerr;
+
+    nmerr = nl_recvmsgs(sk, &cb);
+    if (nmerr < 0)
+        return nmerr;
+
+    /* If search was successful, request may be ACKed after data */
+    nmerr = nl_wait_for_ack(sk, NULL);
+    if (nmerr < 0)
+        return nmerr;
+
+    if (response_data < 0)
+        return -NME_UNSPEC;
+
+    return response_data;
+}
+
+/*****************************************************************************/
+
+struct nl_sock *
+nl_socket_alloc(void)
+{
+    struct nl_sock *sk;
+
+    sk = g_slice_new0(struct nl_sock);
+
+    sk->s_fd              = -1;
+    sk->s_local.nl_family = AF_NETLINK;
+    sk->s_peer.nl_family  = AF_NETLINK;
+    sk->s_seq_expect = sk->s_seq_next = time(NULL);
+
+    return sk;
+}
+
+void
+nl_socket_free(struct nl_sock *sk)
+{
+    if (!sk)
+        return;
+
+    if (sk->s_fd >= 0)
+        nm_close(sk->s_fd);
+    g_slice_free(struct nl_sock, sk);
+}
+
+int
+nl_socket_get_fd(const struct nl_sock *sk)
+{
+    return sk->s_fd;
+}
+
+uint32_t
+nl_socket_get_local_port(const struct nl_sock *sk)
+{
+    return sk->s_local.nl_pid;
+}
+
+size_t
+nl_socket_get_msg_buf_size(struct nl_sock *sk)
+{
+    return sk->s_bufsize;
+}
+
+int
+nl_socket_set_passcred(struct nl_sock *sk, int state)
+{
+    int err;
+
+    if (sk->s_fd == -1)
+        return -NME_NL_BAD_SOCK;
+
+    err = setsockopt(sk->s_fd, SOL_SOCKET, SO_PASSCRED, &state, sizeof(state));
+    if (err < 0)
+        return -nm_errno_from_native(errno);
+
+    if (state)
+        sk->s_flags |= NL_SOCK_PASSCRED;
+    else
+        sk->s_flags &= ~NL_SOCK_PASSCRED;
+
+    return 0;
+}
+
+int
+nl_socket_set_msg_buf_size(struct nl_sock *sk, size_t bufsize)
+{
+    sk->s_bufsize = bufsize;
+
+    return 0;
+}
+
+struct sockaddr_nl *
+nlmsg_get_dst(struct nl_msg *msg)
+{
+    return &msg->nm_dst;
+}
+
+int
+nl_socket_set_nonblocking(const struct nl_sock *sk)
+{
+    if (sk->s_fd == -1)
+        return -NME_NL_BAD_SOCK;
+
+    if (fcntl(sk->s_fd, F_SETFL, O_NONBLOCK) < 0)
+        return -nm_errno_from_native(errno);
+
+    return 0;
+}
+
+int
+nl_socket_set_buffer_size(struct nl_sock *sk, int rxbuf, int txbuf)
+{
+    int err;
+
+    if (rxbuf <= 0)
+        rxbuf = 32768;
+
+    if (txbuf <= 0)
+        txbuf = 32768;
+
+    if (sk->s_fd == -1)
+        return -NME_NL_BAD_SOCK;
+
+    err = setsockopt(sk->s_fd, SOL_SOCKET, SO_SNDBUF, &txbuf, sizeof(txbuf));
+    if (err < 0) {
+        return -nm_errno_from_native(errno);
+    }
+
+    err = setsockopt(sk->s_fd, SOL_SOCKET, SO_RCVBUF, &rxbuf, sizeof(rxbuf));
+    if (err < 0) {
+        return -nm_errno_from_native(errno);
+    }
+
+    return 0;
+}
+
+int
+nl_socket_add_memberships(struct nl_sock *sk, int group, ...)
+{
+    int     err;
+    va_list ap;
+
+    if (sk->s_fd == -1)
+        return -NME_NL_BAD_SOCK;
+
+    va_start(ap, group);
+
+    while (group != 0) {
+        if (group < 0) {
+            va_end(ap);
+            g_return_val_if_reached(-NME_BUG);
+        }
+
+        err = setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group));
+        if (err < 0) {
+            int errsv = errno;
+
+            va_end(ap);
+            return -nm_errno_from_native(errsv);
+        }
+
+        group = va_arg(ap, int);
+    }
+
+    va_end(ap);
+
+    return 0;
+}
+
+int
+nl_socket_set_ext_ack(struct nl_sock *sk, gboolean enable)
+{
+    int err, val;
+
+    if (sk->s_fd == -1)
+        return -NME_NL_BAD_SOCK;
+
+    val = !!enable;
+    err = setsockopt(sk->s_fd, SOL_NETLINK, NETLINK_EXT_ACK, &val, sizeof(val));
+    if (err < 0)
+        return -nm_errno_from_native(errno);
+
+    return 0;
+}
+
+void
+nl_socket_disable_msg_peek(struct nl_sock *sk)
+{
+    sk->s_flags |= NL_MSG_PEEK_EXPLICIT;
+    sk->s_flags &= ~NL_MSG_PEEK;
+}
+
+int
+nl_connect(struct nl_sock *sk, int protocol)
+{
+    int                err, nmerr;
+    socklen_t          addrlen;
+    struct sockaddr_nl local = {0};
+
+    if (sk->s_fd != -1)
+        return -NME_NL_BAD_SOCK;
+
+    sk->s_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
+    if (sk->s_fd < 0) {
+        nmerr = -nm_errno_from_native(errno);
+        goto errout;
+    }
+
+    nmerr = nl_socket_set_buffer_size(sk, 0, 0);
+    if (nmerr < 0)
+        goto errout;
+
+    nm_assert(sk->s_local.nl_pid == 0);
+
+    err = bind(sk->s_fd, (struct sockaddr *) &sk->s_local, sizeof(sk->s_local));
+    if (err != 0) {
+        nmerr = -nm_errno_from_native(errno);
+        goto errout;
+    }
+
+    addrlen = sizeof(local);
+    err     = getsockname(sk->s_fd, (struct sockaddr *) &local, &addrlen);
+    if (err < 0) {
+        nmerr = -nm_errno_from_native(errno);
+        goto errout;
+    }
+
+    if (addrlen != sizeof(local)) {
+        nmerr = -NME_UNSPEC;
+        goto errout;
+    }
+
+    if (local.nl_family != AF_NETLINK) {
+        nmerr = -NME_UNSPEC;
+        goto errout;
+    }
+
+    sk->s_local = local;
+    sk->s_proto = protocol;
+
+    return 0;
+
+errout:
+    if (sk->s_fd != -1) {
+        close(sk->s_fd);
+        sk->s_fd = -1;
+    }
+    return nmerr;
+}
+
+/*****************************************************************************/
+
+static void
+_cb_init(struct nl_cb *dst, const struct nl_cb *src)
+{
+    nm_assert(dst);
+
+    if (src)
+        *dst = *src;
+    else
+        memset(dst, 0, sizeof(*dst));
+}
+
+static int
+ack_wait_handler(struct nl_msg *msg, void *arg)
+{
+    return NL_STOP;
+}
+
+int
+nl_wait_for_ack(struct nl_sock *sk, const struct nl_cb *cb)
+{
+    struct nl_cb cb2;
+
+    _cb_init(&cb2, cb);
+    cb2.ack_cb = ack_wait_handler;
+    return nl_recvmsgs(sk, &cb2);
+}
+
+#define NL_CB_CALL(cb, type, msg)                                \
+    do {                                                         \
+        const struct nl_cb *_cb = (cb);                          \
+                                                                 \
+        if (_cb && _cb->type##_cb) {                             \
+            /* the returned value here must be either a negative
+         * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */ \
+            nmerr = _cb->type##_cb((msg), _cb->type##_arg);      \
+            switch (nmerr) {                                     \
+            case NL_OK:                                          \
+                nm_assert(nmerr == 0);                           \
+                break;                                           \
+            case NL_SKIP:                                        \
+                goto skip;                                       \
+            case NL_STOP:                                        \
+                goto stop;                                       \
+            default:                                             \
+                if (nmerr >= 0) {                                \
+                    nm_assert_not_reached();                     \
+                    nmerr = -NME_BUG;                            \
+                }                                                \
+                goto out;                                        \
+            }                                                    \
+        }                                                        \
+    } while (0)
+
+int
+nl_recvmsgs(struct nl_sock *sk, const struct nl_cb *cb)
+{
+    int                    n, nmerr = 0, multipart = 0, interrupted = 0, nrecv = 0;
+    gs_free unsigned char *buf = NULL;
+    struct nlmsghdr *      hdr;
+    struct sockaddr_nl     nla = {0};
+    struct ucred           creds;
+    gboolean               creds_has;
+
+continue_reading:
+    n = nl_recv(sk, &nla, &buf, &creds, &creds_has);
+    if (n <= 0)
+        return n;
+
+    hdr = (struct nlmsghdr *) buf;
+    while (nlmsg_ok(hdr, n)) {
+        nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+        msg = nlmsg_alloc_convert(hdr);
+
+        nlmsg_set_proto(msg, sk->s_proto);
+        nlmsg_set_src(msg, &nla);
+        nlmsg_set_creds(msg, creds_has ? &creds : NULL);
+
+        nrecv++;
+
+        /* Only do sequence checking if auto-ack mode is enabled */
+        if (!(sk->s_flags & NL_NO_AUTO_ACK)) {
+            if (hdr->nlmsg_seq != sk->s_seq_expect) {
+                nmerr = -NME_NL_SEQ_MISMATCH;
+                goto out;
+            }
+        }
+
+        if (hdr->nlmsg_type == NLMSG_DONE || hdr->nlmsg_type == NLMSG_ERROR
+            || hdr->nlmsg_type == NLMSG_NOOP || hdr->nlmsg_type == NLMSG_OVERRUN) {
+            /* We can't check for !NLM_F_MULTI since some netlink
+             * users in the kernel are broken. */
+            sk->s_seq_expect++;
+        }
+
+        if (hdr->nlmsg_flags & NLM_F_MULTI)
+            multipart = 1;
+
+        if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
+            /*
+             * We have to continue reading to clear
+             * all messages until a NLMSG_DONE is
+             * received and report the inconsistency.
+             */
+            interrupted = 1;
+        }
+
+        /* messages terminates a multipart message, this is
+         * usually the end of a message and therefore we slip
+         * out of the loop by default. the user may overrule
+         * this action by skipping this packet. */
+        if (hdr->nlmsg_type == NLMSG_DONE) {
+            multipart = 0;
+            NL_CB_CALL(cb, finish, msg);
+        }
+
+        /* Message to be ignored, the default action is to
+         * skip this message if no callback is specified. The
+         * user may overrule this action by returning
+         * NL_PROCEED. */
+        else if (hdr->nlmsg_type == NLMSG_NOOP)
+            goto skip;
+
+        /* Data got lost, report back to user. The default action is to
+         * quit parsing. The user may overrule this action by returning
+         * NL_SKIP or NL_PROCEED (dangerous) */
+        else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
+            nmerr = -NME_NL_MSG_OVERFLOW;
+            goto out;
+        }
+
+        /* Message carries a nlmsgerr */
+        else if (hdr->nlmsg_type == NLMSG_ERROR) {
+            struct nlmsgerr *e = nlmsg_data(hdr);
+
+            if (hdr->nlmsg_len < nlmsg_size(sizeof(*e))) {
+                /* Truncated error message, the default action
+                 * is to stop parsing. The user may overrule
+                 * this action by returning NL_SKIP or
+                 * NL_PROCEED (dangerous) */
+                nmerr = -NME_NL_MSG_TRUNC;
+                goto out;
+            }
+            if (e->error) {
+                /* Error message reported back from kernel. */
+                if (cb && cb->err_cb) {
+                    /* the returned value here must be either a negative
+                     * netlink error number, or one of NL_SKIP, NL_STOP, NL_OK. */
+                    nmerr = cb->err_cb(&nla, e, cb->err_arg);
+                    if (nmerr < 0)
+                        goto out;
+                    else if (nmerr == NL_SKIP)
+                        goto skip;
+                    else if (nmerr == NL_STOP) {
+                        nmerr = -nm_errno_from_native(e->error);
+                        goto out;
+                    }
+                    nm_assert(nmerr == NL_OK);
+                } else {
+                    nmerr = -nm_errno_from_native(e->error);
+                    goto out;
+                }
+            } else
+                NL_CB_CALL(cb, ack, msg);
+        } else {
+            /* Valid message (not checking for MULTIPART bit to
+             * get along with broken kernels. NL_SKIP has no
+             * effect on this.  */
+            NL_CB_CALL(cb, valid, msg);
+        }
+skip:
+        nmerr = 0;
+        hdr   = nlmsg_next(hdr, &n);
+    }
+
+    if (multipart) {
+        /* Multipart message not yet complete, continue reading */
+        nm_clear_g_free(&buf);
+
+        nmerr = 0;
+        goto continue_reading;
+    }
+
+stop:
+    nmerr = 0;
+
+out:
+    if (interrupted)
+        nmerr = -NME_NL_DUMP_INTR;
+
+    nm_assert(nmerr <= 0);
+    return nmerr ?: nrecv;
+}
+
+int
+nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr)
+{
+    int ret;
+
+    if (sk->s_fd < 0)
+        return -NME_NL_BAD_SOCK;
+
+    nlmsg_set_src(msg, &sk->s_local);
+
+    ret = sendmsg(sk->s_fd, hdr, 0);
+    if (ret < 0)
+        return -nm_errno_from_native(errno);
+
+    return ret;
+}
+
+int
+nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen)
+{
+    struct sockaddr_nl *dst;
+    struct ucred *      creds;
+    struct msghdr       hdr = {
+        .msg_name    = (void *) &sk->s_peer,
+        .msg_namelen = sizeof(struct sockaddr_nl),
+        .msg_iov     = iov,
+        .msg_iovlen  = iovlen,
+    };
+    char buf[CMSG_SPACE(sizeof(struct ucred))];
+
+    /* Overwrite destination if specified in the message itself, defaults
+     * to the peer address of the socket.
+     */
+    dst = nlmsg_get_dst(msg);
+    if (dst->nl_family == AF_NETLINK)
+        hdr.msg_name = dst;
+
+    /* Add credentials if present. */
+    creds = nlmsg_get_creds(msg);
+    if (creds != NULL) {
+        struct cmsghdr *cmsg;
+
+        hdr.msg_control    = buf;
+        hdr.msg_controllen = sizeof(buf);
+
+        cmsg             = CMSG_FIRSTHDR(&hdr);
+        cmsg->cmsg_level = SOL_SOCKET;
+        cmsg->cmsg_type  = SCM_CREDENTIALS;
+        cmsg->cmsg_len   = CMSG_LEN(sizeof(struct ucred));
+        memcpy(CMSG_DATA(cmsg), creds, sizeof(struct ucred));
+    }
+
+    return nl_sendmsg(sk, msg, &hdr);
+}
+
+void
+nl_complete_msg(struct nl_sock *sk, struct nl_msg *msg)
+{
+    struct nlmsghdr *nlh;
+
+    nlh = nlmsg_hdr(msg);
+    if (nlh->nlmsg_pid == NL_AUTO_PORT)
+        nlh->nlmsg_pid = nl_socket_get_local_port(sk);
+
+    if (nlh->nlmsg_seq == NL_AUTO_SEQ)
+        nlh->nlmsg_seq = sk->s_seq_next++;
+
+    if (msg->nm_protocol == -1)
+        msg->nm_protocol = sk->s_proto;
+
+    nlh->nlmsg_flags |= NLM_F_REQUEST;
+
+    if (!(sk->s_flags & NL_NO_AUTO_ACK))
+        nlh->nlmsg_flags |= NLM_F_ACK;
+}
+
+int
+nl_send(struct nl_sock *sk, struct nl_msg *msg)
+{
+    struct iovec iov = {
+        .iov_base = (void *) nlmsg_hdr(msg),
+        .iov_len  = nlmsg_hdr(msg)->nlmsg_len,
+    };
+
+    return nl_send_iovec(sk, msg, &iov, 1);
+}
+
+int
+nl_send_auto(struct nl_sock *sk, struct nl_msg *msg)
+{
+    nl_complete_msg(sk, msg);
+
+    return nl_send(sk, msg);
+}
+
+int
+nl_recv(struct nl_sock *    sk,
+        struct sockaddr_nl *nla,
+        unsigned char **    buf,
+        struct ucred *      out_creds,
+        gboolean *          out_creds_has)
+{
+    ssize_t       n;
+    int           flags = 0;
+    struct iovec  iov;
+    struct msghdr msg = {
+        .msg_name    = (void *) nla,
+        .msg_namelen = sizeof(struct sockaddr_nl),
+        .msg_iov     = &iov,
+        .msg_iovlen  = 1,
+    };
+    struct ucred tmpcreds;
+    gboolean     tmpcreds_has = FALSE;
+    int          retval;
+    int          errsv;
+
+    nm_assert(nla);
+    nm_assert(buf && !*buf);
+    nm_assert(!out_creds_has == !out_creds);
+
+    if ((sk->s_flags & NL_MSG_PEEK)
+        || (!(sk->s_flags & NL_MSG_PEEK_EXPLICIT) && sk->s_bufsize == 0))
+        flags |= MSG_PEEK | MSG_TRUNC;
+
+    iov.iov_len  = sk->s_bufsize ?: (((size_t) nm_utils_getpagesize()) * 4u);
+    iov.iov_base = g_malloc(iov.iov_len);
+
+    if (out_creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
+        msg.msg_controllen = CMSG_SPACE(sizeof(struct ucred));
+        msg.msg_control    = g_malloc(msg.msg_controllen);
+    }
+
+retry:
+    n = recvmsg(sk->s_fd, &msg, flags);
+    if (!n) {
+        retval = 0;
+        goto abort;
+    }
+
+    if (n < 0) {
+        errsv = errno;
+        if (errsv == EINTR)
+            goto retry;
+        retval = -nm_errno_from_native(errsv);
+        goto abort;
+    }
+
+    if (msg.msg_flags & MSG_CTRUNC) {
+        if (msg.msg_controllen == 0) {
+            retval = -NME_NL_MSG_TRUNC;
+            goto abort;
+        }
+
+        msg.msg_controllen *= 2;
+        msg.msg_control = g_realloc(msg.msg_control, msg.msg_controllen);
+        goto retry;
+    }
+
+    if (iov.iov_len < n || (msg.msg_flags & MSG_TRUNC)) {
+        /* respond with error to an incomplete message */
+        if (flags == 0) {
+            retval = -NME_NL_MSG_TRUNC;
+            goto abort;
+        }
+
+        /* Provided buffer is not long enough, enlarge it
+         * to size of n (which should be total length of the message)
+         * and try again. */
+        iov.iov_base = g_realloc(iov.iov_base, n);
+        iov.iov_len  = n;
+        flags        = 0;
+        goto retry;
+    }
+
+    if (flags != 0) {
+        /* Buffer is big enough, do the actual reading */
+        flags = 0;
+        goto retry;
+    }
+
+    if (msg.msg_namelen != sizeof(struct sockaddr_nl)) {
+        retval = -NME_UNSPEC;
+        goto abort;
+    }
+
+    if (out_creds && (sk->s_flags & NL_SOCK_PASSCRED)) {
+        struct cmsghdr *cmsg;
+
+        for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+            if (cmsg->cmsg_level != SOL_SOCKET)
+                continue;
+            if (cmsg->cmsg_type != SCM_CREDENTIALS)
+                continue;
+            memcpy(&tmpcreds, CMSG_DATA(cmsg), sizeof(tmpcreds));
+            tmpcreds_has = TRUE;
+            break;
+        }
+    }
+
+    retval = n;
+
+abort:
+    g_free(msg.msg_control);
+
+    if (retval <= 0) {
+        g_free(iov.iov_base);
+        return retval;
+    }
+
+    *buf = iov.iov_base;
+    if (out_creds && tmpcreds_has)
+        *out_creds = tmpcreds;
+    NM_SET_OUT(out_creds_has, tmpcreds_has);
+    return retval;
+}
diff --git a/shared/nm-platform/nm-netlink.h b/shared/nm-platform/nm-netlink.h
new file mode 100644
index 00000000..8de42531
--- /dev/null
+++ b/shared/nm-platform/nm-netlink.h
@@ -0,0 +1,616 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_NETLINK_H__
+#define __NM_NETLINK_H__
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/genetlink.h>
+
+#include "nm-std-aux/unaligned.h"
+
+/*****************************************************************************/
+
+#define NLMSGERR_ATTR_UNUSED 0
+#define NLMSGERR_ATTR_MSG    1
+#define NLMSGERR_ATTR_OFFS   2
+#define NLMSGERR_ATTR_COOKIE 3
+#define NLMSGERR_ATTR_MAX    3
+
+#ifndef NLM_F_ACK_TLVS
+    #define NLM_F_ACK_TLVS 0x200
+#endif
+
+/*****************************************************************************/
+
+/* Basic attribute data types */
+enum {
+    NLA_UNSPEC, /* Unspecified type, binary data chunk */
+    NLA_U8,     /* 8 bit integer */
+    NLA_U16,    /* 16 bit integer */
+    NLA_U32,    /* 32 bit integer */
+    NLA_U64,    /* 64 bit integer */
+    NLA_STRING, /* NUL terminated character string */
+    NLA_FLAG,   /* Flag */
+    NLA_MSECS,  /* Micro seconds (64bit) */
+    NLA_NESTED, /* Nested attributes */
+    NLA_NESTED_COMPAT,
+    NLA_NUL_STRING,
+    NLA_BINARY,
+    NLA_S8,
+    NLA_S16,
+    NLA_S32,
+    NLA_S64,
+    __NLA_TYPE_MAX,
+};
+
+#define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1)
+
+struct nl_msg;
+
+/*****************************************************************************/
+
+const char *nl_nlmsgtype2str(int type, char *buf, size_t size);
+
+const char *nl_nlmsg_flags2str(int flags, char *buf, size_t len);
+
+const char *nl_nlmsghdr_to_str(const struct nlmsghdr *hdr, char *buf, gsize len);
+
+/*****************************************************************************/
+
+struct nla_policy {
+    /* Type of attribute or NLA_UNSPEC */
+    uint16_t type;
+
+    /* Minimal length of payload required */
+    uint16_t minlen;
+
+    /* Maximal length of payload allowed */
+    uint16_t maxlen;
+};
+
+/*****************************************************************************/
+
+/* static asserts that @tb and @policy are suitable arguments to nla_parse(). */
+#define _nl_static_assert_tb(tb, policy)                                                      \
+    G_STMT_START                                                                              \
+    {                                                                                         \
+        G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(tb) > 0);                                           \
+                                                                                              \
+        /* We allow @policy to be either a C array or NULL. The sizeof()
+         * must either match the expected array size or the sizeof(NULL),
+         * but not both. */                      \
+        G_STATIC_ASSERT_EXPR((sizeof(policy) == G_N_ELEMENTS(tb) * sizeof(struct nla_policy)) \
+                             ^ (sizeof(policy) == sizeof(NULL)));                             \
+    }                                                                                         \
+    G_STMT_END
+
+/*****************************************************************************/
+
+static inline int
+nla_attr_size(int payload)
+{
+    nm_assert(payload >= 0);
+
+    return NLA_HDRLEN + payload;
+}
+
+static inline int
+nla_total_size(int payload)
+{
+    return NLA_ALIGN(nla_attr_size(payload));
+}
+
+static inline int
+nla_padlen(int payload)
+{
+    return nla_total_size(payload) - nla_attr_size(payload);
+}
+
+struct nlattr *nla_reserve(struct nl_msg *msg, int attrtype, int attrlen);
+
+static inline int
+nla_len(const struct nlattr *nla)
+{
+    nm_assert(nla);
+    nm_assert(nla->nla_len >= NLA_HDRLEN);
+
+    return ((int) nla->nla_len) - NLA_HDRLEN;
+}
+
+static inline int
+nla_type(const struct nlattr *nla)
+{
+    nm_assert(nla_len(nla) >= 0);
+
+    return nla->nla_type & NLA_TYPE_MASK;
+}
+
+static inline void *
+nla_data(const struct nlattr *nla)
+{
+    nm_assert(nla_len(nla) >= 0);
+
+    return &(((char *) nla)[NLA_HDRLEN]);
+}
+
+#define nla_data_as(type, nla)                                          \
+    ({                                                                  \
+        const struct nlattr *_nla = (nla);                              \
+                                                                        \
+        nm_assert(nla_len(_nla) >= sizeof(type));                       \
+                                                                        \
+        /* note that casting the pointer is undefined behavior in C, if
+         * the data has wrong alignment. Netlink data is aligned to 4 bytes,
+         * that means, if the alignment is larger than 4, this is invalid. */ \
+        G_STATIC_ASSERT_EXPR(_nm_alignof(type) <= NLA_ALIGNTO);         \
+                                                                        \
+        (type *) nla_data(_nla);                                        \
+    })
+
+static inline uint8_t
+nla_get_u8(const struct nlattr *nla)
+{
+    nm_assert(nla_len(nla) >= sizeof(uint8_t));
+
+    return *((const uint8_t *) nla_data(nla));
+}
+
+static inline int8_t
+nla_get_s8(const struct nlattr *nla)
+{
+    nm_assert(nla_len(nla) >= sizeof(int8_t));
+
+    return *((const int8_t *) nla_data(nla));
+}
+
+static inline uint8_t
+nla_get_u8_cond(/*const*/ struct nlattr *const *tb, int attr, uint8_t default_val)
+{
+    nm_assert(tb);
+    nm_assert(attr >= 0);
+
+    return tb[attr] ? nla_get_u8(tb[attr]) : default_val;
+}
+
+static inline uint16_t
+nla_get_u16(const struct nlattr *nla)
+{
+    nm_assert(nla_len(nla) >= sizeof(uint16_t));
+
+    return *((const uint16_t *) nla_data(nla));
+}
+
+static inline uint32_t
+nla_get_u32(const struct nlattr *nla)
+{
+    nm_assert(nla_len(nla) >= sizeof(uint32_t));
+
+    return *((const uint32_t *) nla_data(nla));
+}
+
+static inline int32_t
+nla_get_s32(const struct nlattr *nla)
+{
+    nm_assert(nla_len(nla) >= sizeof(int32_t));
+
+    return *((const int32_t *) nla_data(nla));
+}
+
+static inline uint64_t
+nla_get_u64(const struct nlattr *nla)
+{
+    nm_assert(nla_len(nla) >= sizeof(uint64_t));
+
+    return unaligned_read_ne64(nla_data(nla));
+}
+
+static inline uint64_t
+nla_get_be64(const struct nlattr *nla)
+{
+    nm_assert(nla_len(nla) >= sizeof(uint64_t));
+
+    return unaligned_read_be64(nla_data(nla));
+}
+
+static inline char *
+nla_get_string(const struct nlattr *nla)
+{
+    nm_assert(nla_len(nla) >= 0);
+
+    return (char *) nla_data(nla);
+}
+
+size_t nla_strlcpy(char *dst, const struct nlattr *nla, size_t dstsize);
+
+size_t nla_memcpy(void *dst, const struct nlattr *nla, size_t dstsize);
+
+#define nla_memcpy_checked_size(dst, nla, dstsize)                       \
+    G_STMT_START                                                         \
+    {                                                                    \
+        void *const                _dst     = (dst);                     \
+        const struct nlattr *const _nla     = (nla);                     \
+        const size_t               _dstsize = (dstsize);                 \
+        size_t                     _srcsize;                             \
+                                                                         \
+        /* assert that, if @nla is given, that it has the exact expected
+         * size. This implies that the caller previously verified the length
+         * of the attribute (via minlen/maxlen at nla_parse()). */ \
+                                                                         \
+        if (_nla) {                                                      \
+            _srcsize = nla_memcpy(_dst, _nla, _dstsize);                 \
+            nm_assert(_srcsize == _dstsize);                             \
+        }                                                                \
+    }                                                                    \
+    G_STMT_END
+
+int nla_put(struct nl_msg *msg, int attrtype, int datalen, const void *data);
+
+static inline int
+nla_put_string(struct nl_msg *msg, int attrtype, const char *str)
+{
+    nm_assert(str);
+
+    return nla_put(msg, attrtype, strlen(str) + 1, str);
+}
+
+static inline int
+nla_put_uint8(struct nl_msg *msg, int attrtype, uint8_t val)
+{
+    return nla_put(msg, attrtype, sizeof(val), &val);
+}
+
+static inline int
+nla_put_uint16(struct nl_msg *msg, int attrtype, uint16_t val)
+{
+    return nla_put(msg, attrtype, sizeof(val), &val);
+}
+
+static inline int
+nla_put_uint32(struct nl_msg *msg, int attrtype, uint32_t val)
+{
+    return nla_put(msg, attrtype, sizeof(val), &val);
+}
+
+#define NLA_PUT(msg, attrtype, attrlen, data)          \
+    G_STMT_START                                       \
+    {                                                  \
+        if (nla_put(msg, attrtype, attrlen, data) < 0) \
+            goto nla_put_failure;                      \
+    }                                                  \
+    G_STMT_END
+
+#define NLA_PUT_TYPE(msg, type, attrtype, value)          \
+    G_STMT_START                                          \
+    {                                                     \
+        type __nla_tmp = value;                           \
+        NLA_PUT(msg, attrtype, sizeof(type), &__nla_tmp); \
+    }                                                     \
+    G_STMT_END
+
+#define NLA_PUT_U8(msg, attrtype, value) NLA_PUT_TYPE(msg, uint8_t, attrtype, value)
+
+#define NLA_PUT_S8(msg, attrtype, value) NLA_PUT_TYPE(msg, int8_t, attrtype, value)
+
+#define NLA_PUT_U16(msg, attrtype, value) NLA_PUT_TYPE(msg, uint16_t, attrtype, value)
+
+#define NLA_PUT_U32(msg, attrtype, value) NLA_PUT_TYPE(msg, uint32_t, attrtype, value)
+
+#define NLA_PUT_S32(msg, attrtype, value) NLA_PUT_TYPE(msg, int32_t, attrtype, value)
+
+#define NLA_PUT_U64(msg, attrtype, value) NLA_PUT_TYPE(msg, uint64_t, attrtype, value)
+
+#define NLA_PUT_STRING(msg, attrtype, value) NLA_PUT(msg, attrtype, (int) strlen(value) + 1, value)
+
+#define NLA_PUT_FLAG(msg, attrtype) NLA_PUT(msg, attrtype, 0, NULL)
+
+struct nlattr *nla_find(const struct nlattr *head, int len, int attrtype);
+
+static inline int
+nla_ok(const struct nlattr *nla, int remaining)
+{
+    return remaining >= (int) sizeof(*nla) && nla->nla_len >= sizeof(*nla)
+           && nla->nla_len <= remaining;
+}
+
+static inline struct nlattr *
+nla_next(const struct nlattr *nla, int *remaining)
+{
+    int totlen = NLA_ALIGN(nla->nla_len);
+
+    *remaining -= totlen;
+    return (struct nlattr *) ((char *) nla + totlen);
+}
+
+#define nla_for_each_attr(pos, head, len, rem) \
+    for (pos = head, rem = len; nla_ok(pos, rem); pos = nla_next(pos, &(rem)))
+
+#define nla_for_each_nested(pos, nla, rem)                                            \
+    for (pos = (struct nlattr *) nla_data(nla), rem = nla_len(nla); nla_ok(pos, rem); \
+         pos = nla_next(pos, &(rem)))
+
+void           nla_nest_cancel(struct nl_msg *msg, const struct nlattr *attr);
+struct nlattr *nla_nest_start(struct nl_msg *msg, int attrtype);
+int            nla_nest_end(struct nl_msg *msg, struct nlattr *start);
+
+int nla_parse(struct nlattr *          tb[],
+              int                      maxtype,
+              struct nlattr *          head,
+              int                      len,
+              const struct nla_policy *policy);
+
+#define nla_parse_arr(tb, head, len, policy)                            \
+    ({                                                                  \
+        _nl_static_assert_tb((tb), (policy));                           \
+                                                                        \
+        nla_parse((tb), G_N_ELEMENTS(tb) - 1, (head), (len), (policy)); \
+    })
+
+static inline int
+nla_parse_nested(struct nlattr *          tb[],
+                 int                      maxtype,
+                 struct nlattr *          nla,
+                 const struct nla_policy *policy)
+{
+    return nla_parse(tb, maxtype, nla_data(nla), nla_len(nla), policy);
+}
+
+#define nla_parse_nested_arr(tb, nla, policy)                          \
+    ({                                                                 \
+        _nl_static_assert_tb((tb), (policy));                          \
+                                                                       \
+        nla_parse_nested((tb), G_N_ELEMENTS(tb) - 1, (nla), (policy)); \
+    })
+
+/*****************************************************************************/
+
+struct nl_msg *nlmsg_alloc(void);
+
+struct nl_msg *nlmsg_alloc_size(size_t max);
+
+struct nl_msg *nlmsg_alloc_convert(struct nlmsghdr *hdr);
+
+struct nl_msg *nlmsg_alloc_simple(int nlmsgtype, int flags);
+
+void *nlmsg_reserve(struct nl_msg *n, size_t len, int pad);
+
+int nlmsg_append(struct nl_msg *n, const void *data, size_t len, int pad);
+
+#define nlmsg_append_struct(n, data) nlmsg_append(n, (data), sizeof(*(data)), NLMSG_ALIGNTO)
+
+void nlmsg_free(struct nl_msg *msg);
+
+static inline int
+nlmsg_size(int payload)
+{
+    nm_assert(payload >= 0 && payload < G_MAXINT - NLMSG_HDRLEN - 4);
+    return NLMSG_HDRLEN + payload;
+}
+
+static inline int
+nlmsg_total_size(int payload)
+{
+    return NLMSG_ALIGN(nlmsg_size(payload));
+}
+
+static inline int
+nlmsg_ok(const struct nlmsghdr *nlh, int remaining)
+{
+    return (remaining >= (int) sizeof(struct nlmsghdr) && nlh->nlmsg_len >= sizeof(struct nlmsghdr)
+            && nlh->nlmsg_len <= remaining);
+}
+
+static inline struct nlmsghdr *
+nlmsg_next(struct nlmsghdr *nlh, int *remaining)
+{
+    int totlen = NLMSG_ALIGN(nlh->nlmsg_len);
+
+    *remaining -= totlen;
+
+    return (struct nlmsghdr *) ((unsigned char *) nlh + totlen);
+}
+
+int  nlmsg_get_proto(struct nl_msg *msg);
+void nlmsg_set_proto(struct nl_msg *msg, int protocol);
+
+void nlmsg_set_src(struct nl_msg *msg, struct sockaddr_nl *addr);
+
+struct ucred *nlmsg_get_creds(struct nl_msg *msg);
+void          nlmsg_set_creds(struct nl_msg *msg, struct ucred *creds);
+
+static inline void
+_nm_auto_nl_msg_cleanup(struct nl_msg **ptr)
+{
+    nlmsg_free(*ptr);
+}
+#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup)
+
+static inline void *
+nlmsg_data(const struct nlmsghdr *nlh)
+{
+    return (unsigned char *) nlh + NLMSG_HDRLEN;
+}
+
+static inline void *
+nlmsg_tail(const struct nlmsghdr *nlh)
+{
+    return (unsigned char *) nlh + NLMSG_ALIGN(nlh->nlmsg_len);
+}
+
+struct nlmsghdr *nlmsg_hdr(struct nl_msg *n);
+
+static inline int
+nlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen)
+{
+    if (nlh->nlmsg_len < nlmsg_size(hdrlen))
+        return 0;
+
+    return 1;
+}
+
+static inline int
+nlmsg_datalen(const struct nlmsghdr *nlh)
+{
+    return nlh->nlmsg_len - NLMSG_HDRLEN;
+}
+
+static inline int
+nlmsg_attrlen(const struct nlmsghdr *nlh, int hdrlen)
+{
+    return NM_MAX((int) (nlmsg_datalen(nlh) - NLMSG_ALIGN(hdrlen)), 0);
+}
+
+static inline struct nlattr *
+nlmsg_attrdata(const struct nlmsghdr *nlh, int hdrlen)
+{
+    unsigned char *data = nlmsg_data(nlh);
+    return (struct nlattr *) (data + NLMSG_ALIGN(hdrlen));
+}
+
+static inline struct nlattr *
+nlmsg_find_attr(struct nlmsghdr *nlh, int hdrlen, int attrtype)
+{
+    return nla_find(nlmsg_attrdata(nlh, hdrlen), nlmsg_attrlen(nlh, hdrlen), attrtype);
+}
+
+int nlmsg_parse(struct nlmsghdr *        nlh,
+                int                      hdrlen,
+                struct nlattr *          tb[],
+                int                      maxtype,
+                const struct nla_policy *policy);
+
+#define nlmsg_parse_arr(nlh, hdrlen, tb, policy)                            \
+    ({                                                                      \
+        _nl_static_assert_tb((tb), (policy));                               \
+        G_STATIC_ASSERT_EXPR((hdrlen) >= 0);                                \
+                                                                            \
+        nlmsg_parse((nlh), (hdrlen), (tb), G_N_ELEMENTS(tb) - 1, (policy)); \
+    })
+
+struct nlmsghdr *
+nlmsg_put(struct nl_msg *n, uint32_t pid, uint32_t seq, int type, int payload, int flags);
+
+/*****************************************************************************/
+
+#define NL_AUTO_PORT 0
+#define NL_AUTO_SEQ  0
+
+struct nl_sock;
+
+struct nl_sock *nl_socket_alloc(void);
+
+void nl_socket_free(struct nl_sock *sk);
+
+int nl_socket_get_fd(const struct nl_sock *sk);
+
+struct sockaddr_nl *nlmsg_get_dst(struct nl_msg *msg);
+
+size_t nl_socket_get_msg_buf_size(struct nl_sock *sk);
+int    nl_socket_set_msg_buf_size(struct nl_sock *sk, size_t bufsize);
+
+int nl_socket_set_buffer_size(struct nl_sock *sk, int rxbuf, int txbuf);
+
+int nl_socket_set_passcred(struct nl_sock *sk, int state);
+
+int nl_socket_set_nonblocking(const struct nl_sock *sk);
+
+void nl_socket_disable_msg_peek(struct nl_sock *sk);
+
+uint32_t nl_socket_get_local_port(const struct nl_sock *sk);
+
+int nl_socket_add_memberships(struct nl_sock *sk, int group, ...);
+
+int nl_connect(struct nl_sock *sk, int protocol);
+
+int nl_recv(struct nl_sock *    sk,
+            struct sockaddr_nl *nla,
+            unsigned char **    buf,
+            struct ucred *      out_creds,
+            gboolean *          out_creds_has);
+
+int nl_send(struct nl_sock *sk, struct nl_msg *msg);
+
+int nl_send_auto(struct nl_sock *sk, struct nl_msg *msg);
+
+/*****************************************************************************/
+
+enum nl_cb_action {
+    /* Proceed with wathever would come next */
+    NL_OK,
+    /* Skip this message */
+    NL_SKIP,
+    /* Stop parsing altogether and discard remaining messages */
+    NL_STOP,
+};
+
+typedef int (*nl_recvmsg_msg_cb_t)(struct nl_msg *msg, void *arg);
+
+typedef int (*nl_recvmsg_err_cb_t)(struct sockaddr_nl *nla, struct nlmsgerr *nlerr, void *arg);
+
+struct nl_cb {
+    nl_recvmsg_msg_cb_t valid_cb;
+    void *              valid_arg;
+
+    nl_recvmsg_msg_cb_t finish_cb;
+    void *              finish_arg;
+
+    nl_recvmsg_msg_cb_t ack_cb;
+    void *              ack_arg;
+
+    nl_recvmsg_err_cb_t err_cb;
+    void *              err_arg;
+};
+
+int nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr);
+
+int nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen);
+
+void nl_complete_msg(struct nl_sock *sk, struct nl_msg *msg);
+
+int nl_recvmsgs(struct nl_sock *sk, const struct nl_cb *cb);
+
+int nl_wait_for_ack(struct nl_sock *sk, const struct nl_cb *cb);
+
+int nl_socket_set_ext_ack(struct nl_sock *sk, gboolean enable);
+
+/*****************************************************************************/
+
+void *             genlmsg_put(struct nl_msg *msg,
+                               uint32_t       port,
+                               uint32_t       seq,
+                               int            family,
+                               int            hdrlen,
+                               int            flags,
+                               uint8_t        cmd,
+                               uint8_t        version);
+void *             genlmsg_data(const struct genlmsghdr *gnlh);
+void *             genlmsg_user_hdr(const struct genlmsghdr *gnlh);
+struct genlmsghdr *genlmsg_hdr(struct nlmsghdr *nlh);
+void *             genlmsg_user_data(const struct genlmsghdr *gnlh, const int hdrlen);
+struct nlattr *    genlmsg_attrdata(const struct genlmsghdr *gnlh, int hdrlen);
+int                genlmsg_len(const struct genlmsghdr *gnlh);
+int                genlmsg_attrlen(const struct genlmsghdr *gnlh, int hdrlen);
+int                genlmsg_valid_hdr(struct nlmsghdr *nlh, int hdrlen);
+
+int genlmsg_parse(struct nlmsghdr *        nlh,
+                  int                      hdrlen,
+                  struct nlattr *          tb[],
+                  int                      maxtype,
+                  const struct nla_policy *policy);
+
+#define genlmsg_parse_arr(nlh, hdrlen, tb, policy)                            \
+    ({                                                                        \
+        _nl_static_assert_tb((tb), (policy));                                 \
+        G_STATIC_ASSERT_EXPR((hdrlen) >= 0);                                  \
+                                                                              \
+        genlmsg_parse((nlh), (hdrlen), (tb), G_N_ELEMENTS(tb) - 1, (policy)); \
+    })
+
+int genl_ctrl_resolve(struct nl_sock *sk, const char *name);
+
+/*****************************************************************************/
+
+#endif /* __NM_NETLINK_H__ */
diff --git a/shared/nm-platform/nm-platform-utils.c b/shared/nm-platform/nm-platform-utils.c
new file mode 100644
index 00000000..c1c5b1a3
--- /dev/null
+++ b/shared/nm-platform/nm-platform-utils.c
@@ -0,0 +1,1807 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
+
+#include "nm-platform-utils.h"
+
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <linux/ethtool.h>
+#include <linux/sockios.h>
+#include <linux/mii.h>
+#include <linux/if.h>
+#include <linux/version.h>
+#include <linux/rtnetlink.h>
+#include <fcntl.h>
+#include <libudev.h>
+
+#include "nm-base/nm-ethtool-base.h"
+#include "nm-log-core/nm-logging.h"
+
+/*****************************************************************************/
+
+#define ONOFF(bool_val) ((bool_val) ? "on" : "off")
+
+/******************************************************************************
+ * utils
+ *****************************************************************************/
+
+extern char *if_indextoname(unsigned __ifindex, char *__ifname);
+unsigned     if_nametoindex(const char *__ifname);
+
+const char *
+nmp_utils_if_indextoname(int ifindex, char *out_ifname /*IFNAMSIZ*/)
+{
+    g_return_val_if_fail(ifindex > 0, NULL);
+    g_return_val_if_fail(out_ifname, NULL);
+
+    return if_indextoname(ifindex, out_ifname);
+}
+
+int
+nmp_utils_if_nametoindex(const char *ifname)
+{
+    g_return_val_if_fail(ifname, 0);
+
+    return if_nametoindex(ifname);
+}
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE(nm_platform_link_duplex_type_to_string,
+                           NMPlatformLinkDuplexType,
+                           NM_UTILS_LOOKUP_DEFAULT_WARN(NULL),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_PLATFORM_LINK_DUPLEX_UNKNOWN, "unknown"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_PLATFORM_LINK_DUPLEX_FULL, "full"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_PLATFORM_LINK_DUPLEX_HALF, "half"), );
+
+/*****************************************************************************/
+
+typedef struct {
+    int       fd;
+    const int ifindex;
+    char      ifname[IFNAMSIZ];
+} SocketHandle;
+
+#define SOCKET_HANDLE_INIT(_ifindex)     \
+    {                                    \
+        .fd = -1, .ifindex = (_ifindex), \
+    }
+
+static void
+_nm_auto_socket_handle(SocketHandle *shandle)
+{
+    if (shandle->fd >= 0)
+        nm_close(shandle->fd);
+}
+
+#define nm_auto_socket_handle nm_auto(_nm_auto_socket_handle)
+
+/*****************************************************************************/
+
+typedef enum {
+    IOCTL_CALL_DATA_TYPE_NONE,
+    IOCTL_CALL_DATA_TYPE_IFRDATA,
+    IOCTL_CALL_DATA_TYPE_IFRU,
+} IoctlCallDataType;
+
+static int
+_ioctl_call(const char *      log_ioctl_type,
+            const char *      log_subtype,
+            unsigned long int ioctl_request,
+            int               ifindex,
+            int *             inout_fd,
+            char *            inout_ifname,
+            IoctlCallDataType edata_type,
+            gpointer          edata,
+            gsize             edata_size,
+            struct ifreq *    out_ifreq)
+{
+    nm_auto_close int fd_close = -1;
+    int               fd;
+    int               r;
+    gpointer          edata_backup      = NULL;
+    gs_free gpointer  edata_backup_free = NULL;
+    guint             try_count;
+    char              known_ifnames[2][IFNAMSIZ];
+    const char *      failure_reason = NULL;
+    struct ifreq      ifr;
+
+    nm_assert(ifindex > 0);
+    nm_assert(NM_IN_SET(edata_type,
+                        IOCTL_CALL_DATA_TYPE_NONE,
+                        IOCTL_CALL_DATA_TYPE_IFRDATA,
+                        IOCTL_CALL_DATA_TYPE_IFRU));
+    nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_NONE || edata_size == 0);
+    nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_IFRDATA || edata_size > 0);
+    nm_assert(edata_type != IOCTL_CALL_DATA_TYPE_IFRU
+              || (edata_size > 0 && edata_size <= sizeof(ifr.ifr_ifru)));
+    nm_assert(edata_size == 0 || edata);
+
+    /* open a file descriptor (or use the one provided). */
+    if (inout_fd && *inout_fd >= 0)
+        fd = *inout_fd;
+    else {
+        fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+        if (fd < 0) {
+            r              = -NM_ERRNO_NATIVE(errno);
+            failure_reason = "failed creating socket or ioctl";
+            goto out;
+        }
+        if (inout_fd)
+            *inout_fd = fd;
+        else
+            fd_close = fd;
+    }
+
+    /* resolve the ifindex to name (or use the one provided). */
+    if (inout_ifname && inout_ifname[0])
+        nm_utils_ifname_cpy(known_ifnames[0], inout_ifname);
+    else {
+        if (!nmp_utils_if_indextoname(ifindex, known_ifnames[0])) {
+            failure_reason = "cannot resolve ifindex";
+            r              = -ENODEV;
+            goto out;
+        }
+        if (inout_ifname)
+            nm_utils_ifname_cpy(inout_ifname, known_ifnames[0]);
+    }
+
+    /* we might need to retry the request. Backup edata so that we can
+     * restore it on retry. */
+    if (edata_size > 0)
+        edata_backup = nm_memdup_maybe_a(500, edata, edata_size, &edata_backup_free);
+
+    try_count = 0;
+
+again:
+{
+    const char *ifname = known_ifnames[try_count % 2];
+
+    nm_assert(ifindex > 0);
+    nm_assert(ifname && nm_utils_ifname_valid_kernel(ifname, NULL));
+    nm_assert(fd >= 0);
+
+    memset(&ifr, 0, sizeof(ifr));
+    nm_utils_ifname_cpy(ifr.ifr_name, ifname);
+    if (edata_type == IOCTL_CALL_DATA_TYPE_IFRDATA)
+        ifr.ifr_data = edata;
+    else if (edata_type == IOCTL_CALL_DATA_TYPE_IFRU)
+        memcpy(&ifr.ifr_ifru, edata, NM_MIN(edata_size, sizeof(ifr.ifr_ifru)));
+
+    if (ioctl(fd, ioctl_request, &ifr) < 0) {
+        r = -NM_ERRNO_NATIVE(errno);
+        nm_log_trace(LOGD_PLATFORM,
+                     "%s[%d]: %s, %s: failed: %s",
+                     log_ioctl_type,
+                     ifindex,
+                     log_subtype,
+                     ifname,
+                     nm_strerror_native(-r));
+    } else {
+        r = 0;
+        nm_log_trace(LOGD_PLATFORM,
+                     "%s[%d]: %s, %s: success",
+                     log_ioctl_type,
+                     ifindex,
+                     log_subtype,
+                     ifname);
+    }
+}
+
+    try_count++;
+
+    /* resolve the name again to see whether the ifindex still has the same name. */
+    if (!nmp_utils_if_indextoname(ifindex, known_ifnames[try_count % 2])) {
+        /* we could not find the ifindex again. Probably the device just got
+         * removed.
+         *
+         * In both cases we return the error code we got from ioctl above.
+         * Either it failed because the device was gone already or it still
+         * managed to complete the call. In both cases, the error code is good. */
+        failure_reason =
+            "cannot resolve ifindex after ioctl call. Probably the device was just removed";
+        goto out;
+    }
+
+    /* check whether the ifname changed in the meantime. If yes, would render the result
+     * invalid. Note that this cannot detect every race regarding renames, for example:
+     *
+     *  - if_indextoname(#10) gives eth0
+     *  - rename(#10) => eth0_tmp
+     *  - rename(#11) => eth0
+     *  - ioctl(eth0) (wrongly fetching #11, formerly eth1)
+     *  - rename(#11) => eth_something
+     *  - rename(#10) => eth0
+     *  - if_indextoname(#10) gives eth0
+     */
+    if (!nm_streq(known_ifnames[0], known_ifnames[1])) {
+        gboolean retry;
+
+        /* we detected a possible(!) rename.
+         *
+         * For getters it's straight forward to just retry the call.
+         *
+         * For setters we also always retry. If our previous call operated on the right device,
+         * calling it again should have no bad effect (just setting the same thing more than once).
+         *
+         * The only potential bad thing is if there was a race involving swapping names, and we just
+         * set the ioctl option on the wrong device. But then the bad thing already happenned and
+         * we cannot detect it (nor do anything about it). At least, we can retry and set the
+         * option on the right interface. */
+        retry = (try_count < 5);
+
+        nm_log_trace(LOGD_PLATFORM,
+                     "%s[%d]: %s: rename detected from \"%s\" to \"%s\". %s",
+                     log_ioctl_type,
+                     ifindex,
+                     log_subtype,
+                     known_ifnames[(try_count - 1) % 2],
+                     known_ifnames[try_count % 2],
+                     retry ? "Retry" : "No retry");
+        if (inout_ifname)
+            nm_utils_ifname_cpy(inout_ifname, known_ifnames[try_count % 2]);
+        if (retry) {
+            if (edata_size > 0)
+                memcpy(edata, edata_backup, edata_size);
+            goto again;
+        }
+    }
+
+out:
+    if (failure_reason) {
+        nm_log_trace(LOGD_PLATFORM,
+                     "%s[%d]: %s: %s: %s",
+                     log_ioctl_type,
+                     ifindex,
+                     log_subtype,
+                     failure_reason,
+                     r < 0 ? nm_strerror_native(-r) : "assume success");
+    }
+    if (r >= 0)
+        NM_SET_OUT(out_ifreq, ifr);
+    return r;
+}
+
+/******************************************************************************
+ * ethtool
+ *****************************************************************************/
+
+static NM_UTILS_ENUM2STR_DEFINE(_ethtool_cmd_to_string,
+                                guint32,
+                                NM_UTILS_ENUM2STR(ETHTOOL_GCOALESCE, "ETHTOOL_GCOALESCE"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_GDRVINFO, "ETHTOOL_GDRVINFO"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_GFEATURES, "ETHTOOL_GFEATURES"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_GLINK, "ETHTOOL_GLINK"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_GPERMADDR, "ETHTOOL_GPERMADDR"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_GRINGPARAM, "ETHTOOL_GRINGPARAM"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_GSET, "ETHTOOL_GSET"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_GSSET_INFO, "ETHTOOL_GSSET_INFO"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_GSTATS, "ETHTOOL_GSTATS"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_GSTRINGS, "ETHTOOL_GSTRINGS"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_GWOL, "ETHTOOL_GWOL"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_SCOALESCE, "ETHTOOL_SCOALESCE"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_SFEATURES, "ETHTOOL_SFEATURES"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_SRINGPARAM, "ETHTOOL_SRINGPARAM"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_SSET, "ETHTOOL_SSET"),
+                                NM_UTILS_ENUM2STR(ETHTOOL_SWOL, "ETHTOOL_SWOL"), );
+
+static const char *
+_ethtool_edata_to_string(gpointer edata, gsize edata_size, char *sbuf, gsize sbuf_len)
+{
+    nm_assert(edata);
+    nm_assert(edata_size >= sizeof(guint32));
+    nm_assert((((intptr_t) edata) % _nm_alignof(guint32)) == 0);
+
+    return _ethtool_cmd_to_string(*((guint32 *) edata), sbuf, sbuf_len);
+}
+
+/*****************************************************************************/
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
+    #define ethtool_cmd_speed(pedata) ((pedata)->speed)
+
+    #define ethtool_cmd_speed_set(pedata, speed) \
+        G_STMT_START                             \
+        {                                        \
+            (pedata)->speed = (guint16)(speed);  \
+        }                                        \
+        G_STMT_END
+#endif
+
+static int
+_ethtool_call_handle(SocketHandle *shandle, gpointer edata, gsize edata_size)
+{
+    char sbuf[50];
+
+    return _ioctl_call("ethtool",
+                       _ethtool_edata_to_string(edata, edata_size, sbuf, sizeof(sbuf)),
+                       SIOCETHTOOL,
+                       shandle->ifindex,
+                       &shandle->fd,
+                       shandle->ifname,
+                       IOCTL_CALL_DATA_TYPE_IFRDATA,
+                       edata,
+                       edata_size,
+                       NULL);
+}
+
+static int
+_ethtool_call_once(int ifindex, gpointer edata, gsize edata_size)
+{
+    char sbuf[50];
+
+    return _ioctl_call("ethtool",
+                       _ethtool_edata_to_string(edata, edata_size, sbuf, sizeof(sbuf)),
+                       SIOCETHTOOL,
+                       ifindex,
+                       NULL,
+                       NULL,
+                       IOCTL_CALL_DATA_TYPE_IFRDATA,
+                       edata,
+                       edata_size,
+                       NULL);
+}
+
+/*****************************************************************************/
+
+static struct ethtool_gstrings *
+ethtool_get_stringset(SocketHandle *shandle, int stringset_id)
+{
+    struct {
+        struct ethtool_sset_info info;
+        guint32                  sentinel;
+    } sset_info = {
+        .info.cmd       = ETHTOOL_GSSET_INFO,
+        .info.reserved  = 0,
+        .info.sset_mask = (1ULL << stringset_id),
+    };
+    const guint32 *                  pdata;
+    gs_free struct ethtool_gstrings *gstrings = NULL;
+    gsize                            gstrings_len;
+    guint32                          i, len;
+
+    if (_ethtool_call_handle(shandle, &sset_info, sizeof(sset_info)) < 0)
+        return NULL;
+    if (!sset_info.info.sset_mask)
+        return NULL;
+
+    pdata = (guint32 *) sset_info.info.data;
+
+    len = *pdata;
+
+    gstrings_len         = sizeof(*gstrings) + (len * ETH_GSTRING_LEN);
+    gstrings             = g_malloc0(gstrings_len);
+    gstrings->cmd        = ETHTOOL_GSTRINGS;
+    gstrings->string_set = stringset_id;
+    gstrings->len        = len;
+    if (gstrings->len > 0) {
+        if (_ethtool_call_handle(shandle, gstrings, gstrings_len) < 0)
+            return NULL;
+        for (i = 0; i < gstrings->len; i++) {
+            /* ensure NUL terminated */
+            gstrings->data[i * ETH_GSTRING_LEN + (ETH_GSTRING_LEN - 1)] = '\0';
+        }
+    }
+
+    return g_steal_pointer(&gstrings);
+}
+
+static int
+ethtool_gstrings_find(const struct ethtool_gstrings *gstrings, const char *needle)
+{
+    guint32 i;
+
+    /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN.
+     * that means, we cannot possibly request longer names. */
+    nm_assert(needle && strlen(needle) < ETH_GSTRING_LEN);
+
+    for (i = 0; i < gstrings->len; i++) {
+        if (nm_streq((char *) &gstrings->data[i * ETH_GSTRING_LEN], needle))
+            return i;
+    }
+    return -1;
+}
+
+static int
+ethtool_get_stringset_index(SocketHandle *shandle, int stringset_id, const char *needle)
+{
+    gs_free struct ethtool_gstrings *gstrings = NULL;
+
+    /* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN.
+     * that means, we cannot possibly request longer names. */
+    nm_assert(needle && strlen(needle) < ETH_GSTRING_LEN);
+
+    gstrings = ethtool_get_stringset(shandle, stringset_id);
+    if (gstrings)
+        return ethtool_gstrings_find(gstrings, needle);
+    return -1;
+}
+
+/*****************************************************************************/
+
+static const NMEthtoolFeatureInfo _ethtool_feature_infos[_NM_ETHTOOL_ID_FEATURE_NUM] = {
+#define ETHT_FEAT(eid, ...)                                        \
+    {                                                              \
+        .ethtool_id = eid, .n_kernel_names = NM_NARG(__VA_ARGS__), \
+        .kernel_names = ((const char *const[]){__VA_ARGS__}),      \
+    }
+
+    /* the order does only matter for one thing: if it happens that more than one NMEthtoolID
+     * reference the same kernel-name, then the one that is mentioned *later* will win in
+     * case these NMEthtoolIDs are set. That mostly only makes sense for ethtool-ids which
+     * refer to multiple features ("feature-tso"), while also having more specific ids
+     * ("feature-tx-tcp-segmentation"). */
+
+    /* names from ethtool utility, which are aliases for multiple features. */
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_SG, "tx-scatter-gather", "tx-scatter-gather-fraglist"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TSO,
+              "tx-tcp-segmentation",
+              "tx-tcp-ecn-segmentation",
+              "tx-tcp-mangleid-segmentation",
+              "tx-tcp6-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX,
+              "tx-checksum-ipv4",
+              "tx-checksum-ip-generic",
+              "tx-checksum-ipv6",
+              "tx-checksum-fcoe-crc",
+              "tx-checksum-sctp"),
+
+    /* names from ethtool utility, which are aliases for one feature. */
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_GRO, "rx-gro"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_GSO, "tx-generic-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_LRO, "rx-lro"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_NTUPLE, "rx-ntuple-filter"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX, "rx-checksum"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RXHASH, "rx-hashing"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert"),
+
+    /* names of features, as known by kernel. */
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD, "esp-hw-offload"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD, "esp-tx-csum-hw-offload"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_FCOE_MTU, "fcoe-mtu"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_HIGHDMA, "highdma"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD, "hw-tc-offload"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD, "l2-fwd-offload"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_LOOPBACK, "loopback"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_MACSEC_HW_OFFLOAD, "macsec-hw-offload"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_ALL, "rx-all"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_FCS, "rx-fcs"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_GRO_HW, "rx-gro-hw"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_GRO_LIST, "rx-gro-list"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_UDP_GRO_FORWARDING, "rx-udp-gro-forwarding"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD, "rx-udp_tunnel-port-offload"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER, "rx-vlan-filter"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER, "rx-vlan-stag-filter"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE, "rx-vlan-stag-hw-parse"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD, "tls-hw-record"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_RX_OFFLOAD, "tls-hw-rx-offload"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD, "tls-hw-tx-offload"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC, "tx-checksum-fcoe-crc"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4, "tx-checksum-ipv4"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6, "tx-checksum-ipv6"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC, "tx-checksum-ip-generic"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP, "tx-checksum-sctp"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION, "tx-esp-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION, "tx-fcoe-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION, "tx-gre-csum-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION, "tx-gre-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_LIST, "tx-gso-list"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL, "tx-gso-partial"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST, "tx-gso-robust"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION, "tx-ipxip4-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION, "tx-ipxip6-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY, "tx-nocache-copy"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER, "tx-scatter-gather"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST, "tx-scatter-gather-fraglist"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION, "tx-sctp-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION, "tx-tcp6-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION, "tx-tcp-ecn-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION, "tx-tcp-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_TUNNEL_REMCSUM_SEGMENTATION,
+              "tx-tunnel-remcsum-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION, "tx-udp-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION, "tx-udp_tnl-segmentation"),
+    ETHT_FEAT(NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT, "tx-vlan-stag-hw-insert"),
+};
+
+/* the number of kernel features that we handle. It essentially is the sum of all
+ * kernel_names. So, all ethtool-ids that reference exactly one kernel-name
+ * (_NM_ETHTOOL_ID_FEATURE_NUM) + some extra, for ethtool-ids that are aliases
+ * for multiple kernel-names. */
+#define N_ETHTOOL_KERNEL_FEATURES (((guint) _NM_ETHTOOL_ID_FEATURE_NUM) + 8u)
+
+static void
+_ASSERT_ethtool_feature_infos(void)
+{
+#if NM_MORE_ASSERTS > 10
+    guint i, k, n;
+    bool  found[_NM_ETHTOOL_ID_FEATURE_NUM] = {};
+
+    G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(_ethtool_feature_infos) == _NM_ETHTOOL_ID_FEATURE_NUM);
+
+    n = 0;
+    for (i = 0; i < G_N_ELEMENTS(_ethtool_feature_infos); i++) {
+        NMEthtoolFeatureState       kstate;
+        const NMEthtoolFeatureInfo *inf = &_ethtool_feature_infos[i];
+
+        g_assert(inf->ethtool_id >= _NM_ETHTOOL_ID_FEATURE_FIRST);
+        g_assert(inf->ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST);
+        g_assert(inf->n_kernel_names > 0);
+
+        for (k = 0; k < i; k++)
+            g_assert(inf->ethtool_id != _ethtool_feature_infos[k].ethtool_id);
+
+        g_assert(!found[_NM_ETHTOOL_ID_FEATURE_AS_IDX(inf->ethtool_id)]);
+        found[_NM_ETHTOOL_ID_FEATURE_AS_IDX(inf->ethtool_id)] = TRUE;
+
+        kstate.idx_kernel_name = inf->n_kernel_names - 1;
+        g_assert((guint) kstate.idx_kernel_name == (guint)(inf->n_kernel_names - 1));
+
+        n += inf->n_kernel_names;
+        for (k = 0; k < inf->n_kernel_names; k++) {
+            const char *name = inf->kernel_names[k];
+
+            g_assert(nm_utils_strv_find_first((char **) inf->kernel_names, k, name) < 0);
+
+            /* these offload features are only informational and cannot be set from user-space
+             * (NETIF_F_NEVER_CHANGE). We should not track them in _ethtool_feature_infos. */
+            g_assert(!nm_streq(name, "netns-local"));
+            g_assert(!nm_streq(name, "tx-lockless"));
+            g_assert(!nm_streq(name, "vlan-challenged"));
+        }
+    }
+
+    for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++)
+        g_assert(found[i]);
+
+    g_assert(n == N_ETHTOOL_KERNEL_FEATURES);
+#endif
+}
+
+static NMEthtoolFeatureStates *
+ethtool_get_features(SocketHandle *shandle)
+{
+    gs_free NMEthtoolFeatureStates * states      = NULL;
+    gs_free struct ethtool_gstrings *ss_features = NULL;
+
+    _ASSERT_ethtool_feature_infos();
+
+    ss_features = ethtool_get_stringset(shandle, ETH_SS_FEATURES);
+    if (!ss_features)
+        return NULL;
+
+    if (ss_features->len > 0) {
+        gs_free struct ethtool_gfeatures *  gfeatures_free = NULL;
+        struct ethtool_gfeatures *          gfeatures;
+        gsize                               gfeatures_len;
+        guint                               idx;
+        const NMEthtoolFeatureState *       states_list0   = NULL;
+        const NMEthtoolFeatureState *const *states_plist0  = NULL;
+        guint                               states_plist_n = 0;
+
+        gfeatures_len = sizeof(struct ethtool_gfeatures)
+                        + (NM_DIV_ROUND_UP(ss_features->len, 32u) * sizeof(gfeatures->features[0]));
+        gfeatures       = nm_malloc0_maybe_a(300, gfeatures_len, &gfeatures_free);
+        gfeatures->cmd  = ETHTOOL_GFEATURES;
+        gfeatures->size = NM_DIV_ROUND_UP(ss_features->len, 32u);
+        if (_ethtool_call_handle(shandle, gfeatures, gfeatures_len) < 0)
+            return NULL;
+
+        for (idx = 0; idx < G_N_ELEMENTS(_ethtool_feature_infos); idx++) {
+            const NMEthtoolFeatureInfo *info = &_ethtool_feature_infos[idx];
+            guint                       idx_kernel_name;
+
+            for (idx_kernel_name = 0; idx_kernel_name < info->n_kernel_names; idx_kernel_name++) {
+                NMEthtoolFeatureState *kstate;
+                const char *           kernel_name = info->kernel_names[idx_kernel_name];
+                int                    i_feature;
+                guint                  i_block;
+                guint32                i_flag;
+
+                i_feature = ethtool_gstrings_find(ss_features, kernel_name);
+                if (i_feature < 0)
+                    continue;
+
+                i_block = ((guint) i_feature) / 32u;
+                i_flag  = (guint32)(1u << (((guint) i_feature) % 32u));
+
+                if (!states) {
+                    states = g_malloc0(
+                        sizeof(NMEthtoolFeatureStates)
+                        + (N_ETHTOOL_KERNEL_FEATURES * sizeof(NMEthtoolFeatureState))
+                        + ((N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos))
+                           * sizeof(NMEthtoolFeatureState *)));
+                    states_list0          = &states->states_list[0];
+                    states_plist0         = (gpointer) &states_list0[N_ETHTOOL_KERNEL_FEATURES];
+                    states->n_ss_features = ss_features->len;
+                }
+
+                nm_assert(states->n_states < N_ETHTOOL_KERNEL_FEATURES);
+                kstate = (NMEthtoolFeatureState *) &states_list0[states->n_states];
+                states->n_states++;
+
+                kstate->info            = info;
+                kstate->idx_ss_features = i_feature;
+                kstate->idx_kernel_name = idx_kernel_name;
+                kstate->available       = !!(gfeatures->features[i_block].available & i_flag);
+                kstate->requested       = !!(gfeatures->features[i_block].requested & i_flag);
+                kstate->active          = !!(gfeatures->features[i_block].active & i_flag);
+                kstate->never_changed   = !!(gfeatures->features[i_block].never_changed & i_flag);
+
+                nm_assert(states_plist_n
+                          < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos));
+
+                if (!states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)])
+                    states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)] =
+                        &states_plist0[states_plist_n];
+                ((const NMEthtoolFeatureState **) states_plist0)[states_plist_n] = kstate;
+                states_plist_n++;
+            }
+
+            if (states && states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX(info->ethtool_id)]) {
+                nm_assert(states_plist_n
+                          < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS(_ethtool_feature_infos));
+                nm_assert(!states_plist0[states_plist_n]);
+                states_plist_n++;
+            }
+        }
+    }
+
+    return g_steal_pointer(&states);
+}
+
+NMEthtoolFeatureStates *
+nmp_utils_ethtool_get_features(int ifindex)
+{
+    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
+    NMEthtoolFeatureStates *           features;
+
+    g_return_val_if_fail(ifindex > 0, 0);
+
+    features = ethtool_get_features(&shandle);
+
+    if (!features) {
+        nm_log_trace(LOGD_PLATFORM,
+                     "ethtool[%d]: %s: failure getting features",
+                     ifindex,
+                     "get-features");
+        return NULL;
+    }
+
+    nm_log_trace(LOGD_PLATFORM,
+                 "ethtool[%d]: %s: retrieved kernel features",
+                 ifindex,
+                 "get-features");
+    return features;
+}
+
+static const char *
+_ethtool_feature_state_to_string(char *                       buf,
+                                 gsize                        buf_size,
+                                 const NMEthtoolFeatureState *s,
+                                 const char *                 prefix)
+{
+    int l;
+
+    l = g_snprintf(buf,
+                   buf_size,
+                   "%s %s%s",
+                   prefix ?: "",
+                   ONOFF(s->active),
+                   (!s->available || s->never_changed)
+                       ? ", [fixed]"
+                       : ((s->requested != s->active)
+                              ? (s->requested ? ", [requested on]" : ", [requested off]")
+                              : ""));
+    nm_assert(l < buf_size);
+    return buf;
+}
+
+gboolean
+nmp_utils_ethtool_set_features(
+    int                           ifindex,
+    const NMEthtoolFeatureStates *features,
+    const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+    gboolean            do_set /* or reset */)
+{
+    nm_auto_socket_handle SocketHandle shandle        = SOCKET_HANDLE_INIT(ifindex);
+    gs_free struct ethtool_sfeatures * sfeatures_free = NULL;
+    struct ethtool_sfeatures *         sfeatures;
+    gsize                              sfeatures_len;
+    int                                r;
+    guint                              i, j;
+    struct {
+        const NMEthtoolFeatureState *f_state;
+        NMOptionBool                 requested;
+    } set_states[N_ETHTOOL_KERNEL_FEATURES];
+    guint    set_states_n = 0;
+    gboolean success      = TRUE;
+
+    g_return_val_if_fail(ifindex > 0, 0);
+    g_return_val_if_fail(features, 0);
+    g_return_val_if_fail(requested, 0);
+
+    nm_assert(features->n_states <= N_ETHTOOL_KERNEL_FEATURES);
+
+    for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) {
+        const NMEthtoolFeatureState *const *states_indexed;
+
+        if (requested[i] == NM_OPTION_BOOL_DEFAULT)
+            continue;
+
+        if (!(states_indexed = features->states_indexed[i])) {
+            if (do_set) {
+                nm_log_trace(LOGD_PLATFORM,
+                             "ethtool[%d]: %s: set feature %s: skip (not found)",
+                             ifindex,
+                             "set-features",
+                             nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname);
+                success = FALSE;
+            }
+            continue;
+        }
+
+        for (j = 0; states_indexed[j]; j++) {
+            const NMEthtoolFeatureState *s = states_indexed[j];
+            char                         sbuf[255];
+
+            if (set_states_n >= G_N_ELEMENTS(set_states))
+                g_return_val_if_reached(FALSE);
+
+            if (s->never_changed) {
+                nm_log_trace(LOGD_PLATFORM,
+                             "ethtool[%d]: %s: %s feature %s (%s): %s, %s (skip feature marked as "
+                             "never changed)",
+                             ifindex,
+                             "set-features",
+                             do_set ? "set" : "reset",
+                             nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
+                             s->info->kernel_names[s->idx_kernel_name],
+                             ONOFF(do_set ? requested[i] == NM_OPTION_BOOL_TRUE : s->active),
+                             _ethtool_feature_state_to_string(sbuf,
+                                                              sizeof(sbuf),
+                                                              s,
+                                                              do_set ? " currently:" : " before:"));
+                continue;
+            }
+
+            nm_log_trace(LOGD_PLATFORM,
+                         "ethtool[%d]: %s: %s feature %s (%s): %s, %s",
+                         ifindex,
+                         "set-features",
+                         do_set ? "set" : "reset",
+                         nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
+                         s->info->kernel_names[s->idx_kernel_name],
+                         ONOFF(do_set ? requested[i] == NM_OPTION_BOOL_TRUE : s->active),
+                         _ethtool_feature_state_to_string(sbuf,
+                                                          sizeof(sbuf),
+                                                          s,
+                                                          do_set ? " currently:" : " before:"));
+
+            if (do_set && (!s->available || s->never_changed)
+                && (s->active != (requested[i] == NM_OPTION_BOOL_TRUE))) {
+                /* we request to change a flag which kernel reported as fixed.
+                 * While the ethtool operation will silently succeed, mark the request
+                 * as failure. */
+                success = FALSE;
+            }
+
+            set_states[set_states_n].f_state   = s;
+            set_states[set_states_n].requested = requested[i];
+            set_states_n++;
+        }
+    }
+
+    if (set_states_n == 0) {
+        nm_log_trace(LOGD_PLATFORM,
+                     "ethtool[%d]: %s: no feature requested",
+                     ifindex,
+                     "set-features");
+        return TRUE;
+    }
+
+    sfeatures_len =
+        sizeof(struct ethtool_sfeatures)
+        + (NM_DIV_ROUND_UP(features->n_ss_features, 32U) * sizeof(sfeatures->features[0]));
+    sfeatures       = nm_malloc0_maybe_a(300, sfeatures_len, &sfeatures_free);
+    sfeatures->cmd  = ETHTOOL_SFEATURES;
+    sfeatures->size = NM_DIV_ROUND_UP(features->n_ss_features, 32U);
+
+    for (i = 0; i < set_states_n; i++) {
+        const NMEthtoolFeatureState *s = set_states[i].f_state;
+        guint                        i_block;
+        guint32                      i_flag;
+        gboolean                     is_requested;
+
+        i_block = s->idx_ss_features / 32u;
+        i_flag  = (guint32)(1u << (s->idx_ss_features % 32u));
+
+        sfeatures->features[i_block].valid |= i_flag;
+
+        if (do_set)
+            is_requested = (set_states[i].requested == NM_OPTION_BOOL_TRUE);
+        else
+            is_requested = s->active;
+
+        if (is_requested)
+            sfeatures->features[i_block].requested |= i_flag;
+        else
+            sfeatures->features[i_block].requested &= ~i_flag;
+    }
+
+    r = _ethtool_call_handle(&shandle, sfeatures, sfeatures_len);
+    if (r < 0) {
+        success = FALSE;
+        nm_log_trace(LOGD_PLATFORM,
+                     "ethtool[%d]: %s: failure setting features (%s)",
+                     ifindex,
+                     "set-features",
+                     nm_strerror_native(-r));
+        return FALSE;
+    }
+
+    nm_log_trace(LOGD_PLATFORM,
+                 "ethtool[%d]: %s: %s",
+                 ifindex,
+                 "set-features",
+                 success ? "successfully setting features"
+                         : "at least some of the features were not successfully set");
+    return success;
+}
+
+static gboolean
+ethtool_get_coalesce(SocketHandle *shandle, NMEthtoolCoalesceState *coalesce)
+{
+    struct ethtool_coalesce eth_data;
+
+    eth_data.cmd = ETHTOOL_GCOALESCE;
+
+    if (_ethtool_call_handle(shandle, &eth_data, sizeof(struct ethtool_coalesce)) != 0)
+        return FALSE;
+
+    *coalesce = (NMEthtoolCoalesceState){
+        .s = {
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)] =
+                eth_data.rx_coalesce_usecs,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES)] =
+                eth_data.rx_max_coalesced_frames,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)] =
+                eth_data.rx_coalesce_usecs_irq,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)] =
+                eth_data.rx_max_coalesced_frames_irq,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS)] =
+                eth_data.tx_coalesce_usecs,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES)] =
+                eth_data.tx_max_coalesced_frames,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)] =
+                eth_data.tx_coalesce_usecs_irq,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)] =
+                eth_data.tx_max_coalesced_frames_irq,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)] =
+                eth_data.stats_block_coalesce_usecs,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)] =
+                eth_data.use_adaptive_rx_coalesce,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)] =
+                eth_data.use_adaptive_tx_coalesce,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)] =
+                eth_data.pkt_rate_low,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)] =
+                eth_data.rx_coalesce_usecs_low,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)] =
+                eth_data.rx_max_coalesced_frames_low,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)] =
+                eth_data.tx_coalesce_usecs_low,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)] =
+                eth_data.tx_max_coalesced_frames_low,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)] =
+                eth_data.pkt_rate_high,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)] =
+                eth_data.rx_coalesce_usecs_high,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)] =
+                eth_data.rx_max_coalesced_frames_high,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)] =
+                eth_data.tx_coalesce_usecs_high,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)] =
+                eth_data.tx_max_coalesced_frames_high,
+            [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)] =
+                eth_data.rate_sample_interval,
+        }};
+    return TRUE;
+}
+
+gboolean
+nmp_utils_ethtool_get_coalesce(int ifindex, NMEthtoolCoalesceState *coalesce)
+{
+    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+    g_return_val_if_fail(coalesce, FALSE);
+
+    if (!ethtool_get_coalesce(&shandle, coalesce)) {
+        nm_log_trace(LOGD_PLATFORM,
+                     "ethtool[%d]: %s: failure getting coalesce settings",
+                     ifindex,
+                     "get-coalesce");
+        return FALSE;
+    }
+
+    nm_log_trace(LOGD_PLATFORM,
+                 "ethtool[%d]: %s: retrieved kernel coalesce settings",
+                 ifindex,
+                 "get-coalesce");
+    return TRUE;
+}
+
+static gboolean
+ethtool_set_coalesce(SocketHandle *shandle, const NMEthtoolCoalesceState *coalesce)
+{
+    struct ethtool_coalesce eth_data;
+    gboolean                success;
+
+    nm_assert(shandle);
+    nm_assert(coalesce);
+
+    eth_data = (struct ethtool_coalesce){
+        .cmd = ETHTOOL_SCOALESCE,
+        .rx_coalesce_usecs =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)],
+        .rx_max_coalesced_frames =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES)],
+        .rx_coalesce_usecs_irq =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)],
+        .rx_max_coalesced_frames_irq =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)],
+        .tx_coalesce_usecs =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS)],
+        .tx_max_coalesced_frames =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES)],
+        .tx_coalesce_usecs_irq =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)],
+        .tx_max_coalesced_frames_irq =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)],
+        .stats_block_coalesce_usecs =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)],
+        .use_adaptive_rx_coalesce =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)],
+        .use_adaptive_tx_coalesce =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)],
+        .pkt_rate_low =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)],
+        .rx_coalesce_usecs_low =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)],
+        .rx_max_coalesced_frames_low =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)],
+        .tx_coalesce_usecs_low =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)],
+        .tx_max_coalesced_frames_low =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)],
+        .pkt_rate_high =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)],
+        .rx_coalesce_usecs_high =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)],
+        .rx_max_coalesced_frames_high =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)],
+        .tx_coalesce_usecs_high =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)],
+        .tx_max_coalesced_frames_high =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)],
+        .rate_sample_interval =
+            coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)],
+    };
+
+    success = (_ethtool_call_handle(shandle, &eth_data, sizeof(struct ethtool_coalesce)) == 0);
+    return success;
+}
+
+gboolean
+nmp_utils_ethtool_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coalesce)
+{
+    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+    g_return_val_if_fail(coalesce, FALSE);
+
+    if (!ethtool_set_coalesce(&shandle, coalesce)) {
+        nm_log_trace(LOGD_PLATFORM,
+                     "ethtool[%d]: %s: failure setting coalesce settings",
+                     ifindex,
+                     "set-coalesce");
+        return FALSE;
+    }
+
+    nm_log_trace(LOGD_PLATFORM,
+                 "ethtool[%d]: %s: set kernel coalesce settings",
+                 ifindex,
+                 "set-coalesce");
+    return TRUE;
+}
+
+static gboolean
+ethtool_get_ring(SocketHandle *shandle, NMEthtoolRingState *ring)
+{
+    struct ethtool_ringparam eth_data;
+
+    eth_data.cmd = ETHTOOL_GRINGPARAM;
+
+    if (_ethtool_call_handle(shandle, &eth_data, sizeof(struct ethtool_ringparam)) != 0)
+        return FALSE;
+
+    ring->rx_pending       = eth_data.rx_pending;
+    ring->rx_jumbo_pending = eth_data.rx_jumbo_pending;
+    ring->rx_mini_pending  = eth_data.rx_mini_pending;
+    ring->tx_pending       = eth_data.tx_pending;
+
+    return TRUE;
+}
+
+gboolean
+nmp_utils_ethtool_get_ring(int ifindex, NMEthtoolRingState *ring)
+{
+    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+    g_return_val_if_fail(ring, FALSE);
+
+    if (!ethtool_get_ring(&shandle, ring)) {
+        nm_log_trace(LOGD_PLATFORM,
+                     "ethtool[%d]: %s: failure getting ring settings",
+                     ifindex,
+                     "get-ring");
+        return FALSE;
+    }
+
+    nm_log_trace(LOGD_PLATFORM,
+                 "ethtool[%d]: %s: retrieved kernel ring settings",
+                 ifindex,
+                 "get-ring");
+    return TRUE;
+}
+
+static gboolean
+ethtool_set_ring(SocketHandle *shandle, const NMEthtoolRingState *ring)
+{
+    gboolean                 success;
+    struct ethtool_ringparam eth_data;
+
+    g_return_val_if_fail(shandle, FALSE);
+    g_return_val_if_fail(ring, FALSE);
+
+    eth_data = (struct ethtool_ringparam){
+        .cmd              = ETHTOOL_SRINGPARAM,
+        .rx_pending       = ring->rx_pending,
+        .rx_jumbo_pending = ring->rx_jumbo_pending,
+        .rx_mini_pending  = ring->rx_mini_pending,
+        .tx_pending       = ring->tx_pending,
+    };
+
+    success = (_ethtool_call_handle(shandle, &eth_data, sizeof(struct ethtool_ringparam)) == 0);
+    return success;
+}
+
+gboolean
+nmp_utils_ethtool_set_ring(int ifindex, const NMEthtoolRingState *ring)
+{
+    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+    g_return_val_if_fail(ring, FALSE);
+
+    if (!ethtool_set_ring(&shandle, ring)) {
+        nm_log_trace(LOGD_PLATFORM,
+                     "ethtool[%d]: %s: failure setting ring settings",
+                     ifindex,
+                     "set-ring");
+        return FALSE;
+    }
+
+    nm_log_trace(LOGD_PLATFORM, "ethtool[%d]: %s: set kernel ring settings", ifindex, "set-ring");
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+gboolean
+nmp_utils_ethtool_get_driver_info(int ifindex, NMPUtilsEthtoolDriverInfo *data)
+{
+    struct ethtool_drvinfo *drvinfo;
+
+    G_STATIC_ASSERT_EXPR(sizeof(*data) == sizeof(*drvinfo));
+    G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, driver)
+                         == offsetof(struct ethtool_drvinfo, driver));
+    G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, version)
+                         == offsetof(struct ethtool_drvinfo, version));
+    G_STATIC_ASSERT_EXPR(offsetof(NMPUtilsEthtoolDriverInfo, fw_version)
+                         == offsetof(struct ethtool_drvinfo, fw_version));
+    G_STATIC_ASSERT_EXPR(sizeof(data->driver) == sizeof(drvinfo->driver));
+    G_STATIC_ASSERT_EXPR(sizeof(data->version) == sizeof(drvinfo->version));
+    G_STATIC_ASSERT_EXPR(sizeof(data->fw_version) == sizeof(drvinfo->fw_version));
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+    g_return_val_if_fail(data, FALSE);
+
+    drvinfo  = (struct ethtool_drvinfo *) data;
+    *drvinfo = (struct ethtool_drvinfo){
+        .cmd = ETHTOOL_GDRVINFO,
+    };
+    return _ethtool_call_once(ifindex, drvinfo, sizeof(*drvinfo)) >= 0;
+}
+
+gboolean
+nmp_utils_ethtool_get_permanent_address(int ifindex, guint8 *buf, size_t *length)
+{
+    struct {
+        struct ethtool_perm_addr e;
+        guint8                   _extra_data[_NM_UTILS_HWADDR_LEN_MAX + 1];
+    } edata = {
+        .e.cmd  = ETHTOOL_GPERMADDR,
+        .e.size = _NM_UTILS_HWADDR_LEN_MAX,
+    };
+    const guint8 *pdata;
+
+    guint i;
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    if (_ethtool_call_once(ifindex, &edata, sizeof(edata)) < 0)
+        return FALSE;
+
+    if (edata.e.size > _NM_UTILS_HWADDR_LEN_MAX)
+        return FALSE;
+    if (edata.e.size < 1)
+        return FALSE;
+
+    pdata = (const guint8 *) edata.e.data;
+
+    if (NM_IN_SET(pdata[0], 0, 0xFF)) {
+        /* Some drivers might return a permanent address of all zeros.
+         * Reject that (rh#1264024)
+         *
+         * Some drivers return a permanent address of all ones. Reject that too */
+        for (i = 1; i < edata.e.size; i++) {
+            if (pdata[0] != pdata[i])
+                goto not_all_0or1;
+        }
+        return FALSE;
+    }
+
+not_all_0or1:
+    memcpy(buf, pdata, edata.e.size);
+    *length = edata.e.size;
+    return TRUE;
+}
+
+gboolean
+nmp_utils_ethtool_supports_carrier_detect(int ifindex)
+{
+    struct ethtool_cmd edata = {.cmd = ETHTOOL_GLINK};
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    /* We ignore the result. If the ETHTOOL_GLINK call succeeded, then we
+     * assume the device supports carrier-detect, otherwise we assume it
+     * doesn't.
+     */
+    return _ethtool_call_once(ifindex, &edata, sizeof(edata)) >= 0;
+}
+
+gboolean
+nmp_utils_ethtool_supports_vlans(int ifindex)
+{
+    nm_auto_socket_handle SocketHandle shandle       = SOCKET_HANDLE_INIT(ifindex);
+    gs_free struct ethtool_gfeatures * features_free = NULL;
+    struct ethtool_gfeatures *         features;
+    gsize                              features_len;
+    int                                idx, block, bit, size;
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    idx = ethtool_get_stringset_index(&shandle, ETH_SS_FEATURES, "vlan-challenged");
+    if (idx < 0) {
+        nm_log_dbg(LOGD_PLATFORM,
+                   "ethtool[%d]: vlan-challenged ethtool feature does not exist?",
+                   ifindex);
+        return FALSE;
+    }
+
+    block = idx / 32;
+    bit   = idx % 32;
+    size  = block + 1;
+
+    features_len   = sizeof(*features) + (size * sizeof(struct ethtool_get_features_block));
+    features       = nm_malloc0_maybe_a(300, features_len, &features_free);
+    features->cmd  = ETHTOOL_GFEATURES;
+    features->size = size;
+
+    if (_ethtool_call_handle(&shandle, features, features_len) < 0)
+        return FALSE;
+
+    return !(features->features[block].active & (1 << bit));
+}
+
+int
+nmp_utils_ethtool_get_peer_ifindex(int ifindex)
+{
+    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
+    gsize                              stats_len;
+    gs_free struct ethtool_stats *     stats_free = NULL;
+    struct ethtool_stats *             stats;
+    int                                peer_ifindex_stat;
+
+    g_return_val_if_fail(ifindex > 0, 0);
+
+    peer_ifindex_stat = ethtool_get_stringset_index(&shandle, ETH_SS_STATS, "peer_ifindex");
+    if (peer_ifindex_stat < 0) {
+        nm_log_dbg(LOGD_PLATFORM, "ethtool[%d]: peer_ifindex stat does not exist?", ifindex);
+        return FALSE;
+    }
+
+    stats_len      = sizeof(*stats) + (peer_ifindex_stat + 1) * sizeof(guint64);
+    stats          = nm_malloc0_maybe_a(300, stats_len, &stats_free);
+    stats->cmd     = ETHTOOL_GSTATS;
+    stats->n_stats = peer_ifindex_stat + 1;
+    if (_ethtool_call_handle(&shandle, stats, stats_len) < 0)
+        return 0;
+
+    return stats->data[peer_ifindex_stat];
+}
+
+gboolean
+nmp_utils_ethtool_get_wake_on_lan(int ifindex)
+{
+    struct ethtool_wolinfo wol = {
+        .cmd = ETHTOOL_GWOL,
+    };
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    if (_ethtool_call_once(ifindex, &wol, sizeof(wol)) < 0)
+        return FALSE;
+
+    return wol.wolopts != 0;
+}
+
+gboolean
+nmp_utils_ethtool_get_link_settings(int                       ifindex,
+                                    gboolean *                out_autoneg,
+                                    guint32 *                 out_speed,
+                                    NMPlatformLinkDuplexType *out_duplex)
+{
+    struct ethtool_cmd edata = {
+        .cmd = ETHTOOL_GSET,
+    };
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    if (_ethtool_call_once(ifindex, &edata, sizeof(edata)) < 0)
+        return FALSE;
+
+    NM_SET_OUT(out_autoneg, (edata.autoneg == AUTONEG_ENABLE));
+
+    if (out_speed) {
+        guint32 speed;
+
+        speed = ethtool_cmd_speed(&edata);
+        if (speed == G_MAXUINT16 || speed == G_MAXUINT32)
+            speed = 0;
+
+        *out_speed = speed;
+    }
+
+    if (out_duplex) {
+        switch (edata.duplex) {
+        case DUPLEX_HALF:
+            *out_duplex = NM_PLATFORM_LINK_DUPLEX_HALF;
+            break;
+        case DUPLEX_FULL:
+            *out_duplex = NM_PLATFORM_LINK_DUPLEX_FULL;
+            break;
+        default: /* DUPLEX_UNKNOWN */
+            *out_duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN;
+            break;
+        }
+    }
+
+    return TRUE;
+}
+
+#define ADVERTISED_INVALID 0
+#define BASET_ALL_MODES                                                                 \
+    (ADVERTISED_10baseT_Half | ADVERTISED_10baseT_Full | ADVERTISED_100baseT_Half       \
+     | ADVERTISED_100baseT_Full | ADVERTISED_1000baseT_Half | ADVERTISED_1000baseT_Full \
+     | ADVERTISED_10000baseT_Full)
+
+static guint32
+get_baset_mode(guint32 speed, NMPlatformLinkDuplexType duplex)
+{
+    if (duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN)
+        return ADVERTISED_INVALID;
+
+    if (duplex == NM_PLATFORM_LINK_DUPLEX_HALF) {
+        switch (speed) {
+        case 10:
+            return ADVERTISED_10baseT_Half;
+        case 100:
+            return ADVERTISED_100baseT_Half;
+        case 1000:
+            return ADVERTISED_1000baseT_Half;
+        default:
+            return ADVERTISED_INVALID;
+        }
+    } else {
+        switch (speed) {
+        case 10:
+            return ADVERTISED_10baseT_Full;
+        case 100:
+            return ADVERTISED_100baseT_Full;
+        case 1000:
+            return ADVERTISED_1000baseT_Full;
+        case 10000:
+            return ADVERTISED_10000baseT_Full;
+        default:
+            return ADVERTISED_INVALID;
+        }
+    }
+}
+
+gboolean
+nmp_utils_ethtool_set_link_settings(int                      ifindex,
+                                    gboolean                 autoneg,
+                                    guint32                  speed,
+                                    NMPlatformLinkDuplexType duplex)
+{
+    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
+    struct ethtool_cmd                 edata   = {
+        .cmd = ETHTOOL_GSET,
+    };
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+    g_return_val_if_fail((speed && duplex != NM_PLATFORM_LINK_DUPLEX_UNKNOWN)
+                             || (!speed && duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN),
+                         FALSE);
+
+    /* retrieve first current settings */
+    if (_ethtool_call_handle(&shandle, &edata, sizeof(edata)) < 0)
+        return FALSE;
+
+    /* FIXME: try first new ETHTOOL_GLINKSETTINGS/SLINKSETTINGS API
+     * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3f1ac7a700d039c61d8d8b99f28d605d489a60cf
+     */
+
+    /* then change the needed ones */
+    edata.cmd = ETHTOOL_SSET;
+    if (autoneg) {
+        edata.autoneg = AUTONEG_ENABLE;
+        if (!speed)
+            edata.advertising = edata.supported;
+        else {
+            guint32 mode;
+
+            mode = get_baset_mode(speed, duplex);
+
+            if (!mode) {
+                nm_log_trace(LOGD_PLATFORM,
+                             "ethtool[%d]: %uBASE-T %s duplex mode cannot be advertised",
+                             ifindex,
+                             speed,
+                             nm_platform_link_duplex_type_to_string(duplex));
+                return FALSE;
+            }
+            if (!(edata.supported & mode)) {
+                nm_log_trace(LOGD_PLATFORM,
+                             "ethtool[%d]: device does not support %uBASE-T %s duplex mode",
+                             ifindex,
+                             speed,
+                             nm_platform_link_duplex_type_to_string(duplex));
+                return FALSE;
+            }
+            edata.advertising = (edata.supported & ~BASET_ALL_MODES) | mode;
+        }
+    } else {
+        edata.autoneg = AUTONEG_DISABLE;
+
+        if (speed)
+            ethtool_cmd_speed_set(&edata, speed);
+
+        switch (duplex) {
+        case NM_PLATFORM_LINK_DUPLEX_HALF:
+            edata.duplex = DUPLEX_HALF;
+            break;
+        case NM_PLATFORM_LINK_DUPLEX_FULL:
+            edata.duplex = DUPLEX_FULL;
+            break;
+        case NM_PLATFORM_LINK_DUPLEX_UNKNOWN:
+            break;
+        default:
+            g_return_val_if_reached(FALSE);
+        }
+    }
+
+    return _ethtool_call_handle(&shandle, &edata, sizeof(edata)) >= 0;
+}
+
+gboolean
+nmp_utils_ethtool_set_wake_on_lan(int                      ifindex,
+                                  _NMSettingWiredWakeOnLan wol,
+                                  const char *             wol_password)
+{
+    struct ethtool_wolinfo wol_info = {
+        .cmd     = ETHTOOL_SWOL,
+        .wolopts = 0,
+    };
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    if (wol == _NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE)
+        return TRUE;
+
+    nm_log_dbg(LOGD_PLATFORM,
+               "ethtool[%d]: setting Wake-on-LAN options 0x%x, password '%s'",
+               ifindex,
+               (unsigned) wol,
+               wol_password);
+
+    if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_PHY))
+        wol_info.wolopts |= WAKE_PHY;
+    if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST))
+        wol_info.wolopts |= WAKE_UCAST;
+    if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST))
+        wol_info.wolopts |= WAKE_MCAST;
+    if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST))
+        wol_info.wolopts |= WAKE_BCAST;
+    if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_ARP))
+        wol_info.wolopts |= WAKE_ARP;
+    if (NM_FLAGS_HAS(wol, _NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC))
+        wol_info.wolopts |= WAKE_MAGIC;
+
+    if (wol_password) {
+        if (!_nm_utils_hwaddr_aton_exact(wol_password, wol_info.sopass, ETH_ALEN)) {
+            nm_log_dbg(LOGD_PLATFORM,
+                       "ethtool[%d]: couldn't parse Wake-on-LAN password '%s'",
+                       ifindex,
+                       wol_password);
+            return FALSE;
+        }
+        wol_info.wolopts |= WAKE_MAGICSECURE;
+    }
+
+    return _ethtool_call_once(ifindex, &wol_info, sizeof(wol_info)) >= 0;
+}
+
+/******************************************************************************
+ * mii
+ *****************************************************************************/
+
+gboolean
+nmp_utils_mii_supports_carrier_detect(int ifindex)
+{
+    nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT(ifindex);
+    int                                r;
+    struct ifreq                       ifr;
+    struct mii_ioctl_data *            mii;
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    r = _ioctl_call("mii",
+                    "SIOCGMIIPHY",
+                    SIOCGMIIPHY,
+                    shandle.ifindex,
+                    &shandle.fd,
+                    shandle.ifname,
+                    IOCTL_CALL_DATA_TYPE_NONE,
+                    NULL,
+                    0,
+                    &ifr);
+    if (r < 0)
+        return FALSE;
+
+    /* If we can read the BMSR register, we assume that the card supports MII link detection */
+    mii          = (struct mii_ioctl_data *) &ifr.ifr_ifru;
+    mii->reg_num = MII_BMSR;
+
+    r = _ioctl_call("mii",
+                    "SIOCGMIIREG",
+                    SIOCGMIIREG,
+                    shandle.ifindex,
+                    &shandle.fd,
+                    shandle.ifname,
+                    IOCTL_CALL_DATA_TYPE_IFRU,
+                    mii,
+                    sizeof(*mii),
+                    &ifr);
+    if (r < 0)
+        return FALSE;
+
+    mii = (struct mii_ioctl_data *) &ifr.ifr_ifru;
+    nm_log_trace(LOGD_PLATFORM,
+                 "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X",
+                 ifindex,
+                 shandle.ifname,
+                 mii->val_out);
+    return TRUE;
+}
+
+/******************************************************************************
+ * udev
+ *****************************************************************************/
+
+const char *
+nmp_utils_udev_get_driver(struct udev_device *udevice)
+{
+    struct udev_device *parent = NULL, *grandparent = NULL;
+    const char *        driver, *subsys;
+
+    driver = udev_device_get_driver(udevice);
+    if (driver)
+        goto out;
+
+    /* Try the parent */
+    parent = udev_device_get_parent(udevice);
+    if (parent) {
+        driver = udev_device_get_driver(parent);
+        if (!driver) {
+            /* Try the grandparent if it's an ibmebus device or if the
+             * subsys is NULL which usually indicates some sort of
+             * platform device like a 'gadget' net interface.
+             */
+            subsys = udev_device_get_subsystem(parent);
+            if ((g_strcmp0(subsys, "ibmebus") == 0) || (subsys == NULL)) {
+                grandparent = udev_device_get_parent(parent);
+                if (grandparent)
+                    driver = udev_device_get_driver(grandparent);
+            }
+        }
+    }
+
+out:
+    /* Intern the string so we don't have to worry about memory
+     * management in NMPlatformLink. */
+    return g_intern_string(driver);
+}
+
+/******************************************************************************
+ * utils
+ *****************************************************************************/
+
+NMIPConfigSource
+nmp_utils_ip_config_source_from_rtprot(guint8 rtprot)
+{
+    return ((int) rtprot) + 1;
+}
+
+NMIPConfigSource
+nmp_utils_ip_config_source_round_trip_rtprot(NMIPConfigSource source)
+{
+    /* when adding a route to kernel for a give @source, the resulting route
+     * will be put into the cache with a source of NM_IP_CONFIG_SOURCE_RTPROT_*.
+     * This function returns that. */
+    return nmp_utils_ip_config_source_from_rtprot(
+        nmp_utils_ip_config_source_coerce_to_rtprot(source));
+}
+
+guint8
+nmp_utils_ip_config_source_coerce_to_rtprot(NMIPConfigSource source)
+{
+    /* when adding a route to kernel, we coerce the @source field
+     * to rtm_protocol. This is not lossless as we map different
+     * source values to the same RTPROT uint8 value. */
+    if (source <= NM_IP_CONFIG_SOURCE_UNKNOWN)
+        return RTPROT_UNSPEC;
+
+    if (source <= _NM_IP_CONFIG_SOURCE_RTPROT_LAST)
+        return source - 1;
+
+    switch (source) {
+    case NM_IP_CONFIG_SOURCE_KERNEL:
+        return RTPROT_KERNEL;
+    case NM_IP_CONFIG_SOURCE_IP6LL:
+        return RTPROT_KERNEL;
+    case NM_IP_CONFIG_SOURCE_DHCP:
+        return RTPROT_DHCP;
+    case NM_IP_CONFIG_SOURCE_NDISC:
+        return RTPROT_RA;
+
+    default:
+        return RTPROT_STATIC;
+    }
+}
+
+NMIPConfigSource
+nmp_utils_ip_config_source_coerce_from_rtprot(NMIPConfigSource source)
+{
+    /* When we receive a route from kernel and put it into the platform cache,
+     * we preserve the protocol field by converting it to a NMIPConfigSource
+     * via nmp_utils_ip_config_source_from_rtprot().
+     *
+     * However, that is not the inverse of nmp_utils_ip_config_source_coerce_to_rtprot().
+     * Instead, to go back to the original value, you need another step:
+     *   nmp_utils_ip_config_source_coerce_from_rtprot (nmp_utils_ip_config_source_from_rtprot (rtprot)).
+     *
+     * This might partly restore the original source value, but of course that
+     * is not really possible because nmp_utils_ip_config_source_coerce_to_rtprot()
+     * is not injective.
+     * */
+    switch (source) {
+    case NM_IP_CONFIG_SOURCE_RTPROT_UNSPEC:
+        return NM_IP_CONFIG_SOURCE_UNKNOWN;
+
+    case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL:
+    case NM_IP_CONFIG_SOURCE_RTPROT_REDIRECT:
+        return NM_IP_CONFIG_SOURCE_KERNEL;
+
+    case NM_IP_CONFIG_SOURCE_RTPROT_RA:
+        return NM_IP_CONFIG_SOURCE_NDISC;
+
+    case NM_IP_CONFIG_SOURCE_RTPROT_DHCP:
+        return NM_IP_CONFIG_SOURCE_DHCP;
+
+    default:
+        return NM_IP_CONFIG_SOURCE_USER;
+    }
+}
+
+const char *
+nmp_utils_ip_config_source_to_string(NMIPConfigSource source, char *buf, gsize len)
+{
+    const char *s = NULL;
+    nm_utils_to_string_buffer_init(&buf, &len);
+
+    if (!len)
+        return buf;
+
+    switch (source) {
+    case NM_IP_CONFIG_SOURCE_UNKNOWN:
+        s = "unknown";
+        break;
+
+    case NM_IP_CONFIG_SOURCE_RTPROT_UNSPEC:
+        s = "rt-unspec";
+        break;
+    case NM_IP_CONFIG_SOURCE_RTPROT_REDIRECT:
+        s = "rt-redirect";
+        break;
+    case NM_IP_CONFIG_SOURCE_RTPROT_KERNEL:
+        s = "rt-kernel";
+        break;
+    case NM_IP_CONFIG_SOURCE_RTPROT_BOOT:
+        s = "rt-boot";
+        break;
+    case NM_IP_CONFIG_SOURCE_RTPROT_STATIC:
+        s = "rt-static";
+        break;
+    case NM_IP_CONFIG_SOURCE_RTPROT_DHCP:
+        s = "rt-dhcp";
+        break;
+    case NM_IP_CONFIG_SOURCE_RTPROT_RA:
+        s = "rt-ra";
+        break;
+
+    case NM_IP_CONFIG_SOURCE_KERNEL:
+        s = "kernel";
+        break;
+    case NM_IP_CONFIG_SOURCE_SHARED:
+        s = "shared";
+        break;
+    case NM_IP_CONFIG_SOURCE_IP4LL:
+        s = "ipv4ll";
+        break;
+    case NM_IP_CONFIG_SOURCE_IP6LL:
+        s = "ipv6ll";
+        break;
+    case NM_IP_CONFIG_SOURCE_PPP:
+        s = "ppp";
+        break;
+    case NM_IP_CONFIG_SOURCE_WWAN:
+        s = "wwan";
+        break;
+    case NM_IP_CONFIG_SOURCE_VPN:
+        s = "vpn";
+        break;
+    case NM_IP_CONFIG_SOURCE_DHCP:
+        s = "dhcp";
+        break;
+    case NM_IP_CONFIG_SOURCE_NDISC:
+        s = "ndisc";
+        break;
+    case NM_IP_CONFIG_SOURCE_USER:
+        s = "user";
+        break;
+    default:
+        break;
+    }
+
+    if (source >= 1 && source <= 0x100) {
+        if (s)
+            g_snprintf(buf, len, "%s", s);
+        else
+            g_snprintf(buf, len, "rt-%d", ((int) source) - 1);
+    } else {
+        if (s)
+            g_strlcpy(buf, s, len);
+        else
+            g_snprintf(buf, len, "(%d)", source);
+    }
+    return buf;
+}
+
+/**
+ * nmp_utils_sysctl_open_netdir:
+ * @ifindex: the ifindex for which to open "/sys/class/net/%s"
+ * @ifname_guess: (allow-none): optional argument, if present used as initial
+ *   guess as the current name for @ifindex. If guessed right,
+ *   it saves an additional if_indextoname() call.
+ * @out_ifname: (allow-none): if present, must be at least IFNAMSIZ
+ *   characters. On success, this will contain the actual ifname
+ *   found while opening the directory.
+ *
+ * Returns: a negative value on failure, on success returns the open fd
+ *   to the "/sys/class/net/%s" directory for @ifindex.
+ */
+int
+nmp_utils_sysctl_open_netdir(int ifindex, const char *ifname_guess, char *out_ifname)
+{
+#define SYS_CLASS_NET "/sys/class/net/"
+    const char *ifname = ifname_guess;
+    char        ifname_buf_last_try[IFNAMSIZ];
+    char        ifname_buf[IFNAMSIZ];
+    guint       try_count                                   = 0;
+    char        sysdir[NM_STRLEN(SYS_CLASS_NET) + IFNAMSIZ] = SYS_CLASS_NET;
+    char        fd_buf[256];
+    ssize_t     nn;
+
+    g_return_val_if_fail(ifindex >= 0, -1);
+
+    ifname_buf_last_try[0] = '\0';
+
+    for (try_count = 0; try_count < 10; try_count++, ifname = NULL) {
+        nm_auto_close int fd_dir     = -1;
+        nm_auto_close int fd_ifindex = -1;
+
+        if (!ifname) {
+            ifname = nmp_utils_if_indextoname(ifindex, ifname_buf);
+            if (!ifname)
+                return -1;
+        }
+
+        nm_assert(nm_utils_ifname_valid_kernel(ifname, NULL));
+
+        if (g_strlcpy(&sysdir[NM_STRLEN(SYS_CLASS_NET)], ifname, IFNAMSIZ) >= IFNAMSIZ)
+            g_return_val_if_reached(-1);
+
+        /* we only retry, if the name changed since previous attempt.
+         * Hence, it is extremely unlikely that this loop runes until the
+         * end of the @try_count. */
+        if (nm_streq(ifname, ifname_buf_last_try))
+            return -1;
+        strcpy(ifname_buf_last_try, ifname);
+
+        fd_dir = open(sysdir, O_DIRECTORY | O_CLOEXEC);
+        if (fd_dir < 0)
+            continue;
+
+        fd_ifindex = openat(fd_dir, "ifindex", O_CLOEXEC);
+        if (fd_ifindex < 0)
+            continue;
+
+        nn = nm_utils_fd_read_loop(fd_ifindex, fd_buf, sizeof(fd_buf) - 2, FALSE);
+        if (nn <= 0)
+            continue;
+        fd_buf[nn] = '\0';
+
+        if (ifindex != (int) _nm_utils_ascii_str_to_int64(fd_buf, 10, 1, G_MAXINT, -1))
+            continue;
+
+        if (out_ifname)
+            strcpy(out_ifname, ifname);
+
+        return nm_steal_fd(&fd_dir);
+    }
+
+    return -1;
+}
diff --git a/shared/nm-platform/nm-platform-utils.h b/shared/nm-platform/nm-platform-utils.h
new file mode 100644
index 00000000..d74723eb
--- /dev/null
+++ b/shared/nm-platform/nm-platform-utils.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_PLATFORM_UTILS_H__
+#define __NM_PLATFORM_UTILS_H__
+
+#include "nm-base/nm-base.h"
+#include "nm-platform/nmp-base.h"
+
+/*****************************************************************************/
+
+const char *nmp_utils_ethtool_get_driver(int ifindex);
+gboolean    nmp_utils_ethtool_supports_carrier_detect(int ifindex);
+gboolean    nmp_utils_ethtool_supports_vlans(int ifindex);
+int         nmp_utils_ethtool_get_peer_ifindex(int ifindex);
+gboolean    nmp_utils_ethtool_get_wake_on_lan(int ifindex);
+gboolean    nmp_utils_ethtool_set_wake_on_lan(int                      ifindex,
+                                              _NMSettingWiredWakeOnLan wol,
+                                              const char *             wol_password);
+
+const char *nm_platform_link_duplex_type_to_string(NMPlatformLinkDuplexType duplex);
+
+gboolean nmp_utils_ethtool_get_link_settings(int                       ifindex,
+                                             gboolean *                out_autoneg,
+                                             guint32 *                 out_speed,
+                                             NMPlatformLinkDuplexType *out_duplex);
+gboolean nmp_utils_ethtool_set_link_settings(int                      ifindex,
+                                             gboolean                 autoneg,
+                                             guint32                  speed,
+                                             NMPlatformLinkDuplexType duplex);
+
+gboolean nmp_utils_ethtool_get_permanent_address(int ifindex, guint8 *buf, size_t *length);
+
+gboolean nmp_utils_ethtool_get_driver_info(int ifindex, NMPUtilsEthtoolDriverInfo *data);
+
+NMEthtoolFeatureStates *nmp_utils_ethtool_get_features(int ifindex);
+
+gboolean nmp_utils_ethtool_set_features(
+    int                           ifindex,
+    const NMEthtoolFeatureStates *features,
+    const NMOptionBool *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+    gboolean            do_set /* or reset */);
+
+gboolean nmp_utils_ethtool_get_coalesce(int ifindex, NMEthtoolCoalesceState *coalesce);
+
+gboolean nmp_utils_ethtool_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coalesce);
+
+gboolean nmp_utils_ethtool_get_ring(int ifindex, NMEthtoolRingState *ring);
+
+gboolean nmp_utils_ethtool_set_ring(int ifindex, const NMEthtoolRingState *ring);
+
+/*****************************************************************************/
+
+gboolean nmp_utils_mii_supports_carrier_detect(int ifindex);
+
+struct udev_device;
+
+const char *nmp_utils_udev_get_driver(struct udev_device *udevice);
+
+NMIPConfigSource nmp_utils_ip_config_source_from_rtprot(guint8 rtprot) _nm_const;
+guint8           nmp_utils_ip_config_source_coerce_to_rtprot(NMIPConfigSource source) _nm_const;
+NMIPConfigSource nmp_utils_ip_config_source_coerce_from_rtprot(NMIPConfigSource source) _nm_const;
+NMIPConfigSource nmp_utils_ip_config_source_round_trip_rtprot(NMIPConfigSource source) _nm_const;
+const char *nmp_utils_ip_config_source_to_string(NMIPConfigSource source, char *buf, gsize len);
+
+const char *nmp_utils_if_indextoname(int ifindex, char *out_ifname /*IFNAMSIZ*/);
+int         nmp_utils_if_nametoindex(const char *ifname);
+
+int nmp_utils_sysctl_open_netdir(int ifindex, const char *ifname_guess, char *out_ifname);
+
+#endif /* __NM_PLATFORM_UTILS_H__ */
diff --git a/shared/nm-platform/nmp-base.h b/shared/nm-platform/nmp-base.h
new file mode 100644
index 00000000..210c26d6
--- /dev/null
+++ b/shared/nm-platform/nmp-base.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __NMP_FWD_H__
+#define __NMP_FWD_H__
+
+#include "nm-base/nm-base.h"
+
+/*****************************************************************************/
+
+typedef enum {
+    NM_PLATFORM_LINK_DUPLEX_UNKNOWN,
+    NM_PLATFORM_LINK_DUPLEX_HALF,
+    NM_PLATFORM_LINK_DUPLEX_FULL,
+} NMPlatformLinkDuplexType;
+
+/*****************************************************************************/
+
+typedef struct {
+    /* We don't want to include <linux/ethtool.h> in header files,
+     * thus create a ABI compatible version of struct ethtool_drvinfo.*/
+    guint32 _private_cmd;
+    char    driver[32];
+    char    version[32];
+    char    fw_version[32];
+    char    _private_bus_info[32];
+    char    _private_erom_version[32];
+    char    _private_reserved2[12];
+    guint32 _private_n_priv_flags;
+    guint32 _private_n_stats;
+    guint32 _private_testinfo_len;
+    guint32 _private_eedump_len;
+    guint32 _private_regdump_len;
+} NMPUtilsEthtoolDriverInfo;
+
+typedef struct {
+    NMEthtoolID ethtool_id;
+
+    guint8 n_kernel_names;
+
+    /* one NMEthtoolID refers to one or more kernel_names. The reason for supporting this complexity
+     * (where one NMSettingEthtool option refers to multiple kernel features)  is to follow what
+     * ethtool does, where "tx" is an alias for multiple features. */
+    const char *const *kernel_names;
+} NMEthtoolFeatureInfo;
+
+typedef struct {
+    const NMEthtoolFeatureInfo *info;
+
+    guint idx_ss_features;
+
+    /* one NMEthtoolFeatureInfo references one or more kernel_names. This is the index
+     * of the matching info->kernel_names */
+    guint8 idx_kernel_name;
+
+    bool available : 1;
+    bool requested : 1;
+    bool active : 1;
+    bool never_changed : 1;
+} NMEthtoolFeatureState;
+
+typedef struct {
+    guint n_states;
+
+    guint n_ss_features;
+
+    /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */
+    const NMEthtoolFeatureState *const *states_indexed[_NM_ETHTOOL_ID_FEATURE_NUM];
+
+    /* the same content, here as a list of n_states entries. */
+    const NMEthtoolFeatureState states_list[];
+} NMEthtoolFeatureStates;
+
+/*****************************************************************************/
+
+typedef struct {
+    guint32
+        s[_NM_ETHTOOL_ID_COALESCE_NUM /* indexed by (NMEthtoolID - _NM_ETHTOOL_ID_COALESCE_FIRST) */
+    ];
+} NMEthtoolCoalesceState;
+
+/*****************************************************************************/
+
+typedef struct {
+    guint32 rx_pending;
+    guint32 rx_mini_pending;
+    guint32 rx_jumbo_pending;
+    guint32 tx_pending;
+} NMEthtoolRingState;
+
+/*****************************************************************************/
+
+typedef struct _NMPNetns NMPNetns;
+
+#endif /* __NMP_FWD_H__ */
diff --git a/shared/nm-platform/nmp-netns.c b/shared/nm-platform/nmp-netns.c
new file mode 100644
index 00000000..c7cb617b
--- /dev/null
+++ b/shared/nm-platform/nmp-netns.c
@@ -0,0 +1,766 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2016 Red Hat, Inc.
+ */
+
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
+
+#include "nmp-netns.h"
+
+#include <fcntl.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pthread.h>
+
+#include "nm-log-core/nm-logging.h"
+
+/*****************************************************************************/
+
+/* NOTE: NMPNetns and all code used here must be thread-safe! */
+
+/* we may not call logging functions from the main-thread alone. Hence, we
+ * require locking from nm-logging. Indicate that by setting NM_THREAD_SAFE_ON_MAIN_THREAD
+ * to zero. */
+#undef NM_THREAD_SAFE_ON_MAIN_THREAD
+#define NM_THREAD_SAFE_ON_MAIN_THREAD 0
+
+/*****************************************************************************/
+
+#define PROC_SELF_NS_MNT "/proc/self/ns/mnt"
+#define PROC_SELF_NS_NET "/proc/self/ns/net"
+
+#define _CLONE_NS_ALL   ((int) (CLONE_NEWNS | CLONE_NEWNET))
+#define _CLONE_NS_ALL_V CLONE_NEWNS, CLONE_NEWNET
+
+static NM_UTILS_FLAGS2STR_DEFINE(_clone_ns_to_str,
+                                 int,
+                                 NM_UTILS_FLAGS2STR(CLONE_NEWNS, "mnt"),
+                                 NM_UTILS_FLAGS2STR(CLONE_NEWNET, "net"), );
+
+static const char *
+__ns_types_to_str(int ns_types, int ns_types_already_set, char *buf, gsize len)
+{
+    const char *b = buf;
+    char        bb[200];
+
+    nm_utils_strbuf_append_c(&buf, &len, '[');
+    if (ns_types & ~ns_types_already_set) {
+        nm_utils_strbuf_append_str(
+            &buf,
+            &len,
+            _clone_ns_to_str(ns_types & ~ns_types_already_set, bb, sizeof(bb)));
+    }
+    if (ns_types & ns_types_already_set) {
+        if (ns_types & ~ns_types_already_set)
+            nm_utils_strbuf_append_c(&buf, &len, '/');
+        nm_utils_strbuf_append_str(
+            &buf,
+            &len,
+            _clone_ns_to_str(ns_types & ns_types_already_set, bb, sizeof(bb)));
+    }
+    nm_utils_strbuf_append_c(&buf, &len, ']');
+    return b;
+}
+#define _ns_types_to_str(ns_types, ns_types_already_set, buf) \
+    __ns_types_to_str(ns_types, ns_types_already_set, buf, sizeof(buf))
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_PLATFORM
+#define _NMLOG_PREFIX_NAME "netns"
+#define _NMLOG(level, netns, ...)                                     \
+    G_STMT_START                                                      \
+    {                                                                 \
+        NMLogLevel _level = (level);                                  \
+                                                                      \
+        if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) {              \
+            NMPNetns *_netns = (netns);                               \
+            char      _sbuf[20];                                      \
+                                                                      \
+            _nm_log(_level,                                           \
+                    _NMLOG_DOMAIN,                                    \
+                    0,                                                \
+                    NULL,                                             \
+                    NULL,                                             \
+                    "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),      \
+                    _NMLOG_PREFIX_NAME,                               \
+                    (_netns ? nm_sprintf_buf(_sbuf, "[%p]", _netns)   \
+                            : "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        }                                                             \
+    }                                                                 \
+    G_STMT_END
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_FD_NET, PROP_FD_MNT, );
+
+typedef struct {
+    int fd_net;
+    int fd_mnt;
+} NMPNetnsPrivate;
+
+struct _NMPNetns {
+    GObject         parent;
+    NMPNetnsPrivate _priv;
+};
+
+struct _NMPNetnsClass {
+    GObjectClass parent;
+};
+
+G_DEFINE_TYPE(NMPNetns, nmp_netns, G_TYPE_OBJECT);
+
+#define NMP_NETNS_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMPNetns, NMP_IS_NETNS)
+
+/*****************************************************************************/
+
+typedef struct {
+    NMPNetns *netns;
+    int       count;
+    int       ns_types;
+} NetnsInfo;
+
+static void      _stack_push(GArray *netns_stack, NMPNetns *netns, int ns_types);
+static NMPNetns *_netns_new(GError **error);
+
+/*****************************************************************************/
+
+static NMPNetns *
+_netns_get(NetnsInfo *info)
+{
+    nm_assert(!info || NMP_IS_NETNS(info->netns));
+    return info ? info->netns : NULL;
+}
+
+/*****************************************************************************/
+
+static _nm_thread_local GArray *_netns_stack = NULL;
+
+static void
+_netns_stack_clear_cb(gpointer data)
+{
+    NetnsInfo *info = data;
+
+    nm_assert(NMP_IS_NETNS(info->netns));
+    g_object_unref(info->netns);
+}
+
+static GArray *
+_netns_stack_get_impl(void)
+{
+    gs_unref_object NMPNetns *netns = NULL;
+    gs_free_error GError *error     = NULL;
+    pthread_key_t         key;
+    GArray *              s;
+
+    s = g_array_new(FALSE, FALSE, sizeof(NetnsInfo));
+    g_array_set_clear_func(s, _netns_stack_clear_cb);
+    _netns_stack = s;
+
+    /* at the bottom of the stack we must try to create a netns instance
+     * that we never pop. It's the base to which we need to return. */
+    netns = _netns_new(&error);
+    if (!netns) {
+        _LOGE(NULL, "failed to create initial netns: %s", error->message);
+        return s;
+    }
+
+    /* we leak this instance inside the stack. */
+    _stack_push(s, netns, _CLONE_NS_ALL);
+
+    /* finally, register a destructor function to cleanup the array. If we fail
+     * to do so, we will leak NMPNetns instances (and their file descriptor) when the
+     * thread exits. */
+    if (pthread_key_create(&key, (void (*)(void *)) g_array_unref) != 0)
+        _LOGE(NULL, "failure to initialize thread-local storage");
+    else if (pthread_setspecific(key, s) != 0)
+        _LOGE(NULL, "failure to set thread-local storage");
+
+    return s;
+}
+
+#define _netns_stack_get()                \
+    ({                                    \
+        GArray *_s = _netns_stack;        \
+                                          \
+        if (G_UNLIKELY(!_s))              \
+            _s = _netns_stack_get_impl(); \
+        _s;                               \
+    })
+
+/*****************************************************************************/
+
+static NMPNetns *
+_stack_current_netns(GArray *netns_stack, int ns_types)
+{
+    guint j;
+
+    nm_assert(netns_stack && netns_stack->len > 0);
+
+    /* we search the stack top-down to find the netns that has
+     * all @ns_types set. */
+    for (j = netns_stack->len; ns_types && j >= 1;) {
+        NetnsInfo *info;
+
+        info = &g_array_index(netns_stack, NetnsInfo, --j);
+
+        if (NM_FLAGS_ALL(info->ns_types, ns_types))
+            return info->netns;
+    }
+
+    g_return_val_if_reached(NULL);
+}
+
+static int
+_stack_current_ns_types(GArray *netns_stack, NMPNetns *netns, int ns_types)
+{
+    const int ns_types_check[] = {_CLONE_NS_ALL_V};
+    guint     i, j;
+    int       res = 0;
+
+    nm_assert(netns);
+    nm_assert(netns_stack && netns_stack->len > 0);
+
+    /* we search the stack top-down to check which of @ns_types
+     * are already set to @netns. */
+    for (j = netns_stack->len; ns_types && j >= 1;) {
+        NetnsInfo *info;
+
+        info = &g_array_index(netns_stack, NetnsInfo, --j);
+        if (info->netns != netns) {
+            ns_types = NM_FLAGS_UNSET(ns_types, info->ns_types);
+            continue;
+        }
+
+        for (i = 0; i < G_N_ELEMENTS(ns_types_check); i++) {
+            if (NM_FLAGS_ANY(ns_types, ns_types_check[i])
+                && NM_FLAGS_ANY(info->ns_types, ns_types_check[i])) {
+                res      = NM_FLAGS_SET(res, ns_types_check[i]);
+                ns_types = NM_FLAGS_UNSET(ns_types, ns_types_check[i]);
+            }
+        }
+    }
+
+    return res;
+}
+
+static NetnsInfo *
+_stack_peek(GArray *netns_stack)
+{
+    if (netns_stack->len > 0)
+        return &g_array_index(netns_stack, NetnsInfo, (netns_stack->len - 1));
+    return NULL;
+}
+
+static NetnsInfo *
+_stack_bottom(GArray *netns_stack)
+{
+    if (netns_stack->len > 0)
+        return &g_array_index(netns_stack, NetnsInfo, 0);
+    return NULL;
+}
+
+static void
+_stack_push(GArray *netns_stack, NMPNetns *netns, int ns_types)
+{
+    NetnsInfo *info;
+
+    nm_assert(netns_stack);
+    nm_assert(NMP_IS_NETNS(netns));
+    nm_assert(NM_FLAGS_ANY(ns_types, _CLONE_NS_ALL));
+    nm_assert(!NM_FLAGS_ANY(ns_types, ~_CLONE_NS_ALL));
+
+    g_array_set_size(netns_stack, netns_stack->len + 1);
+
+    info  = &g_array_index(netns_stack, NetnsInfo, (netns_stack->len - 1));
+    *info = (NetnsInfo){
+        .netns    = g_object_ref(netns),
+        .ns_types = ns_types,
+        .count    = 1,
+    };
+}
+
+static void
+_stack_pop(GArray *netns_stack)
+{
+    NetnsInfo *info;
+
+    nm_assert(netns_stack);
+    nm_assert(netns_stack->len > 1);
+
+    info = &g_array_index(netns_stack, NetnsInfo, (netns_stack->len - 1));
+
+    nm_assert(NMP_IS_NETNS(info->netns));
+    nm_assert(info->count == 1);
+
+    g_array_set_size(netns_stack, netns_stack->len - 1);
+}
+
+static guint
+_stack_size(GArray *netns_stack)
+{
+    nm_assert(netns_stack);
+
+    return netns_stack->len;
+}
+
+/*****************************************************************************/
+
+static NMPNetns *
+_netns_new(GError **error)
+{
+    NMPNetns *self;
+    int       fd_net, fd_mnt;
+    int       errsv;
+
+    fd_net = open(PROC_SELF_NS_NET, O_RDONLY | O_CLOEXEC);
+    if (fd_net == -1) {
+        errsv = errno;
+        g_set_error(error,
+                    NM_UTILS_ERROR,
+                    NM_UTILS_ERROR_UNKNOWN,
+                    "Failed opening netns: %s",
+                    nm_strerror_native(errsv));
+        errno = errsv;
+        return NULL;
+    }
+
+    fd_mnt = open(PROC_SELF_NS_MNT, O_RDONLY | O_CLOEXEC);
+    if (fd_mnt == -1) {
+        errsv = errno;
+        g_set_error(error,
+                    NM_UTILS_ERROR,
+                    NM_UTILS_ERROR_UNKNOWN,
+                    "Failed opening mntns: %s",
+                    nm_strerror_native(errsv));
+        nm_close(fd_net);
+        errno = errsv;
+        return NULL;
+    }
+
+    self = g_object_new(NMP_TYPE_NETNS, NMP_NETNS_FD_NET, fd_net, NMP_NETNS_FD_MNT, fd_mnt, NULL);
+
+    _LOGD(self, "new netns (net:%d, mnt:%d)", fd_net, fd_mnt);
+
+    return self;
+}
+
+static int
+_setns(NMPNetns *self, int type)
+{
+    char             buf[100];
+    int              fd;
+    NMPNetnsPrivate *priv = NMP_NETNS_GET_PRIVATE(self);
+
+    nm_assert(NM_IN_SET(type, _CLONE_NS_ALL_V));
+
+    fd = (type == CLONE_NEWNET) ? priv->fd_net : priv->fd_mnt;
+
+    _LOGt(self, "set netns(%s, %d)", _ns_types_to_str(type, 0, buf), fd);
+
+    return setns(fd, type);
+}
+
+static gboolean
+_netns_switch_push(GArray *netns_stack, NMPNetns *self, int ns_types)
+{
+    int errsv;
+
+    if (NM_FLAGS_HAS(ns_types, CLONE_NEWNET)
+        && !_stack_current_ns_types(netns_stack, self, CLONE_NEWNET)
+        && _setns(self, CLONE_NEWNET) != 0) {
+        errsv = errno;
+        _LOGE(self, "failed to switch netns: %s", nm_strerror_native(errsv));
+        return FALSE;
+    }
+    if (NM_FLAGS_HAS(ns_types, CLONE_NEWNS)
+        && !_stack_current_ns_types(netns_stack, self, CLONE_NEWNS)
+        && _setns(self, CLONE_NEWNS) != 0) {
+        errsv = errno;
+        _LOGE(self, "failed to switch mntns: %s", nm_strerror_native(errsv));
+
+        /* try to fix the mess by returning to the previous netns. */
+        if (NM_FLAGS_HAS(ns_types, CLONE_NEWNET)
+            && !_stack_current_ns_types(netns_stack, self, CLONE_NEWNET)) {
+            self = _stack_current_netns(netns_stack, CLONE_NEWNET);
+            if (self && _setns(self, CLONE_NEWNET) != 0) {
+                errsv = errno;
+                _LOGE(self, "failed to restore netns: %s", nm_strerror_native(errsv));
+            }
+        }
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
+_netns_switch_pop(GArray *netns_stack, NMPNetns *self, int ns_types)
+{
+    int       errsv;
+    NMPNetns *current;
+    int       success = TRUE;
+
+    if (NM_FLAGS_HAS(ns_types, CLONE_NEWNET)
+        && (!self || !_stack_current_ns_types(netns_stack, self, CLONE_NEWNET))) {
+        current = _stack_current_netns(netns_stack, CLONE_NEWNET);
+        if (!current) {
+            g_warn_if_reached();
+            success = FALSE;
+        } else if (_setns(current, CLONE_NEWNET) != 0) {
+            errsv = errno;
+            _LOGE(self, "failed to switch netns: %s", nm_strerror_native(errsv));
+            success = FALSE;
+        }
+    }
+    if (NM_FLAGS_HAS(ns_types, CLONE_NEWNS)
+        && (!self || !_stack_current_ns_types(netns_stack, self, CLONE_NEWNS))) {
+        current = _stack_current_netns(netns_stack, CLONE_NEWNS);
+        if (!current) {
+            g_warn_if_reached();
+            success = FALSE;
+        } else if (_setns(current, CLONE_NEWNS) != 0) {
+            errsv = errno;
+            _LOGE(self, "failed to switch mntns: %s", nm_strerror_native(errsv));
+            success = FALSE;
+        }
+    }
+
+    return success;
+}
+
+/*****************************************************************************/
+
+int
+nmp_netns_get_fd_net(NMPNetns *self)
+{
+    g_return_val_if_fail(NMP_IS_NETNS(self), 0);
+
+    return NMP_NETNS_GET_PRIVATE(self)->fd_net;
+}
+
+int
+nmp_netns_get_fd_mnt(NMPNetns *self)
+{
+    g_return_val_if_fail(NMP_IS_NETNS(self), 0);
+
+    return NMP_NETNS_GET_PRIVATE(self)->fd_mnt;
+}
+
+/*****************************************************************************/
+
+static gboolean
+_nmp_netns_push_type(NMPNetns *self, int ns_types)
+{
+    GArray *   netns_stack = _netns_stack_get();
+    NetnsInfo *info;
+    char       sbuf[100];
+
+    info = _stack_peek(netns_stack);
+    g_return_val_if_fail(info, FALSE);
+
+    if (info->netns == self && info->ns_types == ns_types) {
+        info->count++;
+        _LOGt(self,
+              "push#%u* %s (increase count to %d)",
+              _stack_size(netns_stack) - 1,
+              _ns_types_to_str(ns_types, ns_types, sbuf),
+              info->count);
+        return TRUE;
+    }
+
+    _LOGD(self,
+          "push#%u %s",
+          _stack_size(netns_stack),
+          _ns_types_to_str(ns_types, _stack_current_ns_types(netns_stack, self, ns_types), sbuf));
+
+    if (!_netns_switch_push(netns_stack, self, ns_types))
+        return FALSE;
+
+    _stack_push(netns_stack, self, ns_types);
+    return TRUE;
+}
+
+gboolean
+nmp_netns_push(NMPNetns *self)
+{
+    g_return_val_if_fail(NMP_IS_NETNS(self), FALSE);
+
+    return _nmp_netns_push_type(self, _CLONE_NS_ALL);
+}
+
+gboolean
+nmp_netns_push_type(NMPNetns *self, int ns_types)
+{
+    g_return_val_if_fail(NMP_IS_NETNS(self), FALSE);
+    g_return_val_if_fail(!NM_FLAGS_ANY(ns_types, ~_CLONE_NS_ALL), FALSE);
+
+    return _nmp_netns_push_type(self, ns_types == 0 ? _CLONE_NS_ALL : ns_types);
+}
+
+NMPNetns *
+nmp_netns_new(void)
+{
+    GArray *      netns_stack = _netns_stack_get();
+    NMPNetns *    self;
+    int           errsv;
+    GError *      error      = NULL;
+    unsigned long mountflags = 0;
+
+    if (!_stack_peek(netns_stack)) {
+        /* there are no netns instances. We cannot create a new one
+         * (because after unshare we couldn't return to the original one). */
+        errno = ENOTSUP;
+        return NULL;
+    }
+
+    if (unshare(_CLONE_NS_ALL) != 0) {
+        errsv = errno;
+        _LOGE(NULL, "failed to create new net and mnt namespace: %s", nm_strerror_native(errsv));
+        return NULL;
+    }
+
+    if (mount("", "/", "none", MS_SLAVE | MS_REC, NULL) != 0) {
+        errsv = errno;
+        _LOGE(NULL, "failed mount --make-rslave: %s", nm_strerror_native(errsv));
+        goto err_out;
+    }
+
+    if (umount2("/sys", MNT_DETACH) != 0) {
+        errsv = errno;
+        _LOGE(NULL, "failed umount /sys: %s", nm_strerror_native(errsv));
+        goto err_out;
+    }
+
+    if (access("/sys", W_OK) == -1)
+        mountflags = MS_RDONLY;
+
+    if (mount("sysfs", "/sys", "sysfs", mountflags, NULL) != 0) {
+        errsv = errno;
+        _LOGE(NULL, "failed mount /sys: %s", nm_strerror_native(errsv));
+        goto err_out;
+    }
+
+    self = _netns_new(&error);
+    if (!self) {
+        errsv = errno;
+        _LOGE(NULL, "failed to create netns after unshare: %s", error->message);
+        g_clear_error(&error);
+        goto err_out;
+    }
+
+    _stack_push(netns_stack, self, _CLONE_NS_ALL);
+
+    return self;
+err_out:
+    _netns_switch_pop(netns_stack, NULL, _CLONE_NS_ALL);
+    errno = errsv;
+    return NULL;
+}
+
+gboolean
+nmp_netns_pop(NMPNetns *self)
+{
+    GArray *   netns_stack = _netns_stack_get();
+    NetnsInfo *info;
+    int        ns_types;
+
+    g_return_val_if_fail(NMP_IS_NETNS(self), FALSE);
+
+    info = _stack_peek(netns_stack);
+
+    g_return_val_if_fail(info, FALSE);
+    g_return_val_if_fail(info->netns == self, FALSE);
+
+    if (info->count > 1) {
+        info->count--;
+        _LOGt(self, "pop#%u* (decrease count to %d)", _stack_size(netns_stack) - 1, info->count);
+        return TRUE;
+    }
+    g_return_val_if_fail(info->count == 1, FALSE);
+
+    /* cannot pop the original netns. */
+    g_return_val_if_fail(_stack_size(netns_stack) > 1, FALSE);
+
+    _LOGD(self, "pop#%u", _stack_size(netns_stack) - 1);
+
+    ns_types = info->ns_types;
+
+    _stack_pop(netns_stack);
+
+    return _netns_switch_pop(netns_stack, self, ns_types);
+}
+
+NMPNetns *
+nmp_netns_get_current(void)
+{
+    return _netns_get(_stack_peek(_netns_stack_get()));
+}
+
+NMPNetns *
+nmp_netns_get_initial(void)
+{
+    return _netns_get(_stack_bottom(_netns_stack_get()));
+}
+
+gboolean
+nmp_netns_is_initial(void)
+{
+    GArray *netns_stack = _netns_stack_get();
+
+    return (_netns_get(_stack_peek(netns_stack)) == _netns_get(_stack_bottom(netns_stack)));
+}
+
+/*****************************************************************************/
+
+gboolean
+nmp_netns_bind_to_path(NMPNetns *self, const char *filename, int *out_fd)
+{
+    gs_free char *    dirname = NULL;
+    int               errsv;
+    int               fd;
+    nm_auto_pop_netns NMPNetns *netns_pop = NULL;
+
+    g_return_val_if_fail(NMP_IS_NETNS(self), FALSE);
+    g_return_val_if_fail(filename && filename[0] == '/', FALSE);
+
+    if (!nmp_netns_push_type(self, CLONE_NEWNET))
+        return FALSE;
+    netns_pop = self;
+
+    dirname = g_path_get_dirname(filename);
+    if (mkdir(dirname, 0) != 0) {
+        errsv = errno;
+        if (errsv != EEXIST) {
+            _LOGE(self,
+                  "bind: failed to create directory %s: %s",
+                  dirname,
+                  nm_strerror_native(errsv));
+            return FALSE;
+        }
+    }
+
+    if ((fd = creat(filename, S_IRUSR | S_IRGRP | S_IROTH)) == -1) {
+        errsv = errno;
+        _LOGE(self, "bind: failed to create %s: %s", filename, nm_strerror_native(errsv));
+        return FALSE;
+    }
+    nm_close(fd);
+
+    if (mount(PROC_SELF_NS_NET, filename, "none", MS_BIND, NULL) != 0) {
+        errsv = errno;
+        _LOGE(self,
+              "bind: failed to mount %s to %s: %s",
+              PROC_SELF_NS_NET,
+              filename,
+              nm_strerror_native(errsv));
+        unlink(filename);
+        return FALSE;
+    }
+
+    if (out_fd) {
+        if ((fd = open(filename, O_RDONLY | O_CLOEXEC)) == -1) {
+            errsv = errno;
+            _LOGE(self, "bind: failed to open %s: %s", filename, nm_strerror_native(errsv));
+            umount2(filename, MNT_DETACH);
+            unlink(filename);
+            return FALSE;
+        }
+        *out_fd = fd;
+    }
+
+    return TRUE;
+}
+
+gboolean
+nmp_netns_bind_to_path_destroy(NMPNetns *self, const char *filename)
+{
+    int errsv;
+
+    g_return_val_if_fail(NMP_IS_NETNS(self), FALSE);
+    g_return_val_if_fail(filename && filename[0] == '/', FALSE);
+
+    if (umount2(filename, MNT_DETACH) != 0) {
+        errsv = errno;
+        _LOGE(self, "bind: failed to unmount2 %s: %s", filename, nm_strerror_native(errsv));
+        return FALSE;
+    }
+    if (unlink(filename) != 0) {
+        errsv = errno;
+        _LOGE(self, "bind: failed to unlink %s: %s", filename, nm_strerror_native(errsv));
+        return FALSE;
+    }
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NMPNetns *       self = NMP_NETNS(object);
+    NMPNetnsPrivate *priv = NMP_NETNS_GET_PRIVATE(self);
+
+    switch (prop_id) {
+    case PROP_FD_NET:
+        /* construct-only */
+        priv->fd_net = g_value_get_int(value);
+        g_return_if_fail(priv->fd_net > 0);
+        break;
+    case PROP_FD_MNT:
+        /* construct-only */
+        priv->fd_mnt = g_value_get_int(value);
+        g_return_if_fail(priv->fd_mnt > 0);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmp_netns_init(NMPNetns *self)
+{}
+
+static void
+dispose(GObject *object)
+{
+    NMPNetns *       self = NMP_NETNS(object);
+    NMPNetnsPrivate *priv = NMP_NETNS_GET_PRIVATE(self);
+
+    nm_close(priv->fd_net);
+    priv->fd_net = -1;
+
+    nm_close(priv->fd_mnt);
+    priv->fd_mnt = -1;
+
+    G_OBJECT_CLASS(nmp_netns_parent_class)->dispose(object);
+}
+
+static void
+nmp_netns_class_init(NMPNetnsClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    object_class->set_property = set_property;
+    object_class->dispose      = dispose;
+
+    obj_properties[PROP_FD_NET] =
+        g_param_spec_int(NMP_NETNS_FD_NET,
+                         "",
+                         "",
+                         0,
+                         G_MAXINT,
+                         0,
+                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_FD_MNT] =
+        g_param_spec_int(NMP_NETNS_FD_MNT,
+                         "",
+                         "",
+                         0,
+                         G_MAXINT,
+                         0,
+                         G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/shared/nm-platform/nmp-netns.h b/shared/nm-platform/nmp-netns.h
new file mode 100644
index 00000000..b18bd03e
--- /dev/null
+++ b/shared/nm-platform/nmp-netns.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2016 Red Hat, Inc.
+ */
+
+#ifndef __NMP_NETNS_UTILS_H__
+#define __NMP_NETNS_UTILS_H__
+
+#include "nmp-base.h"
+
+/*****************************************************************************/
+
+#define NMP_TYPE_NETNS            (nmp_netns_get_type())
+#define NMP_NETNS(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), NMP_TYPE_NETNS, NMPNetns))
+#define NMP_NETNS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), NMP_TYPE_NETNS, NMPNetnsClass))
+#define NMP_IS_NETNS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMP_TYPE_NETNS))
+#define NMP_IS_NETNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMP_TYPE_NETNS))
+#define NMP_NETNS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), NMP_TYPE_NETNS, NMPNetnsClass))
+
+#define NMP_NETNS_FD_NET "fd-net"
+#define NMP_NETNS_FD_MNT "fd-mnt"
+
+typedef struct _NMPNetns      NMPNetns;
+typedef struct _NMPNetnsClass NMPNetnsClass;
+
+GType nmp_netns_get_type(void);
+
+NMPNetns *nmp_netns_new(void);
+
+gboolean nmp_netns_push(NMPNetns *self);
+gboolean nmp_netns_push_type(NMPNetns *self, int ns_types);
+gboolean nmp_netns_pop(NMPNetns *self);
+
+NMPNetns *nmp_netns_get_current(void);
+NMPNetns *nmp_netns_get_initial(void);
+gboolean  nmp_netns_is_initial(void);
+
+int nmp_netns_get_fd_net(NMPNetns *self);
+int nmp_netns_get_fd_mnt(NMPNetns *self);
+
+static inline void
+_nm_auto_pop_netns(NMPNetns **p)
+{
+    if (*p) {
+        int errsv = errno;
+
+        nmp_netns_pop(*p);
+        errno = errsv;
+    }
+}
+#define nm_auto_pop_netns nm_auto(_nm_auto_pop_netns)
+
+gboolean nmp_netns_bind_to_path(NMPNetns *self, const char *filename, int *out_fd);
+gboolean nmp_netns_bind_to_path_destroy(NMPNetns *self, const char *filename);
+
+#endif /* __NMP_NETNS_UTILS_H__ */
diff --git a/shared/nm-platform/tests/meson.build b/shared/nm-platform/tests/meson.build
new file mode 100644
index 00000000..a8fcdbca
--- /dev/null
+++ b/shared/nm-platform/tests/meson.build
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+exe = executable(
+  'test-nm-platform',
+  'test-nm-platform.c',
+  c_args: [
+    '-DG_LOG_DOMAIN="test"',
+  ],
+  dependencies: [
+    libnm_log_core_dep,
+    libnm_platform_dep,
+  ],
+)
+
+test(
+  'shared/nm-glib-aux/test-nm-platform',
+  test_script,
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
+)
diff --git a/shared/nm-platform/tests/test-nm-platform.c b/shared/nm-platform/tests/test-nm-platform.c
new file mode 100644
index 00000000..a3e9ff13
--- /dev/null
+++ b/shared/nm-platform/tests/test-nm-platform.c
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-glib-aux/nm-default-glib-i18n-prog.h"
+
+#include "nm-log-core/nm-logging.h"
+#include "nm-platform/nm-netlink.h"
+#include "nm-platform/nmp-netns.h"
+
+#include "nm-utils/nm-test-utils.h"
+
+/*****************************************************************************/
+
+void
+_nm_logging_clear_platform_logging_cache(void)
+{
+    /* this symbols is required by nm-log-core library. */
+}
+
+/*****************************************************************************/
+
+static void
+test_use_symbols(void)
+{
+    static void (*const SYMBOLS[])(void) = {
+        (void (*)(void)) nl_nlmsghdr_to_str,
+        (void (*)(void)) nlmsg_hdr,
+        (void (*)(void)) nlmsg_reserve,
+        (void (*)(void)) nla_reserve,
+        (void (*)(void)) nlmsg_alloc_size,
+        (void (*)(void)) nlmsg_alloc,
+        (void (*)(void)) nlmsg_alloc_convert,
+        (void (*)(void)) nlmsg_alloc_simple,
+        (void (*)(void)) nlmsg_free,
+        (void (*)(void)) nlmsg_append,
+        (void (*)(void)) nlmsg_parse,
+        (void (*)(void)) nlmsg_put,
+        (void (*)(void)) nla_strlcpy,
+        (void (*)(void)) nla_memcpy,
+        (void (*)(void)) nla_put,
+        (void (*)(void)) nla_find,
+        (void (*)(void)) nla_nest_cancel,
+        (void (*)(void)) nla_nest_start,
+        (void (*)(void)) nla_nest_end,
+        (void (*)(void)) nla_parse,
+        (void (*)(void)) nlmsg_get_proto,
+        (void (*)(void)) nlmsg_set_proto,
+        (void (*)(void)) nlmsg_set_src,
+        (void (*)(void)) nlmsg_get_creds,
+        (void (*)(void)) nlmsg_set_creds,
+        (void (*)(void)) genlmsg_put,
+        (void (*)(void)) genlmsg_data,
+        (void (*)(void)) genlmsg_user_hdr,
+        (void (*)(void)) genlmsg_hdr,
+        (void (*)(void)) genlmsg_user_data,
+        (void (*)(void)) genlmsg_attrdata,
+        (void (*)(void)) genlmsg_len,
+        (void (*)(void)) genlmsg_attrlen,
+        (void (*)(void)) genlmsg_valid_hdr,
+        (void (*)(void)) genlmsg_parse,
+        (void (*)(void)) genl_ctrl_resolve,
+        (void (*)(void)) nl_socket_alloc,
+        (void (*)(void)) nl_socket_free,
+        (void (*)(void)) nl_socket_get_fd,
+        (void (*)(void)) nl_socket_get_local_port,
+        (void (*)(void)) nl_socket_get_msg_buf_size,
+        (void (*)(void)) nl_socket_set_passcred,
+        (void (*)(void)) nl_socket_set_msg_buf_size,
+        (void (*)(void)) nlmsg_get_dst,
+        (void (*)(void)) nl_socket_set_nonblocking,
+        (void (*)(void)) nl_socket_set_buffer_size,
+        (void (*)(void)) nl_socket_add_memberships,
+        (void (*)(void)) nl_socket_set_ext_ack,
+        (void (*)(void)) nl_socket_disable_msg_peek,
+        (void (*)(void)) nl_connect,
+        (void (*)(void)) nl_wait_for_ack,
+        (void (*)(void)) nl_recvmsgs,
+        (void (*)(void)) nl_sendmsg,
+        (void (*)(void)) nl_send_iovec,
+        (void (*)(void)) nl_complete_msg,
+        (void (*)(void)) nl_send,
+        (void (*)(void)) nl_send_auto,
+        (void (*)(void)) nl_recv,
+
+        (void (*)(void)) nmp_netns_bind_to_path,
+        (void (*)(void)) nmp_netns_bind_to_path_destroy,
+        (void (*)(void)) nmp_netns_get_current,
+        (void (*)(void)) nmp_netns_get_fd_mnt,
+        (void (*)(void)) nmp_netns_get_fd_net,
+        (void (*)(void)) nmp_netns_get_initial,
+        (void (*)(void)) nmp_netns_is_initial,
+        (void (*)(void)) nmp_netns_new,
+        (void (*)(void)) nmp_netns_pop,
+        (void (*)(void)) nmp_netns_push,
+        (void (*)(void)) nmp_netns_push_type,
+
+        NULL,
+    };
+
+    /* The only (not very exciting) purpose of this test is to see that
+     * we can use various symbols and don't get a linker error. */
+    assert(G_N_ELEMENTS(SYMBOLS) == NM_PTRARRAY_LEN(SYMBOLS) + 1);
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE();
+
+int
+main(int argc, char **argv)
+{
+    nmtst_init(&argc, &argv, TRUE);
+
+    g_test_add_func("/nm-platform/test_use_symbols", test_use_symbols);
+
+    return g_test_run();
+}
diff --git a/shared/nm-std-aux/c-list-util.c b/shared/nm-std-aux/c-list-util.c
index a5837edd..d16bd6b7 100644
--- a/shared/nm-std-aux/c-list-util.c
+++ b/shared/nm-std-aux/c-list-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
@@ -20,7 +20,7 @@
  * pointer to @lst.
  *
  * The use of this function is to do a bulk update, that lets the
- * list degredate by not updating the prev pointers. At the end,
+ * list degenerate by not updating the prev pointers. At the end,
  * the list can be fixed by c_list_relink().
  */
 void
@@ -111,7 +111,8 @@ _c_list_sort(CList *ls, CListSortCmp cmp, const void *user_data)
     /* A simple top-down, non-recursive, stable merge-sort.
      *
      * Maybe natural merge-sort would be better, to do better for
-     * partially sorted lists. */
+     * partially sorted lists. Doing that would be much more complicated,
+     * so it's not done. */
 _split:
     stack_head[0].ls2 = _c_list_srt_split(stack_head[0].ls1);
     if (stack_head[0].ls2) {
diff --git a/shared/nm-std-aux/c-list-util.h b/shared/nm-std-aux/c-list-util.h
index 828481e1..9d86d663 100644
--- a/shared/nm-std-aux/c-list-util.h
+++ b/shared/nm-std-aux/c-list-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
@@ -42,4 +42,15 @@ c_list_length_is(const CList *list, unsigned long check_len)
     return n == check_len;
 }
 
+#define c_list_for_each_prev(_iter, _list) \
+    for (_iter = (_list)->prev; (_iter) != (_list); _iter = (_iter)->prev)
+
+#define c_list_for_each_prev_safe(_iter, _safe, _list)                     \
+    for (_iter = (_list)->prev, _safe = (_iter)->prev; (_iter) != (_list); \
+         _iter = (_safe), _safe = (_safe)->prev)
+
+#define c_list_for_each_entry_prev(_iter, _list, _m)                                           \
+    for (_iter = c_list_entry((_list)->prev, __typeof__(*_iter), _m); &(_iter)->_m != (_list); \
+         _iter = c_list_entry((_iter)->_m.prev, __typeof__(*_iter), _m))
+
 #endif /* __C_LIST_UTIL_H__ */
diff --git a/shared/nm-std-aux/nm-dbus-compat.h b/shared/nm-std-aux/nm-dbus-compat.h
index 4100e343..c1076200 100644
--- a/shared/nm-std-aux/nm-dbus-compat.h
+++ b/shared/nm-std-aux/nm-dbus-compat.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #ifndef __NM_DBUS_COMPAT_H__
 #define __NM_DBUS_COMPAT_H__
diff --git a/shared/nm-std-aux/nm-default-std.h b/shared/nm-std-aux/nm-default-std.h
new file mode 100644
index 00000000..fe16d35f
--- /dev/null
+++ b/shared/nm-std-aux/nm-default-std.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEFAULT_STD_H__
+#define __NM_DEFAULT_STD_H__
+
+#include "nm-networkmanager-compilation.h"
+
+#ifdef NETWORKMANAGER_COMPILATION
+    #error Dont define NETWORKMANAGER_COMPILATION
+#endif
+
+#ifndef G_LOG_DOMAIN
+    #error Define G_LOG_DOMAIN
+#endif
+
+/*****************************************************************************/
+
+#define NETWORKMANAGER_COMPILATION 0
+
+/*****************************************************************************/
+
+/* always include these headers for our internal source files. */
+
+#ifndef ___CONFIG_H__
+    #define ___CONFIG_H__
+    #include <config.h>
+#endif
+
+#include "config-extra.h"
+
+/* for internal compilation we don't want the deprecation macros
+ * to be in effect. Define the widest range of versions to effectively
+ * disable deprecation checks */
+#define NM_VERSION_MIN_REQUIRED NM_VERSION_0_9_8
+
+#ifndef NM_MORE_ASSERTS
+    #define NM_MORE_ASSERTS 0
+#endif
+
+#if NM_MORE_ASSERTS == 0
+    /* The cast macros like NM_TYPE() are implemented via G_TYPE_CHECK_INSTANCE_CAST()
+     * and _G_TYPE_CIC(). The latter, by default performs runtime checks of the type
+     * by calling g_type_check_instance_cast().
+     * This check has a certain overhead without being helpful.
+     *
+     * Example 1:
+     *     static void foo (NMType *obj)
+     *     {
+     *         access_obj_without_check (obj);
+     *     }
+     *     foo ((NMType *) obj);
+     *     // There is no runtime check and passing an invalid pointer
+     *     // leads to a crash.
+     *
+     * Example 2:
+     *     static void foo (NMType *obj)
+     *     {
+     *         access_obj_without_check (obj);
+     *     }
+     *     foo (NM_TYPE (obj));
+     *     // There is a runtime check which prints a g_warning(), but that doesn't
+     *     // avoid the crash as NM_TYPE() cannot do anything then passing on the
+     *     // invalid pointer.
+     *
+     * Example 3:
+     *     static void foo (NMType *obj)
+     *     {
+     *         g_return_if_fail (NM_IS_TYPE (obj));
+     *         access_obj_without_check (obj);
+     *     }
+     *     foo ((NMType *) obj);
+     *     // There is a runtime check which prints a g_critical() which also avoids
+     *     // the crash. That is actually helpful to catch bugs and avoid crashes.
+     *
+     * Example 4:
+     *     static void foo (NMType *obj)
+     *     {
+     *         g_return_if_fail (NM_IS_TYPE (obj));
+     *         access_obj_without_check (obj);
+     *     }
+     *     foo (NM_TYPE (obj));
+     *     // The runtime check is performed twice, with printing a g_warning() and
+     *     // a g_critical() and avoiding the crash.
+     *
+     * Example 3 is how it should be done. Type checks in NM_TYPE() are pointless.
+     * Disable them for our production builds.
+     */
+    #ifndef G_DISABLE_CAST_CHECKS
+        #define G_DISABLE_CAST_CHECKS
+    #endif
+#endif
+
+/*****************************************************************************/
+
+#include <stdlib.h>
+
+/*****************************************************************************/
+
+#endif /* __NM_DEFAULT_STD_H__ */
diff --git a/shared/nm-std-aux/nm-networkmanager-compilation.h b/shared/nm-std-aux/nm-networkmanager-compilation.h
new file mode 100644
index 00000000..025a158d
--- /dev/null
+++ b/shared/nm-std-aux/nm-networkmanager-compilation.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_NETWORKMANAGER_COMPILATION_H__
+#define __NM_NETWORKMANAGER_COMPILATION_H__
+
+#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB                (1 << 0)
+#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB       (1 << 1)
+#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG      (1 << 2)
+#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM               (1 << 3)
+#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE       (1 << 4)
+#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE          (1 << 5)
+#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL (1 << 6)
+#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE  (1 << 7)
+#define NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON              (1 << 10)
+#define NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD             (1 << 11)
+
+#define NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE             \
+    (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB             \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB      \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE         \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)
+
+#define NM_NETWORKMANAGER_COMPILATION_LIBNM                                                        \
+    (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB                                                   \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE                                            \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE                                               \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)
+
+#define NM_NETWORKMANAGER_COMPILATION_CLIENT             \
+    (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB         \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE)
+
+#define NM_NETWORKMANAGER_COMPILATION_DAEMON                  \
+    (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB              \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG      \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE          \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL \
+     | NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON)
+
+#define NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED \
+    (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB | NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD)
+
+#define NM_NETWORKMANAGER_COMPILATION_SYSTEMD \
+    (0 | NM_NETWORKMANAGER_COMPILATION_DAEMON | NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED)
+
+#define NM_NETWORKMANAGER_COMPILATION_GLIB (0 | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB)
+
+#endif /* __NM_NETWORKMANAGER_COMPILATION_H__ */
diff --git a/shared/nm-std-aux/nm-std-aux.h b/shared/nm-std-aux/nm-std-aux.h
index 762f104b..30adeb58 100644
--- a/shared/nm-std-aux/nm-std-aux.h
+++ b/shared/nm-std-aux/nm-std-aux.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #ifndef __NM_STD_AUX_H__
 #define __NM_STD_AUX_H__
@@ -189,6 +189,37 @@ typedef uint64_t _nm_bitwise nm_be64_t;
 
 /*****************************************************************************/
 
+static inline uint32_t
+nm_add_clamped_u32(uint32_t a, uint32_t b)
+{
+    uint32_t c;
+
+    /* returns a+b, or UINT32_MAX if the result would overflow. */
+
+    c = a + b;
+    if (c < a)
+        return UINT32_MAX;
+    return c;
+}
+
+static inline unsigned
+nm_mult_clamped_u(unsigned a, unsigned b)
+{
+    unsigned c;
+
+    /* returns a*b, or UINT_MAX if the result would overflow. */
+
+    if (b == 0)
+        return 0;
+
+    c = a * b;
+
+    if (c / b != a)
+        return (unsigned) -1;
+
+    return c;
+}
+
 /* glib's MIN()/MAX() macros don't have function-like behavior, in that they evaluate
  * the argument possibly twice.
  *
@@ -767,6 +798,21 @@ nm_steal_fd(int *p_fd)
 
 /*****************************************************************************/
 
+static inline uintptr_t
+nm_ptr_to_uintptr(const void *p)
+{
+    /* in C, pointers can only be compared (with less-than or greater-than) under certain
+     * circumstances. Since uintptr_t is supposed to be able to represent the pointer
+     * as a plain integer and also support to convert the integer back to the pointer,
+     * it should be safer to compare the pointers directly.
+     *
+     * Of course, this function isn't very useful beyond that its use makes it clear
+     * that we want to compare pointers by value, which otherwise may not be valid. */
+    return (uintptr_t) p;
+}
+
+/*****************************************************************************/
+
 #define NM_CMP_RETURN(c)             \
     do {                             \
         const int _cc = (c);         \
@@ -813,7 +859,7 @@ nm_steal_fd(int *p_fd)
  * Avoid that by casting pointers to void* and then to uintptr_t. This comparison
  * is not really meaningful, except that it provides some kind of stable sort order
  * between pointers (that can otherwise not be compared). */
-#define NM_CMP_DIRECT_PTR(a, b) NM_CMP_DIRECT((uintptr_t)((void *) (a)), (uintptr_t)((void *) (b)))
+#define NM_CMP_DIRECT_PTR(a, b) NM_CMP_DIRECT(nm_ptr_to_uintptr(a), nm_ptr_to_uintptr(b))
 
 #define NM_CMP_DIRECT_MEMCMP(a, b, size) NM_CMP_RETURN(memcmp((a), (b), (size)))
 
diff --git a/shared/nm-std-aux/nm-std-utils.c b/shared/nm-std-aux/nm-std-utils.c
index 6f7f4c58..18692b19 100644
--- a/shared/nm-std-aux/nm-std-utils.c
+++ b/shared/nm-std-aux/nm-std-utils.c
@@ -1,10 +1,12 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include "nm-default.h"
+#include "nm-default-std.h"
 
 #include "nm-std-utils.h"
 
 #include <stdint.h>
+#include <assert.h>
+#include <limits.h>
 
 /*****************************************************************************/
 
@@ -50,7 +52,7 @@ nm_utils_get_next_realloc_size(bool true_realloc, size_t requested)
          * We get thus sizes of 104, 232, 488, 1000, 2024, 4072, 8168... */
 
         if (NM_UNLIKELY(requested > SIZE_MAX / 2u - 24u))
-            return SIZE_MAX;
+            goto out_huge;
 
         x = requested + 24u;
         n = 128u;
@@ -63,8 +65,10 @@ nm_utils_get_next_realloc_size(bool true_realloc, size_t requested)
         return n - 24u;
     }
 
-    if (NM_UNLIKELY(requested > SIZE_MAX - 0x1000u - 24u))
-        return SIZE_MAX;
+    if (NM_UNLIKELY(requested > SIZE_MAX - 0x1000u - 24u)) {
+        /* overflow happened. */
+        goto out_huge;
+    }
 
     /* For large allocations (with !true_realloc) we allocate memory in chunks of
      * 4K (- 24 bytes extra), assuming that the memory gets mmapped and thus
@@ -72,4 +76,15 @@ nm_utils_get_next_realloc_size(bool true_realloc, size_t requested)
     n = ((requested + (0x0FFFu + 24u)) & ~((size_t) 0x0FFFu)) - 24u;
     nm_assert(n >= requested);
     return n;
+
+out_huge:
+    if (sizeof(size_t) > 4u) {
+        /* on s390x (64 bit), gcc with LTO can complain that the size argument to
+         * malloc must not be larger than 9223372036854775807.
+         *
+         * Work around that by returning SSIZE_MAX. It should be plenty still! */
+        assert(requested <= (size_t) SSIZE_MAX);
+        return (size_t) SSIZE_MAX;
+    }
+    return SIZE_MAX;
 }
diff --git a/shared/nm-std-aux/nm-std-utils.h b/shared/nm-std-aux/nm-std-utils.h
index 1f908191..9c851f1f 100644
--- a/shared/nm-std-aux/nm-std-utils.h
+++ b/shared/nm-std-aux/nm-std-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #ifndef __NM_STD_UTILS_H__
 #define __NM_STD_UTILS_H__
diff --git a/shared/nm-std-aux/unaligned.h b/shared/nm-std-aux/unaligned.h
index e0bc1043..4100be08 100644
--- a/shared/nm-std-aux/unaligned.h
+++ b/shared/nm-std-aux/unaligned.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <endian.h>
@@ -6,142 +6,94 @@
 
 /* BE */
 
-static inline uint16_t
-unaligned_read_be16(const void *_u)
-{
-    const struct __attribute__((__packed__, __may_alias__)) {
-        uint16_t x;
-    } *u = _u;
+static inline uint16_t unaligned_read_be16(const void *_u) {
+        const struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u;
 
-    return be16toh(u->x);
+        return be16toh(u->x);
 }
 
-static inline uint32_t
-unaligned_read_be32(const void *_u)
-{
-    const struct __attribute__((__packed__, __may_alias__)) {
-        uint32_t x;
-    } *u = _u;
+static inline uint32_t unaligned_read_be32(const void *_u) {
+        const struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u;
 
-    return be32toh(u->x);
+        return be32toh(u->x);
 }
 
-static inline uint64_t
-unaligned_read_be64(const void *_u)
-{
-    const struct __attribute__((__packed__, __may_alias__)) {
-        uint64_t x;
-    } *u = _u;
+static inline uint64_t unaligned_read_be64(const void *_u) {
+        const struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u;
 
-    return be64toh(u->x);
+        return be64toh(u->x);
 }
 
-static inline void
-unaligned_write_be16(void *_u, uint16_t a)
-{
-    struct __attribute__((__packed__, __may_alias__)) {
-        uint16_t x;
-    } *u = _u;
+static inline void unaligned_write_be16(void *_u, uint16_t a) {
+        struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u;
 
-    u->x = be16toh(a);
+        u->x = be16toh(a);
 }
 
-static inline void
-unaligned_write_be32(void *_u, uint32_t a)
-{
-    struct __attribute__((__packed__, __may_alias__)) {
-        uint32_t x;
-    } *u = _u;
+static inline void unaligned_write_be32(void *_u, uint32_t a) {
+        struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u;
 
-    u->x = be32toh(a);
+        u->x = be32toh(a);
 }
 
-static inline void
-unaligned_write_be64(void *_u, uint64_t a)
-{
-    struct __attribute__((__packed__, __may_alias__)) {
-        uint64_t x;
-    } *u = _u;
+static inline void unaligned_write_be64(void *_u, uint64_t a) {
+        struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u;
 
-    u->x = be64toh(a);
+        u->x = be64toh(a);
 }
 
 /* LE */
 
-static inline uint16_t
-unaligned_read_le16(const void *_u)
-{
-    const struct __attribute__((__packed__, __may_alias__)) {
-        uint16_t x;
-    } *u = _u;
+static inline uint16_t unaligned_read_le16(const void *_u) {
+        const struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u;
 
-    return le16toh(u->x);
+        return le16toh(u->x);
 }
 
-static inline uint32_t
-unaligned_read_le32(const void *_u)
-{
-    const struct __attribute__((__packed__, __may_alias__)) {
-        uint32_t x;
-    } *u = _u;
+static inline uint32_t unaligned_read_le32(const void *_u) {
+        const struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u;
 
-    return le32toh(u->x);
+        return le32toh(u->x);
 }
 
-static inline uint64_t
-unaligned_read_le64(const void *_u)
-{
-    const struct __attribute__((__packed__, __may_alias__)) {
-        uint64_t x;
-    } *u = _u;
+static inline uint64_t unaligned_read_le64(const void *_u) {
+        const struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u;
 
-    return le64toh(u->x);
+        return le64toh(u->x);
 }
 
-static inline void
-unaligned_write_le16(void *_u, uint16_t a)
-{
-    struct __attribute__((__packed__, __may_alias__)) {
-        uint16_t x;
-    } *u = _u;
+static inline void unaligned_write_le16(void *_u, uint16_t a) {
+        struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u;
 
-    u->x = le16toh(a);
+        u->x = le16toh(a);
 }
 
-static inline void
-unaligned_write_le32(void *_u, uint32_t a)
-{
-    struct __attribute__((__packed__, __may_alias__)) {
-        uint32_t x;
-    } *u = _u;
+static inline void unaligned_write_le32(void *_u, uint32_t a) {
+        struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u;
 
-    u->x = le32toh(a);
+        u->x = le32toh(a);
 }
 
-static inline void
-unaligned_write_le64(void *_u, uint64_t a)
-{
-    struct __attribute__((__packed__, __may_alias__)) {
-        uint64_t x;
-    } *u = _u;
+static inline void unaligned_write_le64(void *_u, uint64_t a) {
+        struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u;
 
-    u->x = le64toh(a);
+        u->x = le64toh(a);
 }
 
 #if __BYTE_ORDER == __BIG_ENDIAN
-    #define unaligned_read_ne16 unaligned_read_be16
-    #define unaligned_read_ne32 unaligned_read_be32
-    #define unaligned_read_ne64 unaligned_read_be64
+#define unaligned_read_ne16 unaligned_read_be16
+#define unaligned_read_ne32 unaligned_read_be32
+#define unaligned_read_ne64 unaligned_read_be64
 
-    #define unaligned_write_ne16 unaligned_write_be16
-    #define unaligned_write_ne32 unaligned_write_be32
-    #define unaligned_write_ne64 unaligned_write_be64
+#define unaligned_write_ne16 unaligned_write_be16
+#define unaligned_write_ne32 unaligned_write_be32
+#define unaligned_write_ne64 unaligned_write_be64
 #else
-    #define unaligned_read_ne16 unaligned_read_le16
-    #define unaligned_read_ne32 unaligned_read_le32
-    #define unaligned_read_ne64 unaligned_read_le64
+#define unaligned_read_ne16 unaligned_read_le16
+#define unaligned_read_ne32 unaligned_read_le32
+#define unaligned_read_ne64 unaligned_read_le64
 
-    #define unaligned_write_ne16 unaligned_write_le16
-    #define unaligned_write_ne32 unaligned_write_le32
-    #define unaligned_write_ne64 unaligned_write_le64
+#define unaligned_write_ne16 unaligned_write_le16
+#define unaligned_write_ne32 unaligned_write_le32
+#define unaligned_write_ne64 unaligned_write_le64
 #endif
diff --git a/shared/nm-test-libnm-utils.h b/shared/nm-test-libnm-utils.h
index b44db961..cac54076 100644
--- a/shared/nm-test-libnm-utils.h
+++ b/shared/nm-test-libnm-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014 - 2015 Red Hat, Inc.
  */
diff --git a/shared/nm-test-utils-impl.c b/shared/nm-test-utils-impl.c
index 57f30ea9..f8f98d81 100644
--- a/shared/nm-test-utils-impl.c
+++ b/shared/nm-test-utils-impl.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2010 - 2015 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "libnm/nm-default-libnm.h"
 
 #include <sys/wait.h>
 
diff --git a/shared/nm-udev-aux/nm-udev-utils.c b/shared/nm-udev-aux/nm-udev-utils.c
index 8131c04e..0b941dff 100644
--- a/shared/nm-udev-aux/nm-udev-utils.c
+++ b/shared/nm-udev-aux/nm-udev-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
 
 #include "nm-udev-utils.h"
 
@@ -241,11 +241,11 @@ nm_udev_client_new(const char *const *subsystems,
     return self;
 
 fail:
-    return nm_udev_client_unref(self);
+    return nm_udev_client_destroy(self);
 }
 
 NMUdevClient *
-nm_udev_client_unref(NMUdevClient *self)
+nm_udev_client_destroy(NMUdevClient *self)
 {
     if (!self)
         return NULL;
diff --git a/shared/nm-udev-aux/nm-udev-utils.h b/shared/nm-udev-aux/nm-udev-utils.h
index f24659eb..191f9a89 100644
--- a/shared/nm-udev-aux/nm-udev-utils.h
+++ b/shared/nm-udev-aux/nm-udev-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
@@ -24,7 +24,7 @@ NMUdevClient *nm_udev_client_new(const char *const *subsystems,
                                  NMUdevClientEvent  event_handler,
                                  gpointer           event_user_data);
 
-NMUdevClient *nm_udev_client_unref(NMUdevClient *self);
+NMUdevClient *nm_udev_client_destroy(NMUdevClient *self);
 
 struct udev *nm_udev_client_get_udev(NMUdevClient *self);
 
diff --git a/shared/nm-utils/nm-compat.c b/shared/nm-utils/nm-compat.c
index f87ef6b9..dad48227 100644
--- a/shared/nm-utils/nm-compat.c
+++ b/shared/nm-utils/nm-compat.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "libnm/nm-default-libnm.h"
 
 #include "nm-compat.h"
 
diff --git a/shared/nm-utils/nm-compat.h b/shared/nm-utils/nm-compat.h
index fb136a5b..559afd5d 100644
--- a/shared/nm-utils/nm-compat.h
+++ b/shared/nm-utils/nm-compat.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2017 Red Hat, Inc.
  */
diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h
index 62d608c6..6b41c11e 100644
--- a/shared/nm-utils/nm-test-utils.h
+++ b/shared/nm-utils/nm-test-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014 Red Hat, Inc.
  */
@@ -6,10 +6,6 @@
 #ifndef __NM_TEST_UTILS_H__
 #define __NM_TEST_UTILS_H__
 
-#if defined(NETWORKMANAGER_COMPILATION) && !defined(NETWORKMANAGER_COMPILATION_TEST)
-    #error Need to mark the compilation with NETWORKMANAGER_COMPILATION_TEST.
-#endif
-
 /*******************************************************************************
  * HOWTO run tests.
  *
@@ -84,8 +80,6 @@
  *
  *******************************************************************************/
 
-#include "nm-default.h"
-
 #if defined(NM_ASSERT_NO_MSG) && NM_ASSERT_NO_MSG
     #undef g_return_if_fail_warning
     #undef g_assertion_message_expr
@@ -98,10 +92,6 @@
 #include <string.h>
 #include <errno.h>
 
-#ifndef NM_TEST_UTILS_NO_LIBNM
-    #include "nm-utils.h"
-#endif
-
 /*****************************************************************************/
 
 #define NMTST_G_RETURN_MSG_S(expr) "*: assertion '" NM_ASSERT_G_RETURN_EXPR(expr) "' failed"
@@ -900,14 +890,16 @@ nmtst_get_rand_uint64(void)
 static inline guint
 nmtst_get_rand_uint(void)
 {
-    G_STATIC_ASSERT_EXPR(sizeof(guint32) == sizeof(guint));
-    return nmtst_get_rand_uint32();
+    G_STATIC_ASSERT_EXPR((sizeof(guint) == sizeof(guint32) || (sizeof(guint) == sizeof(guint64))));
+    if (sizeof(guint32) == sizeof(guint))
+        return nmtst_get_rand_uint32();
+    return nmtst_get_rand_uint64();
 }
 
 static inline gsize
 nmtst_get_rand_size(void)
 {
-    G_STATIC_ASSERT_EXPR(sizeof(gsize) == sizeof(guint32) || sizeof(gsize) == sizeof(guint64));
+    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();
@@ -919,6 +911,17 @@ nmtst_get_rand_bool(void)
     return nmtst_get_rand_uint32() % 2;
 }
 
+static inline gboolean
+nmtst_get_rand_one_case_in(guint32 num)
+{
+    /* num=1 doesn't make much sense, because it will always return %TRUE.
+     * Still accept it, it might be that @num is calculated, so 1 might be
+     * a valid edge case. */
+    g_assert(num > 0);
+
+    return (nmtst_get_rand_uint32() % num) == 0;
+}
+
 static inline gpointer
 nmtst_rand_buf(GRand *rand, gpointer buffer, gsize buffer_length)
 {
@@ -998,6 +1001,23 @@ nmtst_rand_perm(GRand *rand, void *dst, const void *src, gsize elmt_size, gsize
     return dst;
 }
 
+static inline const char **
+nmtst_rand_perm_strv(const char *const *strv)
+{
+    const char **res;
+    gsize        n;
+
+    if (!strv)
+        return NULL;
+
+    /* this returns a (scrambled) SHALLOW copy of the strv array! */
+
+    n   = NM_PTRARRAY_LEN(strv);
+    res = (const char **) (nm_utils_strv_dup(strv, n, FALSE) ?: g_new0(char *, 1));
+    nmtst_rand_perm(NULL, res, res, sizeof(char *), n);
+    return res;
+}
+
 static inline GSList *
 nmtst_rand_perm_gslist(GRand *rand, GSList *list)
 {
@@ -1181,6 +1201,14 @@ nmtst_main_loop_run(GMainLoop *loop, guint timeout_msec)
     return loopx != NULL;
 }
 
+#define nmtst_main_loop_run_assert(loop, timeout_msec)    \
+    G_STMT_START                                          \
+    {                                                     \
+        if (!nmtst_main_loop_run((loop), (timeout_msec))) \
+            g_assert_not_reached();                       \
+    }                                                     \
+    G_STMT_END
+
 static inline void
 _nmtst_main_loop_quit_on_notify(GObject *object, GParamSpec *pspec, gpointer user_data)
 {
@@ -2751,4 +2779,29 @@ nmtst_keyfile_get_num_keys(GKeyFile *keyfile, const char *group_name)
 
 /*****************************************************************************/
 
+#if defined(NM_SETTING_IP_CONFIG_H) && defined(__NM_SHARED_UTILS_H__)
+
+static inline NMIPAddress *
+nmtst_ip_address_new(int addr_family, const char *str)
+{
+    NMIPAddr     addr;
+    int          plen;
+    GError *     error = NULL;
+    NMIPAddress *a;
+
+    if (!nm_utils_parse_inaddr_prefix_bin(addr_family, str, &addr_family, &addr, &plen))
+        g_assert_not_reached();
+
+    if (plen == -1)
+        plen = addr_family == AF_INET ? 32 : 128;
+
+    a = nm_ip_address_new_binary(addr_family, &addr, plen, &error);
+    nmtst_assert_success(a, error);
+    return a;
+}
+
+#endif
+
+/*****************************************************************************/
+
 #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 780ef193..5772b843 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: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
diff --git a/shared/nm-utils/nm-vpn-plugin-macros.h b/shared/nm-utils/nm-vpn-plugin-macros.h
index 2fec94e5..4ca22427 100644
--- a/shared/nm-utils/nm-vpn-plugin-macros.h
+++ b/shared/nm-utils/nm-vpn-plugin-macros.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
diff --git a/shared/nm-utils/nm-vpn-plugin-utils.c b/shared/nm-utils/nm-vpn-plugin-utils.c
index 1af640da..89285ecd 100644
--- a/shared/nm-utils/nm-vpn-plugin-utils.c
+++ b/shared/nm-utils/nm-vpn-plugin-utils.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2016, 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "libnm/nm-default-client.h"
 
 #include "nm-vpn-plugin-utils.h"
 
diff --git a/shared/nm-utils/nm-vpn-plugin-utils.h b/shared/nm-utils/nm-vpn-plugin-utils.h
index 0e5e1dab..881a368a 100644
--- a/shared/nm-utils/nm-vpn-plugin-utils.h
+++ b/shared/nm-utils/nm-vpn-plugin-utils.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2016 Red Hat, Inc.
  */
diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h
deleted file mode 100644
index bbe82a7d..00000000
--- a/shared/nm-version-macros.h
+++ /dev/null
@@ -1,90 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-/*
- * Copyright (C) 2011, 2015 Red Hat, Inc.
- */
-
-#ifndef __NM_VERSION_MACROS_H__
-#define __NM_VERSION_MACROS_H__
-
-/* This header must not include glib or libnm. */
-
-/**
- * NM_MAJOR_VERSION:
- *
- * Evaluates to the major version number of NetworkManager which this source
- * is compiled against.
- */
-#define NM_MAJOR_VERSION (1)
-
-/**
- * NM_MINOR_VERSION:
- *
- * Evaluates to the minor version number of NetworkManager which this source
- * is compiled against.
- */
-#define NM_MINOR_VERSION (28)
-
-/**
- * NM_MICRO_VERSION:
- *
- * Evaluates to the micro version number of NetworkManager which this source
- * compiled against.
- */
-#define NM_MICRO_VERSION (0)
-
-/**
- * NM_CHECK_VERSION:
- * @major: major version (e.g. 1 for version 1.2.5)
- * @minor: minor version (e.g. 2 for version 1.2.5)
- * @micro: micro version (e.g. 5 for version 1.2.5)
- *
- * Returns: %TRUE if the version of the NetworkManager header files
- * is the same as or newer than the passed-in version.
- */
-#define NM_CHECK_VERSION(major,minor,micro)                         \
-    (NM_MAJOR_VERSION > (major) ||                                  \
-     (NM_MAJOR_VERSION == (major) && NM_MINOR_VERSION > (minor)) || \
-     (NM_MAJOR_VERSION == (major) && NM_MINOR_VERSION == (minor) && NM_MICRO_VERSION >= (micro)))
-
-
-#define NM_ENCODE_VERSION(major,minor,micro) ((major) << 16 | (minor) << 8 | (micro))
-
-#define NM_VERSION_0_9_8  (NM_ENCODE_VERSION (0, 9, 8))
-#define NM_VERSION_0_9_10 (NM_ENCODE_VERSION (0, 9, 10))
-#define NM_VERSION_1_0    (NM_ENCODE_VERSION (1, 0, 0))
-#define NM_VERSION_1_2    (NM_ENCODE_VERSION (1, 2, 0))
-#define NM_VERSION_1_4    (NM_ENCODE_VERSION (1, 4, 0))
-#define NM_VERSION_1_6    (NM_ENCODE_VERSION (1, 6, 0))
-#define NM_VERSION_1_8    (NM_ENCODE_VERSION (1, 8, 0))
-#define NM_VERSION_1_10   (NM_ENCODE_VERSION (1, 10, 0))
-#define NM_VERSION_1_12   (NM_ENCODE_VERSION (1, 12, 0))
-#define NM_VERSION_1_14   (NM_ENCODE_VERSION (1, 14, 0))
-#define NM_VERSION_1_16   (NM_ENCODE_VERSION (1, 16, 0))
-#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_24   (NM_ENCODE_VERSION (1, 24, 0))
-#define NM_VERSION_1_26   (NM_ENCODE_VERSION (1, 26, 0))
-#define NM_VERSION_1_28   (NM_ENCODE_VERSION (1, 28, 0))
-
-/* For releases, NM_API_VERSION is equal to NM_VERSION.
- *
- * For development builds, NM_API_VERSION is the next
- * stable API after NM_VERSION. When you run a development
- * version, you are already using the future API, even if
- * it is not yet release. Hence, the currently used API
- * version is the future one.  */
-#define NM_API_VERSION \
-    (((NM_MINOR_VERSION % 2) == 1) \
-        ? NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION + 1, 0                               ) \
-        : NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION    , ((NM_MICRO_VERSION + 1) / 2) * 2))
-
-/* deprecated. */
-#define NM_VERSION_CUR_STABLE  NM_API_VERSION
-
-/* deprecated. */
-#define NM_VERSION_NEXT_STABLE NM_API_VERSION
-
-#define NM_VERSION NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION, NM_MICRO_VERSION)
-
-#endif  /* __NM_VERSION_MACROS_H__ */
diff --git a/shared/nm-version-macros.h.in b/shared/nm-version-macros.h.in
deleted file mode 100644
index c4d6efbc..00000000
--- a/shared/nm-version-macros.h.in
+++ /dev/null
@@ -1,90 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1+
-/*
- * Copyright (C) 2011, 2015 Red Hat, Inc.
- */
-
-#ifndef __NM_VERSION_MACROS_H__
-#define __NM_VERSION_MACROS_H__
-
-/* This header must not include glib or libnm. */
-
-/**
- * NM_MAJOR_VERSION:
- *
- * Evaluates to the major version number of NetworkManager which this source
- * is compiled against.
- */
-#define NM_MAJOR_VERSION (@NM_MAJOR_VERSION@)
-
-/**
- * NM_MINOR_VERSION:
- *
- * Evaluates to the minor version number of NetworkManager which this source
- * is compiled against.
- */
-#define NM_MINOR_VERSION (@NM_MINOR_VERSION@)
-
-/**
- * NM_MICRO_VERSION:
- *
- * Evaluates to the micro version number of NetworkManager which this source
- * compiled against.
- */
-#define NM_MICRO_VERSION (@NM_MICRO_VERSION@)
-
-/**
- * NM_CHECK_VERSION:
- * @major: major version (e.g. 1 for version 1.2.5)
- * @minor: minor version (e.g. 2 for version 1.2.5)
- * @micro: micro version (e.g. 5 for version 1.2.5)
- *
- * Returns: %TRUE if the version of the NetworkManager header files
- * is the same as or newer than the passed-in version.
- */
-#define NM_CHECK_VERSION(major,minor,micro)                         \
-    (NM_MAJOR_VERSION > (major) ||                                  \
-     (NM_MAJOR_VERSION == (major) && NM_MINOR_VERSION > (minor)) || \
-     (NM_MAJOR_VERSION == (major) && NM_MINOR_VERSION == (minor) && NM_MICRO_VERSION >= (micro)))
-
-
-#define NM_ENCODE_VERSION(major,minor,micro) ((major) << 16 | (minor) << 8 | (micro))
-
-#define NM_VERSION_0_9_8  (NM_ENCODE_VERSION (0, 9, 8))
-#define NM_VERSION_0_9_10 (NM_ENCODE_VERSION (0, 9, 10))
-#define NM_VERSION_1_0    (NM_ENCODE_VERSION (1, 0, 0))
-#define NM_VERSION_1_2    (NM_ENCODE_VERSION (1, 2, 0))
-#define NM_VERSION_1_4    (NM_ENCODE_VERSION (1, 4, 0))
-#define NM_VERSION_1_6    (NM_ENCODE_VERSION (1, 6, 0))
-#define NM_VERSION_1_8    (NM_ENCODE_VERSION (1, 8, 0))
-#define NM_VERSION_1_10   (NM_ENCODE_VERSION (1, 10, 0))
-#define NM_VERSION_1_12   (NM_ENCODE_VERSION (1, 12, 0))
-#define NM_VERSION_1_14   (NM_ENCODE_VERSION (1, 14, 0))
-#define NM_VERSION_1_16   (NM_ENCODE_VERSION (1, 16, 0))
-#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_24   (NM_ENCODE_VERSION (1, 24, 0))
-#define NM_VERSION_1_26   (NM_ENCODE_VERSION (1, 26, 0))
-#define NM_VERSION_1_28   (NM_ENCODE_VERSION (1, 28, 0))
-
-/* For releases, NM_API_VERSION is equal to NM_VERSION.
- *
- * For development builds, NM_API_VERSION is the next
- * stable API after NM_VERSION. When you run a development
- * version, you are already using the future API, even if
- * it is not yet release. Hence, the currently used API
- * version is the future one.  */
-#define NM_API_VERSION \
-    (((NM_MINOR_VERSION % 2) == 1) \
-        ? NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION + 1, 0                               ) \
-        : NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION    , ((NM_MICRO_VERSION + 1) / 2) * 2))
-
-/* deprecated. */
-#define NM_VERSION_CUR_STABLE  NM_API_VERSION
-
-/* deprecated. */
-#define NM_VERSION_NEXT_STABLE NM_API_VERSION
-
-#define NM_VERSION NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION, NM_MICRO_VERSION)
-
-#endif  /* __NM_VERSION_MACROS_H__ */
diff --git a/shared/systemd/nm-default-systemd-shared.h b/shared/systemd/nm-default-systemd-shared.h
new file mode 100644
index 00000000..bc0e6c4c
--- /dev/null
+++ b/shared/systemd/nm-default-systemd-shared.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEFAULT_SYSTEMD_SHARED_H__
+#define __NM_DEFAULT_SYSTEMD_SHARED_H__
+
+/*****************************************************************************/
+
+#include "nm-glib-aux/nm-default-glib.h"
+
+#undef NETWORKMANAGER_COMPILATION
+#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_SYSTEMD_SHARED
+
+/*****************************************************************************/
+
+#endif /* __NM_DEFAULT_SYSTEMD_SHARED_H__ */
diff --git a/shared/systemd/nm-logging-stub.c b/shared/systemd/nm-logging-stub.c
index fa2328f3..8db90cd9 100644
--- a/shared/systemd/nm-logging-stub.c
+++ b/shared/systemd/nm-logging-stub.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "shared/systemd/nm-default-systemd-shared.h"
 
 #include "nm-glib-aux/nm-logging-fwd.h"
 
diff --git a/shared/systemd/nm-sd-utils-shared.c b/shared/systemd/nm-sd-utils-shared.c
index 83c91af7..f0504aa9 100644
--- a/shared/systemd/nm-sd-utils-shared.c
+++ b/shared/systemd/nm-sd-utils-shared.c
@@ -1,9 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
-#include "nm-default.h"
+#include "shared/systemd/nm-default-systemd-shared.h"
 
 #include "nm-sd-utils-shared.h"
 
@@ -88,7 +88,27 @@ nm_sd_dns_name_is_valid(const char *s)
 gboolean
 nm_sd_hostname_is_valid(const char *s, bool allow_trailing_dot)
 {
-    return hostname_is_valid(s, allow_trailing_dot);
+    return hostname_is_valid(s,
+                             allow_trailing_dot ? VALID_HOSTNAME_TRAILING_DOT
+                                                : (ValidHostnameFlags) 0);
+}
+
+char *
+nm_sd_dns_name_normalize(const char *s)
+{
+    nm_auto_free char *n = NULL;
+    int                r;
+
+    r = dns_name_normalize(s, 0, &n);
+    if (r < 0)
+        return NULL;
+
+    nm_assert(n);
+
+    /* usually we try not to mix malloc/g_malloc and free/g_free. In practice,
+     * they are the same. So here we return a buffer allocated with malloc(),
+     * and the caller should free it with g_free(). */
+    return g_steal_pointer(&n);
 }
 
 /*****************************************************************************/
diff --git a/shared/systemd/nm-sd-utils-shared.h b/shared/systemd/nm-sd-utils-shared.h
index b871fc29..45089c07 100644
--- a/shared/systemd/nm-sd-utils-shared.h
+++ b/shared/systemd/nm-sd-utils-shared.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2018 Red Hat, Inc.
  */
@@ -28,6 +28,8 @@ nm_sd_dns_name_to_wire_format(const char *domain, guint8 *buffer, size_t len, gb
 int      nm_sd_dns_name_is_valid(const char *s);
 gboolean nm_sd_hostname_is_valid(const char *s, bool allow_trailing_dot);
 
+char *nm_sd_dns_name_normalize(const char *s);
+
 /*****************************************************************************/
 
 gboolean nm_sd_http_url_is_valid_https(const char *url);
diff --git a/shared/systemd/sd-adapt-shared/idn-util.h b/shared/systemd/sd-adapt-shared/idn-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/shared/systemd/sd-adapt-shared/idn-util.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 0160284d..b094ce40 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: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2014 - 2018 Red Hat, Inc.
  */
@@ -6,7 +6,7 @@
 #ifndef __NM_SD_ADAPT_SHARED_H__
 #define __NM_SD_ADAPT_SHARED_H__
 
-#include "nm-default.h"
+#include "shared/systemd/nm-default-systemd-shared.h"
 
 #include "nm-glib-aux/nm-logging-fwd.h"
 
@@ -127,6 +127,7 @@ _nm_log_get_max_level_realm(void)
 
     #include <sys/syscall.h>
     #include <sys/ioctl.h>
+    #include <pthread.h>
 
     #define ENABLE_GSHADOW FALSE
 
@@ -195,6 +196,24 @@ _nm_gettid(void)
         #define HAVE_RT_SIGQUEUEINFO 0
     #endif
 
+    #ifndef __COMPAR_FN_T
+        #define __COMPAR_FN_T
+typedef int (*__compar_fn_t)(const void *, const void *);
+typedef __compar_fn_t comparison_fn_t;
+typedef int (*__compar_d_fn_t)(const void *, const void *, void *);
+    #endif
+
+    #ifndef __GLIBC__
+static inline int
+__register_atfork(void (*prepare)(void),
+                  void (*parent)(void),
+                  void (*child)(void),
+                  void *dso_handle)
+{
+    return pthread_atfork(prepare, parent, child);
+}
+    #endif
+
 #endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */
 
 /*****************************************************************************/
diff --git a/shared/systemd/src/basic/alloc-util.c b/shared/systemd/src/basic/alloc-util.c
index e355b60f..7f7eb433 100644
--- a/shared/systemd/src/basic/alloc-util.c
+++ b/shared/systemd/src/basic/alloc-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/basic/alloc-util.h b/shared/systemd/src/basic/alloc-util.h
index 64d9e003..f3e192dd 100644
--- a/shared/systemd/src/basic/alloc-util.h
+++ b/shared/systemd/src/basic/alloc-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <alloca.h>
@@ -27,7 +27,7 @@ typedef void (*free_func_t)(void *p);
                 size_t _n_ = n;                                         \
                 assert(!size_multiply_overflow(sizeof(t), _n_));        \
                 assert(sizeof(t)*_n_ <= ALLOCA_MAX);                    \
-                (t*) alloca(sizeof(t)*_n_);                             \
+                (t*) alloca((sizeof(t)*_n_) ?: 1);                      \
         })
 
 #define newa0(t, n)                                                     \
@@ -35,14 +35,14 @@ typedef void (*free_func_t)(void *p);
                 size_t _n_ = n;                                         \
                 assert(!size_multiply_overflow(sizeof(t), _n_));        \
                 assert(sizeof(t)*_n_ <= ALLOCA_MAX);                    \
-                (t*) alloca0(sizeof(t)*_n_);                            \
+                (t*) alloca0((sizeof(t)*_n_) ?: 1);                     \
         })
 
 #define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n)))
 
 #define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, sizeof(t), (n)))
 
-#define malloc0(n) (calloc(1, (n)))
+#define malloc0(n) (calloc(1, (n) ?: 1))
 
 static inline void *mfree(void *memory) {
         free(memory);
@@ -65,7 +65,7 @@ void* memdup_suffix0(const void *p, size_t l); /* We can't use _alloc_() here, s
                 void *_q_;                      \
                 size_t _l_ = l;                 \
                 assert(_l_ <= ALLOCA_MAX);      \
-                _q_ = alloca(_l_);              \
+                _q_ = alloca(_l_ ?: 1);         \
                 memcpy(_q_, p, _l_);            \
         })
 
@@ -135,7 +135,7 @@ void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size);
                 char *_new_;                            \
                 size_t _len_ = n;                       \
                 assert(_len_ <= ALLOCA_MAX);            \
-                _new_ = alloca(_len_);                  \
+                _new_ = alloca(_len_ ?: 1);             \
                 (void *) memset(_new_, 0, _len_);       \
         })
 
@@ -146,7 +146,7 @@ void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size);
                 size_t _mask_ = (align) - 1;                            \
                 size_t _size_ = size;                                   \
                 assert(_size_ <= ALLOCA_MAX);                           \
-                _ptr_ = alloca(_size_ + _mask_);                        \
+                _ptr_ = alloca((_size_ + _mask_) ?: 1);                 \
                 (void*)(((uintptr_t)_ptr_ + _mask_) & ~_mask_);         \
         })
 
diff --git a/shared/systemd/src/basic/async.h b/shared/systemd/src/basic/async.h
index 31606131..e0bbaa56 100644
--- a/shared/systemd/src/basic/async.h
+++ b/shared/systemd/src/basic/async.h
@@ -1,7 +1,13 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <sys/types.h>
+
+#include "macro.h"
+
 int asynchronous_job(void* (*func)(void *p), void *arg);
 
 int asynchronous_sync(pid_t *ret_pid);
 int asynchronous_close(int fd);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(int, asynchronous_close);
diff --git a/shared/systemd/src/basic/cgroup-util.h b/shared/systemd/src/basic/cgroup-util.h
index 2b88571b..bdc0d0d0 100644
--- a/shared/systemd/src/basic/cgroup-util.h
+++ b/shared/systemd/src/basic/cgroup-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <dirent.h>
@@ -208,6 +208,9 @@ static inline int cg_get_keyed_attribute_graceful(
 
 int cg_get_attribute_as_uint64(const char *controller, const char *path, const char *attribute, uint64_t *ret);
 
+/* Does a parse_boolean() on the attribute contents and sets ret accordingly */
+int cg_get_attribute_as_bool(const char *controller, const char *path, const char *attribute, bool *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);
@@ -275,3 +278,13 @@ CGroupController cgroup_controller_from_string(const char *s) _pure_;
 
 bool is_cgroup_fs(const struct statfs *s);
 bool fd_is_cgroup_fs(int fd);
+
+typedef enum ManagedOOMMode {
+        MANAGED_OOM_AUTO,
+        MANAGED_OOM_KILL,
+        _MANAGED_OOM_MODE_MAX,
+        _MANAGED_OOM_MODE_INVALID = -1,
+} ManagedOOMMode;
+
+const char* managed_oom_mode_to_string(ManagedOOMMode m) _const_;
+ManagedOOMMode managed_oom_mode_from_string(const char *s) _pure_;
diff --git a/shared/systemd/src/basic/env-file.c b/shared/systemd/src/basic/env-file.c
index 1e93f18e..568b742f 100644
--- a/shared/systemd/src/basic/env-file.c
+++ b/shared/systemd/src/basic/env-file.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/basic/env-file.h b/shared/systemd/src/basic/env-file.h
index e1ca195f..de475885 100644
--- a/shared/systemd/src/basic/env-file.h
+++ b/shared/systemd/src/basic/env-file.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdarg.h>
diff --git a/shared/systemd/src/basic/env-util.c b/shared/systemd/src/basic/env-util.c
index 11f4b29b..a311ee00 100644
--- a/shared/systemd/src/basic/env-util.c
+++ b/shared/systemd/src/basic/env-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -19,7 +19,8 @@
 #include "utf8.h"
 
 #if 0 /* NM_IGNORED */
-#define VALID_CHARS_ENV_NAME                    \
+/* We follow bash for the character set. Different shells have different rules. */
+#define VALID_BASH_ENV_NAME_CHARS               \
         DIGITS LETTERS                          \
         "_"
 
@@ -44,17 +45,14 @@ static bool env_name_is_valid_n(const char *e, size_t n) {
                 return false;
 
         for (p = e; p < e + n; p++)
-                if (!strchr(VALID_CHARS_ENV_NAME, *p))
+                if (!strchr(VALID_BASH_ENV_NAME_CHARS, *p))
                         return false;
 
         return true;
 }
 
 bool env_name_is_valid(const char *e) {
-        if (!e)
-                return false;
-
-        return env_name_is_valid_n(e, strlen(e));
+        return env_name_is_valid_n(e, strlen_ptr(e));
 }
 
 bool env_value_is_valid(const char *e) {
@@ -549,7 +547,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                                 word = e+1;
                                 state = WORD;
 
-                        } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_CHARS_ENV_NAME, *e)) {
+                        } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
                                 k = strnappend(r, word, e-word-1);
                                 if (!k)
                                         return NULL;
@@ -639,7 +637,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                 case VARIABLE_RAW:
                         assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
 
-                        if (!strchr(VALID_CHARS_ENV_NAME, *e)) {
+                        if (!strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
                                 const char *t;
 
                                 t = strv_env_get_n(env, word+1, e-word-1, flags);
@@ -753,3 +751,17 @@ int getenv_bool_secure(const char *p) {
 
         return parse_boolean(e);
 }
+
+#if 0 /* NM_IGNORED */
+int set_unset_env(const char *name, const char *value, bool overwrite) {
+        int r;
+
+        if (value)
+                r = setenv(name, value, overwrite);
+        else
+                r = unsetenv(name);
+        if (r < 0)
+                return -errno;
+        return 0;
+}
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/env-util.h b/shared/systemd/src/basic/env-util.h
index 92802ed7..6684b335 100644
--- a/shared/systemd/src/basic/env-util.h
+++ b/shared/systemd/src/basic/env-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
@@ -52,3 +52,6 @@ char *strv_env_get(char **x, const char *n) _pure_;
 
 int getenv_bool(const char *p);
 int getenv_bool_secure(const char *p);
+
+/* Like setenv, but calls unsetenv if value == NULL. */
+int set_unset_env(const char *name, const char *value, bool overwrite);
diff --git a/shared/systemd/src/basic/errno-util.h b/shared/systemd/src/basic/errno-util.h
index 0ca650f4..5609820b 100644
--- a/shared/systemd/src/basic/errno-util.h
+++ b/shared/systemd/src/basic/errno-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdlib.h>
@@ -50,7 +50,10 @@ static inline int errno_or_else(int fallback) {
 /* Hint #1: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5.
  *
  * Hint #2: The kernel sends e.g., EHOSTUNREACH or ENONET to userspace in some ICMP error cases.  See the
- *          icmp_err_convert[] in net/ipv4/icmp.c in the kernel sources */
+ *          icmp_err_convert[] in net/ipv4/icmp.c in the kernel sources.
+ *
+ * Hint #3: When asynchronous connect() on TCP fails because the host never acknowledges a single packet,
+ *          kernel tells us that with ETIMEDOUT, see tcp(7). */
 static inline bool ERRNO_IS_DISCONNECT(int r) {
         return IN_SET(abs(r),
                       ECONNABORTED,
@@ -66,7 +69,8 @@ static inline bool ERRNO_IS_DISCONNECT(int r) {
                       ENOTCONN,
                       EPIPE,
                       EPROTO,
-                      ESHUTDOWN);
+                      ESHUTDOWN,
+                      ETIMEDOUT);
 }
 
 /* Transient errors we might get on accept() that we should ignore. As per error handling comment in
diff --git a/shared/systemd/src/basic/escape.c b/shared/systemd/src/basic/escape.c
index 1261fd48..094b5c5f 100644
--- a/shared/systemd/src/basic/escape.c
+++ b/shared/systemd/src/basic/escape.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/basic/escape.h b/shared/systemd/src/basic/escape.h
index fa267813..691b6d80 100644
--- a/shared/systemd/src/basic/escape.h
+++ b/shared/systemd/src/basic/escape.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <inttypes.h>
@@ -16,7 +16,7 @@
 
 /* Those that can be escaped or double-quoted.
  *
- * Stricly speaking, ! does not need to be escaped, except in interactive
+ * Strictly speaking, ! does not need to be escaped, except in interactive
  * mode, but let's be extra nice to the user and quote ! in case this
  * output is ever used in interactive mode. */
 #define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;!"
diff --git a/shared/systemd/src/basic/ether-addr-util.c b/shared/systemd/src/basic/ether-addr-util.c
index 4878a3d2..ae83eade 100644
--- a/shared/systemd/src/basic/ether-addr-util.c
+++ b/shared/systemd/src/basic/ether-addr-util.c
@@ -1,8 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
 #include <errno.h>
+#include <inttypes.h>
 #include <net/ethernet.h>
 #include <stdio.h>
 #include <sys/types.h>
@@ -11,6 +12,20 @@
 #include "macro.h"
 #include "string-util.h"
 
+char* hw_addr_to_string(const hw_addr_data *addr, char buffer[HW_ADDR_TO_STRING_MAX]) {
+        assert(addr);
+        assert(buffer);
+        assert(addr->length <= HW_ADDR_MAX_SIZE);
+
+        for (size_t i = 0; i < addr->length; i++) {
+                sprintf(&buffer[3*i], "%02"PRIx8, addr->addr.bytes[i]);
+                if (i < addr->length - 1)
+                        buffer[3*i + 2] = ':';
+        }
+
+        return buffer;
+}
+
 char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]) {
         assert(addr);
         assert(buffer);
diff --git a/shared/systemd/src/basic/ether-addr-util.h b/shared/systemd/src/basic/ether-addr-util.h
index 4e44b30b..942ce556 100644
--- a/shared/systemd/src/basic/ether-addr-util.h
+++ b/shared/systemd/src/basic/ether-addr-util.h
@@ -1,11 +1,35 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <linux/if_infiniband.h>
 #include <net/ethernet.h>
 #include <stdbool.h>
 
 #include "hash-funcs.h"
 
+/* This is MAX_ADDR_LEN as defined in linux/netdevice.h, but net/if_arp.h
+ * defines a macro of the same name with a much lower size. */
+#define HW_ADDR_MAX_SIZE 32
+
+union hw_addr_union {
+        struct ether_addr ether;
+        uint8_t infiniband[INFINIBAND_ALEN];
+        uint8_t bytes[HW_ADDR_MAX_SIZE];
+};
+
+typedef struct hw_addr_data {
+        union hw_addr_union addr;
+        size_t length;
+} hw_addr_data;
+
+#define HW_ADDR_TO_STRING_MAX (3*HW_ADDR_MAX_SIZE)
+char* hw_addr_to_string(const hw_addr_data *addr, char buffer[HW_ADDR_TO_STRING_MAX]);
+
+/* Use only as function argument, never stand-alone! */
+#define HW_ADDR_TO_STR(hw_addr) hw_addr_to_string((hw_addr), (char[HW_ADDR_TO_STRING_MAX]){})
+
+#define HW_ADDR_NULL ((const hw_addr_data){})
+
 #define ETHER_ADDR_FORMAT_STR "%02X%02X%02X%02X%02X%02X"
 #define ETHER_ADDR_FORMAT_VAL(x) (x).ether_addr_octet[0], (x).ether_addr_octet[1], (x).ether_addr_octet[2], (x).ether_addr_octet[3], (x).ether_addr_octet[4], (x).ether_addr_octet[5]
 
diff --git a/shared/systemd/src/basic/extract-word.c b/shared/systemd/src/basic/extract-word.c
index e32b4c7f..1d86033f 100644
--- a/shared/systemd/src/basic/extract-word.c
+++ b/shared/systemd/src/basic/extract-word.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/basic/extract-word.h b/shared/systemd/src/basic/extract-word.h
index f028577c..d1de32e5 100644
--- a/shared/systemd/src/basic/extract-word.h
+++ b/shared/systemd/src/basic/extract-word.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include "macro.h"
diff --git a/shared/systemd/src/basic/fd-util.c b/shared/systemd/src/basic/fd-util.c
index 525d3828..e53cf18d 100644
--- a/shared/systemd/src/basic/fd-util.c
+++ b/shared/systemd/src/basic/fd-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -23,6 +23,7 @@
 #include "path-util.h"
 #include "process-util.h"
 #include "socket-util.h"
+#include "sort-util.h"
 #include "stat-util.h"
 #include "stdio-util.h"
 #include "tmpfile-util.h"
@@ -214,12 +215,97 @@ static int get_max_fd(void) {
 }
 
 int close_all_fds(const int except[], size_t n_except) {
+        static bool have_close_range = true; /* Assume we live in the future */
         _cleanup_closedir_ DIR *d = NULL;
         struct dirent *de;
         int r = 0;
 
         assert(n_except == 0 || except);
 
+        if (have_close_range) {
+                /* In the best case we have close_range() to close all fds between a start and an end fd,
+                 * which we can use on the "inverted" exception array, i.e. all intervals between all
+                 * adjacent pairs from the sorted exception array. This changes loop complexity from O(n)
+                 * where n is number of open fds to O(m⋅log(m)) where m is the number of fds to keep
+                 * open. Given that we assume n ≫ m that's preferable to us. */
+
+                if (n_except == 0) {
+                        /* Close everything. Yay! */
+
+                        if (close_range(3, -1, 0) >= 0)
+                                return 1;
+
+                        if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
+                                return -errno;
+
+                        have_close_range = false;
+                } else {
+                        _cleanup_free_ int *sorted_malloc = NULL;
+                        size_t n_sorted;
+                        int *sorted;
+
+                        assert(n_except < SIZE_MAX);
+                        n_sorted = n_except + 1;
+
+                        if (n_sorted > 64) /* Use heap for large numbers of fds, stack otherwise */
+                                sorted = sorted_malloc = new(int, n_sorted);
+                        else
+                                sorted = newa(int, n_sorted);
+
+                        if (sorted) {
+                                int c = 0;
+
+                                memcpy(sorted, except, n_except * sizeof(int));
+
+                                /* Let's add fd 2 to the list of fds, to simplify the loop below, as this
+                                 * allows us to cover the head of the array the same way as the body */
+                                sorted[n_sorted-1] = 2;
+
+                                typesafe_qsort(sorted, n_sorted, cmp_int);
+
+                                for (size_t i = 0; i < n_sorted-1; i++) {
+                                        int start, end;
+
+                                        start = MAX(sorted[i], 2); /* The first three fds shall always remain open */
+                                        end = MAX(sorted[i+1], 2);
+
+                                        assert(end >= start);
+
+                                        if (end - start <= 1)
+                                                continue;
+
+                                        /* Close everything between the start and end fds (both of which shall stay open) */
+                                        if (close_range(start + 1, end - 1, 0) < 0) {
+                                                if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
+                                                        return -errno;
+
+                                                have_close_range = false;
+                                                break;
+                                        }
+
+                                        c += end - start - 1;
+                                }
+
+                                if (have_close_range) {
+                                        /* The loop succeeded. Let's now close everything beyond the end */
+
+                                        if (sorted[n_sorted-1] >= INT_MAX) /* Dont let the addition below overflow */
+                                                return c;
+
+                                        if (close_range(sorted[n_sorted-1] + 1, -1, 0) >= 0)
+                                                return c + 1;
+
+                                        if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
+                                                return -errno;
+
+                                        have_close_range = false;
+                                }
+                        }
+                }
+
+                /* Fallback on OOM or if close_range() is not supported */
+        }
+
         d = opendir("/proc/self/fd");
         if (!d) {
                 int fd, max_fd;
diff --git a/shared/systemd/src/basic/fd-util.h b/shared/systemd/src/basic/fd-util.h
index 93ce95cd..2162537b 100644
--- a/shared/systemd/src/basic/fd-util.h
+++ b/shared/systemd/src/basic/fd-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <dirent.h>
diff --git a/shared/systemd/src/basic/fileio.c b/shared/systemd/src/basic/fileio.c
index 664b894a..ea90614a 100644
--- a/shared/systemd/src/basic/fileio.c
+++ b/shared/systemd/src/basic/fileio.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -120,7 +120,7 @@ int write_string_stream_ts(
                 FILE *f,
                 const char *line,
                 WriteStringFileFlags flags,
-                struct timespec *ts) {
+                const struct timespec *ts) {
 
         bool needs_nl;
         int r, fd;
@@ -164,7 +164,7 @@ int write_string_stream_ts(
                 return r;
 
         if (ts) {
-                struct timespec twice[2] = {*ts, *ts};
+                const struct timespec twice[2] = {*ts, *ts};
 
                 if (futimens(fd, twice) < 0)
                         return -errno;
@@ -177,7 +177,7 @@ static int write_string_file_atomic(
                 const char *fn,
                 const char *line,
                 WriteStringFileFlags flags,
-                struct timespec *ts) {
+                const struct timespec *ts) {
 
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *p = NULL;
@@ -224,7 +224,7 @@ int write_string_file_ts(
                 const char *fn,
                 const char *line,
                 WriteStringFileFlags flags,
-                struct timespec *ts) {
+                const struct timespec *ts) {
 
         _cleanup_fclose_ FILE *f = NULL;
         int q, r, fd;
@@ -255,7 +255,8 @@ int write_string_file_ts(
         /* 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_CREATE) ? O_CREAT : 0) |
+                  (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0),
                   (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666));
         if (fd < 0) {
                 r = -errno;
@@ -475,12 +476,13 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re
 int read_full_stream_full(
                 FILE *f,
                 const char *filename,
+                uint64_t offset,
+                size_t size,
                 ReadFullFileFlags flags,
                 char **ret_contents,
                 size_t *ret_size) {
 
         _cleanup_free_ char *buf = NULL;
-        struct stat st;
         size_t n, n_next, l;
         int fd, r;
 
@@ -488,32 +490,45 @@ int read_full_stream_full(
         assert(ret_contents);
         assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX));
 
-        n_next = LINE_MAX; /* Start size */
+        if (offset != UINT64_MAX && offset > LONG_MAX)
+                return -ERANGE;
+
+        n_next = size != SIZE_MAX ? size : LINE_MAX; /* Start size */
 
         fd = fileno(f);
-        if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen()), let's
-                        * optimize our buffering */
+        if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see
+                        * fmemopen()), let's optimize our buffering */
+                struct stat st;
 
                 if (fstat(fd, &st) < 0)
                         return -errno;
 
                 if (S_ISREG(st.st_mode)) {
-
-                        /* Safety check */
-                        if (st.st_size > READ_FULL_BYTES_MAX)
-                                return -E2BIG;
-
-                        /* Start with the right file size. Note that we increase the size
-                         * to read here by one, so that the first read attempt already
-                         * makes us notice the EOF. */
-                        if (st.st_size > 0)
-                                n_next = st.st_size + 1;
+                        if (size == SIZE_MAX) {
+                                uint64_t rsize =
+                                        LESS_BY((uint64_t) st.st_size, offset == UINT64_MAX ? 0 : offset);
+
+                                /* Safety check */
+                                if (rsize > READ_FULL_BYTES_MAX)
+                                        return -E2BIG;
+
+                                /* Start with the right file size. Note that we increase the size to read
+                                 * here by one, so that the first read attempt already makes us notice the
+                                 * EOF. If the reported size of the file is zero, we avoid this logic
+                                 * however, since quite likely it might be a virtual file in procfs that all
+                                 * report a zero file size. */
+                                if (st.st_size > 0)
+                                        n_next = rsize + 1;
+                        }
 
                         if (flags & READ_FULL_FILE_WARN_WORLD_READABLE)
                                 (void) warn_file_is_world_accessible(filename, &st, NULL, 0);
                 }
         }
 
+        if (offset != UINT64_MAX && fseek(f, offset, SEEK_SET) < 0)
+                return -errno;
+
         n = l = 0;
         for (;;) {
                 char *t;
@@ -550,6 +565,11 @@ int read_full_stream_full(
                 if (feof(f))
                         break;
 
+                if (size != SIZE_MAX) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */
+                        assert(l == size);
+                        break;
+                }
+
                 assert(k > 0); /* we can't have read zero bytes because that would have been EOF */
 
                 /* Safety check */
@@ -605,12 +625,21 @@ finalize:
         return r;
 }
 
-int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) {
+int read_full_file_full(
+                int dir_fd,
+                const char *filename,
+                uint64_t offset,
+                size_t size,
+                ReadFullFileFlags flags,
+                const char *bind_name,
+                char **ret_contents,
+                size_t *ret_size) {
+
         _cleanup_fclose_ FILE *f = NULL;
         int r;
 
         assert(filename);
-        assert(contents);
+        assert(ret_contents);
 
         r = xfopenat(dir_fd, filename, "re", 0, &f);
         if (r < 0) {
@@ -625,6 +654,10 @@ int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flag
                 if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET))
                         return -ENXIO;
 
+                /* Seeking is not supported on AF_UNIX sockets */
+                if (offset != UINT64_MAX)
+                        return -ESPIPE;
+
                 if (dir_fd == AT_FDCWD)
                         r = sockaddr_un_set_path(&sa.un, filename);
                 else {
@@ -648,6 +681,20 @@ int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flag
                 if (sk < 0)
                         return -errno;
 
+                if (bind_name) {
+                        /* If the caller specified a socket name to bind to, do so before connecting. This is
+                         * useful to communicate some minor, short meta-information token from the client to
+                         * the server. */
+                        union sockaddr_union bsa;
+
+                        r = sockaddr_un_set_path(&bsa.un, bind_name);
+                        if (r < 0)
+                                return r;
+
+                        if (bind(sk, &bsa.sa, r) < 0)
+                                return r;
+                }
+
                 if (connect(sk, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
                         return errno == ENOTSOCK ? -ENXIO : -errno; /* propagate original error if this is
                                                                      * not a socket after all */
@@ -664,7 +711,7 @@ int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flag
 
         (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
 
-        return read_full_stream_full(f, filename, flags, contents, size);
+        return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size);
 }
 
 #if 0 /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/fileio.h b/shared/systemd/src/basic/fileio.h
index 9cba5a90..5a028561 100644
--- a/shared/systemd/src/basic/fileio.h
+++ b/shared/systemd/src/basic/fileio.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <dirent.h>
@@ -6,7 +6,11 @@
 #include <stddef.h>
 #include <stdio.h>
 #include <sys/stat.h>
+#if 0 /* NM_IGNORED */
 #include <sys/fcntl.h>
+#else /* NM_IGNORED */
+#include <fcntl.h>
+#endif /* NM_IGNORED */
 #include <sys/types.h>
 
 #include "macro.h"
@@ -16,14 +20,15 @@
 
 typedef enum {
         WRITE_STRING_FILE_CREATE            = 1 << 0,
-        WRITE_STRING_FILE_ATOMIC            = 1 << 1,
-        WRITE_STRING_FILE_AVOID_NEWLINE     = 1 << 2,
-        WRITE_STRING_FILE_VERIFY_ON_FAILURE = 1 << 3,
-        WRITE_STRING_FILE_SYNC              = 1 << 4,
-        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,
+        WRITE_STRING_FILE_TRUNCATE          = 1 << 1,
+        WRITE_STRING_FILE_ATOMIC            = 1 << 2,
+        WRITE_STRING_FILE_AVOID_NEWLINE     = 1 << 3,
+        WRITE_STRING_FILE_VERIFY_ON_FAILURE = 1 << 4,
+        WRITE_STRING_FILE_SYNC              = 1 << 5,
+        WRITE_STRING_FILE_DISABLE_BUFFER    = 1 << 6,
+        WRITE_STRING_FILE_NOFOLLOW          = 1 << 7,
+        WRITE_STRING_FILE_MKDIR_0755        = 1 << 8,
+        WRITE_STRING_FILE_MODE_0600         = 1 << 9,
 
         /* 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()
@@ -47,11 +52,11 @@ 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);
 
-int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags, struct timespec *ts);
+int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags, const struct timespec *ts);
 static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) {
         return write_string_stream_ts(f, line, flags, NULL);
 }
-int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, struct timespec *ts);
+int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts);
 static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) {
         return write_string_file_ts(fn, line, flags, NULL);
 }
@@ -59,14 +64,14 @@ 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(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(AT_FDCWD, filename, 0, contents, size);
+int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size);
+static inline int read_full_file(const char *filename, char **ret_contents, size_t *ret_size) {
+        return read_full_file_full(AT_FDCWD, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_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);
-static inline int read_full_stream(FILE *f, char **contents, size_t *size) {
-        return read_full_stream_full(f, NULL, 0, contents, size);
+int read_full_stream_full(FILE *f, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, char **ret_contents, size_t *ret_size);
+static inline int read_full_stream(FILE *f, char **ret_contents, size_t *ret_size) {
+        return read_full_stream_full(f, NULL, UINT64_MAX, SIZE_MAX, 0, ret_contents, ret_size);
 }
 
 int verify_file(const char *fn, const char *blob, bool accept_extra_nl);
diff --git a/shared/systemd/src/basic/format-util.c b/shared/systemd/src/basic/format-util.c
index 62477f53..42224b6f 100644
--- a/shared/systemd/src/basic/format-util.c
+++ b/shared/systemd/src/basic/format-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/basic/format-util.h b/shared/systemd/src/basic/format-util.h
index c47fa76e..b7e18768 100644
--- a/shared/systemd/src/basic/format-util.h
+++ b/shared/systemd/src/basic/format-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <inttypes.h>
@@ -72,11 +72,14 @@ typedef enum {
         FORMAT_BYTES_TRAILING_B  = 1 << 2,
 } FormatBytesFlag;
 
-#define FORMAT_BYTES_MAX 16
+#define FORMAT_BYTES_MAX 16U
+
 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");
diff --git a/shared/systemd/src/basic/fs-util.c b/shared/systemd/src/basic/fs-util.c
index e50252cb..2ed9ee0e 100644
--- a/shared/systemd/src/basic/fs-util.c
+++ b/shared/systemd/src/basic/fs-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -821,7 +821,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
          *
          * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only the first
          *    symlink or ".." component of the path is resolved, and the resulting path is returned. This is useful if
-         *    a caller wants to trace the a path through the file system verbosely. Returns < 0 on error, > 0 if the
+         *    a caller wants to trace the path through the file system verbosely. Returns < 0 on error, > 0 if the
          *    path is fully normalized, and == 0 for each normalization step. This may be combined with
          *    CHASE_NONEXISTENT, in which case 1 is returned when a component is not found.
          *
@@ -945,7 +945,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                         /* Preserve the trailing slash */
 
                         if (flags & CHASE_TRAIL_SLASH)
-                                if (!strextend(&done, "/", NULL))
+                                if (!strextend(&done, "/"))
                                         return -ENOMEM;
 
                         break;
@@ -1016,7 +1016,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                                 if (streq_ptr(done, "/"))
                                         *done = '\0';
 
-                                if (!strextend(&done, first, todo, NULL))
+                                if (!strextend(&done, first, todo))
                                         return -ENOMEM;
 
                                 exists = false;
@@ -1109,7 +1109,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                         if (streq(done, "/"))
                                 *done = '\0';
 
-                        if (!strextend(&done, first, NULL))
+                        if (!strextend(&done, first))
                                 return -ENOMEM;
                 }
 
@@ -1626,4 +1626,81 @@ int path_is_encrypted(const char *path) {
 
         return blockdev_is_encrypted(p, 10 /* safety net: maximum recursion depth */);
 }
+
+int conservative_rename(
+                int olddirfd, const char *oldpath,
+                int newdirfd, const char *newpath) {
+
+        _cleanup_close_ int old_fd = -1, new_fd = -1;
+        struct stat old_stat, new_stat;
+
+        /* Renames the old path to thew new path, much like renameat() — except if both are regular files and
+         * have the exact same contents and basic file attributes already. In that case remove the new file
+         * instead. This call is useful for reducing inotify wakeups on files that are updated but don't
+         * actually change. This function is written in a style that we rather rename too often than suppress
+         * too much. i.e. whenever we are in doubt we rather rename than fail. After all reducing inotify
+         * events is an optimization only, not more. */
+
+        old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
+        if (old_fd < 0)
+                goto do_rename;
+
+        new_fd = openat(newdirfd, newpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
+        if (new_fd < 0)
+                goto do_rename;
+
+        if (fstat(old_fd, &old_stat) < 0)
+                goto do_rename;
+
+        if (!S_ISREG(old_stat.st_mode))
+                goto do_rename;
+
+        if (fstat(new_fd, &new_stat) < 0)
+                goto do_rename;
+
+        if (new_stat.st_ino == old_stat.st_ino &&
+            new_stat.st_dev == old_stat.st_dev)
+                goto is_same;
+
+        if (old_stat.st_mode != new_stat.st_mode ||
+            old_stat.st_size != new_stat.st_size ||
+            old_stat.st_uid != new_stat.st_uid ||
+            old_stat.st_gid != new_stat.st_gid)
+                goto do_rename;
+
+        for (;;) {
+                char buf1[16*1024];
+                char buf2[sizeof(buf1) + 1];
+                ssize_t l1, l2;
+
+                l1 = read(old_fd, buf1, sizeof(buf1));
+                if (l1 < 0)
+                        goto do_rename;
+
+                l2 = read(new_fd, buf2, l1 + 1);
+                if (l1 != l2)
+                        goto do_rename;
+
+                if (l1 == 0) /* EOF on both! And everything's the same so far, yay! */
+                        break;
+
+                if (memcmp(buf1, buf2, l1) != 0)
+                        goto do_rename;
+        }
+
+is_same:
+        /* Everything matches? Then don't rename, instead remove the source file, and leave the existing
+         * destination in place */
+
+        if (unlinkat(olddirfd, oldpath, 0) < 0)
+                goto do_rename;
+
+        return 0;
+
+do_rename:
+        if (renameat(olddirfd, oldpath, newdirfd, newpath) < 0)
+                return -errno;
+
+        return 1;
+}
 #endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/fs-util.h b/shared/systemd/src/basic/fs-util.h
index 241cc6ef..9a394735 100644
--- a/shared/systemd/src/basic/fs-util.h
+++ b/shared/systemd/src/basic/fs-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <dirent.h>
@@ -132,3 +132,5 @@ int syncfs_path(int atfd, const char *path);
 int open_parent(const char *path, int flags, mode_t mode);
 
 int path_is_encrypted(const char *path);
+
+int conservative_rename(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
diff --git a/shared/systemd/src/basic/hash-funcs.c b/shared/systemd/src/basic/hash-funcs.c
index b1c19c95..6f540b29 100644
--- a/shared/systemd/src/basic/hash-funcs.c
+++ b/shared/systemd/src/basic/hash-funcs.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -75,6 +75,19 @@ const struct hash_ops trivial_hash_ops = {
         .compare = trivial_compare_func,
 };
 
+const struct hash_ops trivial_hash_ops_free = {
+        .hash = trivial_hash_func,
+        .compare = trivial_compare_func,
+        .free_key = free,
+};
+
+const struct hash_ops trivial_hash_ops_free_free = {
+        .hash = trivial_hash_func,
+        .compare = trivial_compare_func,
+        .free_key = free,
+        .free_value = free,
+};
+
 void uint64_hash_func(const uint64_t *p, struct siphash *state) {
         siphash24_compress(p, sizeof(uint64_t), state);
 }
diff --git a/shared/systemd/src/basic/hash-funcs.h b/shared/systemd/src/basic/hash-funcs.h
index 005d1b21..5672df1d 100644
--- a/shared/systemd/src/basic/hash-funcs.h
+++ b/shared/systemd/src/basic/hash-funcs.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include "alloc-util.h"
@@ -88,6 +88,8 @@ extern const struct hash_ops path_hash_ops_free;
 void trivial_hash_func(const void *p, struct siphash *state);
 int trivial_compare_func(const void *a, const void *b) _const_;
 extern const struct hash_ops trivial_hash_ops;
+extern const struct hash_ops trivial_hash_ops_free;
+extern const struct hash_ops trivial_hash_ops_free_free;
 
 /* 32bit values we can always just embed in the pointer itself, but in order to support 32bit archs we need store 64bit
  * values indirectly, since they don't fit in a pointer. */
diff --git a/shared/systemd/src/basic/hashmap.c b/shared/systemd/src/basic/hashmap.c
index b57814e6..0a5deabf 100644
--- a/shared/systemd/src/basic/hashmap.c
+++ b/shared/systemd/src/basic/hashmap.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -1797,10 +1797,10 @@ int set_consume(Set *s, void *value) {
 }
 
 #if 0 /* NM_IGNORED */
-int _hashmap_put_strdup(Hashmap **h, const char *k, const char *v  HASHMAP_DEBUG_PARAMS) {
+int _hashmap_put_strdup_full(Hashmap **h, const struct hash_ops *hash_ops, const char *k, const char *v  HASHMAP_DEBUG_PARAMS) {
         int r;
 
-        r = _hashmap_ensure_allocated(h, &string_hash_ops_free_free  HASHMAP_DEBUG_PASS_ARGS);
+        r = _hashmap_ensure_allocated(h, hash_ops  HASHMAP_DEBUG_PASS_ARGS);
         if (r < 0)
                 return r;
 
@@ -1832,14 +1832,14 @@ int _hashmap_put_strdup(Hashmap **h, const char *k, const char *v  HASHMAP_DEBUG
 }
 #endif /* NM_IGNORED */
 
-int _set_put_strdup(Set **s, const char *p  HASHMAP_DEBUG_PARAMS) {
+int _set_put_strdup_full(Set **s, const struct hash_ops *hash_ops, const char *p  HASHMAP_DEBUG_PARAMS) {
         char *c;
         int r;
 
         assert(s);
         assert(p);
 
-        r = _set_ensure_allocated(s, &string_hash_ops_free  HASHMAP_DEBUG_PASS_ARGS);
+        r = _set_ensure_allocated(s, hash_ops  HASHMAP_DEBUG_PASS_ARGS);
         if (r < 0)
                 return r;
 
@@ -1853,14 +1853,14 @@ int _set_put_strdup(Set **s, const char *p  HASHMAP_DEBUG_PARAMS) {
         return set_consume(*s, c);
 }
 
-int _set_put_strdupv(Set **s, char **l  HASHMAP_DEBUG_PARAMS) {
+int _set_put_strdupv_full(Set **s, const struct hash_ops *hash_ops, char **l  HASHMAP_DEBUG_PARAMS) {
         int n = 0, r;
         char **i;
 
         assert(s);
 
         STRV_FOREACH(i, l) {
-                r = _set_put_strdup(s, *i  HASHMAP_DEBUG_PASS_ARGS);
+                r = _set_put_strdup_full(s, hash_ops, *i  HASHMAP_DEBUG_PASS_ARGS);
                 if (r < 0)
                         return r;
 
@@ -1980,3 +1980,53 @@ IteratedCache* iterated_cache_free(IteratedCache *cache) {
 
         return mfree(cache);
 }
+
+int set_strjoin(Set *s, const char *separator, bool wrap_with_separator, char **ret) {
+        size_t separator_len, allocated = 0, len = 0;
+        _cleanup_free_ char *str = NULL;
+        const char *value;
+        bool first;
+
+        assert(ret);
+
+        if (set_isempty(s)) {
+                *ret = NULL;
+                return 0;
+        }
+
+        separator_len = strlen_ptr(separator);
+
+        if (separator_len == 0)
+                wrap_with_separator = false;
+
+        first = !wrap_with_separator;
+
+        SET_FOREACH(value, s) {
+                size_t l = strlen_ptr(value);
+
+                if (l == 0)
+                        continue;
+
+                if (!GREEDY_REALLOC(str, allocated, len + l + (first ? 0 : separator_len) + (wrap_with_separator ? separator_len : 0) + 1))
+                        return -ENOMEM;
+
+                if (separator_len > 0 && !first) {
+                        memcpy(str + len, separator, separator_len);
+                        len += separator_len;
+                }
+
+                memcpy(str + len, value, l);
+                len += l;
+                first = false;
+        }
+
+        if (wrap_with_separator) {
+                memcpy(str + len, separator, separator_len);
+                len += separator_len;
+        }
+
+        str[len] = '\0';
+
+        *ret = TAKE_PTR(str);
+        return 0;
+}
diff --git a/shared/systemd/src/basic/hashmap.h b/shared/systemd/src/basic/hashmap.h
index 890f90a9..e9944837 100644
--- a/shared/systemd/src/basic/hashmap.h
+++ b/shared/systemd/src/basic/hashmap.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <limits.h>
@@ -153,8 +153,9 @@ static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *
         return hashmap_put(PLAIN_HASHMAP(h), key, value);
 }
 
-int _hashmap_put_strdup(Hashmap **h, const char *k, const char *v  HASHMAP_DEBUG_PARAMS);
-#define hashmap_put_strdup(h, k, v) _hashmap_put_strdup(h, k, v  HASHMAP_DEBUG_SRC_ARGS)
+int _hashmap_put_strdup_full(Hashmap **h, const struct hash_ops *hash_ops, const char *k, const char *v  HASHMAP_DEBUG_PARAMS);
+#define hashmap_put_strdup_full(h, hash_ops, k, v) _hashmap_put_strdup_full(h, hash_ops, k, v  HASHMAP_DEBUG_SRC_ARGS)
+#define hashmap_put_strdup(h, k, v) hashmap_put_strdup_full(h, &string_hash_ops_free_free, k, v)
 
 int hashmap_update(Hashmap *h, const void *key, void *value);
 static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) {
diff --git a/shared/systemd/src/basic/hexdecoct.c b/shared/systemd/src/basic/hexdecoct.c
index 1fb52305..78930b32 100644
--- a/shared/systemd/src/basic/hexdecoct.c
+++ b/shared/systemd/src/basic/hexdecoct.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/basic/hexdecoct.h b/shared/systemd/src/basic/hexdecoct.h
index dfdff1e9..7e2a6892 100644
--- a/shared/systemd/src/basic/hexdecoct.h
+++ b/shared/systemd/src/basic/hexdecoct.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
diff --git a/shared/systemd/src/basic/hostname-util.c b/shared/systemd/src/basic/hostname-util.c
index 82fc56db..a3cdc62e 100644
--- a/shared/systemd/src/basic/hostname-util.c
+++ b/shared/systemd/src/basic/hostname-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -9,29 +9,11 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "fd-util.h"
-#include "fileio.h"
 #include "hostname-util.h"
-#include "macro.h"
 #include "string-util.h"
 #include "strv.h"
 
 #if 0 /* NM_IGNORED */
-bool hostname_is_set(void) {
-        struct utsname u;
-
-        assert_se(uname(&u) >= 0);
-
-        if (isempty(u.nodename))
-                return false;
-
-        /* This is the built-in kernel default hostname */
-        if (streq(u.nodename, "(none)"))
-                return false;
-
-        return true;
-}
-
 char* gethostname_malloc(void) {
         struct utsname u;
         const char *s;
@@ -93,6 +75,8 @@ int gethostname_strict(char **ret) {
 }
 
 bool valid_ldh_char(char c) {
+        /* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */
+
         return
                 (c >= 'a' && c <= 'z') ||
                 (c >= 'A' && c <= 'Z') ||
@@ -100,28 +84,24 @@ bool valid_ldh_char(char c) {
                 c == '-';
 }
 
-/**
- * Check if s looks like a valid hostname or FQDN. This does not do
- * full DNS validation, but only checks if the name is composed of
- * allowed characters and the length is not above the maximum allowed
- * by Linux (c.f. dns_name_is_valid()). Trailing dot is allowed if
- * allow_trailing_dot is true and at least two components are present
- * in the name. Note that due to the restricted charset and length
- * this call is substantially more conservative than
- * dns_name_is_valid().
- */
-bool hostname_is_valid(const char *s, bool allow_trailing_dot) {
+bool hostname_is_valid(const char *s, ValidHostnameFlags flags) {
         unsigned n_dots = 0;
         const char *p;
         bool dot, hyphen;
 
+        /* Check if s looks like a valid hostname or FQDN. This does not do full DNS validation, but only
+         * checks if the name is composed of allowed characters and the length is not above the maximum
+         * allowed by Linux (c.f. dns_name_is_valid()). A trailing dot is allowed if
+         * VALID_HOSTNAME_TRAILING_DOT flag is set and at least two components are present in the name. Note
+         * that due to the restricted charset and length this call is substantially more conservative than
+         * dns_name_is_valid(). Doesn't accept empty hostnames, hostnames with leading dots, and hostnames
+         * with multiple dots in a sequence. Doesn't allow hyphens at the beginning or end of label. */
+
         if (isempty(s))
                 return false;
 
-        /* Doesn't accept empty hostnames, hostnames with
-         * leading dots, and hostnames with multiple dots in a
-         * sequence. Also ensures that the length stays below
-         * HOST_NAME_MAX. */
+        if (streq(s, ".host")) /* Used by the container logic to denote the "root container" */
+                return FLAGS_SET(flags, VALID_HOSTNAME_DOT_HOST);
 
         for (p = s, dot = hyphen = true; *p; p++)
                 if (*p == '.') {
@@ -147,14 +127,13 @@ bool hostname_is_valid(const char *s, bool allow_trailing_dot) {
                         hyphen = false;
                 }
 
-        if (dot && (n_dots < 2 || !allow_trailing_dot))
+        if (dot && (n_dots < 2 || !FLAGS_SET(flags, VALID_HOSTNAME_TRAILING_DOT)))
                 return false;
         if (hyphen)
                 return false;
 
-        if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on
-                                  * Linux, but DNS allows domain names
-                                  * up to 255 characters */
+        if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on Linux, but DNS allows domain names up to
+                                  * 255 characters */
                 return false;
 
         return true;
@@ -215,121 +194,3 @@ bool is_localhost(const char *hostname) {
                 endswith_no_case(hostname, ".localhost.localdomain") ||
                 endswith_no_case(hostname, ".localhost.localdomain.");
 }
-
-#if 0 /* NM_IGNORED */
-bool is_gateway_hostname(const char *hostname) {
-        assert(hostname);
-
-        /* This tries to identify the valid syntaxes for the our
-         * synthetic "gateway" host. */
-
-        return
-                strcaseeq(hostname, "_gateway") || strcaseeq(hostname, "_gateway.")
-#if ENABLE_COMPAT_GATEWAY_HOSTNAME
-                || strcaseeq(hostname, "gateway") || strcaseeq(hostname, "gateway.")
-#endif
-                ;
-}
-
-int sethostname_idempotent(const char *s) {
-        char buf[HOST_NAME_MAX + 1] = {};
-
-        assert(s);
-
-        if (gethostname(buf, sizeof(buf)) < 0)
-                return -errno;
-
-        if (streq(buf, s))
-                return 0;
-
-        if (sethostname(s, strlen(s)) < 0)
-                return -errno;
-
-        return 1;
-}
-
-int shorten_overlong(const char *s, char **ret) {
-        char *h, *p;
-
-        /* Shorten an overlong name to HOST_NAME_MAX or to the first dot,
-         * whatever comes earlier. */
-
-        assert(s);
-
-        h = strdup(s);
-        if (!h)
-                return -ENOMEM;
-
-        if (hostname_is_valid(h, false)) {
-                *ret = h;
-                return 0;
-        }
-
-        p = strchr(h, '.');
-        if (p)
-                *p = 0;
-
-        strshorten(h, HOST_NAME_MAX);
-
-        if (!hostname_is_valid(h, false)) {
-                free(h);
-                return -EDOM;
-        }
-
-        *ret = h;
-        return 1;
-}
-
-int read_etc_hostname_stream(FILE *f, char **ret) {
-        int r;
-
-        assert(f);
-        assert(ret);
-
-        for (;;) {
-                _cleanup_free_ char *line = NULL;
-                char *p;
-
-                r = read_line(f, LONG_LINE_MAX, &line);
-                if (r < 0)
-                        return r;
-                if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */
-                        return -ENOENT;
-
-                p = strstrip(line);
-
-                /* File may have empty lines or comments, ignore them */
-                if (!IN_SET(*p, '\0', '#')) {
-                        char *copy;
-
-                        hostname_cleanup(p); /* normalize the hostname */
-
-                        if (!hostname_is_valid(p, true)) /* check that the hostname we return is valid */
-                                return -EBADMSG;
-
-                        copy = strdup(p);
-                        if (!copy)
-                                return -ENOMEM;
-
-                        *ret = copy;
-                        return 0;
-                }
-        }
-}
-
-int read_etc_hostname(const char *path, char **ret) {
-        _cleanup_fclose_ FILE *f = NULL;
-
-        assert(ret);
-
-        if (!path)
-                path = "/etc/hostname";
-
-        f = fopen(path, "re");
-        if (!f)
-                return -errno;
-
-        return read_etc_hostname_stream(f, ret);
-
-}
-#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/hostname-util.h b/shared/systemd/src/basic/hostname-util.h
index cafd6f02..6cff9c1d 100644
--- a/shared/systemd/src/basic/hostname-util.h
+++ b/shared/systemd/src/basic/hostname-util.h
@@ -1,29 +1,29 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
 #include <stdio.h>
 
 #include "macro.h"
-
-bool hostname_is_set(void);
+#include "strv.h"
 
 char* gethostname_malloc(void);
 char* gethostname_short_malloc(void);
 int gethostname_strict(char **ret);
 
 bool valid_ldh_char(char c) _const_;
-bool hostname_is_valid(const char *s, bool allow_trailing_dot) _pure_;
-char* hostname_cleanup(char *s);
-
-#define machine_name_is_valid(s) hostname_is_valid(s, false)
 
-bool is_localhost(const char *hostname);
-bool is_gateway_hostname(const char *hostname);
+typedef enum ValidHostnameFlags {
+        VALID_HOSTNAME_TRAILING_DOT = 1 << 0,   /* Accept trailing dot on multi-label names */
+        VALID_HOSTNAME_DOT_HOST     = 1 << 1,   /* Accept ".host" as valid hostname */
+} ValidHostnameFlags;
 
-int sethostname_idempotent(const char *s);
+bool hostname_is_valid(const char *s, ValidHostnameFlags flags) _pure_;
+char* hostname_cleanup(char *s);
 
-int shorten_overlong(const char *s, char **ret);
+bool is_localhost(const char *hostname);
 
-int read_etc_hostname_stream(FILE *f, char **ret);
-int read_etc_hostname(const char *path, char **ret);
+static inline bool is_gateway_hostname(const char *hostname) {
+        /* This tries to identify the valid syntaxes for the our synthetic "gateway" host. */
+        return STRCASE_IN_SET(hostname, "_gateway", "_gateway.");
+}
diff --git a/shared/systemd/src/basic/in-addr-util.c b/shared/systemd/src/basic/in-addr-util.c
index 1ea3e7ff..c315dcbb 100644
--- a/shared/systemd/src/basic/in-addr-util.c
+++ b/shared/systemd/src/basic/in-addr-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/basic/in-addr-util.h b/shared/systemd/src/basic/in-addr-util.h
index 45c93a00..24308b70 100644
--- a/shared/systemd/src/basic/in-addr-util.h
+++ b/shared/systemd/src/basic/in-addr-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <netinet/in.h>
diff --git a/shared/systemd/src/basic/io-util.c b/shared/systemd/src/basic/io-util.c
index 8b0e354a..f09c7fdd 100644
--- a/shared/systemd/src/basic/io-util.c
+++ b/shared/systemd/src/basic/io-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -298,7 +298,7 @@ int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) {
                 return -E2BIG;
 
         if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1))
-                return log_oom();
+                return -ENOMEM;
 
         iovw->iovec[iovw->count++] = IOVEC_MAKE(data, len);
         return 0;
@@ -310,7 +310,7 @@ int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const c
 
         x = strjoin(field, value);
         if (!x)
-                return log_oom();
+                return -ENOMEM;
 
         r = iovw_put(iovw, x, strlen(x));
         if (r >= 0)
diff --git a/shared/systemd/src/basic/io-util.h b/shared/systemd/src/basic/io-util.h
index 719e19e8..d817714b 100644
--- a/shared/systemd/src/basic/io-util.h
+++ b/shared/systemd/src/basic/io-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
diff --git a/shared/systemd/src/basic/list.h b/shared/systemd/src/basic/list.h
index b62c3749..256b7187 100644
--- a/shared/systemd/src/basic/list.h
+++ b/shared/systemd/src/basic/list.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include "macro.h"
diff --git a/shared/systemd/src/basic/log.h b/shared/systemd/src/basic/log.h
index c2ffbb5d..ee2e4839 100644
--- a/shared/systemd/src/basic/log.h
+++ b/shared/systemd/src/basic/log.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdarg.h>
@@ -44,10 +44,17 @@ typedef enum LogTarget{
 #define ERRNO_VALUE(val)                    (abs(val) & 255)
 
 void log_set_target(LogTarget target);
+
 void log_set_max_level_realm(LogRealm realm, int level);
+
 #define log_set_max_level(level)                \
         log_set_max_level_realm(LOG_REALM, (level))
 
+static inline void log_set_max_level_all_realms(int level) {
+        for (LogRealm realm = 0; realm < _LOG_REALM_MAX; realm++)
+                log_set_max_level_realm(realm, level);
+}
+
 void log_set_facility(int facility);
 
 int log_set_target_from_string(const char *e);
@@ -122,6 +129,31 @@ int log_internal_realm(
 #define log_internal(level, ...) \
         log_internal_realm(LOG_REALM_PLUS_LEVEL(LOG_REALM, (level)), __VA_ARGS__)
 
+#define log_object_internal(level,              \
+                            error,              \
+                            file,               \
+                            line,               \
+                            func,               \
+                            object_field,       \
+                            object,             \
+                            extra_field,        \
+                            extra,              \
+                            format,             \
+                            ...)                \
+    ({                                          \
+        const char *const _object = (object);   \
+                                                \
+        log_internal_realm((level),             \
+                           (error),             \
+                           file,                \
+                           (line),              \
+                           (func),              \
+                           "%s%s" format,       \
+                           _object ?: "",       \
+                           _object ? ": " : "", \
+                           ##__VA_ARGS__);        \
+    })
+
 #if 0 /* NM_IGNORED */
 int log_internalv_realm(
                 int level,
@@ -169,7 +201,7 @@ int log_struct_internal(
                 const char *format, ...) _printf_(6,0) _sentinel_;
 
 int log_oom_internal(
-                LogRealm realm,
+                int level,
                 const char *file,
                 int line,
                 const char *func);
@@ -293,7 +325,8 @@ int log_emergency_level(void);
         log_dump_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
                           0, PROJECT_FILE, __LINE__, __func__, buffer)
 
-#define log_oom() log_oom_internal(LOG_REALM, PROJECT_FILE, __LINE__, __func__)
+#define log_oom() log_oom_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, LOG_ERR), PROJECT_FILE, __LINE__, __func__)
+#define log_oom_debug() log_oom_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, LOG_DEBUG), PROJECT_FILE, __LINE__, __func__)
 
 bool log_on_console(void) _pure_;
 
diff --git a/shared/systemd/src/basic/macro.h b/shared/systemd/src/basic/macro.h
index f872d41f..34416b3d 100644
--- a/shared/systemd/src/basic/macro.h
+++ b/shared/systemd/src/basic/macro.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <assert.h>
@@ -94,6 +94,10 @@
 
 #if (defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) || defined (__clang__)
 /* Temporarily disable some warnings */
+#define DISABLE_WARNING_DEPRECATED_DECLARATIONS                         \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
+
 #define DISABLE_WARNING_FORMAT_NONLITERAL                               \
         _Pragma("GCC diagnostic push");                                 \
         _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"")
@@ -286,6 +290,12 @@ static inline size_t GREEDY_ALLOC_ROUND_UP(size_t l) {
                 MAX(_c, z);                             \
         })
 
+#define MAX4(x, y, z, a)                                \
+        ({                                              \
+                const typeof(x) _d = MAX3(x, y, z);     \
+                MAX(_d, a);                             \
+        })
+
 #undef MIN
 #define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b))
 #define __MIN(aq, a, bq, b)                             \
@@ -449,6 +459,9 @@ static inline int __coverity_check_and_return__(int condition) {
 #define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p)))
 #define ULONG_TO_PTR(u) ((void *) ((uintptr_t) (u)))
 
+#define PTR_TO_UINT8(p) ((uint8_t) ((uintptr_t) (p)))
+#define UINT8_TO_PTR(u) ((void *) ((uintptr_t) (u)))
+
 #define PTR_TO_INT32(p) ((int32_t) ((intptr_t) (p)))
 #define INT32_TO_PTR(u) ((void *) ((intptr_t) (u)))
 #define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p)))
@@ -550,10 +563,13 @@ static inline int __coverity_check_and_return__(int condition) {
 #define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL }))
 #define STRV_MAKE_EMPTY ((char*[1]) { NULL })
 
-/* Iterates through a specified list of pointers. Accepts NULL pointers, but uses (void*) -1 as internal marker for EOL. */
-#define FOREACH_POINTER(p, x, ...)                                                      \
-        for (typeof(p) *_l = (typeof(p)[]) { ({ p = x; }), ##__VA_ARGS__, (void*) -1 }; \
-             p != (typeof(p)) (void*) -1;                                               \
+/* Pointers range from NULL to POINTER_MAX */
+#define POINTER_MAX ((void*) UINTPTR_MAX)
+
+/* Iterates through a specified list of pointers. Accepts NULL pointers, but uses POINTER_MAX as internal marker for EOL. */
+#define FOREACH_POINTER(p, x, ...)                                                       \
+        for (typeof(p) *_l = (typeof(p)[]) { ({ p = x; }), ##__VA_ARGS__, POINTER_MAX }; \
+             p != (typeof(p)) POINTER_MAX;                                               \
              p = *(++_l))
 
 /* Define C11 thread_local attribute even on older gcc compiler
@@ -643,4 +659,8 @@ static inline int __coverity_check_and_return__(int condition) {
                 _copy;                                                  \
         })
 
+static inline size_t size_add(size_t x, size_t y) {
+        return y >= SIZE_MAX - x ? SIZE_MAX : x + y;
+}
+
 #include "log.h"
diff --git a/shared/systemd/src/basic/memory-util.c b/shared/systemd/src/basic/memory-util.c
index bd1f5d73..7ee7c94e 100644
--- a/shared/systemd/src/basic/memory-util.c
+++ b/shared/systemd/src/basic/memory-util.c
@@ -1,3 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
 #include "nm-sd-adapt-shared.h"
 
 #include <unistd.h>
diff --git a/shared/systemd/src/basic/memory-util.h b/shared/systemd/src/basic/memory-util.h
index 4f596cff..179edd24 100644
--- a/shared/systemd/src/basic/memory-util.h
+++ b/shared/systemd/src/basic/memory-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <inttypes.h>
diff --git a/shared/systemd/src/basic/mempool.c b/shared/systemd/src/basic/mempool.c
index 8b8337db..46c44914 100644
--- a/shared/systemd/src/basic/mempool.c
+++ b/shared/systemd/src/basic/mempool.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/basic/mempool.h b/shared/systemd/src/basic/mempool.h
index 0eecca0f..0fe2f278 100644
--- a/shared/systemd/src/basic/mempool.h
+++ b/shared/systemd/src/basic/mempool.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
diff --git a/shared/systemd/src/basic/missing_fcntl.h b/shared/systemd/src/basic/missing_fcntl.h
index 5d1c6352..00937d2a 100644
--- a/shared/systemd/src/basic/missing_fcntl.h
+++ b/shared/systemd/src/basic/missing_fcntl.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <fcntl.h>
diff --git a/shared/systemd/src/basic/missing_random.h b/shared/systemd/src/basic/missing_random.h
index 17af87a3..443b9136 100644
--- a/shared/systemd/src/basic/missing_random.h
+++ b/shared/systemd/src/basic/missing_random.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #if USE_SYS_RANDOM_H
diff --git a/shared/systemd/src/basic/missing_socket.h b/shared/systemd/src/basic/missing_socket.h
index fe4d35bd..a4f6836f 100644
--- a/shared/systemd/src/basic/missing_socket.h
+++ b/shared/systemd/src/basic/missing_socket.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <sys/socket.h>
@@ -69,6 +69,14 @@ struct sockaddr_vm {
 #define IPV6_FREEBIND 78
 #endif
 
+#ifndef IP_RECVFRAGSIZE
+#define IP_RECVFRAGSIZE 25
+#endif
+
+#ifndef IPV6_RECVFRAGSIZE
+#define IPV6_RECVFRAGSIZE 77
+#endif
+
 /* linux/sockios.h */
 #ifndef SIOCGSKNS
 #define SIOCGSKNS 0x894C
diff --git a/shared/systemd/src/basic/missing_stat.h b/shared/systemd/src/basic/missing_stat.h
index 5d59a214..9c1df698 100644
--- a/shared/systemd/src/basic/missing_stat.h
+++ b/shared/systemd/src/basic/missing_stat.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <linux/types.h>
diff --git a/shared/systemd/src/basic/missing_syscall.h b/shared/systemd/src/basic/missing_syscall.h
index d11a77d5..42d64753 100644
--- a/shared/systemd/src/basic/missing_syscall.h
+++ b/shared/systemd/src/basic/missing_syscall.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 /* Missing glibc definitions to access certain kernel APIs */
@@ -15,6 +15,26 @@
 #include <asm/sgidefs.h>
 #endif
 
+#if defined(__alpha__)
+#  define systemd_SC_arch_bias(x) (110 + (x))
+#elif defined(__ia64__)
+#  define systemd_SC_arch_bias(x) (1024 + (x))
+#elif defined(_MIPS_SIM)
+#  if _MIPS_SIM == _MIPS_SIM_ABI32
+#    define systemd_SC_arch_bias(x) (4000 + (x))
+#  elif _MIPS_SIM == _MIPS_SIM_NABI32
+#    define systemd_SC_arch_bias(x) (6000 + (x))
+#  elif _MIPS_SIM == _MIPS_SIM_ABI64
+#    define systemd_SC_arch_bias(x) (5000 + (x))
+#  else
+#    error "Unknown MIPS ABI"
+#  endif
+#elif defined(__x86_64__) && defined(__ILP32__)
+#  define systemd_SC_arch_bias(x) ((x) | /* __X32_SYSCALL_BIT */ 0x40000000)
+#else
+#  define systemd_SC_arch_bias(x) (x)
+#endif
+
 #include "missing_keyctl.h"
 #include "missing_stat.h"
 
@@ -35,32 +55,38 @@ static inline int missing_pivot_root(const char *new_root, const char *put_old)
 
 /* ======================================================================= */
 
-#if defined __x86_64__
-#  define systemd_NR_memfd_create 319
-#elif defined __arm__
-#  define systemd_NR_memfd_create 385
-#elif defined __aarch64__
+#if defined(__aarch64__)
 #  define systemd_NR_memfd_create 279
+#elif defined(__alpha__)
+#  define systemd_NR_memfd_create 512
+#elif defined(__arc__) || defined(__tilegx__)
+#  define systemd_NR_memfd_create 279
+#elif defined(__arm__)
+#  define systemd_NR_memfd_create 385
+#elif defined(__i386__)
+#  define systemd_NR_memfd_create 356
+#elif defined(__ia64__)
+#  define systemd_NR_memfd_create systemd_SC_arch_bias(316)
+#elif defined(__m68k__)
+#  define systemd_NR_memfd_create 353
+#elif defined(_MIPS_SIM)
+#  if _MIPS_SIM == _MIPS_SIM_ABI32
+#    define systemd_NR_memfd_create systemd_SC_arch_bias(354)
+#  elif _MIPS_SIM == _MIPS_SIM_NABI32
+#    define systemd_NR_memfd_create systemd_SC_arch_bias(318)
+#  elif _MIPS_SIM == _MIPS_SIM_ABI64
+#    define systemd_NR_memfd_create systemd_SC_arch_bias(314)
+#  endif
 #elif defined(__powerpc__)
 #  define systemd_NR_memfd_create 360
-#elif defined __s390__
+#elif defined(__s390__)
 #  define systemd_NR_memfd_create 350
-#elif defined _MIPS_SIM
-#  if _MIPS_SIM == _MIPS_SIM_ABI32
-#    define systemd_NR_memfd_create 4354
-#  endif
-#  if _MIPS_SIM == _MIPS_SIM_NABI32
-#    define systemd_NR_memfd_create 6318
-#  endif
-#  if _MIPS_SIM == _MIPS_SIM_ABI64
-#    define systemd_NR_memfd_create 5314
-#  endif
-#elif defined __i386__
-#  define systemd_NR_memfd_create 356
-#elif defined __arc__
-#  define systemd_NR_memfd_create 279
+#elif defined(__sparc__)
+#  define systemd_NR_memfd_create 348
+#elif defined(__x86_64__)
+#  define systemd_NR_memfd_create systemd_SC_arch_bias(319)
 #else
-#  warning "memfd_create() syscall number unknown for your architecture"
+#  warning "memfd_create() syscall number is unknown for your architecture"
 #endif
 
 /* may be (invalid) negative number due to libseccomp, see PR 13319 */
@@ -92,36 +118,38 @@ static inline int missing_memfd_create(const char *name, unsigned int flags) {
 
 /* ======================================================================= */
 
-#if defined __x86_64__
-#  define systemd_NR_getrandom 318
-#elif defined(__i386__)
-#  define systemd_NR_getrandom 355
+#if defined(__aarch64__)
+#  define systemd_NR_getrandom 278
+#elif defined(__alpha__)
+#  define systemd_NR_getrandom 511
+#elif defined(__arc__) || defined(__tilegx__)
+#  define systemd_NR_getrandom 278
 #elif defined(__arm__)
 #  define systemd_NR_getrandom 384
-#elif defined(__aarch64__)
-#  define systemd_NR_getrandom 278
+#elif defined(__i386__)
+#  define systemd_NR_getrandom 355
 #elif defined(__ia64__)
-#  define systemd_NR_getrandom 1339
+#  define systemd_NR_getrandom systemd_SC_arch_bias(318)
 #elif defined(__m68k__)
 #  define systemd_NR_getrandom 352
-#elif defined(__s390x__)
-#  define systemd_NR_getrandom 349
-#elif defined(__powerpc__)
-#  define systemd_NR_getrandom 359
-#elif defined _MIPS_SIM
+#elif defined(_MIPS_SIM)
 #  if _MIPS_SIM == _MIPS_SIM_ABI32
-#    define systemd_NR_getrandom 4353
-#  endif
-#  if _MIPS_SIM == _MIPS_SIM_NABI32
-#    define systemd_NR_getrandom 6317
+#    define systemd_NR_getrandom systemd_SC_arch_bias(353)
+#  elif _MIPS_SIM == _MIPS_SIM_NABI32
+#    define systemd_NR_getrandom systemd_SC_arch_bias(317)
+#  elif _MIPS_SIM == _MIPS_SIM_ABI64
+#    define systemd_NR_getrandom systemd_SC_arch_bias(313)
 #  endif
-#  if _MIPS_SIM == _MIPS_SIM_ABI64
-#    define systemd_NR_getrandom 5313
-#  endif
-#elif defined(__arc__)
-#  define systemd_NR_getrandom 278
+#elif defined(__powerpc__)
+#  define systemd_NR_getrandom 359
+#elif defined(__s390__)
+#  define systemd_NR_getrandom 349
+#elif defined(__sparc__)
+#  define systemd_NR_getrandom 347
+#elif defined(__x86_64__)
+#  define systemd_NR_getrandom systemd_SC_arch_bias(318)
 #else
-#  warning "getrandom() syscall number unknown for your architecture"
+#  warning "getrandom() syscall number is unknown for your architecture"
 #endif
 
 /* may be (invalid) negative number due to libseccomp, see PR 13319 */
@@ -168,22 +196,38 @@ static inline pid_t missing_gettid(void) {
 
 /* ======================================================================= */
 
-#if defined(__x86_64__)
-#  define systemd_NR_name_to_handle_at 303
-#elif defined(__i386__)
-#  define systemd_NR_name_to_handle_at 341
+#if defined(__aarch64__)
+#  define systemd_NR_name_to_handle_at 264
+#elif defined(__alpha__)
+#  define systemd_NR_name_to_handle_at 497
+#elif defined(__arc__) || defined(__tilegx__)
+#  define systemd_NR_name_to_handle_at 264
 #elif defined(__arm__)
 #  define systemd_NR_name_to_handle_at 370
-#elif defined __aarch64__
-#  define systemd_NR_name_to_handle_at 264
+#elif defined(__i386__)
+#  define systemd_NR_name_to_handle_at 341
+#elif defined(__ia64__)
+#  define systemd_NR_name_to_handle_at systemd_SC_arch_bias(302)
+#elif defined(__m68k__)
+#  define systemd_NR_name_to_handle_at 340
+#elif defined(_MIPS_SIM)
+#  if _MIPS_SIM == _MIPS_SIM_ABI32
+#    define systemd_NR_name_to_handle_at systemd_SC_arch_bias(339)
+#  elif _MIPS_SIM == _MIPS_SIM_NABI32
+#    define systemd_NR_name_to_handle_at systemd_SC_arch_bias(303)
+#  elif _MIPS_SIM == _MIPS_SIM_ABI64
+#    define systemd_NR_name_to_handle_at systemd_SC_arch_bias(298)
+#  endif
 #elif defined(__powerpc__)
 #  define systemd_NR_name_to_handle_at 345
-#elif defined __s390__ || defined __s390x__
+#elif defined(__s390__)
 #  define systemd_NR_name_to_handle_at 335
-#elif defined(__arc__)
-#  define systemd_NR_name_to_handle_at 264
+#elif defined(__sparc__)
+#  define systemd_NR_name_to_handle_at 332
+#elif defined(__x86_64__)
+#  define systemd_NR_name_to_handle_at systemd_SC_arch_bias(303)
 #else
-#  warning "name_to_handle_at number is not defined"
+#  warning "name_to_handle_at() syscall number is unknown for your architecture"
 #endif
 
 /* may be (invalid) negative number due to libseccomp, see PR 13319 */
@@ -221,22 +265,38 @@ static inline int missing_name_to_handle_at(int fd, const char *name, struct fil
 
 /* ======================================================================= */
 
-#if defined __aarch64__
+#if defined(__aarch64__)
 #  define systemd_NR_setns 268
-#elif defined __arm__
+#elif defined(__alpha__)
+#  define systemd_NR_setns 501
+#elif defined(__arc__) || defined(__tilegx__)
+#  define systemd_NR_setns 268
+#elif defined(__arm__)
 #  define systemd_NR_setns 375
-#elif defined(__x86_64__)
-#  define systemd_NR_setns 308
 #elif defined(__i386__)
 #  define systemd_NR_setns 346
+#elif defined(__ia64__)
+#  define systemd_NR_setns systemd_SC_arch_bias(306)
+#elif defined(__m68k__)
+#  define systemd_NR_setns 344
+#elif defined(_MIPS_SIM)
+#  if _MIPS_SIM == _MIPS_SIM_ABI32
+#    define systemd_NR_setns systemd_SC_arch_bias(344)
+#  elif _MIPS_SIM == _MIPS_SIM_NABI32
+#    define systemd_NR_setns systemd_SC_arch_bias(308)
+#  elif _MIPS_SIM == _MIPS_SIM_ABI64
+#    define systemd_NR_setns systemd_SC_arch_bias(303)
+#  endif
 #elif defined(__powerpc__)
 #  define systemd_NR_setns 350
-#elif defined __s390__ || defined __s390x__
+#elif defined(__s390__)
 #  define systemd_NR_setns 339
-#elif defined(__arc__)
-#  define systemd_NR_setns 268
+#elif defined(__sparc__)
+#  define systemd_NR_setns 337
+#elif defined(__x86_64__)
+#  define systemd_NR_setns systemd_SC_arch_bias(308)
 #else
-#  warning "setns() syscall number unknown for your architecture"
+#  warning "setns() syscall number is unknown for your architecture"
 #endif
 
 /* may be (invalid) negative number due to libseccomp, see PR 13319 */
@@ -278,32 +338,38 @@ static inline pid_t raw_getpid(void) {
 
 /* ======================================================================= */
 
-#if defined __x86_64__
-#  define systemd_NR_renameat2 316
-#elif defined __arm__
-#  define systemd_NR_renameat2 382
-#elif defined __aarch64__
+#if defined(__aarch64__)
 #  define systemd_NR_renameat2 276
-#elif defined _MIPS_SIM
+#elif defined(__alpha__)
+#  define systemd_NR_renameat2 510
+#elif defined(__arc__) || defined(__tilegx__)
+#  define systemd_NR_renameat2 276
+#elif defined(__arm__)
+#  define systemd_NR_renameat2 382
+#elif defined(__i386__)
+#  define systemd_NR_renameat2 353
+#elif defined(__ia64__)
+#  define systemd_NR_renameat2 systemd_SC_arch_bias(314)
+#elif defined(__m68k__)
+#  define systemd_NR_renameat2 351
+#elif defined(_MIPS_SIM)
 #  if _MIPS_SIM == _MIPS_SIM_ABI32
-#    define systemd_NR_renameat2 4351
+#    define systemd_NR_renameat2 systemd_SC_arch_bias(351)
+#  elif _MIPS_SIM == _MIPS_SIM_NABI32
+#    define systemd_NR_renameat2 systemd_SC_arch_bias(315)
+#  elif _MIPS_SIM == _MIPS_SIM_ABI64
+#    define systemd_NR_renameat2 systemd_SC_arch_bias(311)
 #  endif
-#  if _MIPS_SIM == _MIPS_SIM_NABI32
-#    define systemd_NR_renameat2 6315
-#  endif
-#  if _MIPS_SIM == _MIPS_SIM_ABI64
-#    define systemd_NR_renameat2 5311
-#  endif
-#elif defined __i386__
-#  define systemd_NR_renameat2 353
-#elif defined __powerpc64__
+#elif defined(__powerpc__)
 #  define systemd_NR_renameat2 357
-#elif defined __s390__ || defined __s390x__
+#elif defined(__s390__)
 #  define systemd_NR_renameat2 347
-#elif defined __arc__
-#  define systemd_NR_renameat2 276
+#elif defined(__sparc__)
+#  define systemd_NR_renameat2 345
+#elif defined(__x86_64__)
+#  define systemd_NR_renameat2 systemd_SC_arch_bias(316)
 #else
-#  warning "renameat2() syscall number unknown for your architecture"
+#  warning "renameat2() syscall number is unknown for your architecture"
 #endif
 
 /* may be (invalid) negative number due to libseccomp, see PR 13319 */
@@ -387,22 +453,38 @@ static inline key_serial_t missing_request_key(const char *type, const char *des
 
 /* ======================================================================= */
 
-#if defined(__x86_64__)
-#  define systemd_NR_copy_file_range 326
+#if defined(__aarch64__)
+#  define systemd_NR_copy_file_range 285
+#elif defined(__alpha__)
+#  define systemd_NR_copy_file_range 519
+#elif defined(__arc__) || defined(__tilegx__)
+#  define systemd_NR_copy_file_range 285
+#elif defined(__arm__)
+#  define systemd_NR_copy_file_range 391
 #elif defined(__i386__)
 #  define systemd_NR_copy_file_range 377
-#elif defined __s390__
-#  define systemd_NR_copy_file_range 375
-#elif defined __arm__
-#  define systemd_NR_copy_file_range 391
-#elif defined __aarch64__
-#  define systemd_NR_copy_file_range 285
-#elif defined __powerpc__
+#elif defined(__ia64__)
+#  define systemd_NR_copy_file_range systemd_SC_arch_bias(323)
+#elif defined(__m68k__)
+#  define systemd_NR_copy_file_range 376
+#elif defined(_MIPS_SIM)
+#  if _MIPS_SIM == _MIPS_SIM_ABI32
+#    define systemd_NR_copy_file_range systemd_SC_arch_bias(360)
+#  elif _MIPS_SIM == _MIPS_SIM_NABI32
+#    define systemd_NR_copy_file_range systemd_SC_arch_bias(324)
+#  elif _MIPS_SIM == _MIPS_SIM_ABI64
+#    define systemd_NR_copy_file_range systemd_SC_arch_bias(320)
+#  endif
+#elif defined(__powerpc__)
 #  define systemd_NR_copy_file_range 379
-#elif defined __arc__
-#  define systemd_NR_copy_file_range 285
+#elif defined(__s390__)
+#  define systemd_NR_copy_file_range 375
+#elif defined(__sparc__)
+#  define systemd_NR_copy_file_range 357
+#elif defined(__x86_64__)
+#  define systemd_NR_copy_file_range systemd_SC_arch_bias(326)
 #else
-#  warning "copy_file_range() syscall number unknown for your architecture"
+#  warning "copy_file_range() syscall number is unknown for your architecture"
 #endif
 
 /* may be (invalid) negative number due to libseccomp, see PR 13319 */
@@ -437,24 +519,38 @@ static inline ssize_t missing_copy_file_range(int fd_in, loff_t *off_in,
 
 /* ======================================================================= */
 
-#if defined __i386__
-#  define systemd_NR_bpf 357
-#elif defined __x86_64__
-#  define systemd_NR_bpf 321
-#elif defined __aarch64__
+#if defined(__aarch64__)
+#  define systemd_NR_bpf 280
+#elif defined(__alpha__)
+#  define systemd_NR_bpf 515
+#elif defined(__arc__) || defined(__tilegx__)
 #  define systemd_NR_bpf 280
-#elif defined __arm__
+#elif defined(__arm__)
 #  define systemd_NR_bpf 386
+#elif defined(__i386__)
+#  define systemd_NR_bpf 357
+#elif defined(__ia64__)
+#  define systemd_NR_bpf systemd_SC_arch_bias(317)
+#elif defined(__m68k__)
+#  define systemd_NR_bpf 354
+#elif defined(_MIPS_SIM)
+#  if _MIPS_SIM == _MIPS_SIM_ABI32
+#    define systemd_NR_bpf systemd_SC_arch_bias(355)
+#  elif _MIPS_SIM == _MIPS_SIM_NABI32
+#    define systemd_NR_bpf systemd_SC_arch_bias(319)
+#  elif _MIPS_SIM == _MIPS_SIM_ABI64
+#    define systemd_NR_bpf systemd_SC_arch_bias(315)
+#  endif
 #elif defined(__powerpc__)
 #  define systemd_NR_bpf 361
-#elif defined __sparc__
-#  define systemd_NR_bpf 349
-#elif defined __s390__
+#elif defined(__s390__)
 #  define systemd_NR_bpf 351
-#elif defined __tilegx__
-#  define systemd_NR_bpf 280
+#elif defined(__sparc__)
+#  define systemd_NR_bpf 349
+#elif defined(__x86_64__)
+#  define systemd_NR_bpf systemd_SC_arch_bias(321)
 #else
-#  warning "bpf() syscall number unknown for your architecture"
+#  warning "bpf() syscall number is unknown for your architecture"
 #endif
 
 /* may be (invalid) negative number due to libseccomp, see PR 13319 */
@@ -489,30 +585,38 @@ static inline int missing_bpf(int cmd, union bpf_attr *attr, size_t size) {
 /* ======================================================================= */
 
 #ifndef __IGNORE_pkey_mprotect
-#  if defined __i386__
-#    define systemd_NR_pkey_mprotect 380
-#  elif defined __x86_64__
-#    define systemd_NR_pkey_mprotect 329
-#  elif defined __aarch64__
+#  if defined(__aarch64__)
 #    define systemd_NR_pkey_mprotect 288
-#  elif defined __arm__
+#  elif defined(__alpha__)
+#    define systemd_NR_pkey_mprotect 524
+#  elif defined(__arc__) || defined(__tilegx__)
+#    define systemd_NR_pkey_mprotect 226
+#  elif defined(__arm__)
 #    define systemd_NR_pkey_mprotect 394
-#  elif defined __powerpc__
-#    define systemd_NR_pkey_mprotect 386
-#  elif defined __s390__
-#    define systemd_NR_pkey_mprotect 384
-#  elif defined _MIPS_SIM
+#  elif defined(__i386__)
+#    define systemd_NR_pkey_mprotect 380
+#  elif defined(__ia64__)
+#    define systemd_NR_pkey_mprotect systemd_SC_arch_bias(330)
+#  elif defined(__m68k__)
+#    define systemd_NR_pkey_mprotect 381
+#  elif defined(_MIPS_SIM)
 #    if _MIPS_SIM == _MIPS_SIM_ABI32
-#      define systemd_NR_pkey_mprotect 4363
-#    endif
-#    if _MIPS_SIM == _MIPS_SIM_NABI32
-#      define systemd_NR_pkey_mprotect 6327
-#    endif
-#    if _MIPS_SIM == _MIPS_SIM_ABI64
-#      define systemd_NR_pkey_mprotect 5323
+#      define systemd_NR_pkey_mprotect systemd_SC_arch_bias(363)
+#    elif _MIPS_SIM == _MIPS_SIM_NABI32
+#      define systemd_NR_pkey_mprotect systemd_SC_arch_bias(327)
+#    elif _MIPS_SIM == _MIPS_SIM_ABI64
+#      define systemd_NR_pkey_mprotect systemd_SC_arch_bias(323)
 #    endif
+#  elif defined(__powerpc__)
+#    define systemd_NR_pkey_mprotect 386
+#  elif defined(__s390__)
+#    define systemd_NR_pkey_mprotect 384
+#  elif defined(__sparc__)
+#    define systemd_NR_pkey_mprotect 362
+#  elif defined(__x86_64__)
+#    define systemd_NR_pkey_mprotect systemd_SC_arch_bias(329)
 #  else
-#    warning "pkey_mprotect() syscall number unknown for your architecture"
+#    warning "pkey_mprotect() syscall number is unknown for your architecture"
 #  endif
 
 /* may be (invalid) negative number due to libseccomp, see PR 13319 */
@@ -532,22 +636,38 @@ assert_cc(__NR_pkey_mprotect == systemd_NR_pkey_mprotect);
 
 /* ======================================================================= */
 
-#if defined __aarch64__
+#if defined(__aarch64__)
 #  define systemd_NR_statx 291
-#elif defined __arm__
-#  define systemd_NR_statx 397
-#elif defined __alpha__
+#elif defined(__alpha__)
 #  define systemd_NR_statx 522
-#elif defined __i386__ || defined __powerpc64__
+#elif defined(__arc__) || defined(__tilegx__)
+#  define systemd_NR_statx 291
+#elif defined(__arm__)
+#  define systemd_NR_statx 397
+#elif defined(__i386__)
+#  define systemd_NR_statx 383
+#elif defined(__ia64__)
+#  define systemd_NR_statx systemd_SC_arch_bias(326)
+#elif defined(__m68k__)
+#  define systemd_NR_statx 379
+#elif defined(_MIPS_SIM)
+#  if _MIPS_SIM == _MIPS_SIM_ABI32
+#    define systemd_NR_statx systemd_SC_arch_bias(366)
+#  elif _MIPS_SIM == _MIPS_SIM_NABI32
+#    define systemd_NR_statx systemd_SC_arch_bias(330)
+#  elif _MIPS_SIM == _MIPS_SIM_ABI64
+#    define systemd_NR_statx systemd_SC_arch_bias(326)
+#  endif
+#elif defined(__powerpc__)
 #  define systemd_NR_statx 383
-#elif defined __s390__ || defined __s390x__
+#elif defined(__s390__)
 #  define systemd_NR_statx 379
-#elif defined __sparc__
+#elif defined(__sparc__)
 #  define systemd_NR_statx 360
-#elif defined __x86_64__
-#  define systemd_NR_statx 332
+#elif defined(__x86_64__)
+#  define systemd_NR_statx systemd_SC_arch_bias(332)
 #else
-#  warning "statx() syscall number unknown for your architecture"
+#  warning "statx() syscall number is unknown for your architecture"
 #endif
 
 /* may be (invalid) negative number due to libseccomp, see PR 13319 */
@@ -632,23 +752,7 @@ static inline long missing_get_mempolicy(int *mode, unsigned long *nodemask,
 /* ======================================================================= */
 
 /* should be always defined, see kernel 39036cd2727395c3369b1051005da74059a85317 */
-#if defined __alpha__
-#  define systemd_NR_pidfd_send_signal 534
-#elif defined _MIPS_SIM
-#  if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
-#    define systemd_NR_pidfd_send_signal (424 + 4000)
-#  endif
-#  if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
-#    define systemd_NR_pidfd_send_signal (424 + 6000)
-#  endif
-#  if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
-#    define systemd_NR_pidfd_send_signal (424 + 5000)
-#  endif
-#elif defined __ia64__
-#  define systemd_NR_pidfd_send_signal (424 + 1024)
-#else
-#  define systemd_NR_pidfd_send_signal 424
-#endif
+#define systemd_NR_pidfd_send_signal systemd_SC_arch_bias(424)
 
 /* may be (invalid) negative number due to libseccomp, see PR 13319 */
 #if defined __NR_pidfd_send_signal && __NR_pidfd_send_signal >= 0
@@ -664,7 +768,7 @@ assert_cc(__NR_pidfd_send_signal == systemd_NR_pidfd_send_signal);
 
 #if !HAVE_PIDFD_SEND_SIGNAL
 static inline int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags) {
-#  ifdef __NR_pidfd_open
+#  ifdef __NR_pidfd_send_signal
         return syscall(__NR_pidfd_send_signal, fd, sig, info, flags);
 #  else
         errno = ENOSYS;
@@ -676,23 +780,7 @@ static inline int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, un
 #endif
 
 /* should be always defined, see kernel 7615d9e1780e26e0178c93c55b73309a5dc093d7 */
-#if defined __alpha__
-#  define systemd_NR_pidfd_open 544
-#elif defined _MIPS_SIM
-#  if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
-#    define systemd_NR_pidfd_open (434 + 4000)
-#  endif
-#  if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
-#    define systemd_NR_pidfd_open (434 + 6000)
-#  endif
-#  if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
-#    define systemd_NR_pidfd_open (434 + 5000)
-#  endif
-#elif defined __ia64__
-#  define systemd_NR_pidfd_open (434 + 1024)
-#else
-#  define systemd_NR_pidfd_open 434
-#endif
+#define systemd_NR_pidfd_open systemd_SC_arch_bias(434)
 
 /* may be (invalid) negative number due to libseccomp, see PR 13319 */
 #if defined __NR_pidfd_open && __NR_pidfd_open >= 0
@@ -732,3 +820,70 @@ static inline int missing_rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t *info)
 
 #  define rt_sigqueueinfo missing_rt_sigqueueinfo
 #endif
+
+/* ======================================================================= */
+
+#if 0 /* NM_IGNORED */
+#if !HAVE_EXECVEAT
+static inline int missing_execveat(int dirfd, const char *pathname,
+                                   char *const argv[], char *const envp[],
+                                   int flags) {
+#  if defined __NR_execveat && __NR_execveat >= 0
+        return syscall(__NR_execveat, dirfd, pathname, argv, envp, flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  undef AT_EMPTY_PATH
+#  define AT_EMPTY_PATH 0x1000
+#  define execveat missing_execveat
+#endif
+
+/* ======================================================================= */
+
+#define systemd_NR_close_range systemd_SC_arch_bias(436)
+
+/* may be (invalid) negative number due to libseccomp, see PR 13319 */
+#if defined __NR_close_range && __NR_close_range >= 0
+#  if defined systemd_NR_close_range
+assert_cc(__NR_close_range == systemd_NR_close_range);
+#  endif
+#else
+#  if defined __NR_close_range
+#    undef __NR_close_range
+#  endif
+#  if defined systemd_NR_close_range
+#    define __NR_close_range systemd_NR_close_range
+#  endif
+#endif
+
+#if !HAVE_CLOSE_RANGE
+static inline int missing_close_range(int first_fd, int end_fd, unsigned flags) {
+#  ifdef __NR_close_range
+        /* Kernel-side the syscall expects fds as unsigned integers (just like close() actually), while
+         * userspace exclusively uses signed integers for fds. We don't know just yet how glibc is going to
+         * wrap this syscall, but let's assume it's going to be similar to what they do for close(),
+         * i.e. make the same unsigned → signed type change from the raw kernel syscall compared to the
+         * userspace wrapper. There's only one caveat for this: unlike for close() there's the special
+         * UINT_MAX fd value for the 'end_fd' argument. Let's safely map that to -1 here. And let's refuse
+         * any other negative values. */
+        if ((first_fd < 0) || (end_fd < 0 && end_fd != -1)) {
+                errno = -EBADF;
+                return -1;
+        }
+
+        return syscall(__NR_close_range,
+                       (unsigned) first_fd,
+                       end_fd == -1 ? UINT_MAX : (unsigned) end_fd, /* Of course, the compiler should figure out that this is the identity mapping IRL */
+                       flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define close_range missing_close_range
+#endif
+#endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/missing_type.h b/shared/systemd/src/basic/missing_type.h
index bf8a6caa..f6233090 100644
--- a/shared/systemd/src/basic/missing_type.h
+++ b/shared/systemd/src/basic/missing_type.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <uchar.h>
diff --git a/shared/systemd/src/basic/parse-util.c b/shared/systemd/src/basic/parse-util.c
index 6b47317a..d53bf620 100644
--- a/shared/systemd/src/basic/parse-util.c
+++ b/shared/systemd/src/basic/parse-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -867,4 +867,46 @@ int parse_oom_score_adjust(const char *s, int *ret) {
         *ret = v;
         return 0;
 }
+
+int store_loadavg_fixed_point(unsigned long i, unsigned long f, loadavg_t *ret) {
+        assert(ret);
+
+        if (i >= (~0UL << FSHIFT))
+                return -ERANGE;
+
+        i = i << FSHIFT;
+        f = DIV_ROUND_UP((f << FSHIFT), 100);
+
+        if (f >= FIXED_1)
+                return -ERANGE;
+
+        *ret = i | f;
+        return 0;
+}
+
+int parse_loadavg_fixed_point(const char *s, loadavg_t *ret) {
+        const char *d, *f_str, *i_str;
+        unsigned long i, f;
+        int r;
+
+        assert(s);
+        assert(ret);
+
+        d = strchr(s, '.');
+        if (!d)
+                return -EINVAL;
+
+        i_str = strndupa(s, d - s);
+        f_str = d + 1;
+
+        r = safe_atolu_full(i_str, 10, &i);
+        if (r < 0)
+                return r;
+
+        r = safe_atolu_full(f_str, 10, &f);
+        if (r < 0)
+                return r;
+
+        return store_loadavg_fixed_point(i, f, ret);
+}
 #endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/parse-util.h b/shared/systemd/src/basic/parse-util.h
index 2cee65c4..ba4e727e 100644
--- a/shared/systemd/src/basic/parse-util.h
+++ b/shared/systemd/src/basic/parse-util.h
@@ -1,14 +1,19 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <inttypes.h>
 #include <limits.h>
+#if 0 /* NM_IGNORED */
+#include <linux/loadavg.h>
+#endif /* NM_IGNORED */
 #include <stddef.h>
 #include <stdint.h>
 #include <sys/types.h>
 
 #include "macro.h"
 
+typedef unsigned long loadavg_t;
+
 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);
@@ -88,18 +93,18 @@ static inline int safe_atoux64(const char *s, uint64_t *ret) {
 }
 
 #if LONG_MAX == INT_MAX
-static inline int safe_atolu(const char *s, unsigned long *ret_u) {
+static inline int safe_atolu_full(const char *s, unsigned base, long unsigned *ret_u) {
         assert_cc(sizeof(unsigned long) == sizeof(unsigned));
-        return safe_atou(s, (unsigned*) ret_u);
+        return safe_atou_full(s, base, (unsigned*) ret_u);
 }
 static inline int safe_atoli(const char *s, long int *ret_u) {
         assert_cc(sizeof(long int) == sizeof(int));
         return safe_atoi(s, (int*) ret_u);
 }
 #else
-static inline int safe_atolu(const char *s, unsigned long *ret_u) {
+static inline int safe_atolu_full(const char *s, unsigned base, unsigned long *ret_u) {
         assert_cc(sizeof(unsigned long) == sizeof(unsigned long long));
-        return safe_atollu(s, (unsigned long long*) ret_u);
+        return safe_atollu_full(s, base, (unsigned long long*) ret_u);
 }
 static inline int safe_atoli(const char *s, long int *ret_u) {
         assert_cc(sizeof(long int) == sizeof(long long int));
@@ -107,6 +112,10 @@ static inline int safe_atoli(const char *s, long int *ret_u) {
 }
 #endif
 
+static inline int safe_atolu(const char *s, unsigned long *ret_u) {
+        return safe_atolu_full(s, 0, ret_u);
+}
+
 #if SIZE_MAX == UINT_MAX
 static inline int safe_atozu(const char *s, size_t *ret_u) {
         assert_cc(sizeof(size_t) == sizeof(unsigned));
@@ -137,3 +146,8 @@ 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);
+
+/* Given a Linux load average (e.g. decimal number 34.89 where 34 is passed as i and 89 is passed as f), convert it
+ * to a loadavg_t. */
+int store_loadavg_fixed_point(unsigned long i, unsigned long f, loadavg_t *ret);
+int parse_loadavg_fixed_point(const char *s, loadavg_t *ret);
diff --git a/shared/systemd/src/basic/path-util.c b/shared/systemd/src/basic/path-util.c
index 644829b2..ea44c32b 100644
--- a/shared/systemd/src/basic/path-util.c
+++ b/shared/systemd/src/basic/path-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -558,7 +558,7 @@ char* path_join_internal(const char *first, ...) {
 
         sz = strlen_ptr(first);
         va_start(ap, first);
-        while ((p = va_arg(ap, char*)) != (const char*) -1)
+        while ((p = va_arg(ap, char*)) != POINTER_MAX)
                 if (!isempty(p))
                         sz += 1 + strlen(p);
         va_end(ap);
@@ -578,7 +578,7 @@ char* path_join_internal(const char *first, ...) {
         }
 
         va_start(ap, first);
-        while ((p = va_arg(ap, char*)) != (const char*) -1) {
+        while ((p = va_arg(ap, char*)) != POINTER_MAX) {
                 if (isempty(p))
                         continue;
 
@@ -594,22 +594,53 @@ char* path_join_internal(const char *first, ...) {
 }
 
 #if 0 /* NM_IGNORED */
-int find_executable_full(const char *name, bool use_path_envvar, char **ret) {
+static int check_x_access(const char *path, int *ret_fd) {
+        if (ret_fd) {
+                _cleanup_close_ int fd = -1;
+                int r;
+
+                /* We need to use O_PATH because there may be executables for which we have only exec
+                 * permissions, but not read (usually suid executables). */
+                fd = open(path, O_PATH|O_CLOEXEC);
+                if (fd < 0)
+                        return -errno;
+
+                r = access_fd(fd, X_OK);
+                if (r < 0)
+                        return r;
+
+                *ret_fd = TAKE_FD(fd);
+        } else {
+                /* Let's optimize things a bit by not opening the file if we don't need the fd. */
+                if (access(path, X_OK) < 0)
+                        return -errno;
+        }
+
+        return 0;
+}
+
+int find_executable_full(const char *name, bool use_path_envvar, char **ret_filename, int *ret_fd) {
         int last_error, r;
         const char *p = NULL;
 
         assert(name);
 
         if (is_path(name)) {
-                if (access(name, X_OK) < 0)
-                        return -errno;
+                _cleanup_close_ int fd = -1;
 
-                if (ret) {
-                        r = path_make_absolute_cwd(name, ret);
+                r = check_x_access(name, ret_fd ? &fd : NULL);
+                if (r < 0)
+                        return r;
+
+                if (ret_filename) {
+                        r = path_make_absolute_cwd(name, ret_filename);
                         if (r < 0)
                                 return r;
                 }
 
+                if (ret_fd)
+                        *ret_fd = TAKE_FD(fd);
+
                 return 0;
         }
 
@@ -622,8 +653,10 @@ int find_executable_full(const char *name, bool use_path_envvar, char **ret) {
 
         last_error = -ENOENT;
 
+        /* Resolve a single-component name to a full path */
         for (;;) {
                 _cleanup_free_ char *j = NULL, *element = NULL;
+                _cleanup_close_ int fd = -1;
 
                 r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS);
                 if (r < 0)
@@ -638,7 +671,8 @@ int find_executable_full(const char *name, bool use_path_envvar, char **ret) {
                 if (!j)
                         return -ENOMEM;
 
-                if (access(j, X_OK) >= 0) {
+                r = check_x_access(j, ret_fd ? &fd : NULL);
+                if (r >= 0) {
                         _cleanup_free_ char *with_dash;
 
                         with_dash = strjoin(j, "/");
@@ -652,8 +686,10 @@ int find_executable_full(const char *name, bool use_path_envvar, char **ret) {
                         /* We can't just `continue` inverting this case, since we need to update last_error. */
                         if (errno == ENOTDIR) {
                                 /* Found it! */
-                                if (ret)
-                                        *ret = path_simplify(TAKE_PTR(j), false);
+                                if (ret_filename)
+                                        *ret_filename = path_simplify(TAKE_PTR(j), false);
+                                if (ret_fd)
+                                        *ret_fd = TAKE_FD(fd);
 
                                 return 0;
                         }
diff --git a/shared/systemd/src/basic/path-util.h b/shared/systemd/src/basic/path-util.h
index 6c265519..988f058b 100644
--- a/shared/systemd/src/basic/path-util.h
+++ b/shared/systemd/src/basic/path-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <alloca.h>
@@ -64,7 +64,7 @@ int path_compare(const char *a, const char *b) _pure_;
 bool path_equal(const char *a, const char *b) _pure_;
 bool path_equal_or_files_same(const char *a, const char *b, int flags);
 char* path_join_internal(const char *first, ...);
-#define path_join(x, ...) path_join_internal(x, __VA_ARGS__, (const char*) -1)
+#define path_join(x, ...) path_join_internal(x, __VA_ARGS__, POINTER_MAX)
 
 char* path_simplify(char *path, bool kill_dots);
 
@@ -90,9 +90,9 @@ int path_strv_make_absolute_cwd(char **l);
 char** path_strv_resolve(char **l, const char *root);
 char** path_strv_resolve_uniq(char **l, const char *root);
 
-int find_executable_full(const char *name, bool use_path_envvar, char **ret);
-static inline int find_executable(const char *name, char **ret) {
-        return find_executable_full(name, true, ret);
+int find_executable_full(const char *name, bool use_path_envvar, char **ret_filename, int *ret_fd);
+static inline int find_executable(const char *name, char **ret_filename) {
+        return find_executable_full(name, true, ret_filename, NULL);
 }
 
 bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update);
diff --git a/shared/systemd/src/basic/prioq.c b/shared/systemd/src/basic/prioq.c
index dc048cc7..19a9bc57 100644
--- a/shared/systemd/src/basic/prioq.c
+++ b/shared/systemd/src/basic/prioq.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 /*
  * Priority Queue
diff --git a/shared/systemd/src/basic/prioq.h b/shared/systemd/src/basic/prioq.h
index 1fb57bfa..951576c0 100644
--- a/shared/systemd/src/basic/prioq.h
+++ b/shared/systemd/src/basic/prioq.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
diff --git a/shared/systemd/src/basic/process-util.c b/shared/systemd/src/basic/process-util.c
index 03ca04e1..0e25b020 100644
--- a/shared/systemd/src/basic/process-util.c
+++ b/shared/systemd/src/basic/process-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/basic/process-util.h b/shared/systemd/src/basic/process-util.h
index 314a8de1..aab2c7a1 100644
--- a/shared/systemd/src/basic/process-util.h
+++ b/shared/systemd/src/basic/process-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <errno.h>
diff --git a/shared/systemd/src/basic/random-util.c b/shared/systemd/src/basic/random-util.c
index 9551e762..4f67d9af 100644
--- a/shared/systemd/src/basic/random-util.c
+++ b/shared/systemd/src/basic/random-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -462,10 +462,21 @@ size_t random_pool_size(void) {
 }
 
 int random_write_entropy(int fd, const void *seed, size_t size, bool credit) {
+        _cleanup_close_ int opened_fd = -1;
         int r;
 
-        assert(fd >= 0);
-        assert(seed && size > 0);
+        assert(seed || size == 0);
+
+        if (size == 0)
+                return 0;
+
+        if (fd < 0) {
+                opened_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY);
+                if (opened_fd < 0)
+                        return -errno;
+
+                fd = opened_fd;
+        }
 
         if (credit) {
                 _cleanup_free_ struct rand_pool_info *info = NULL;
@@ -491,6 +502,6 @@ int random_write_entropy(int fd, const void *seed, size_t size, bool credit) {
                         return r;
         }
 
-        return 0;
+        return 1;
 }
 #endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/random-util.h b/shared/systemd/src/basic/random-util.h
index 7824ffac..f661fc09 100644
--- a/shared/systemd/src/basic/random-util.h
+++ b/shared/systemd/src/basic/random-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
diff --git a/shared/systemd/src/basic/ratelimit.c b/shared/systemd/src/basic/ratelimit.c
new file mode 100644
index 00000000..12c8324d
--- /dev/null
+++ b/shared/systemd/src/basic/ratelimit.c
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <sys/time.h>
+
+#include "macro.h"
+#include "ratelimit.h"
+
+/* Modelled after Linux' lib/ratelimit.c by Dave Young
+ * <hidave.darkstar@gmail.com>, which is licensed GPLv2. */
+
+bool ratelimit_below(RateLimit *r) {
+        usec_t ts;
+
+        assert(r);
+
+        if (!ratelimit_configured(r))
+                return true;
+
+        ts = now(CLOCK_MONOTONIC);
+
+        if (r->begin <= 0 ||
+            ts - r->begin > r->interval) {
+                r->begin = ts;
+
+                /* Reset counter */
+                r->num = 0;
+                goto good;
+        }
+
+        if (r->num < r->burst)
+                goto good;
+
+        return false;
+
+good:
+        r->num++;
+        return true;
+}
diff --git a/shared/systemd/src/basic/ratelimit.h b/shared/systemd/src/basic/ratelimit.h
new file mode 100644
index 00000000..ee1d17c0
--- /dev/null
+++ b/shared/systemd/src/basic/ratelimit.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+
+#include "time-util.h"
+#include "util.h"
+
+typedef struct RateLimit {
+        usec_t interval; /* Keep those two fields first so they can be initialized easily: */
+        unsigned burst;  /*   RateLimit rl = { INTERVAL, BURST }; */
+        unsigned num;
+        usec_t begin;
+} RateLimit;
+
+static inline void ratelimit_reset(RateLimit *rl) {
+        rl->num = rl->begin = 0;
+}
+
+static inline bool ratelimit_configured(RateLimit *rl) {
+        return rl->interval > 0 && rl->burst > 0;
+}
+
+bool ratelimit_below(RateLimit *r);
diff --git a/shared/systemd/src/basic/set.h b/shared/systemd/src/basic/set.h
index 7170eea8..57ff7130 100644
--- a/shared/systemd/src/basic/set.h
+++ b/shared/systemd/src/basic/set.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include "extract-word.h"
@@ -128,10 +128,12 @@ int _set_ensure_consume(Set **s, const struct hash_ops *hash_ops, void *key  HAS
 
 int set_consume(Set *s, void *value);
 
-int _set_put_strdup(Set **s, const char *p  HASHMAP_DEBUG_PARAMS);
-#define set_put_strdup(s, p) _set_put_strdup(s, p  HASHMAP_DEBUG_SRC_ARGS)
-int _set_put_strdupv(Set **s, char **l  HASHMAP_DEBUG_PARAMS);
-#define set_put_strdupv(s, l) _set_put_strdupv(s, l  HASHMAP_DEBUG_SRC_ARGS)
+int _set_put_strdup_full(Set **s, const struct hash_ops *hash_ops, const char *p  HASHMAP_DEBUG_PARAMS);
+#define set_put_strdup_full(s, hash_ops, p) _set_put_strdup_full(s, hash_ops, p  HASHMAP_DEBUG_SRC_ARGS)
+#define set_put_strdup(s, p) set_put_strdup_full(s, &string_hash_ops_free, p)
+int _set_put_strdupv_full(Set **s, const struct hash_ops *hash_ops, char **l  HASHMAP_DEBUG_PARAMS);
+#define set_put_strdupv_full(s, hash_ops, l) _set_put_strdupv_full(s, hash_ops, l  HASHMAP_DEBUG_SRC_ARGS)
+#define set_put_strdupv(s, l) set_put_strdupv_full(s, &string_hash_ops_free, l)
 
 int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags flags);
 
@@ -148,3 +150,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free);
 
 #define _cleanup_set_free_ _cleanup_(set_freep)
 #define _cleanup_set_free_free_ _cleanup_(set_free_freep)
+
+int set_strjoin(Set *s, const char *separator, bool wrap_with_separator, char **ret);
diff --git a/shared/systemd/src/basic/signal-util.c b/shared/systemd/src/basic/signal-util.c
index a4b8163c..0c6f5818 100644
--- a/shared/systemd/src/basic/signal-util.c
+++ b/shared/systemd/src/basic/signal-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -52,16 +52,7 @@ 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) {
+        for (; sig >= 0; sig = va_arg(ap, int)) {
 
                 if (sig == 0)
                         continue;
diff --git a/shared/systemd/src/basic/signal-util.h b/shared/systemd/src/basic/signal-util.h
index 3909ee34..bdd39d42 100644
--- a/shared/systemd/src/basic/signal-util.h
+++ b/shared/systemd/src/basic/signal-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <signal.h>
diff --git a/shared/systemd/src/basic/siphash24.h b/shared/systemd/src/basic/siphash24.h
index 474d9c9d..e46f3cc5 100644
--- a/shared/systemd/src/basic/siphash24.h
+++ b/shared/systemd/src/basic/siphash24.h
@@ -1,11 +1,13 @@
+/* SPDX-License-Identifier: CC0-1.0 */
+
 #pragma once
 
 #include <inttypes.h>
 #include <stddef.h>
 #include <stdint.h>
-#include <string.h>
 #include <sys/types.h>
 
+#include "string-util.h"
 #include "time-util.h"
 
 #if 0 /* NM_IGNORED */
@@ -61,11 +63,15 @@ static inline void siphash24_compress_usec_t(usec_t in, struct siphash *state) {
         siphash24_compress(&in, sizeof in, state);
 }
 
-static inline void siphash24_compress_string(const char *in, struct siphash *state) {
-        if (!in)
+static inline void siphash24_compress_safe(const void *in, size_t inlen, struct siphash *state) {
+        if (inlen == 0)
                 return;
 
-        siphash24_compress(in, strlen(in), state);
+        siphash24_compress(in, inlen, state);
+}
+
+static inline void siphash24_compress_string(const char *in, struct siphash *state) {
+        siphash24_compress_safe(in, strlen_ptr(in), state);
 }
 
 uint64_t siphash24_finalize(struct siphash *state);
diff --git a/shared/systemd/src/basic/socket-util.c b/shared/systemd/src/basic/socket-util.c
index 4d889e56..e224091d 100644
--- a/shared/systemd/src/basic/socket-util.c
+++ b/shared/systemd/src/basic/socket-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -27,10 +27,7 @@
 #include "format-util.h"
 #include "io-util.h"
 #include "log.h"
-#include "macro.h"
 #include "memory-util.h"
-#include "missing_socket.h"
-#include "missing_network.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
@@ -325,7 +322,7 @@ bool socket_address_matches_fd(const SocketAddress *a, int fd) {
 }
 
 int sockaddr_port(const struct sockaddr *_sa, unsigned *ret_port) {
-        union sockaddr_union *sa = (union sockaddr_union*) _sa;
+        const union sockaddr_union *sa = (const union sockaddr_union*) _sa;
 
         /* Note, this returns the port as 'unsigned' rather than 'uint16_t', as AF_VSOCK knows larger ports */
 
@@ -350,6 +347,25 @@ int sockaddr_port(const struct sockaddr *_sa, unsigned *ret_port) {
         }
 }
 
+const union in_addr_union *sockaddr_in_addr(const struct sockaddr *_sa) {
+        const union sockaddr_union *sa = (const union sockaddr_union*) _sa;
+
+        if (!sa)
+                return NULL;
+
+        switch (sa->sa.sa_family) {
+
+        case AF_INET:
+                return (const union in_addr_union*) &sa->in.sin_addr;
+
+        case AF_INET6:
+                return (const union in_addr_union*) &sa->in6.sin6_addr;
+
+        default:
+                return NULL;
+        }
+}
+
 int sockaddr_pretty(
                 const struct sockaddr *_sa,
                 socklen_t salen,
@@ -1251,71 +1267,8 @@ int socket_set_recvpktinfo(int fd, int af, bool b) {
         case AF_NETLINK:
                 return setsockopt_int(fd, SOL_NETLINK, NETLINK_PKTINFO, b);
 
-        default:
-                return -EAFNOSUPPORT;
-        }
-}
-
-int socket_set_recverr(int fd, int af, bool b) {
-        int r;
-
-        if (af == AF_UNSPEC) {
-                r = socket_get_family(fd, &af);
-                if (r < 0)
-                        return r;
-        }
-
-        switch (af) {
-
-        case AF_INET:
-                return setsockopt_int(fd, IPPROTO_IP, IP_RECVERR, b);
-
-        case AF_INET6:
-                return setsockopt_int(fd, IPPROTO_IPV6, IPV6_RECVERR, b);
-
-        default:
-                return -EAFNOSUPPORT;
-        }
-}
-
-int socket_set_recvttl(int fd, int af, bool b) {
-        int r;
-
-        if (af == AF_UNSPEC) {
-                r = socket_get_family(fd, &af);
-                if (r < 0)
-                        return r;
-        }
-
-        switch (af) {
-
-        case AF_INET:
-                return setsockopt_int(fd, IPPROTO_IP, IP_RECVTTL, b);
-
-        case AF_INET6:
-                return setsockopt_int(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, b);
-
-        default:
-                return -EAFNOSUPPORT;
-        }
-}
-
-int socket_set_ttl(int fd, int af, int ttl) {
-        int r;
-
-        if (af == AF_UNSPEC) {
-                r = socket_get_family(fd, &af);
-                if (r < 0)
-                        return r;
-        }
-
-        switch (af) {
-
-        case AF_INET:
-                return setsockopt_int(fd, IPPROTO_IP, IP_TTL, ttl);
-
-        case AF_INET6:
-                return setsockopt_int(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, ttl);
+        case AF_PACKET:
+                return setsockopt_int(fd, SOL_PACKET, PACKET_AUXDATA, b);
 
         default:
                 return -EAFNOSUPPORT;
@@ -1351,7 +1304,7 @@ int socket_set_unicast_if(int fd, int af, int ifi) {
         }
 }
 
-int socket_set_freebind(int fd, int af, bool b) {
+int socket_set_option(int fd, int af, int opt_ipv4, int opt_ipv6, int val) {
         int r;
 
         if (af == AF_UNSPEC) {
@@ -1363,18 +1316,18 @@ int socket_set_freebind(int fd, int af, bool b) {
         switch (af) {
 
         case AF_INET:
-                return setsockopt_int(fd, IPPROTO_IP, IP_FREEBIND, b);
+                return setsockopt_int(fd, IPPROTO_IP, opt_ipv4, val);
 
         case AF_INET6:
-                return setsockopt_int(fd, IPPROTO_IPV6, IPV6_FREEBIND, b);
+                return setsockopt_int(fd, IPPROTO_IPV6, opt_ipv6, val);
 
         default:
                 return -EAFNOSUPPORT;
         }
 }
 
-int socket_set_transparent(int fd, int af, bool b) {
-        int r;
+int socket_get_mtu(int fd, int af, size_t *ret) {
+        int mtu, r;
 
         if (af == AF_UNSPEC) {
                 r = socket_get_family(fd, &af);
@@ -1385,13 +1338,23 @@ int socket_set_transparent(int fd, int af, bool b) {
         switch (af) {
 
         case AF_INET:
-                return setsockopt_int(fd, IPPROTO_IP, IP_TRANSPARENT, b);
+                r = getsockopt_int(fd, IPPROTO_IP, IP_MTU, &mtu);
+                break;
 
         case AF_INET6:
-                return setsockopt_int(fd, IPPROTO_IPV6, IPV6_TRANSPARENT, b);
+                r = getsockopt_int(fd, IPPROTO_IPV6, IPV6_MTU, &mtu);
+                break;
 
         default:
                 return -EAFNOSUPPORT;
         }
+
+        if (r < 0)
+                return r;
+        if (mtu <= 0)
+                return -EINVAL;
+
+        *ret = (size_t) mtu;
+        return 0;
 }
 #endif /* NM_IGNORED */
diff --git a/shared/systemd/src/basic/socket-util.h b/shared/systemd/src/basic/socket-util.h
index 1ece9118..1de06947 100644
--- a/shared/systemd/src/basic/socket-util.h
+++ b/shared/systemd/src/basic/socket-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <inttypes.h>
@@ -15,6 +15,7 @@
 #include <sys/un.h>
 
 #include "macro.h"
+#include "missing_network.h"
 #include "missing_socket.h"
 #include "sparse-endian.h"
 
@@ -104,6 +105,7 @@ const char* socket_address_get_path(const SocketAddress *a);
 bool socket_ipv6_is_supported(void);
 
 int sockaddr_port(const struct sockaddr *_sa, unsigned *port);
+const union in_addr_union *sockaddr_in_addr(const struct sockaddr *sa);
 
 int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret);
 int getpeername_pretty(int fd, bool include_port, char **ret);
@@ -258,6 +260,19 @@ static inline int setsockopt_int(int fd, int level, int optname, int value) {
         return 0;
 }
 
+static inline int getsockopt_int(int fd, int level, int optname, int *ret) {
+        int v;
+        socklen_t sl = sizeof(v);
+
+        if (getsockopt(fd, level, optname, &v, &sl) < 0)
+                return -errno;
+        if (sl != sizeof(v))
+                return -EIO;
+
+        *ret = v;
+        return 0;
+}
+
 int socket_bind_to_ifname(int fd, const char *ifname);
 int socket_bind_to_ifindex(int fd, int ifindex);
 
@@ -265,9 +280,28 @@ ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags);
 
 int socket_get_family(int fd, int *ret);
 int socket_set_recvpktinfo(int fd, int af, bool b);
-int socket_set_recverr(int fd, int af, bool b);
-int socket_set_recvttl(int fd, int af, bool b);
-int socket_set_ttl(int fd, int af, int ttl);
 int socket_set_unicast_if(int fd, int af, int ifi);
-int socket_set_freebind(int fd, int af, bool b);
-int socket_set_transparent(int fd, int af, bool b);
+
+int socket_set_option(int fd, int af, int opt_ipv4, int opt_ipv6, int val);
+#if 0 /* NM_IGNORED */
+static inline int socket_set_recverr(int fd, int af, bool b) {
+        return socket_set_option(fd, af, IP_RECVERR, IPV6_RECVERR, b);
+}
+static inline int socket_set_recvttl(int fd, int af, bool b) {
+        return socket_set_option(fd, af, IP_RECVTTL, IPV6_RECVHOPLIMIT, b);
+}
+static inline int socket_set_ttl(int fd, int af, int ttl) {
+        return socket_set_option(fd, af, IP_TTL, IPV6_UNICAST_HOPS, ttl);
+}
+static inline int socket_set_freebind(int fd, int af, bool b) {
+        return socket_set_option(fd, af, IP_FREEBIND, IPV6_FREEBIND, b);
+}
+static inline int socket_set_transparent(int fd, int af, bool b) {
+        return socket_set_option(fd, af, IP_TRANSPARENT, IPV6_TRANSPARENT, b);
+}
+static inline int socket_set_recvfragsize(int fd, int af, bool b) {
+        return socket_set_option(fd, af, IP_RECVFRAGSIZE, IPV6_RECVFRAGSIZE, b);
+}
+#endif /* NM_IGNORED */
+
+int socket_get_mtu(int fd, int af, size_t *ret);
diff --git a/shared/systemd/src/basic/sort-util.h b/shared/systemd/src/basic/sort-util.h
index a8dc3bb6..a8984fc1 100644
--- a/shared/systemd/src/basic/sort-util.h
+++ b/shared/systemd/src/basic/sort-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdlib.h>
@@ -55,6 +55,7 @@ static inline void _qsort_safe(void *base, size_t nmemb, size_t size, __compar_f
                 _qsort_safe((p), (n), sizeof((p)[0]), (__compar_fn_t) _func_); \
         })
 
+#if 0 /* NM_IGNORED */
 static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, __compar_d_fn_t compar, void *userdata) {
         if (nmemb <= 1)
                 return;
@@ -68,3 +69,6 @@ static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, __compar_
                 int (*_func_)(const typeof(p[0])*, const typeof(p[0])*, typeof(userdata)) = func; \
                 qsort_r_safe((p), (n), sizeof((p)[0]), (__compar_d_fn_t) _func_, userdata); \
         })
+#endif /* NM_IGNORED */
+
+int cmp_int(const int *a, const int *b);
diff --git a/shared/systemd/src/basic/stat-util.c b/shared/systemd/src/basic/stat-util.c
index 6d393041..a9880096 100644
--- a/shared/systemd/src/basic/stat-util.c
+++ b/shared/systemd/src/basic/stat-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -231,13 +231,12 @@ int fd_is_network_fs(int fd) {
 }
 
 int path_is_temporary_fs(const char *path) {
-        _cleanup_close_ int fd = -1;
+        struct statfs s;
 
-        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH);
-        if (fd < 0)
+        if (statfs(path, &s) < 0)
                 return -errno;
 
-        return fd_is_temporary_fs(fd);
+        return is_temporary_fs(&s);
 }
 #endif /* NM_IGNORED */
 
@@ -415,7 +414,8 @@ bool stat_inode_unmodified(const struct stat *a, const struct stat *b) {
         return a && b &&
                 (a->st_mode & S_IFMT) != 0 && /* We use the check for .st_mode if the structure was ever initialized */
                 ((a->st_mode ^ b->st_mode) & S_IFMT) == 0 &&  /* same inode type */
-                a->st_mtime == b->st_mtime &&
+                a->st_mtim.tv_sec == b->st_mtim.tv_sec &&
+                a->st_mtim.tv_nsec == b->st_mtim.tv_nsec &&
                 (!S_ISREG(a->st_mode) || a->st_size == b->st_size) && /* if regular file, compare file size */
                 a->st_dev == b->st_dev &&
                 a->st_ino == b->st_ino &&
diff --git a/shared/systemd/src/basic/stat-util.h b/shared/systemd/src/basic/stat-util.h
index 26ecd635..a566114f 100644
--- a/shared/systemd/src/basic/stat-util.h
+++ b/shared/systemd/src/basic/stat-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <fcntl.h>
diff --git a/shared/systemd/src/basic/stdio-util.h b/shared/systemd/src/basic/stdio-util.h
index c3b9448d..d45d3c1a 100644
--- a/shared/systemd/src/basic/stdio-util.h
+++ b/shared/systemd/src/basic/stdio-util.h
@@ -1,7 +1,9 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#if 0 /* NM_IGNORED */
 #include <printf.h>
+#endif /* NM_IGNORED */
 #include <stdarg.h>
 #include <stdio.h>
 #include <sys/types.h>
diff --git a/shared/systemd/src/basic/string-table.c b/shared/systemd/src/basic/string-table.c
index 46014b18..bd8047e6 100644
--- a/shared/systemd/src/basic/string-table.c
+++ b/shared/systemd/src/basic/string-table.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/basic/string-table.h b/shared/systemd/src/basic/string-table.h
index 96924778..ae4ea145 100644
--- a/shared/systemd/src/basic/string-table.h
+++ b/shared/systemd/src/basic/string-table.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #pragma once
 
@@ -28,13 +28,12 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
 
 #define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \
         scope type name##_from_string(const char *s) {                  \
-                int b;                                                  \
                 if (!s)                                                 \
                         return -1;                                      \
-                b = parse_boolean(s);                                   \
+                int b = parse_boolean(s);                               \
                 if (b == 0)                                             \
                         return (type) 0;                                \
-                else if (b > 0)                                         \
+                if (b > 0)                                              \
                         return yes;                                     \
                 return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \
         }
@@ -79,11 +78,13 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
         _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope)
 
 #define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,)
+#define DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,)
 #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static)
 #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static)
 #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static)
 
 #define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,)
+#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,static)
 
 /* For string conversions where numbers are also acceptable */
 #define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max)         \
diff --git a/shared/systemd/src/basic/string-util.c b/shared/systemd/src/basic/string-util.c
index aea13dcb..744a3606 100644
--- a/shared/systemd/src/basic/string-util.c
+++ b/shared/systemd/src/basic/string-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -147,57 +147,32 @@ char *strnappend(const char *s, const char *suffix, size_t b) {
 
 char *strjoin_real(const char *x, ...) {
         va_list ap;
-        size_t l;
+        size_t l = 1;
         char *r, *p;
 
         va_start(ap, x);
+        for (const char *t = x; t; t = va_arg(ap, const char *)) {
+                size_t n;
 
-        if (x) {
-                l = strlen(x);
-
-                for (;;) {
-                        const char *t;
-                        size_t n;
-
-                        t = va_arg(ap, const char *);
-                        if (!t)
-                                break;
-
-                        n = strlen(t);
-                        if (n > ((size_t) -1) - l) {
-                                va_end(ap);
-                                return NULL;
-                        }
-
-                        l += n;
+                n = strlen(t);
+                if (n > SIZE_MAX - l) {
+                        va_end(ap);
+                        return NULL;
                 }
-        } else
-                l = 0;
-
+                l += n;
+        }
         va_end(ap);
 
-        r = new(char, l+1);
+        p = r = new(char, l);
         if (!r)
                 return NULL;
 
-        if (x) {
-                p = stpcpy(r, x);
-
-                va_start(ap, x);
-
-                for (;;) {
-                        const char *t;
-
-                        t = va_arg(ap, const char *);
-                        if (!t)
-                                break;
-
-                        p = stpcpy(p, t);
-                }
+        va_start(ap, x);
+        for (const char *t = x; t; t = va_arg(ap, const char *))
+                p = stpcpy(p, t);
+        va_end(ap);
 
-                va_end(ap);
-        } else
-                r[0] = 0;
+        *p = 0;
 
         return r;
 }
@@ -823,10 +798,10 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
         return *ibuf;
 }
 
-char *strextend_with_separator(char **x, const char *separator, ...) {
-        bool need_separator;
+char *strextend_with_separator_internal(char **x, const char *separator, ...) {
         size_t f, l, l_separator;
-        char *r, *p;
+        bool need_separator;
+        char *nr, *p;
         va_list ap;
 
         assert(x);
@@ -850,7 +825,7 @@ char *strextend_with_separator(char **x, const char *separator, ...) {
                 if (need_separator)
                         n += l_separator;
 
-                if (n > ((size_t) -1) - l) {
+                if (n >= SIZE_MAX - l) {
                         va_end(ap);
                         return NULL;
                 }
@@ -862,11 +837,12 @@ char *strextend_with_separator(char **x, const char *separator, ...) {
 
         need_separator = !isempty(*x);
 
-        r = realloc(*x, l+1);
-        if (!r)
+        nr = realloc(*x, GREEDY_ALLOC_ROUND_UP(l+1));
+        if (!nr)
                 return NULL;
 
-        p = r + f;
+        *x = nr;
+        p = nr + f;
 
         va_start(ap, separator);
         for (;;) {
@@ -885,12 +861,11 @@ char *strextend_with_separator(char **x, const char *separator, ...) {
         }
         va_end(ap);
 
-        assert(p == r + l);
+        assert(p == nr + l);
 
         *p = 0;
-        *x = r;
 
-        return r + l;
+        return p;
 }
 #endif /* NM_IGNORED */
 
diff --git a/shared/systemd/src/basic/string-util.h b/shared/systemd/src/basic/string-util.h
index cefbda35..593cf04a 100644
--- a/shared/systemd/src/basic/string-util.h
+++ b/shared/systemd/src/basic/string-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
@@ -33,6 +33,12 @@ static inline bool streq_ptr(const char *a, const char *b) {
         return strcmp_ptr(a, b) == 0;
 }
 
+static inline char* strstr_ptr(const char *haystack, const char *needle) {
+        if (!haystack || !needle)
+                return NULL;
+        return strstr(haystack, needle);
+}
+
 static inline const char* strempty(const char *s) {
         return s ?: "";
 }
@@ -53,6 +59,10 @@ static inline const char* true_false(bool b) {
         return b ? "true" : "false";
 }
 
+static inline const char* plus_minus(bool b) {
+        return b ? "+" : "-";
+}
+
 static inline const char* one_zero(bool b) {
         return b ? "1" : "0";
 }
@@ -179,9 +189,10 @@ char *strreplace(const char *text, const char *old_string, const char *new_strin
 
 char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]);
 
-char *strextend_with_separator(char **x, const char *separator, ...) _sentinel_;
+char *strextend_with_separator_internal(char **x, const char *separator, ...) _sentinel_;
 
-#define strextend(x, ...) strextend_with_separator(x, NULL, __VA_ARGS__)
+#define strextend_with_separator(x, separator, ...) strextend_with_separator_internal(x, separator, __VA_ARGS__, NULL)
+#define strextend(x, ...) strextend_with_separator_internal(x, NULL, __VA_ARGS__, NULL)
 
 char *strrep(const char *s, unsigned n);
 
diff --git a/shared/systemd/src/basic/strv.c b/shared/systemd/src/basic/strv.c
index 30d3af46..7d3e3fc7 100644
--- a/shared/systemd/src/basic/strv.c
+++ b/shared/systemd/src/basic/strv.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -125,7 +125,6 @@ size_t strv_length(char * const *l) {
 }
 
 char **strv_new_ap(const char *x, va_list ap) {
-        const char *s;
         _cleanup_strv_free_ char **a = NULL;
         size_t n = 0, i = 0;
         va_list aq;
@@ -135,43 +134,28 @@ char **strv_new_ap(const char *x, va_list ap) {
          * STRV_IFNOTNULL() macro to include possibly NULL strings in
          * the string list. */
 
-        if (x) {
-                n = x == STRV_IGNORE ? 0 : 1;
-
-                va_copy(aq, ap);
-                while ((s = va_arg(aq, const char*))) {
-                        if (s == STRV_IGNORE)
-                                continue;
-
-                        n++;
-                }
+        va_copy(aq, ap);
+        for (const char *s = x; s; s = va_arg(aq, const char*)) {
+                if (s == STRV_IGNORE)
+                        continue;
 
-                va_end(aq);
+                n++;
         }
+        va_end(aq);
 
         a = new(char*, n+1);
         if (!a)
                 return NULL;
 
-        if (x) {
-                if (x != STRV_IGNORE) {
-                        a[i] = strdup(x);
-                        if (!a[i])
-                                return NULL;
-                        i++;
-                }
-
-                while ((s = va_arg(ap, const char*))) {
-
-                        if (s == STRV_IGNORE)
-                                continue;
+        for (const char *s = x; s; s = va_arg(ap, const char*)) {
+                if (s == STRV_IGNORE)
+                        continue;
 
-                        a[i] = strdup(s);
-                        if (!a[i])
-                                return NULL;
+                a[i] = strdup(s);
+                if (!a[i])
+                        return NULL;
 
-                        i++;
-                }
+                i++;
         }
 
         a[i] = NULL;
@@ -543,6 +527,19 @@ int strv_consume_prepend(char ***l, char *value) {
         return r;
 }
 
+int strv_prepend(char ***l, const char *value) {
+        char *v;
+
+        if (!value)
+                return 0;
+
+        v = strdup(value);
+        if (!v)
+                return -ENOMEM;
+
+        return strv_consume_prepend(l, v);
+}
+
 int strv_extend(char ***l, const char *value) {
         char *v;
 
diff --git a/shared/systemd/src/basic/strv.h b/shared/systemd/src/basic/strv.h
index 919fabf7..6b3e8e7f 100644
--- a/shared/systemd/src/basic/strv.h
+++ b/shared/systemd/src/basic/strv.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <fnmatch.h>
@@ -34,6 +34,7 @@ size_t strv_length(char * const *l) _pure_;
 
 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_prepend(char ***l, const char *value);
 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);
@@ -62,7 +63,7 @@ char **strv_new_internal(const char *x, ...) _sentinel_;
 char **strv_new_ap(const char *x, va_list ap);
 #define strv_new(...) strv_new_internal(__VA_ARGS__, NULL)
 
-#define STRV_IGNORE ((const char *) -1)
+#define STRV_IGNORE ((const char *) POINTER_MAX)
 
 static inline const char* STRV_IFNOTNULL(const char *x) {
         return x ? x : STRV_IGNORE;
diff --git a/shared/systemd/src/basic/strxcpyx.c b/shared/systemd/src/basic/strxcpyx.c
index 301e6899..39aebb88 100644
--- a/shared/systemd/src/basic/strxcpyx.c
+++ b/shared/systemd/src/basic/strxcpyx.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 /*
  * Concatenates/copies strings. In any case, terminates in all cases
diff --git a/shared/systemd/src/basic/strxcpyx.h b/shared/systemd/src/basic/strxcpyx.h
index 9b668412..cdef492d 100644
--- a/shared/systemd/src/basic/strxcpyx.h
+++ b/shared/systemd/src/basic/strxcpyx.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stddef.h>
diff --git a/shared/systemd/src/basic/time-util.c b/shared/systemd/src/basic/time-util.c
index 0fac79a7..e5faa334 100644
--- a/shared/systemd/src/basic/time-util.c
+++ b/shared/systemd/src/basic/time-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -1613,7 +1613,7 @@ TimestampStyle timestamp_style_from_string(const char *s) {
                 return t;
         if (streq_ptr(s, "µs"))
                 return TIMESTAMP_US;
-        if (streq_ptr(s, "µs+uts"))
+        if (streq_ptr(s, "µs+utc"))
                 return TIMESTAMP_US_UTC;
         return t;
 }
diff --git a/shared/systemd/src/basic/time-util.h b/shared/systemd/src/basic/time-util.h
index cecd5efa..89ee8b4a 100644
--- a/shared/systemd/src/basic/time-util.h
+++ b/shared/systemd/src/basic/time-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <inttypes.h>
@@ -63,10 +63,10 @@ typedef enum TimestampStyle {
 
 /* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this
  * to 6. Let's rely on that. */
-#define FORMAT_TIMESTAMP_MAX (3+1+10+1+8+1+6+1+6+1)
-#define FORMAT_TIMESTAMP_WIDTH 28 /* when outputting, assume this width */
-#define FORMAT_TIMESTAMP_RELATIVE_MAX 256
-#define FORMAT_TIMESPAN_MAX 64
+#define FORMAT_TIMESTAMP_MAX (3U+1U+10U+1U+8U+1U+6U+1U+6U+1U)
+#define FORMAT_TIMESTAMP_WIDTH 28U /* when outputting, assume this width */
+#define FORMAT_TIMESTAMP_RELATIVE_MAX 256U
+#define FORMAT_TIMESPAN_MAX 64U
 
 #define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1)
 
diff --git a/shared/systemd/src/basic/tmpfile-util.c b/shared/systemd/src/basic/tmpfile-util.c
index 9b3621ba..bbd6a1ed 100644
--- a/shared/systemd/src/basic/tmpfile-util.c
+++ b/shared/systemd/src/basic/tmpfile-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/basic/tmpfile-util.h b/shared/systemd/src/basic/tmpfile-util.h
index 802c85d6..45255fc0 100644
--- a/shared/systemd/src/basic/tmpfile-util.h
+++ b/shared/systemd/src/basic/tmpfile-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdio.h>
diff --git a/shared/systemd/src/basic/umask-util.h b/shared/systemd/src/basic/umask-util.h
index cad74517..bd7c2bdb 100644
--- a/shared/systemd/src/basic/umask-util.h
+++ b/shared/systemd/src/basic/umask-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
diff --git a/shared/systemd/src/basic/utf8.c b/shared/systemd/src/basic/utf8.c
index 8159b187..a7679bfa 100644
--- a/shared/systemd/src/basic/utf8.c
+++ b/shared/systemd/src/basic/utf8.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 /* Parts of this file are based on the GLIB utf8 validation functions. The
  * original license text follows. */
diff --git a/shared/systemd/src/basic/utf8.h b/shared/systemd/src/basic/utf8.h
index f315ea0f..a6ea942c 100644
--- a/shared/systemd/src/basic/utf8.h
+++ b/shared/systemd/src/basic/utf8.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>
diff --git a/shared/systemd/src/basic/util.c b/shared/systemd/src/basic/util.c
index 8a3f95dc..10a5bcff 100644
--- a/shared/systemd/src/basic/util.c
+++ b/shared/systemd/src/basic/util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
@@ -168,7 +168,7 @@ int container_get_leader(const char *machine, pid_t *pid) {
                 return 0;
         }
 
-        if (!machine_name_is_valid(machine))
+        if (!hostname_is_valid(machine, 0))
                 return -EINVAL;
 
         p = strjoina("/run/systemd/machines/", machine);
@@ -196,8 +196,8 @@ int container_get_leader(const char *machine, pid_t *pid) {
 }
 
 int version(void) {
-        puts("systemd " STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")\n"
-             SYSTEMD_FEATURES);
+        printf("systemd " STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")\n%s\n",
+               systemd_features);
         return 0;
 }
 
diff --git a/shared/systemd/src/basic/util.h b/shared/systemd/src/basic/util.h
index 6fc7480f..942d773f 100644
--- a/shared/systemd/src/basic/util.h
+++ b/shared/systemd/src/basic/util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdint.h>
diff --git a/shared/systemd/src/shared/dns-domain.c b/shared/systemd/src/shared/dns-domain.c
index 6d60eb58..95e4a93a 100644
--- a/shared/systemd/src/shared/dns-domain.c
+++ b/shared/systemd/src/shared/dns-domain.c
@@ -1,16 +1,7 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
-#if 0 /* NM_IGNORED */
-#if HAVE_LIBIDN2
-#  include <idn2.h>
-#elif HAVE_LIBIDN
-#  include <idna.h>
-#  include <stringprep.h>
-#endif
-#endif
-
 #include <endian.h>
 #include <netinet/in.h>
 #include <stdio.h>
@@ -21,6 +12,7 @@
 #include "hashmap.h"
 #include "hexdecoct.h"
 #include "hostname-util.h"
+#include "idn-util.h"
 #include "in-addr-util.h"
 #include "macro.h"
 #include "parse-util.h"
@@ -319,12 +311,17 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded
         const char *p;
         bool contains_8bit = false;
         char buffer[DNS_LABEL_MAX+1];
+        int r;
 
         assert(encoded);
         assert(decoded);
 
         /* Converts an U-label into an A-label */
 
+        r = dlopen_idn();
+        if (r < 0)
+                return r;
+
         if (encoded_size <= 0)
                 return -EINVAL;
 
@@ -339,11 +336,11 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded
                 return 0;
         }
 
-        input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
+        input = sym_stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
         if (!input)
                 return -ENOMEM;
 
-        if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0)
+        if (sym_idna_to_ascii_4i(input, input_size, buffer, 0) != 0)
                 return -EINVAL;
 
         l = strlen(buffer);
@@ -369,28 +366,33 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
         _cleanup_free_ char *result = NULL;
         uint32_t *output = NULL;
         size_t w;
+        int r;
 
         /* To be invoked after unescaping. Converts an A-label into an U-label. */
 
         assert(encoded);
         assert(decoded);
 
+        r = dlopen_idn();
+        if (r < 0)
+                return r;
+
         if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX)
                 return -EINVAL;
 
         if (!memory_startswith(encoded, encoded_size, IDNA_ACE_PREFIX))
                 return 0;
 
-        input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
+        input = sym_stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
         if (!input)
                 return -ENOMEM;
 
         output_size = input_size;
         output = newa(uint32_t, output_size);
 
-        idna_to_unicode_44i(input, input_size, output, &output_size, 0);
+        sym_idna_to_unicode_44i(input, input_size, output, &output_size, 0);
 
-        result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w);
+        result = sym_stringprep_ucs4_to_utf8(output, output_size, NULL, &w);
         if (!result)
                 return -ENOMEM;
         if (w <= 0)
@@ -748,12 +750,12 @@ int dns_name_reverse(int family, const union in_addr_union *a, char **ret) {
         return 0;
 }
 
-int dns_name_address(const char *p, int *family, union in_addr_union *address) {
+int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_address) {
         int r;
 
         assert(p);
-        assert(family);
-        assert(address);
+        assert(ret_family);
+        assert(ret_address);
 
         r = dns_name_endswith(p, "in-addr.arpa");
         if (r < 0)
@@ -782,11 +784,11 @@ int dns_name_address(const char *p, int *family, union in_addr_union *address) {
                 if (r <= 0)
                         return r;
 
-                *family = AF_INET;
-                address->in.s_addr = htobe32(((uint32_t) a[3] << 24) |
-                                             ((uint32_t) a[2] << 16) |
-                                             ((uint32_t) a[1] << 8) |
-                                              (uint32_t) a[0]);
+                *ret_family = AF_INET;
+                ret_address->in.s_addr = htobe32(((uint32_t) a[3] << 24) |
+                                                 ((uint32_t) a[2] << 16) |
+                                                 ((uint32_t) a[1] << 8) |
+                                                 (uint32_t) a[0]);
 
                 return 1;
         }
@@ -827,11 +829,14 @@ int dns_name_address(const char *p, int *family, union in_addr_union *address) {
                 if (r <= 0)
                         return r;
 
-                *family = AF_INET6;
-                address->in6 = a;
+                *ret_family = AF_INET6;
+                ret_address->in6 = a;
                 return 1;
         }
 
+        *ret_family = AF_UNSPEC;
+        *ret_address = IN_ADDR_NULL;
+
         return 0;
 }
 #endif /* NM_IGNORED */
@@ -1277,47 +1282,67 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
 }
 
 int dns_name_apply_idna(const char *name, char **ret) {
+
         /* Return negative on error, 0 if not implemented, positive on success. */
 
-#if HAVE_LIBIDN2
+#if HAVE_LIBIDN2 || HAVE_LIBIDN2
         int r;
+
+        r = dlopen_idn();
+        if (r == -EOPNOTSUPP) {
+                *ret = NULL;
+                return 0;
+        }
+        if (r < 0)
+                return r;
+#endif
+
+#if HAVE_LIBIDN2
         _cleanup_free_ char *t = NULL;
 
         assert(name);
         assert(ret);
 
-        r = idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t,
-                           IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
+        /* First, try non-transitional mode (i.e. IDN2008 rules) */
+        r = sym_idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t,
+                               IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
+        if (r == IDN2_DISALLOWED) /* If that failed, because of disallowed characters, try transitional mode.
+                                   * (i.e. IDN2003 rules which supports some unicode chars IDN2008 doesn't allow). */
+                r = sym_idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t,
+                                       IDN2_NFC_INPUT | IDN2_TRANSITIONAL);
+
         log_debug("idn2_lookup_u8: %s → %s", name, t);
         if (r == IDN2_OK) {
                 if (!startswith(name, "xn--")) {
                         _cleanup_free_ char *s = NULL;
 
-                        r = idn2_to_unicode_8z8z(t, &s, 0);
+                        r = sym_idn2_to_unicode_8z8z(t, &s, 0);
                         if (r != IDN2_OK) {
                                 log_debug("idn2_to_unicode_8z8z(\"%s\") failed: %d/%s",
-                                          t, r, idn2_strerror(r));
+                                          t, r, sym_idn2_strerror(r));
+                                *ret = NULL;
                                 return 0;
                         }
 
                         if (!streq_ptr(name, s)) {
                                 log_debug("idn2 roundtrip failed: \"%s\" → \"%s\" → \"%s\", ignoring.",
                                           name, t, s);
+                                *ret = NULL;
                                 return 0;
                         }
                 }
 
                 *ret = TAKE_PTR(t);
-
                 return 1; /* *ret has been written */
         }
 
-        log_debug("idn2_lookup_u8(\"%s\") failed: %d/%s", name, r, idn2_strerror(r));
+        log_debug("idn2_lookup_u8(\"%s\") failed: %d/%s", name, r, sym_idn2_strerror(r));
         if (r == IDN2_2HYPHEN)
                 /* The name has two hyphens — forbidden by IDNA2008 in some cases */
                 return 0;
         if (IN_SET(r, IDN2_TOO_BIG_DOMAIN, IDN2_TOO_BIG_LABEL))
                 return -ENOSPC;
+
         return -EINVAL;
 #elif HAVE_LIBIDN
         _cleanup_free_ char *buf = NULL;
@@ -1369,6 +1394,7 @@ int dns_name_apply_idna(const char *name, char **ret) {
 
         return 1;
 #else
+        *ret = NULL;
         return 0;
 #endif
 }
diff --git a/shared/systemd/src/shared/dns-domain.h b/shared/systemd/src/shared/dns-domain.h
index e4e5b1b9..984f4840 100644
--- a/shared/systemd/src/shared/dns-domain.h
+++ b/shared/systemd/src/shared/dns-domain.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <errno.h>
diff --git a/shared/systemd/src/shared/log-link.h b/shared/systemd/src/shared/log-link.h
new file mode 100644
index 00000000..3a4dcaa2
--- /dev/null
+++ b/shared/systemd/src/shared/log-link.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "log.h"
+
+#define log_interface_full_errno(ifname, level, error, ...)             \
+        ({                                                              \
+                const char *_ifname = (ifname);                         \
+                _ifname ? log_object_internal(level, error, PROJECT_FILE, __LINE__, __func__, "INTERFACE=", _ifname, NULL, NULL, ##__VA_ARGS__) : \
+                        log_internal(level, error, PROJECT_FILE, __LINE__, __func__, ##__VA_ARGS__); \
+        })
+
+/*
+ * The following macros append INTERFACE= to the message.
+ * The macros require a struct named 'Link' which contains 'char *ifname':
+ *
+ *         typedef struct Link {
+ *                 char *ifname;
+ *         } Link;
+ *
+ * See, network/networkd-link.h for example.
+ */
+
+#define log_link_full_errno(link, level, error, ...)                    \
+        ({                                                              \
+                const Link *_l = (link);                                \
+                log_interface_full_errno(_l ? _l->ifname : NULL, level, error, ##__VA_ARGS__); \
+        })
+
+#define log_link_full(link, level, ...) (void) log_link_full_errno(link, level, 0, __VA_ARGS__)
+
+#define log_link_debug(link, ...)   log_link_full_errno(link, LOG_DEBUG, 0, __VA_ARGS__)
+#define log_link_info(link, ...)    log_link_full(link, LOG_INFO, __VA_ARGS__)
+#define log_link_notice(link, ...)  log_link_full(link, LOG_NOTICE, __VA_ARGS__)
+#define log_link_warning(link, ...) log_link_full(link, LOG_WARNING, __VA_ARGS__)
+#define log_link_error(link, ...)   log_link_full(link, LOG_ERR, __VA_ARGS__)
+
+#define log_link_debug_errno(link, error, ...)   log_link_full_errno(link, LOG_DEBUG, error, __VA_ARGS__)
+#define log_link_info_errno(link, error, ...)    log_link_full_errno(link, LOG_INFO, error, __VA_ARGS__)
+#define log_link_notice_errno(link, error, ...)  log_link_full_errno(link, LOG_NOTICE, error, __VA_ARGS__)
+#define log_link_warning_errno(link, error, ...) log_link_full_errno(link, LOG_WARNING, error, __VA_ARGS__)
+#define log_link_error_errno(link, error, ...)   log_link_full_errno(link, LOG_ERR, error, __VA_ARGS__)
+
+#define LOG_LINK_MESSAGE(link, fmt, ...) "MESSAGE=%s: " fmt, (link)->ifname, ##__VA_ARGS__
+#define LOG_LINK_INTERFACE(link) "INTERFACE=%s", (link)->ifname
diff --git a/shared/systemd/src/shared/web-util.c b/shared/systemd/src/shared/web-util.c
index 4cff5e27..35ba1a2e 100644
--- a/shared/systemd/src/shared/web-util.c
+++ b/shared/systemd/src/shared/web-util.c
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include "nm-sd-adapt-shared.h"
 
diff --git a/shared/systemd/src/shared/web-util.h b/shared/systemd/src/shared/web-util.h
index c9e67e5c..ec54669f 100644
--- a/shared/systemd/src/shared/web-util.h
+++ b/shared/systemd/src/shared/web-util.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
 #include <stdbool.h>