about summary refs log tree commit diff
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/c-list/src/c-list.h (renamed from shared/nm-utils/c-list.h)157
-rw-r--r--shared/c-siphash/src/c-siphash.c246
-rw-r--r--shared/c-siphash/src/c-siphash.h60
-rw-r--r--shared/meson.build78
-rw-r--r--shared/n-acd/src/n-acd.c1262
-rw-r--r--shared/n-acd/src/n-acd.h96
-rw-r--r--shared/nm-default.h163
-rw-r--r--shared/nm-test-libnm-utils.h44
-rw-r--r--shared/nm-test-utils-impl.c176
-rw-r--r--shared/nm-utils/c-list-util.c88
-rw-r--r--shared/nm-utils/c-list-util.h2
-rw-r--r--shared/nm-utils/nm-c-list.h81
-rw-r--r--shared/nm-utils/nm-compat.c95
-rw-r--r--shared/nm-utils/nm-compat.h53
-rw-r--r--shared/nm-utils/nm-dedup-multi.c6
-rw-r--r--shared/nm-utils/nm-dedup-multi.h2
-rw-r--r--shared/nm-utils/nm-enum-utils.c181
-rw-r--r--shared/nm-utils/nm-enum-utils.h5
-rw-r--r--shared/nm-utils/nm-glib.h384
-rw-r--r--shared/nm-utils/nm-hash-utils.c134
-rw-r--r--shared/nm-utils/nm-hash-utils.h23
-rw-r--r--shared/nm-utils/nm-jansson.h46
-rw-r--r--shared/nm-utils/nm-macros-internal.h256
-rw-r--r--shared/nm-utils/nm-obj.h2
-rw-r--r--shared/nm-utils/nm-random-utils.c2
-rw-r--r--shared/nm-utils/nm-shared-utils.c384
-rw-r--r--shared/nm-utils/nm-shared-utils.h203
-rw-r--r--shared/nm-utils/nm-test-utils.h92
-rw-r--r--shared/nm-utils/siphash24.c203
-rw-r--r--shared/nm-utils/siphash24.h23
-rw-r--r--shared/nm-utils/unaligned.h74
-rw-r--r--shared/nm-version-macros.h28
-rw-r--r--shared/nm-version-macros.h.in24
33 files changed, 3625 insertions, 1048 deletions
diff --git a/shared/nm-utils/c-list.h b/shared/c-list/src/c-list.h
index a3c4053b..ff434d8d 100644
--- a/shared/nm-utils/c-list.h
+++ b/shared/c-list/src/c-list.h
@@ -1,7 +1,7 @@
 #pragma once
 
 /*
- * Circular Double Linked List Implementation in Standard ISO-C11
+ * Circular Intrusive Double Linked List Collection in ISO-C11
  *
  * This implements a generic circular double linked list. List entries must
  * embed the CList object, which provides pointers to the next and previous
@@ -225,71 +225,6 @@ static inline void c_list_splice(CList *target, CList *source) {
 }
 
 /**
- * c_list_for_each() - loop over all list entries
- * @_iter:              iterator to use
- * @_list:              list to loop over
- *
- * This is a macro to use as for-loop to iterate an entire list. It is meant as
- * convenience macro. Feel free to code your own loop iterator.
- */
-#define c_list_for_each(_iter, _list)                                           \
-        for (_iter = (_list)->next;                                             \
-             (_iter) != (_list);                                                \
-             _iter = (_iter)->next)
-
-
-/**
- * c_list_for_each_safe() - loop over all list entries, safe for removal
- * @_iter:              iterator to use
- * @_safe:              used to store pointer to next element
- * @_list:              list to loop over
- *
- * This is a macro to use as for-loop to iterate an entire list, safe against
- * removal of the current element. It is meant as convenience macro. Feel free
- * to code your own loop iterator.
- *
- * Note that this fetches the next element prior to executing the loop body.
- * This makes it safe against removal of the current entry, but it will go
- * havoc if you remove other list entries. You better not modify anything but
- * the current list entry.
- */
-#define c_list_for_each_safe(_iter, _safe, _list)                               \
-        for (_iter = (_list)->next, _safe = (_iter)->next;                      \
-             (_iter) != (_list);                                                \
-             _iter = (_safe), _safe = (_safe)->next)
-
-/**
- * c_list_for_each_entry() - loop over all list entries
- * @_iter:              iterator to use
- * @_list:              list to loop over
- * @_m:                 member name of CList object in list type
- *
- * This combines c_list_for_each() with c_list_entry(), making it easy to
- * iterate over a list of a specific type.
- */
-#define c_list_for_each_entry(_iter, _list, _m)                                 \
-        for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m);       \
-             &(_iter)->_m != (_list);                                           \
-             _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m))
-
-/**
- * c_list_for_each_entry_safe() - loop over all list entries, safe for removal
- * @_iter:              iterator to use
- * @_safe:              used to store pointer to next element
- * @_list:              list to loop over
- * @_m:                 member name of CList object in list type
- *
- * This combines c_list_for_each_safe() with c_list_entry(), making it easy to
- * iterate over a list of a specific type.
- */
-#define c_list_for_each_entry_safe(_iter, _safe, _list, _m)                     \
-        for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m),       \
-             _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m);    \
-             &(_iter)->_m != (_list);                                           \
-             _iter = (_safe),                                                   \
-             _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m))    \
-
-/**
  * c_list_first() - return pointer to first element, or NULL if empty
  * @list:               list to operate on, or NULL
  *
@@ -342,6 +277,96 @@ static inline CList *c_list_last(CList *list) {
         c_list_entry(c_list_last(_list), _t, _m)
 
 /**
+ * c_list_for_each*() - iterators
+ *
+ * The c_list_for_each*() macros provide simple for-loop wrappers to iterate
+ * a linked list. They come in a set of flavours:
+ *
+ *   - "entry": This combines c_list_entry() with the loop iterator, so the
+ *              iterator always has the type of the surrounding object, rather
+ *              than CList.
+ *
+ *   - "safe": The loop iterator always keeps track of the next element to
+ *             visit. This means, you can safely modify the current element,
+ *             while retaining loop-integrity.
+ *             You still must not touch any other entry of the list. Otherwise,
+ *             the loop-iterator will be corrupted.
+ *
+ *   - "continue": Rather than starting the iteration at the front of the list,
+ *                 use the current value of the iterator as starting position.
+ *                 Note that the first loop iteration will be the following
+ *                 element, not the given element.
+ *
+ *   - "unlink": This unlinks the current element from the list before the loop
+ *               code is run. Note that this only does a partial unlink, since
+ *               it assumes the entire list will be unlinked. You must not
+ *               break out of the loop, or the list will be in an inconsistent
+ *               state.
+ */
+
+#define c_list_for_each(_iter, _list)                                           \
+        for (_iter = (_list)->next;                                             \
+             (_iter) != (_list);                                                \
+             _iter = (_iter)->next)
+
+#define c_list_for_each_entry(_iter, _list, _m)                                 \
+        for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m);       \
+             &(_iter)->_m != (_list);                                           \
+             _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m))
+
+#define c_list_for_each_safe(_iter, _safe, _list)                               \
+        for (_iter = (_list)->next, _safe = (_iter)->next;                      \
+             (_iter) != (_list);                                                \
+             _iter = (_safe), _safe = (_safe)->next)
+
+#define c_list_for_each_entry_safe(_iter, _safe, _list, _m)                     \
+        for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m),       \
+             _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m);    \
+             &(_iter)->_m != (_list);                                           \
+             _iter = (_safe),                                                   \
+             _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m))    \
+
+#define c_list_for_each_continue(_iter, _list)                                  \
+        for (_iter = (_iter) ? (_iter)->next : (_list)->next;                   \
+             (_iter) != (_list);                                                \
+             _iter = (_iter)->next)
+
+#define c_list_for_each_entry_continue(_iter, _list, _m)                        \
+        for (_iter = c_list_entry((_iter) ? (_iter)->_m.next : (_list)->next,   \
+                                  __typeof__(*_iter),                           \
+                                  _m);                                          \
+             &(_iter)->_m != (_list);                                           \
+             _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m))
+
+#define c_list_for_each_safe_continue(_iter, _safe, _list)                      \
+        for (_iter = (_iter) ? (_iter)->next : (_list)->next,                   \
+             _safe = (_iter)->next;                                             \
+             (_iter) != (_list);                                                \
+             _iter = (_safe), _safe = (_safe)->next)
+
+#define c_list_for_each_entry_safe_continue(_iter, _safe, _list, _m)            \
+        for (_iter = c_list_entry((_iter) ? (_iter)->_m.next : (_list)->next,   \
+                                  __typeof__(*_iter),                           \
+                                  _m),                                          \
+             _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m);    \
+             &(_iter)->_m != (_list);                                           \
+             _iter = (_safe),                                                   \
+             _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m))    \
+
+#define c_list_for_each_safe_unlink(_iter, _safe, _list)                        \
+        for (_iter = (_list)->next, _safe = (_iter)->next;                      \
+             ((*_iter = (CList)C_LIST_INIT(*_iter)), (_iter) != (_list));       \
+             _iter = (_safe), _safe = (_safe)->next)
+
+#define c_list_for_each_entry_safe_unlink(_iter, _safe, _list, _m)              \
+        for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m),       \
+             _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m);    \
+             (((_iter)->_m = (CList)C_LIST_INIT((_iter)->_m)),                  \
+              &(_iter)->_m != (_list));                                         \
+             _iter = (_safe),                                                   \
+             _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m))    \
+
+/**
  * c_list_length() - return number of linked entries, excluding the head
  * @list:               list to operate on
  *
diff --git a/shared/c-siphash/src/c-siphash.c b/shared/c-siphash/src/c-siphash.c
new file mode 100644
index 00000000..76b25b86
--- /dev/null
+++ b/shared/c-siphash/src/c-siphash.c
@@ -0,0 +1,246 @@
+/*
+ * SipHash Implementation
+ *
+ * For highlevel documentation of the API see the header file and the docbook
+ * comments. This implementation is based on the reference implementation of
+ * SipHash, written by Jean-Philippe Aumasson and Daniel J. Bernstein, and
+ * released to the Public Domain.
+ *
+ * So far, only SipHash24 is implemented, since there was no need for other
+ * parameters. However, adjusted c_siphash_append_X() and
+ * C_siphash_finalize_Y() can be easily provided, if required.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include "c-siphash.h"
+
+#define _public_ __attribute__((__visibility__("default")))
+
+static inline uint64_t c_siphash_read_le64(const uint8_t bytes[8]) {
+        return  ((uint64_t) bytes[0]) |
+               (((uint64_t) bytes[1]) <<  8) |
+               (((uint64_t) bytes[2]) << 16) |
+               (((uint64_t) bytes[3]) << 24) |
+               (((uint64_t) bytes[4]) << 32) |
+               (((uint64_t) bytes[5]) << 40) |
+               (((uint64_t) bytes[6]) << 48) |
+               (((uint64_t) bytes[7]) << 56);
+}
+
+static inline uint64_t c_siphash_rotate_left(uint64_t x, uint8_t b) {
+        return (x << b) | (x >> (64 - b));
+}
+
+static inline void c_siphash_sipround(CSipHash *state) {
+        state->v0 += state->v1;
+        state->v1 = c_siphash_rotate_left(state->v1, 13);
+        state->v1 ^= state->v0;
+        state->v0 = c_siphash_rotate_left(state->v0, 32);
+        state->v2 += state->v3;
+        state->v3 = c_siphash_rotate_left(state->v3, 16);
+        state->v3 ^= state->v2;
+        state->v0 += state->v3;
+        state->v3 = c_siphash_rotate_left(state->v3, 21);
+        state->v3 ^= state->v0;
+        state->v2 += state->v1;
+        state->v1 = c_siphash_rotate_left(state->v1, 17);
+        state->v1 ^= state->v2;
+        state->v2 = c_siphash_rotate_left(state->v2, 32);
+}
+
+/**
+ * c_siphash_init() - initialize siphash context
+ * @state:              context object
+ * @seed:               128bit seed
+ *
+ * This initializes the siphash state context. Once initialized, it can be used
+ * to hash arbitary input. To feed data into it, use c_siphash_append(). To get
+ * the final hash, use c_siphash_finalize().
+ *
+ * Note that the siphash context does not allocate state. There is no need to
+ * deserialize it before releasing its backing memory.
+ *
+ * The hashes generated by this context change depending on the seed. Every
+ * user is highly inclined to provide their unique seed. If no stable hashes
+ * are needed, a random seed will do fine.
+ *
+ * Right now, only SipHash24 is supported. Other SipHash parameters can be
+ * easily added if required.
+ */
+_public_ void c_siphash_init(CSipHash *state, const uint8_t seed[16]) {
+        uint64_t k0, k1;
+
+        k0 = c_siphash_read_le64(seed);
+        k1 = c_siphash_read_le64(seed + 8);
+
+        *state = (CSipHash) {
+                /*
+                 * Default seed is taken from the reference implementation
+                 * of SipHash24 ("somepseudorandomlygeneratedbytes"). Callers
+                 * are still recommended to provide proper seeds themselves.
+                 */
+                .v0 = 0x736f6d6570736575ULL ^ k0,
+                .v1 = 0x646f72616e646f6dULL ^ k1,
+                .v2 = 0x6c7967656e657261ULL ^ k0,
+                .v3 = 0x7465646279746573ULL ^ k1,
+                .padding = 0,
+                .n_bytes = 0,
+        };
+}
+
+/**
+ * c_siphash_append() - hash stream of data
+ * @state:              context object
+ * @bytes:              array of input bytes
+ * @n_bytes:            number of input bytes
+ *
+ * This feeds an array of bytes into the SipHash state machine. This is a
+ * streaming-capable API. That is, the resulting hash is the same, regardless
+ * of the way you chunk the input.
+ * This function simply feeds the given bytes into the SipHash state machine.
+ * It does not produce a final hash. You can call this function many times to
+ * append more data. To retrieve the final hash, call c_siphash_finalize().
+ *
+ * Note that this implementation works best when used with chunk-sizes of
+ * multiples of 64bit (8-bytes). This is not a requirement, though.
+ */
+_public_ void c_siphash_append(CSipHash *state, const uint8_t *bytes, size_t n_bytes) {
+        const uint8_t *end = bytes + n_bytes;
+        size_t left = state->n_bytes & 7;
+        uint64_t m;
+
+        state->n_bytes += n_bytes;
+
+        /*
+         * SipHash operates on 64bit chunks. If the previous blob was not a
+         * multiple of 64bit in length, we must operate on single bytes.
+         */
+        if (left > 0) {
+                for ( ; bytes < end && left < 8; ++bytes, ++left)
+                        state->padding |= ((uint64_t) *bytes) << (left * 8);
+
+                if (bytes == end && left < 8)
+                        return;
+
+                state->v3 ^= state->padding;
+                c_siphash_sipround(state);
+                c_siphash_sipround(state);
+                state->v0 ^= state->padding;
+
+                state->padding = 0;
+        }
+
+        end -= (state->n_bytes % sizeof(uint64_t));
+
+        /*
+         * We are now guaranteed to be at a 64bit state boudary. Hence, we can
+         * operate in 64bit chunks on all input. This is much faster than the
+         * one-byte-at-a-time loop.
+         */
+        for ( ; bytes < end; bytes += 8) {
+                m = c_siphash_read_le64(bytes);
+
+                state->v3 ^= m;
+                c_siphash_sipround(state);
+                c_siphash_sipround(state);
+                state->v0 ^= m;
+        }
+
+        /*
+         * Now that we hashed as much 64bit chunks as possible, we need to
+         * remember the remaining trailing bytes. Keep them in @padding so the
+         * next round (or the finalizer) get access to them.
+         */
+        left = state->n_bytes & 7;
+        switch (left) {
+                case 7:
+                        state->padding |= ((uint64_t) bytes[6]) << 48;
+                        /* fallthrough */
+                case 6:
+                        state->padding |= ((uint64_t) bytes[5]) << 40;
+                        /* fallthrough */
+                case 5:
+                        state->padding |= ((uint64_t) bytes[4]) << 32;
+                        /* fallthrough */
+                case 4:
+                        state->padding |= ((uint64_t) bytes[3]) << 24;
+                        /* fallthrough */
+                case 3:
+                        state->padding |= ((uint64_t) bytes[2]) << 16;
+                        /* fallthrough */
+                case 2:
+                        state->padding |= ((uint64_t) bytes[1]) <<  8;
+                        /* fallthrough */
+                case 1:
+                        state->padding |= ((uint64_t) bytes[0]);
+                        /* fallthrough */
+                case 0:
+                        break;
+        }
+}
+
+/**
+ * c_siphash_finalize() - finalize hash
+ * @state:              context object
+ *
+ * This produces the final SipHash24 hash value for the given SipHash state.
+ * That is, it produces a hash value corresponding to the SipHash24 hash value
+ * of the concatenated byte-array passed into @state via c_siphash_append().
+ *
+ * Note that @state has an invalid state after this function returns. To reuse
+ * it for another hash, you must call c_siphash_init() again. If you don't need
+ * the object, anymore, you can release it any time. There is no need to
+ * destroy the object explicitly.
+ *
+ * Return: 64bit hash value
+ */
+_public_ uint64_t c_siphash_finalize(CSipHash *state) {
+        uint64_t b;
+
+        b = state->padding | (((uint64_t) state->n_bytes) << 56);
+
+        state->v3 ^= b;
+        c_siphash_sipround(state);
+        c_siphash_sipround(state);
+        state->v0 ^= b;
+
+        state->v2 ^= 0xff;
+
+        c_siphash_sipround(state);
+        c_siphash_sipround(state);
+        c_siphash_sipround(state);
+        c_siphash_sipround(state);
+
+        return state->v0 ^ state->v1 ^ state->v2  ^ state->v3;
+}
+
+/**
+ * c_siphash_hash() - hash data blob
+ * @seed:               128bit seed
+ * @bytes:              byte array to hash
+ * @n_bytes:            number of bytes to hash
+ *
+ * This produces the SipHash24 hash value for the input @bytes / @n_bytes,
+ * using the seed provided as @seed.
+ *
+ * This is functionally equivalent to:
+ *
+ *         CSipHash state;
+ *         c_siphash_init(&state, seed);
+ *         c_siphash_apend(&state, bytes, n_bytes);
+ *         return c_siphash_finalize(&state);
+ *
+ * Unlike the streaming API, this is a one-shot call suitable for any data that
+ * is available in-memory at the same time.
+ *
+ * Return: 64bit hash value
+ */
+_public_ uint64_t c_siphash_hash(const uint8_t seed[16], const uint8_t *bytes, size_t n_bytes) {
+        CSipHash state;
+
+        c_siphash_init(&state, seed);
+        c_siphash_append(&state, bytes, n_bytes);
+
+        return c_siphash_finalize(&state);
+}
diff --git a/shared/c-siphash/src/c-siphash.h b/shared/c-siphash/src/c-siphash.h
new file mode 100644
index 00000000..c0cfc1e0
--- /dev/null
+++ b/shared/c-siphash/src/c-siphash.h
@@ -0,0 +1,60 @@
+#pragma once
+
+/**
+ * Streaming-capable SipHash Implementation
+ *
+ * This library provides a SipHash API, that is fully implemented in ISO-C11
+ * and has no external dependencies. The library performs no memory allocation,
+ * and provides a streaming API where data to be hashed can be appended
+ * piecemeal.
+ *
+ * A streaming-capable hash state is represented by the "CSipHash" structure,
+ * which should be initialized with a unique seed before use. If streaming
+ * capabilities are not required, c_siphash_hash() provides a simple one-shot
+ * API.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef struct CSipHash CSipHash;
+
+/**
+ * struct CSipHash - SipHash state object
+ * @v0-@v3:             internal state
+ * @padding:            pending bytes that were not a multiple of 8
+ * @n_bytes:            number of hashed bytes
+ *
+ * The state of an inflight hash is represenetd by a CSipHash object. Before
+ * hashing, it must be initialized with c_siphash_init(), providing a unique
+ * random hash seed. Data is hashed by appending it to the state object, using
+ * c_siphash_append(). Finally, the hash is read out by calling
+ * c_siphash_finalize().
+ *
+ * This state object has no allocated resources. It is safe to release its
+ * backing memory without any further action.
+ */
+struct CSipHash {
+        uint64_t v0;
+        uint64_t v1;
+        uint64_t v2;
+        uint64_t v3;
+        uint64_t padding;
+        size_t n_bytes;
+};
+
+#define C_SIPHASH_NULL {}
+
+void c_siphash_init(CSipHash *state, const uint8_t seed[16]);
+void c_siphash_append(CSipHash *state, const uint8_t *bytes, size_t n_bytes);
+uint64_t c_siphash_finalize(CSipHash *state);
+
+uint64_t c_siphash_hash(const uint8_t seed[16], const uint8_t *bytes, size_t n_bytes);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/shared/meson.build b/shared/meson.build
new file mode 100644
index 00000000..db6b8a40
--- /dev/null
+++ b/shared/meson.build
@@ -0,0 +1,78 @@
+shared_inc = include_directories('.')
+
+shared_c_siphash = static_library(
+    'c-siphash',
+    sources: 'c-siphash/src/c-siphash.c',
+)
+
+shared_c_siphash_dep = declare_dependency(
+    include_directories: shared_inc,
+    link_with: shared_c_siphash,
+)
+
+shared_n_acd = static_library(
+    'n-acd',
+    sources: 'n-acd/src/n-acd.c',
+    include_directories: [
+        include_directories('c-siphash/src'),
+        include_directories('c-list/src'),
+    ],
+    dependencies: shared_c_siphash_dep,
+)
+
+shared_n_acd_dep = declare_dependency(
+    include_directories: shared_inc,
+    link_with: shared_n_acd,
+)
+
+version_conf = configuration_data()
+version_conf.set('NM_MAJOR_VERSION', nm_major_version)
+version_conf.set('NM_MINOR_VERSION', nm_minor_version)
+version_conf.set('NM_MICRO_VERSION', nm_micro_version)
+
+version_header = configure_file(
+    input: 'nm-version-macros.h.in',
+    output: 'nm-version-macros.h',
+    configuration: version_conf,
+)
+
+shared_nm_utils_nm_meta_setting_c = files('nm-meta-setting.c')
+
+shared_nm_test_utils_impl_c = files('nm-test-utils-impl.c')
+
+shared_nm_utils_nm_vpn_plugin_utils_c = files('nm-utils/nm-vpn-plugin-utils.c')
+
+shared_files_libnm_core = files('''
+    c-siphash/src/c-siphash.c
+    nm-utils/c-list-util.c
+    nm-utils/nm-dedup-multi.c
+    nm-utils/nm-enum-utils.c
+    nm-utils/nm-hash-utils.c
+    nm-utils/nm-random-utils.c
+    nm-utils/nm-shared-utils.c
+    nm-utils/nm-udev-utils.c
+'''.split())
+
+shared_files_clients_common = files('''
+    c-siphash/src/c-siphash.c
+    nm-utils/nm-enum-utils.c
+    nm-utils/nm-hash-utils.c
+    nm-utils/nm-random-utils.c
+    nm-utils/nm-shared-utils.c
+'''.split())
+
+shared_files_libnm_util = files('''
+    nm-utils/nm-shared-utils.c
+'''.split())
+
+shared_files_libnm_glib = files('''
+    nm-utils/nm-udev-utils.c
+'''.split())
+
+shared_dep = declare_dependency(
+    include_directories: [
+        top_inc,
+        shared_inc,
+    ],
+    dependencies: glib_dep,
+)
diff --git a/shared/n-acd/src/n-acd.c b/shared/n-acd/src/n-acd.c
new file mode 100644
index 00000000..9164f958
--- /dev/null
+++ b/shared/n-acd/src/n-acd.c
@@ -0,0 +1,1262 @@
+/*
+ * IPv4 Address Conflict Detection
+ *
+ * This implements the main n-acd API. It is built around an epoll-fd to
+ * encapsulate a timerfd+socket. The n-acd context has quite straightforward
+ * lifetime rules. The parameters must be set when the engine is started, and
+ * they can only be changed by stopping and restartding the engine. The engine
+ * is started on demand and stopped when no longer needed.
+ * During the entire lifetime the context can be dispatched. That is, the
+ * dispatcher does not have to be aware of the context state. After each call
+ * to dispatch(), the caller must pop all pending events until -EAGAIN is
+ * returned.
+ *
+ * If a conflict is detected, the ACD engine reports to the caller and stops
+ * the engine. The caller can now modify parameters and restart the engine, if
+ * required.
+ */
+
+#include <assert.h>
+#include <c-list.h>
+#include <c-siphash.h>
+#include <endian.h>
+#include <errno.h>
+#include <limits.h>
+#include <linux/filter.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
+#include <netinet/if_ether.h>
+#include <netinet/in.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/auxv.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/timerfd.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "n-acd.h"
+
+#define _public_ __attribute__((__visibility__("default")))
+
+/*
+ * These parameters and timing intervals specified in RFC-5227. The original
+ * values are:
+ *
+ *     PROBE_NUM                                3
+ *     PROBE_WAIT                               1s
+ *     PROBE_MIN                                1s
+ *     PROBE_MAX                                3s
+ *     ANNOUNCE_NUM                             3
+ *     ANNOUNCE_WAIT                            2s
+ *     ANNOUNCE_INTERVAL                        2s
+ *     MAX_CONFLICTS                            10
+ *     RATE_LIMIT_INTERVAL                      60s
+ *     DEFEND_INTERVAL                          10s
+ *
+ * If we assume a best-case and worst-case scenario for non-conflicted runs, we
+ * end up with a runtime between 4s and 9s to finish the probe. Then it still
+ * takes a fixed 4s to finish the announcements.
+ *
+ * RFC 5227 section 1.1:
+ *     [...] (Note that the values listed here are fixed constants; they are
+ *     not intended to be modifiable by implementers, operators, or end users.
+ *     These constants are given symbolic names here to facilitate the writing
+ *     of future standards that may want to reference this document with
+ *     different values for these named constants; however, at the present time
+ *     no such future standards exist.) [...]
+ *
+ * Unfortunately, no-one ever stepped up to write a "future standard" to revise
+ * the timings. A 9s timeout for successful link setups is not acceptable today.
+ * Hence, we will just go forward and ignore the proposed values. On both
+ * wired and wireless local links round-trip latencies of below 3ms are common,
+ * while latencies above 10ms are rarely seen. We require the caller to set a
+ * timeout multiplier, where 1 corresponds to a total probe time of 0.5 ms and
+ * 1.0 ms. On modern networks a multiplier of about 100 should be a reasonable
+ * default. To comply with the RFC select a multiplier of 9000.
+ */
+#define N_ACD_RFC_PROBE_NUM                     (3)
+#define N_ACD_RFC_PROBE_WAIT_USEC               (UINT64_C(111)) /* 111us */
+#define N_ACD_RFC_PROBE_MIN_USEC                (UINT64_C(111)) /* 111us */
+#define N_ACD_RFC_PROBE_MAX_USEC                (UINT64_C(333)) /* 333us */
+#define N_ACD_RFC_ANNOUNCE_NUM                  (3)
+#define N_ACD_RFC_ANNOUNCE_WAIT_USEC            (UINT64_C(222)) /* 222us */
+#define N_ACD_RFC_ANNOUNCE_INTERVAL_USEC        (UINT64_C(222)) /* 222us */
+#define N_ACD_RFC_MAX_CONFLICTS                 (10)
+#define N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC      (UINT64_C(60000000)) /* 60s */
+#define N_ACD_RFC_DEFEND_INTERVAL_USEC          (UINT64_C(10000000)) /* 10s */
+
+/*
+ * Fake ENETDOWN error-code. We use this as replacement for known EFOOBAR error
+ * codes. It is explicitly chosen to be outside the known error-code range.
+ * Whenever we are deep down in a call-stack and notice a ENETDOWN error, we
+ * return this instead. It is caught by the top-level dispatcher and then
+ * properly handled.
+ * This avoids gracefully handling ENETDOWN in call-stacks, but then continuing
+ * with some work in the callers without noticing the soft failure.
+ */
+#define N_ACD_E_DOWN (INT_MAX)
+
+#define TIME_INFINITY ((uint64_t) -1)
+
+enum {
+        N_ACD_EPOLL_TIMER,
+        N_ACD_EPOLL_SOCKET,
+};
+
+enum {
+        N_ACD_STATE_INIT,
+        N_ACD_STATE_PROBING,
+        N_ACD_STATE_CONFIGURING,
+        N_ACD_STATE_ANNOUNCING,
+};
+
+typedef struct NAcdEventNode {
+        NAcdEvent event;
+        uint8_t sender[ETH_ALEN];
+        CList link;
+} NAcdEventNode;
+
+struct NAcd {
+        /* context */
+        unsigned int seed;
+        int fd_epoll;
+        int fd_timer;
+
+        /* configuration */
+        NAcdConfig config;
+        uint8_t mac[ETH_ALEN];
+        uint64_t timeout_multiplier;
+
+        /* runtime */
+        int fd_socket;
+        unsigned int state;
+        unsigned int n_iteration;
+        unsigned int n_conflicts;
+        unsigned int defend;
+        uint64_t last_defend;
+        uint64_t last_conflict;
+
+        /* pending events */
+        CList events;
+        NAcdEventNode *current;
+};
+
+static int n_acd_errno(void) {
+        /*
+         * Compilers continuously warn about uninitialized variables since they
+         * cannot deduce that `return -errno;` will always be negative. This
+         * small wrapper makes sure compilers figure that out. Use it as
+         * replacement for `errno` read access. Yes, it generates worse code,
+         * but only marginally and only affects slow-paths.
+         */
+        return abs(errno) ? : EIO;
+}
+
+static int n_acd_event_node_new(NAcdEventNode **nodep, unsigned int event) {
+        NAcdEventNode *node;
+
+        node = calloc(1, sizeof(*node));
+        if (!node)
+                return -ENOMEM;
+
+        node->event.event = event;
+        node->link = (CList)C_LIST_INIT(node->link);
+
+        *nodep = node;
+
+        return 0;
+}
+
+static NAcdEventNode *n_acd_event_node_free(NAcdEventNode *node) {
+        if (!node)
+                return NULL;
+
+        c_list_unlink(&node->link);
+        free(node);
+
+        return NULL;
+}
+
+static int n_acd_get_random(unsigned int *random) {
+        uint8_t hash_seed[] = { 0x3a, 0x0c, 0xa6, 0xdd, 0x44, 0xef, 0x5f, 0x7a, 0x5e, 0xd7, 0x25, 0x37, 0xbf, 0x4e, 0x80, 0xa1 };
+        CSipHash hash = C_SIPHASH_NULL;
+        struct timespec ts;
+        const uint8_t *p;
+        int r;
+
+        /*
+         * We need random jitter for all timeouts when handling ARP probes. Use
+         * AT_RANDOM to get a seed for rand_r(3p), if available (should always
+         * be available on linux). See the time-out scheduler for details.
+         * Additionally, we include the current time in the seed. This avoids
+         * using the same jitter in case you run multiple ACD engines in the
+         * same process. Lastly, the seed is hashed with SipHash24 to avoid
+         * exposing the value of AT_RANDOM on the network.
+         */
+        c_siphash_init(&hash, hash_seed);
+
+        p = (const uint8_t *)getauxval(AT_RANDOM);
+        if (p)
+                c_siphash_append(&hash, p, 16);
+
+        r = clock_gettime(CLOCK_BOOTTIME, &ts);
+        if (r < 0)
+                return -n_acd_errno();
+
+        c_siphash_append(&hash, (const uint8_t *)&ts.tv_sec, sizeof(ts.tv_sec));
+        c_siphash_append(&hash, (const uint8_t *)&ts.tv_nsec, sizeof(ts.tv_nsec));
+
+        *random = c_siphash_finalize(&hash);
+        return 0;
+}
+
+static void n_acd_reset(NAcd *acd) {
+        acd->state = N_ACD_STATE_INIT;
+        acd->defend = N_ACD_DEFEND_NEVER;
+        acd->n_iteration = 0;
+        acd->last_defend = 0;
+        timerfd_settime(acd->fd_timer, 0, &(struct itimerspec){}, NULL);
+
+        if (acd->fd_socket >= 0) {
+                assert(acd->fd_epoll >= 0);
+                epoll_ctl(acd->fd_epoll, EPOLL_CTL_DEL, acd->fd_socket, NULL);
+                close(acd->fd_socket);
+                acd->fd_socket = -1;
+        }
+}
+
+/**
+ * n_acd_new() - create a new ACD context
+ * @acdp:       output argument for context
+ *
+ * Create a new ACD context and return it in @acdp.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+_public_ int n_acd_new(NAcd **acdp) {
+        NAcd *acd;
+        int r;
+
+        acd = calloc(1, sizeof(*acd));
+        if (!acd)
+                return -ENOMEM;
+
+        acd->fd_epoll = -1;
+        acd->fd_timer = -1;
+        acd->fd_socket = -1;
+        acd->state = N_ACD_STATE_INIT;
+        acd->defend = N_ACD_DEFEND_NEVER;
+        acd->events = (CList)C_LIST_INIT(acd->events);
+        acd->last_conflict = TIME_INFINITY;
+
+        r = n_acd_get_random(&acd->seed);
+        if (r < 0)
+                return r;
+
+        acd->fd_epoll = epoll_create1(EPOLL_CLOEXEC);
+        if (acd->fd_epoll < 0) {
+                r = -n_acd_errno();
+                goto error;
+        }
+
+        acd->fd_timer = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK);
+        if (acd->fd_timer < 0 && errno == EINVAL) {
+                /*
+		 * Fall back to CLOCK_MONOTONIC when CLOCK_BOOTTIME is
+		 * not available (kernel < 3.15).
+                 */
+                acd->fd_timer = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
+        }
+        if (acd->fd_timer < 0) {
+                r = -n_acd_errno();
+                goto error;
+        }
+
+        r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->fd_timer,
+                      &(struct epoll_event){
+                              .events = EPOLLIN,
+                              .data.u32 = N_ACD_EPOLL_TIMER,
+                      });
+        if (r < 0) {
+                r = -n_acd_errno();
+                goto error;
+        }
+
+        *acdp = acd;
+        return 0;
+
+error:
+        n_acd_free(acd);
+        return r;
+}
+
+/**
+ * n_acd_free() - free an ACD context
+ *
+ * Frees all resources held by the context. This may be called at any time,
+ * but doing so invalidates all data owned by the context.
+ *
+ * Return: NULL.
+ */
+_public_ void n_acd_free(NAcd *acd) {
+        NAcdEventNode *node;
+
+        if (!acd)
+                return;
+
+        n_acd_reset(acd);
+
+        acd->current = n_acd_event_node_free(acd->current);
+
+        while ((node = c_list_first_entry(&acd->events, NAcdEventNode, link)))
+                n_acd_event_node_free(node);
+
+        assert(acd->fd_socket < 0);
+
+        if (acd->fd_timer >= 0) {
+                assert(acd->fd_epoll >= 0);
+                epoll_ctl(acd->fd_epoll, EPOLL_CTL_DEL, acd->fd_timer, NULL);
+                close(acd->fd_timer);
+                acd->fd_timer = -1;
+        }
+
+        if (acd->fd_epoll >= 0) {
+                close(acd->fd_epoll);
+                acd->fd_epoll = -1;
+        }
+
+        free(acd);
+}
+
+/**
+ * n_acd_get_fd() - get pollable file descriptor
+ * @acd:        ACD context
+ * @fdp:        output argument for file descriptor
+ *
+ * Returns a file descriptor in @fdp. This filedescriptor can be polled by
+ * the caller to indicate when the ACD context can be dispatched.
+ */
+_public_ void n_acd_get_fd(NAcd *acd, int *fdp) {
+        *fdp = acd->fd_epoll;
+}
+
+static int n_acd_push_event(NAcd *acd, unsigned int event, uint16_t *operation, uint8_t (*sender)[6], uint8_t (*target)[4]) {
+        NAcdEventNode *node;
+        int r;
+
+        r = n_acd_event_node_new(&node, event);
+        if (r < 0)
+                return r;
+
+        switch (event) {
+        case N_ACD_EVENT_USED:
+                node->event.used.operation = be16toh(*operation);
+                memcpy(node->sender, sender, sizeof(node->sender));
+                node->event.used.sender = node->sender;
+                node->event.used.n_sender = sizeof(node->sender);
+                memcpy(&node->event.used.target, target, sizeof(node->event.used.target));
+                break;
+        case N_ACD_EVENT_CONFLICT:
+                node->event.conflict.operation = be16toh(*operation);
+                memcpy(node->sender, sender, sizeof(node->sender));
+                node->event.used.sender = node->sender;
+                node->event.used.n_sender = sizeof(node->sender);
+                memcpy(&node->event.conflict.target, target, sizeof(node->event.conflict.target));
+                break;
+        case N_ACD_EVENT_DEFENDED:
+                node->event.defended.operation = be16toh(*operation);
+                memcpy(node->sender, sender, sizeof(node->sender));
+                node->event.used.sender = node->sender;
+                node->event.used.n_sender = sizeof(node->sender);
+                memcpy(&node->event.defended.target, target, sizeof(node->event.defended.target));
+                break;
+        case N_ACD_EVENT_READY:
+        case N_ACD_EVENT_DOWN:
+                break;
+        default:
+                assert(0);
+        }
+
+        c_list_link_tail(&acd->events, &node->link);
+
+        return 0;
+}
+
+static int n_acd_now(uint64_t *nowp) {
+        struct timespec ts;
+        int r;
+
+        r = clock_gettime(CLOCK_BOOTTIME, &ts);
+        if (r < 0)
+                return -n_acd_errno();
+
+        *nowp = ts.tv_sec * UINT64_C(1000000) + ts.tv_nsec / UINT64_C(1000);
+        return 0;
+}
+
+static int n_acd_schedule(NAcd *acd, uint64_t u_timeout, unsigned int u_jitter) {
+        uint64_t u_next = u_timeout;
+        int r;
+
+        /*
+         * ACD specifies jitter values to reduce packet storms on the local
+         * link. This call accepts the maximum relative jitter value in
+         * microseconds as @u_jitter. We then use rand_r(3p) to get a
+         * pseudo-random jitter on top of the real timeout given as @u_timeout.
+         * Note that rand_r() is fine for this. Before you try to improve the
+         * RNG, you better spend some time securing ARP.
+         */
+        if (u_jitter)
+                u_next += rand_r(&acd->seed) % u_jitter;
+
+        /*
+         * Setting .it_value to 0 in timerfd_settime() disarms the timer. Avoid
+         * this and always schedule at least 1us. Otherwise, we'd have to
+         * recursively call into the time-out handler, which we really want to
+         * avoid. No reason to optimize performance here.
+         */
+        if (!u_next)
+                u_next = 1;
+
+        r = timerfd_settime(acd->fd_timer, 0,
+                            &(struct itimerspec){ .it_value = {
+                                    .tv_sec = u_next / UINT64_C(1000000),
+                                    .tv_nsec = u_next % UINT64_C(1000000) * UINT64_C(1000),
+                            } }, NULL);
+        if (r < 0)
+                return -n_acd_errno();
+
+        return 0;
+}
+
+static int n_acd_send(NAcd *acd, const struct in_addr *spa) {
+        struct sockaddr_ll address = {
+                .sll_family = AF_PACKET,
+                .sll_protocol = htobe16(ETH_P_ARP),
+                .sll_ifindex = acd->config.ifindex,
+                .sll_halen = ETH_ALEN,
+                .sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+        };
+        struct ether_arp arp = {
+                .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER),
+                .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP),
+                .ea_hdr.ar_hln = sizeof(acd->mac),
+                .ea_hdr.ar_pln = sizeof(uint32_t),
+                .ea_hdr.ar_op = htobe16(ARPOP_REQUEST),
+        };
+        ssize_t l;
+
+        memcpy(arp.arp_sha, acd->mac, sizeof(acd->mac));
+        memcpy(arp.arp_tpa, &acd->config.ip.s_addr, sizeof(uint32_t));
+
+        if (spa)
+                memcpy(arp.arp_spa, &spa->s_addr, sizeof(spa->s_addr));
+
+        l = sendto(acd->fd_socket, &arp, sizeof(arp), MSG_NOSIGNAL, (struct sockaddr *)&address, sizeof(address));
+        if (l == (ssize_t)sizeof(arp)) {
+                /* Packet was properly sent. */
+                return 0;
+        } else if (l >= 0) {
+                /*
+                 * Ugh. The packet was truncated. This should not happen, but
+                 * lets just pretend the packet was dropped.
+                 */
+                return 0;
+        } else if (errno == EAGAIN || errno == ENOBUFS) {
+                /*
+                 * In case the output buffer is full, the packet is silently
+                 * dropped. This is just as if the physical layer happened to
+                 * drop the packet. We are not on a reliable medium, so no
+                 * reason to pretend we are.
+                 */
+                return 0;
+        } else if (errno == ENETDOWN || errno == ENXIO) {
+                /*
+                 * We get ENETDOWN if the network-device goes down or is
+                 * removed. ENXIO might happen on async send-operations if the
+                 * network-device was unplugged and thus the kernel is no
+                 * longer aware of it.
+                 * In any case, we do not allow proceeding with this socket. We
+                 * stop the engine and notify the user gracefully.
+                 */
+                return -N_ACD_E_DOWN;
+        }
+
+        return -n_acd_errno();
+}
+
+static void n_acd_remember_conflict(NAcd *acd, uint64_t now) {
+        if (++acd->n_conflicts >= N_ACD_RFC_MAX_CONFLICTS) {
+                acd->n_conflicts = N_ACD_RFC_MAX_CONFLICTS;
+                acd->last_conflict = now;
+        }
+}
+
+static int n_acd_handle_timeout(NAcd *acd) {
+        int r;
+
+        switch (acd->state) {
+        case N_ACD_STATE_PROBING:
+                /*
+                 * We are still PROBING. We send 3 probes with a random timeout
+                 * scheduled between each. If, after a fixed timeout, we did
+                 * not receive any conflict we consider the probing successful.
+                 */
+                if (acd->n_iteration >= N_ACD_RFC_PROBE_NUM) {
+                        /*
+                         * All 3 probes succeeded and we waited enough to
+                         * consider this address usable by now. Do not announce
+                         * the address, yet. We must first give the caller a
+                         * chance to configure the address (so they can answer
+                         * ARP requests), before announcing it. But our
+                         * callbacks are not necessarily synchronous (we want
+                         * to allow IPC there), so just notify the caller and
+                         * wait for further instructions, thus effectively
+                         * increasing the probe-wait.
+                         */
+                        r = n_acd_push_event(acd, N_ACD_EVENT_READY, NULL, NULL, NULL);
+                        if (r)
+                                return r;
+
+                        acd->state = N_ACD_STATE_CONFIGURING;
+                } else {
+                        /*
+                         * We have not sent all 3 probes, yet. A timer fired,
+                         * so we are ready to send the next probe. If this is
+                         * the third probe, schedule a timer for ANNOUNCE_WAIT
+                         * to give other peers a chance to answer. If this is
+                         * not the third probe, wait between PROBE_MIN and
+                         * PROBE_MAX for the next probe.
+                         */
+
+                        r = n_acd_send(acd, NULL);
+                        /*
+                         * During probe we must respect the total timeout and so
+                         * we ignore errors caused by a down interface.
+                         */
+                        if (r < 0 && r != -N_ACD_E_DOWN)
+                                return r;
+
+                        if (++acd->n_iteration >= N_ACD_RFC_PROBE_NUM)
+                                r = n_acd_schedule(acd, acd->timeout_multiplier * N_ACD_RFC_ANNOUNCE_WAIT_USEC, 0);
+                        else
+                                r = n_acd_schedule(acd, acd->timeout_multiplier * N_ACD_RFC_PROBE_MIN_USEC,
+                                                   acd->timeout_multiplier * (N_ACD_RFC_PROBE_MAX_USEC - N_ACD_RFC_PROBE_MIN_USEC));
+                        if (r < 0)
+                                return r;
+                }
+
+                break;
+
+        case N_ACD_STATE_ANNOUNCING:
+                /*
+                 * We are ANNOUNCING, meaning the caller configured the address
+                 * on the interface and is actively using it. We send 3
+                 * announcements out, in a short interval, and then just
+                 * perform passive conflict detection.
+                 * Note that once all 3 announcements are sent, we no longer
+                 * schedule a timer, so this part should not trigger, anymore.
+                 */
+
+                r = n_acd_send(acd, &acd->config.ip);
+                if (r < 0) {
+                        if (r != -N_ACD_E_DOWN)
+                                return r;
+                        /*
+                         * We want to send all the 3 announcements even if the
+                         * interface goes temporarily down. Therefore, if send()
+                         * fails, don't increment the iteration and try again.
+                         */
+                } else
+                        acd->n_iteration++;
+
+                if (acd->n_iteration < N_ACD_RFC_ANNOUNCE_NUM) {
+                        /*
+                         * Announcements are always scheduled according to the
+                         * time-intervals specified in the spec. We always use
+                         * the RFC5227-mandated multiplier.
+                         * If you reconsider this, note that timeout_multiplier
+                         * might be 0 here.
+                         */
+                        r = n_acd_schedule(acd, N_ACD_TIMEOUT_RFC5227 * N_ACD_RFC_ANNOUNCE_INTERVAL_USEC, 0);
+                        if (r < 0)
+                                return r;
+                }
+
+                break;
+
+        case N_ACD_STATE_INIT:
+        case N_ACD_STATE_CONFIGURING:
+        default:
+                /*
+                 * There are no timeouts in these states. If we trigger one,
+                 * something is fishy. Let the caller deal with this.
+                 */
+                return -EIO;
+        }
+
+        return 0;
+}
+
+static int n_acd_handle_packet(NAcd *acd, struct ether_arp *packet) {
+        bool hard_conflict;
+        uint64_t now;
+        int r;
+
+        /*
+         * Via BPF we discard any non-conflict packets. There are only 2 types
+         * that can pass: A conflict on the Sender Protocol Address, or a
+         * conflict on the Target Protocol Address.
+         *
+         * The former we call a hard-conflict. It implies that the sender uses
+         * the address already. We must always catch this and in some way react
+         * to it. Any kind, REQUEST or REPLY must be caught (though it is
+         * unlikely that we ever catch REPLIES since they tend to be unicasts).
+         *
+         * However, in case the Target Protocol Address matches, we just know
+         * that somebody is looking for the address. Hence, we must also check
+         * that the packet is an ARP-Probe (Sender Protocol Address is 0). If
+         * it is, it means someone else does ACD on our address. We call this a
+         * soft conflict.
+         */
+        if (!memcmp(packet->arp_spa, (uint8_t[4]){ }, sizeof(packet->arp_spa)) &&
+            !memcmp(packet->arp_tpa, &acd->config.ip.s_addr, sizeof(packet->arp_tpa)) &&
+            packet->ea_hdr.ar_op == htobe16(ARPOP_REQUEST)) {
+                hard_conflict = false;
+        } else if (!memcmp(packet->arp_spa, &acd->config.ip.s_addr, sizeof(packet->arp_spa))) {
+                hard_conflict = true;
+        } else {
+                /*
+                 * Ignore anything that is specific enough to match the BPF
+                 * filter, but is none of the conflicts described above.
+                 */
+                return 0;
+        }
+
+        r = n_acd_now(&now);
+        if (r < 0)
+                return r;
+
+        switch (acd->state) {
+        case N_ACD_STATE_PROBING:
+                /*
+                 * Regardless whether this is a hard or soft conflict, we must
+                 * treat this as a probe failure. That is, notify the caller of
+                 * the conflict and wait for further instructions. We do not
+                 * react to this, until the caller tells us what to do. But we
+                 * immediately stop the engine, since there is no point in
+                 * continuing the probing.
+                 */
+                n_acd_remember_conflict(acd, now);
+                n_acd_reset(acd);
+                r = n_acd_push_event(acd, N_ACD_EVENT_USED, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa);
+                if (r)
+                        return r;
+
+                break;
+
+        case N_ACD_STATE_CONFIGURING:
+                /*
+                 * We are waiting for the caller to configure the interface and
+                 * start ANNOUNCING. In this state, we cannot defend the address
+                 * as that would indicate that it is ready to be used, and we
+                 * cannot signal CONFLICT or USED as the caller may already have
+                 * started to use the address (and may have configured the engine
+                 * to always defend it, which means they should be able to rely on
+                 * never losing it after READY). Simply drop the event, and rely
+                 * on the anticipated ANNOUNCE to trigger it again.
+                 */
+
+                break;
+
+        case N_ACD_STATE_ANNOUNCING:
+                /*
+                 * We were already instructed to announce the address, which
+                 * means the address is configured and in use. Hence, the
+                 * caller is responsible to serve regular ARP queries. Meaning,
+                 * we can ignore any soft conflicts (other peers doing ACD).
+                 *
+                 * But if we see a hard-conflict, we either defend the address
+                 * according to the caller's instructions, or we report the
+                 * conflict and bail out.
+                 */
+
+                if (!hard_conflict)
+                        break;
+
+                if (acd->defend == N_ACD_DEFEND_NEVER) {
+                        n_acd_remember_conflict(acd, now);
+                        n_acd_reset(acd);
+                        r = n_acd_push_event(acd, N_ACD_EVENT_CONFLICT, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa);
+                        if (r)
+                                return r;
+                } else {
+                        if (now > acd->last_defend + N_ACD_RFC_DEFEND_INTERVAL_USEC) {
+                                r = n_acd_send(acd, &acd->config.ip);
+                                if (r < 0)
+                                        return r;
+
+                                acd->last_defend = now;
+                                r = n_acd_push_event(acd, N_ACD_EVENT_DEFENDED, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa);
+                                if (r)
+                                        return r;
+                        } else if (acd->defend == N_ACD_DEFEND_ONCE) {
+                                n_acd_remember_conflict(acd, now);
+                                n_acd_reset(acd);
+                                r = n_acd_push_event(acd, N_ACD_EVENT_CONFLICT, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa);
+                                if (r)
+                                        return r;
+                        } else {
+                                r = n_acd_push_event(acd, N_ACD_EVENT_DEFENDED, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa);
+                                if (r)
+                                        return r;
+                        }
+                }
+
+                break;
+
+        case N_ACD_STATE_INIT:
+        default:
+                /*
+                 * The socket should not be dispatched in those states, since
+                 * it is neither allocated nor added to epoll. Fail hard if we
+                 * trigger this somehow.
+                 */
+                return -EIO;
+        }
+
+        return 0;
+}
+
+static int n_acd_dispatch_timer(NAcd *acd, struct epoll_event *event) {
+        uint64_t v;
+        int r;
+
+        if (event->events & (EPOLLHUP | EPOLLERR)) {
+                /*
+                 * There is no way to handle either gracefully. If we ignored
+                 * them, we would busy-loop, so lets rather forward the error
+                 * to the caller.
+                 */
+                return -EIO;
+        }
+
+        if (event->events & EPOLLIN) {
+                for (unsigned int i = 0; i < 128; ++i) {
+                        r = read(acd->fd_timer, &v, sizeof(v));
+                        if (r == sizeof(v)) {
+                                /*
+                                 * We successfully read a timer-value. Handle it and
+                                 * return. We do NOT fall-through to EPOLLHUP handling,
+                                 * as we always must drain buffers first.
+                                 */
+                                return n_acd_handle_timeout(acd);
+                        } else if (r >= 0) {
+                                /*
+                                 * Kernel guarantees 8-byte reads; fail hard if it
+                                 * suddenly starts doing weird shit. No clue what to do
+                                 * with those values, anyway.
+                                 */
+                                return -EIO;
+                        } else if (errno == EAGAIN) {
+                                /*
+                                 * No more pending events.
+                                 */
+                                return 0;
+                        } else {
+                                /*
+                                 * Something failed. We use CLOCK_BOOTTIME, so
+                                 * ECANCELED cannot happen. Hence, there is no error
+                                 * that we could gracefully handle. Fail hard and let
+                                 * the caller deal with it.
+                                 */
+                                return -n_acd_errno();
+                        }
+                }
+
+                return N_ACD_E_PREEMPTED;
+        }
+
+        return 0;
+}
+
+static int n_acd_dispatch_socket(NAcd *acd, struct epoll_event *event) {
+        struct ether_arp packet;
+        ssize_t l;
+
+        for (unsigned int i = 0; i < 128; ++i) {
+                /*
+                 * Regardless whether EPOLLIN is set in @event->events, we always
+                 * invoke recv(2). This is a safety-net for sockets, which always fetch
+                 * queued errors on all syscalls. That means, if anything failed on the
+                 * socket, we will be notified via recv(2). This simplifies the code
+                 * and avoid magic EPOLLIN/ERR/HUP juggling.
+                 *
+                 * Note that we must use recv(2) over read(2), since the latter cannot
+                 * deal with empty packets properly.
+                 *
+                 * We explicitly skip passing MSG_TRUNC here. We *WANT*
+                 * overlong packets to be retrieved and truncated. Ethernet
+                 * frames might not have byte-granular lengths. Real hardware
+                 * does add trailing padding/garbage, so we must discard this
+                 * here.
+                 */
+                l = recv(acd->fd_socket, &packet, sizeof(packet), 0);
+                if (l == (ssize_t)sizeof(packet)) {
+                        /*
+                         * We read a full ARP packet. We never fall-through to EPOLLHUP
+                         * handling, as we always must drain buffers first.
+                         */
+                        return n_acd_handle_packet(acd, &packet);
+                } else if (l >= 0) {
+                        /*
+                         * The BPF filter discards short packets, so error out
+                         * if something slips through for any reason. Don't silently
+                         * ignore it, since we explicitly want to know if something
+                         * went fishy.
+                         */
+                        return -EIO;
+                } else if (errno == ENETDOWN || errno == ENXIO) {
+                        /*
+                         * The network device went down or was removed. Ignore
+                         * such errors and let the pending probe time out.
+                         * Subsequent reads will simply return EAGAIN until the
+                         * device is up again and has data queued.
+                         */
+                        return 0;
+                } else if (errno == EAGAIN) {
+                        /*
+                         * We cannot read data from the socket (we got EAGAIN). As a safety net
+                         * check for EPOLLHUP/ERR. Those cannot be disabled with epoll, so we
+                         * must make sure to not busy-loop by ignoring them. Note that we know
+                         * recv(2) on sockets to return an error if either of these epoll-flags
+                         * is set. Hence, if we did not handle it above, we have no other way
+                         * but treating those flags as fatal errors and returning them to the
+                         * caller.
+                         */
+                        if (event->events & (EPOLLHUP | EPOLLERR))
+                                return -EIO;
+
+                        return 0;
+                } else {
+                        /*
+                         * Cannot dispatch the packet. This might be due to OOM, HUP,
+                         * or something else. We cannot handle it gracefully so forward
+                         * to the caller.
+                         */
+                        return -n_acd_errno();
+                }
+        }
+
+        return N_ACD_E_PREEMPTED;
+}
+
+/**
+ * n_acd_dispatch() - dispatch ACD context
+ * @acd:        ACD context
+ *
+ * Return: 0 on successful dispatch of all pending events, N_ACD_E_PREEMPT in
+ *         case there are more still more events to be dispatched, or a
+ *         negative error code on failure.
+ */
+_public_ int n_acd_dispatch(NAcd *acd) {
+        struct epoll_event events[2];
+        int n, i, r = 0;
+        bool preempted = false;
+
+        n = epoll_wait(acd->fd_epoll, events, sizeof(events) / sizeof(*events), 0);
+        if (n < 0) {
+                return -n_acd_errno();
+        }
+
+        for (i = 0; i < n; ++i) {
+                switch (events[i].data.u32) {
+                case N_ACD_EPOLL_TIMER:
+                        r = n_acd_dispatch_timer(acd, events + i);
+                        break;
+                case N_ACD_EPOLL_SOCKET:
+                        r = n_acd_dispatch_socket(acd, events + i);
+                        break;
+                default:
+                        r = 0;
+                        break;
+                }
+
+                if (r == N_ACD_E_PREEMPTED)
+                        preempted = true;
+                else if (r != 0)
+                        break;
+        }
+
+        if (r == -N_ACD_E_DOWN) {
+                /*
+                 * N_ACD_E_DOWN is synthesized whenever we notice
+                 * ENETDOWN-related errors on the network interface. This
+                 * allows bailing out of deep call-paths and then handling the
+                 * error gracefully here.
+                 */
+                n_acd_reset(acd);
+                r = n_acd_push_event(acd, N_ACD_EVENT_DOWN, NULL, NULL, NULL);
+                if (r)
+                        return r;
+
+                return 0;
+        }
+
+        if (preempted)
+                return N_ACD_E_PREEMPTED;
+        else
+                return r;
+}
+
+/**
+ * n_acd_pop_event() - get the next pending event
+ * @acd:        ACD context
+ * @eventp:     output argument for the event
+ *
+ * Returns a pointer to the next pending event. The event is still owend by
+ * the context, and is only valid until the next call to n_acd_pop_event()
+ * or until the context is freed.
+ *
+ * The possible events are:
+ *  * N_ACD_EVENT_READY:    The configured IP address was probed successfully
+ *                          and is ready to be used. Once configured on the
+ *                          interface, the caller must call n_acd_announce()
+ *                          to announce and start defending the address.
+ *                          No further events may be received before
+ *                          n_acd_announce() has been called.
+ *  * N_ACD_EVENT_USED:     Someone is already using the IP address being
+ *                          probed. The engine was stopped, and the caller
+ *                          may restart it to try again.
+ *  * N_ACD_EVENT_DEFENDED: A conflict was detected for the announced IP
+ *                          address, and the engine attempted to defend it.
+ *                          This is purely informational, and no action is
+ *                          required by the caller.
+ *  * N_ACD_EVENT_CONFLICT: A conflict was detected for the announced IP
+ *                          address, and the engine was not able to defend
+ *                          it (according to the configured policy). The
+ *                          engine has stoppde, the caller must stop using
+ *                          the address immediately, and may restart the
+ *                          engine to retry.
+ *  * N_ACD_EVENT_DOWN:     A network error was detected. The engine was
+ *                          stopped and it is the responsibility of the
+ *                          caller to restart it once the network may be
+ *                          functional again.
+ *
+ * Returns: 0 on success, N_ACD_E_STOPPED if there are no more events and
+ *          the engine has been stopped, N_ACD_E_DONE if there are no more
+ *          events, but the engine is still running, or a negative error
+ *          code on failure.
+ */
+_public_ int n_acd_pop_event(NAcd *acd, NAcdEvent **eventp) {
+        acd->current = n_acd_event_node_free(acd->current);
+
+        if (c_list_is_empty(&acd->events)) {
+                if (acd->state == N_ACD_STATE_INIT)
+                        return N_ACD_E_STOPPED;
+                else
+                        return N_ACD_E_DONE;
+        }
+
+        acd->current = c_list_first_entry(&acd->events, NAcdEventNode, link);
+        c_list_unlink(&acd->current->link);
+
+        if (eventp)
+                *eventp = &acd->current->event;
+
+        return 0;
+}
+
+static int n_acd_bind_socket(NAcd *acd, int s) {
+        /*
+         * Due to strict aliasing, we cannot get uint32_t/uint16_t pointers to
+         * acd->config.mac, so provide a union accessor.
+         */
+        const union {
+                uint8_t u8[6];
+                uint16_t u16[3];
+                uint32_t u32[1];
+        } mac = {
+                .u8 = {
+                        acd->mac[0],
+                        acd->mac[1],
+                        acd->mac[2],
+                        acd->mac[3],
+                        acd->mac[4],
+                        acd->mac[5],
+                },
+        };
+        struct sock_filter filter[] = {
+                /*
+                 * Basic ARP header validation. Make sure the packet-length,
+                 * wire type, protocol type, and address lengths are correct.
+                 */
+                BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0),                                                          /* A <- packet length */
+                BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0),                            /* #packet >= #arp-packet ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)),                  /* A <- header */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0),                                        /* header == ethernet ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)),                  /* A <- protocol */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0),                                        /* protocol == IP ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
+                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)),                  /* A <- hardware address length */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0),                           /* length == sizeof(ether_addr)? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
+                BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)),                  /* A <- protocol address length */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0),                              /* length == sizeof(in_addr) ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)),                   /* A <- operation */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0),                                       /* protocol == request ? */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0),                                         /* protocol == reply ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
+
+                /*
+                 * Sender hardware address must be different from ours. Note
+                 * that BPF runs in big-endian mode, but assumes immediates are
+                 * given in native-endian. This might look weird on 6-byte mac
+                 * addresses, but is needed to revert the BPF magic.
+                 */
+                BPF_STMT(BPF_LD + BPF_IMM, be32toh(mac.u32[0])),                                                /* A <- 4 bytes of client's MAC */
+                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                                /* X <- A */
+                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)),                        /* A <- 4 bytes of SHA */
+                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                         /* A xor X */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6),                                                   /* A == 0 ? */
+                BPF_STMT(BPF_LD + BPF_IMM, be16toh(mac.u16[2])),                                                /* A <- remainder of client's MAC */
+                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                                /* X <- A */
+                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4),                    /* A <- remainder of SHA */
+                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                         /* A xor X */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),                                                   /* A == 0 ? */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
+
+                /*
+                 * Sender protocol address or target protocol address must be
+                 * equal to the one we care about. Again, immediates must be
+                 * given in native-endian.
+                 */
+                BPF_STMT(BPF_LD + BPF_IMM, be32toh(acd->config.ip.s_addr)),                                     /* A <- clients IP */
+                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                                /* X <- A */
+                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)),                        /* A <- SPA */
+                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                         /* X xor A */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),                                                   /* A == 0 ? */
+                BPF_STMT(BPF_RET + BPF_K, 65535),                                                               /* return all */
+                BPF_STMT(BPF_LD + BPF_IMM, be32toh(acd->config.ip.s_addr)),                                     /* A <- clients IP */
+                BPF_STMT(BPF_MISC + BPF_TAX, 0),                                                                /* X <- A */
+                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)),                        /* A <- TPA */
+                BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0),                                                         /* X xor A */
+                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),                                                   /* A == 0 ? */
+                BPF_STMT(BPF_RET + BPF_K, 65535),                                                               /* return all */
+                BPF_STMT(BPF_RET + BPF_K, 0),                                                                   /* ignore */
+        };
+        const struct sock_fprog fprog = {
+                .len = sizeof(filter) / sizeof(*filter),
+                .filter = filter,
+        };
+        const struct sockaddr_ll address = {
+                .sll_family = AF_PACKET,
+                .sll_protocol = htobe16(ETH_P_ARP),
+                .sll_ifindex = acd->config.ifindex,
+                .sll_halen = ETH_ALEN,
+                .sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+        };
+        int r;
+
+        /*
+         * Install a packet filter that matches on the ARP header and
+         * addresses, to reduce the number of wake-ups to a minimum.
+         */
+        r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+        if (r < 0)
+                return -n_acd_errno();
+
+        /*
+         * Bind the packet-socket to ETH_P_ARP and the specified network
+         * interface.
+         */
+        r = bind(s, (struct sockaddr *)&address, sizeof(address));
+        if (r < 0)
+                return -n_acd_errno();
+
+        return 0;
+}
+
+static int n_acd_setup_socket(NAcd *acd) {
+        int r, s;
+
+        s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+        if (s < 0)
+                return -n_acd_errno();
+
+        r = n_acd_bind_socket(acd, s);
+        if (r < 0)
+                goto error;
+
+        r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, s,
+                      &(struct epoll_event){
+                              .events = EPOLLIN,
+                              .data.u32 = N_ACD_EPOLL_SOCKET,
+                      });
+        if (r < 0) {
+                r = -n_acd_errno();
+                goto error;
+        }
+
+        acd->fd_socket = s;
+        return 0;
+
+error:
+        close(s);
+        return r;
+}
+
+/**
+ * n_acd_start() - start the ACD engine
+ * @acd:        ACD context
+ * @config:     description of interface and desired IP address
+ *
+ * Start probing the given address on the given interface.
+ *
+ * The engine must not already be running, and there must not be
+ * any pending events.
+ *
+ * Returns: 0 on success, N_ACD_E_INVALID_ARGUMENT in case the configuration
+ *          was invalid, N_ACD_E_BUSY if the engine is running or there are
+ *          pending events, or a negative error code on failure.
+ */
+_public_ int n_acd_start(NAcd *acd, NAcdConfig *config) {
+        uint64_t now, delay;
+        int r;
+
+        if (config->ifindex <= 0 ||
+            config->transport != N_ACD_TRANSPORT_ETHERNET ||
+            config->n_mac != ETH_ALEN ||
+            !memcmp(config->mac, (uint8_t[ETH_ALEN]){ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, ETH_ALEN) ||
+            !config->ip.s_addr)
+                return N_ACD_E_INVALID_ARGUMENT;
+
+        if (acd->state != N_ACD_STATE_INIT || !c_list_is_empty(&acd->events))
+                return N_ACD_E_BUSY;
+
+        acd->config = *config;
+        memcpy(acd->mac, config->mac, config->n_mac);
+        acd->config.mac = acd->mac;
+        acd->timeout_multiplier = config->timeout_msec;
+
+        r = n_acd_setup_socket(acd);
+        if (r < 0)
+                goto error;
+
+        if (acd->timeout_multiplier) {
+                delay = 0;
+                acd->n_iteration = 0;
+
+                if (acd->last_conflict != TIME_INFINITY) {
+                        r = n_acd_now(&now);
+                        if (r < 0)
+                                goto error;
+
+                        if (now < acd->last_conflict + N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC)
+                                delay = acd->last_conflict + N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC - now;
+                }
+
+                r = n_acd_schedule(acd, delay, acd->timeout_multiplier * N_ACD_RFC_PROBE_WAIT_USEC);
+                if (r < 0)
+                        goto error;
+        } else {
+                /*
+                 * A zero timeout means we drop the probing alltogether, and behave as if
+                 * the last probe succeeded immediately.
+                 */
+                acd->n_iteration = N_ACD_RFC_PROBE_NUM;
+
+                r = n_acd_schedule(acd, 0, 0);
+                if (r < 0)
+                        goto error;
+        }
+
+        acd->state = N_ACD_STATE_PROBING;
+        acd->defend = N_ACD_DEFEND_NEVER;
+        acd->last_defend = 0;
+        return 0;
+
+error:
+        n_acd_reset(acd);
+        return r;
+}
+
+/**
+ * n_acd_stop() - stop the ACD engine
+ * @acd:        ACD context
+ *
+ * Stop the engine. No new events may be triggered, but pending events are not
+ * flushed. Before calling n_acd_start() again all pending events must be popped.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+_public_ int n_acd_stop(NAcd *acd) {
+        n_acd_reset(acd);
+        return 0;
+}
+
+/**
+ * n_acd_announce() - announce the configured IP address
+ * @acd:        ACD context
+ * @defend:     defence policy
+ *
+ * Announce the IP address on the local link, and start defending it according
+ * to the given policy, which mut be one of N_ACD_DEFEND_ONCE,
+ * N_ACD_DEFEND_NEVER, or N_ACD_DEFEND_ALWAYS.
+ *
+ * This must be called after the engine in response to an N_ACD_EVENT_READY
+ * event, and only after the given address has been configured on the given
+ * interface.
+ *
+ * Return: 0 on success, N_ACD_E_INVALID_ARGUMENT in case the defence policy
+ *         is invalid, N_ACD_E_BUSY if this is not in response to a
+ *         N_ACD_EVENT_READY event, or a negative error code on failure.
+ */
+_public_ int n_acd_announce(NAcd *acd, unsigned int defend) {
+        uint64_t now;
+        int r;
+
+        if (defend >= _N_ACD_DEFEND_N)
+                return N_ACD_E_INVALID_ARGUMENT;
+        if (acd->state != N_ACD_STATE_CONFIGURING)
+                return N_ACD_E_BUSY;
+
+        /*
+         * Sending announcements means we finished probing and use the address
+         * now. We therefore reset the conflict counter in case we adhered to
+         * the rate-limit. Since probing is properly delayed, a well-behaving
+         * client will always reset the conflict counter here. However, if you
+         * force-use an address regardless of conflicts, then this will not
+         * trigger and the conflict counter stays untouched.
+         */
+        if (acd->last_conflict != TIME_INFINITY) {
+                r = n_acd_now(&now);
+                if (r < 0)
+                        return r;
+
+                if (now >= acd->last_conflict + N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC)
+                        acd->n_conflicts = 0;
+        }
+
+        /*
+         * Instead of sending the first announcement here, we schedule an idle
+         * timer. This avoids possibly recursing into the user callback. We
+         * should never trigger callbacks from arbitrary stacks, but always
+         * restrict them to the dispatcher.
+         */
+        r = n_acd_schedule(acd, 0, 0);
+        if (r < 0)
+                return r;
+
+        acd->state = N_ACD_STATE_ANNOUNCING;
+        acd->defend = defend;
+        acd->n_iteration = 0;
+        return 0;
+}
diff --git a/shared/n-acd/src/n-acd.h b/shared/n-acd/src/n-acd.h
new file mode 100644
index 00000000..75646243
--- /dev/null
+++ b/shared/n-acd/src/n-acd.h
@@ -0,0 +1,96 @@
+#pragma once
+
+/*
+ * IPv4 Address Conflict Detection
+ *
+ * This is the public header of the n-acd library, implementing IPv4 Address
+ * Conflict Detection as described in RFC-5227. This header defines the public
+ * API and all entry points of n-acd.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <netinet/in.h>
+#include <stdbool.h>
+
+#define N_ACD_TIMEOUT_RFC5227 (UINT64_C(9000))
+
+enum {
+        _N_ACD_E_SUCCESS,
+
+        N_ACD_E_DONE,
+        N_ACD_E_STOPPED,
+        N_ACD_E_PREEMPTED,
+
+        N_ACD_E_INVALID_ARGUMENT,
+        N_ACD_E_BUSY,
+};
+
+typedef struct NAcd NAcd;
+
+typedef struct NAcdConfig {
+        int ifindex;
+        unsigned int transport;
+        const uint8_t *mac;
+        size_t n_mac;
+        struct in_addr ip;
+        uint64_t timeout_msec;
+} NAcdConfig;
+
+typedef struct NAcdEvent {
+        unsigned int event;
+        union {
+                struct {
+                } ready, down;
+                struct {
+                        uint16_t operation;
+                        uint8_t *sender;
+                        size_t n_sender;
+                        struct in_addr target;
+                } used, defended, conflict;
+        };
+} NAcdEvent;
+
+enum {
+        N_ACD_TRANSPORT_ETHERNET,
+        _N_ACD_TRANSPORT_N,
+};
+
+enum {
+        N_ACD_EVENT_READY,
+        N_ACD_EVENT_USED,
+        N_ACD_EVENT_DEFENDED,
+        N_ACD_EVENT_CONFLICT,
+        N_ACD_EVENT_DOWN,
+        _N_ACD_EVENT_N,
+};
+
+enum {
+        N_ACD_DEFEND_NEVER,
+        N_ACD_DEFEND_ONCE,
+        N_ACD_DEFEND_ALWAYS,
+        _N_ACD_DEFEND_N,
+};
+
+int n_acd_new(NAcd **acdp);
+void n_acd_free(NAcd *acd);
+
+void n_acd_get_fd(NAcd *acd, int *fdp);
+
+int n_acd_dispatch(NAcd *acd);
+int n_acd_pop_event(NAcd *acd, NAcdEvent **eventp);
+int n_acd_announce(NAcd *acd, unsigned int defend);
+
+int n_acd_start(NAcd *acd, NAcdConfig *config);
+int n_acd_stop(NAcd *acd);
+
+static inline void n_acd_freep(NAcd **acd) {
+        if (*acd)
+                n_acd_free(*acd);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/shared/nm-default.h b/shared/nm-default.h
index 9e2377cf..b9be4768 100644
--- a/shared/nm-default.h
+++ b/shared/nm-default.h
@@ -22,19 +22,85 @@
 #ifndef __NM_DEFAULT_H__
 #define __NM_DEFAULT_H__
 
-/* makefiles define NETWORKMANAGER_COMPILATION for compiling NetworkManager.
- * Depending on which parts are compiled, different values are set. */
-#define NM_NETWORKMANAGER_COMPILATION_DEFAULT             0x0001
-#define NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON       0x0002
-#define NM_NETWORKMANAGER_COMPILATION_LIB                 0x0004
-#define NM_NETWORKMANAGER_COMPILATION_SYSTEMD             0x0008
-#define NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY          0x0010
+#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_LIBNM_UTIL           (1 <<  8)
+#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB           (1 <<  9)
+#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_LIBNM_UTIL     ( 0 \
+                                                     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \
+                                                     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB \
+                                                     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL \
+                                                     )
+
+#define NM_NETWORKMANAGER_COMPILATION_LIBNM_GLIB     ( 0 \
+                                                     | NM_NETWORKMANAGER_COMPILATION_LIBNM_UTIL \
+                                                     | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB \
+                                                     )
+
+#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        ( 0 \
+                                                     | NM_NETWORKMANAGER_COMPILATION_DAEMON \
+                                                     | NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD \
+                                                     )
+
+#define NM_NETWORKMANAGER_COMPILATION_GLIB           ( 0 \
+                                                     | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \
+                                                     )
 
 #ifndef NETWORKMANAGER_COMPILATION
-/* For convenience, we don't require our Makefile.am to define
- * -DNETWORKMANAGER_COMPILATION. As we now include this internal header,
- *  we know we do a NETWORKMANAGER_COMPILATION. */
-#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_DEFAULT
+#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
+#elif defined (NETWORKMANAGER_COMPILATION_TEST) || (NETWORKMANAGER_COMPILATION & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON)
+#error Do not define G_LOG_DOMAIN with NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON
 #endif
 
 /*****************************************************************************/
@@ -49,7 +115,6 @@
 /* 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_MAX_ALLOWED   NM_VERSION_NEXT_STABLE
 #define NM_VERSION_MIN_REQUIRED  NM_VERSION_0_9_8
 
 #ifndef NM_MORE_ASSERTS
@@ -109,9 +174,52 @@
 #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 necesary
+ * 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
@@ -178,31 +286,30 @@ _nm_g_return_if_fail_warning (const char *log_domain,
 #include "nm-utils/nm-macros-internal.h"
 #include "nm-utils/nm-shared-utils.h"
 
-#include "nm-version.h"
-
-/*****************************************************************************/
-
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB) || ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY)
-
-#include <glib/gi18n-lib.h>
-
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL
+/* no hash-utils in legacy code. */
 #else
-
-#include <glib/gi18n.h>
-
-#endif /* NM_NETWORKMANAGER_COMPILATION_LIB || NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY */
+#include "nm-utils/nm-hash-utils.h"
+#endif
 
 /*****************************************************************************/
 
-#if (NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON || (NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_SYSTEMD
+#if (NETWORKMANAGER_COMPILATION) & (NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL)
+#include "nm-version.h"
+#endif
 
-/* the header is used inside src/, where additional
- * headers are available. */
+/*****************************************************************************/
 
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON
 #include "nm-types.h"
 #include "nm-logging.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_INSIDE_DAEMON */
+#endif /* NM_NETWORKMANAGER_COMPILATION_WITH_GLIB */
 
 /*****************************************************************************/
 
diff --git a/shared/nm-test-libnm-utils.h b/shared/nm-test-libnm-utils.h
index 192c089e..20a15e5f 100644
--- a/shared/nm-test-libnm-utils.h
+++ b/shared/nm-test-libnm-utils.h
@@ -22,6 +22,10 @@
 
 #include "nm-utils/nm-test-utils.h"
 
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB
+#include "nm-dbus-glib-types.h"
+#endif
+
 /*****************************************************************************/
 
 typedef struct {
@@ -29,7 +33,7 @@ typedef struct {
 	GDBusProxy *proxy;
 	GPid pid;
 	int keepalive_fd;
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY)
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB
 	struct {
 		DBusGConnection *bus;
 	} libdbus;
@@ -38,23 +42,35 @@ typedef struct {
 
 NMTstcServiceInfo *nmtstc_service_init (void);
 void nmtstc_service_cleanup (NMTstcServiceInfo *info);
+NMTstcServiceInfo *nmtstc_service_available (NMTstcServiceInfo *info);
 
 static inline void _nmtstc_auto_service_cleanup (NMTstcServiceInfo **info)
 {
-	if (info && *info) {
-		nmtstc_service_cleanup (*info);
-		*info = NULL;
-	}
+	nmtstc_service_cleanup (g_steal_pointer (info));
 }
-
 #define NMTSTC_SERVICE_INFO_SETUP(sinfo) \
 	NM_PRAGMA_WARNING_DISABLE ("-Wunused-variable") \
-	__attribute__ ((cleanup(_nmtstc_auto_service_cleanup))) NMTstcServiceInfo *sinfo = nmtstc_service_init (); \
+	__attribute__ ((cleanup(_nmtstc_auto_service_cleanup))) NMTstcServiceInfo *sinfo = ({ \
+		NMTstcServiceInfo *_sinfo; \
+		\
+		_sinfo = nmtstc_service_init (); \
+		if (!nmtstc_service_available (_sinfo)) \
+			return; \
+		_sinfo; \
+	}); \
 	NM_PRAGMA_WARNING_REENABLE
 
 /*****************************************************************************/
 
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB)
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB
+
+#include "nm-client.h"
+#include "nm-remote-settings.h"
+
+NMClient *nmtstc_nm_client_new (void);
+NMRemoteSettings *nmtstc_nm_remote_settings_new (void);
+
+#else
 
 NMDevice *nmtstc_service_add_device (NMTstcServiceInfo *info,
                                      NMClient *client,
@@ -67,17 +83,9 @@ NMDevice * nmtstc_service_add_wired_device (NMTstcServiceInfo *sinfo,
                                             const char *hwaddr,
                                             const char **subchannels);
 
-#endif /* NM_NETWORKMANAGER_COMPILATION_LIB */
-
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY)
-
-#include "nm-client.h"
-#include "nm-remote-settings.h"
-
-NMClient *nmtstc_nm_client_new (void);
-NMRemoteSettings *nmtstc_nm_remote_settings_new (void);
+#endif
 
-#endif /* NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY */
+/*****************************************************************************/
 
 void nmtstc_service_add_connection (NMTstcServiceInfo *sinfo,
                                     NMConnection *connection,
diff --git a/shared/nm-test-utils-impl.c b/shared/nm-test-utils-impl.c
index 3eb726d9..3add88c7 100644
--- a/shared/nm-test-utils-impl.c
+++ b/shared/nm-test-utils-impl.c
@@ -21,16 +21,15 @@
 #include "nm-default.h"
 
 #include <string.h>
+#include <sys/wait.h>
 
 #include "NetworkManager.h"
 #include "nm-dbus-compat.h"
 
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY)
-#include "nm-dbus-glib-types.h"
-#endif
-
 #include "nm-test-libnm-utils.h"
 
+#define NMTSTC_NM_SERVICE NM_BUILD_SRCDIR"/tools/test-networkmanager-service.py"
+
 /*****************************************************************************/
 
 static gboolean
@@ -58,8 +57,7 @@ name_exists (GDBusConnection *c, const char *name)
 	return exists;
 }
 
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY)
-
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB
 static DBusGProxy *
 _libdbus_create_proxy_test (DBusGConnection *bus)
 {
@@ -75,16 +73,62 @@ _libdbus_create_proxy_test (DBusGConnection *bus)
 
 	return proxy;
 }
-
 #endif
 
+typedef struct {
+	GMainLoop *mainloop;
+	GDBusConnection *bus;
+	int exit_code;
+	bool exited:1;
+	bool name_found:1;
+} ServiceInitWaitData;
+
+static gboolean
+_service_init_wait_probe_name (gpointer user_data)
+{
+	ServiceInitWaitData *data = user_data;
+
+	if (!name_exists (data->bus, "org.freedesktop.NetworkManager"))
+		return G_SOURCE_CONTINUE;
+
+	data->name_found = TRUE;
+	g_main_loop_quit (data->mainloop);
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_service_init_wait_child_wait (GPid pid,
+                               gint status,
+                               gpointer user_data)
+{
+	ServiceInitWaitData *data = user_data;
+
+	data->exited = TRUE;
+	data->exit_code = status;
+	g_main_loop_quit (data->mainloop);
+}
+
+NMTstcServiceInfo *
+nmtstc_service_available (NMTstcServiceInfo *info)
+{
+	gs_free char *m = NULL;
+
+	if (info)
+		return info;
+
+	/* This happens, when test-networkmanager-service.py exits with 77 status
+	 * code. */
+	m = g_strdup_printf ("missing dependency for running NetworkManager stub service %s", NMTSTC_NM_SERVICE);
+	g_test_skip (m);
+	return NULL;
+}
+
 NMTstcServiceInfo *
 nmtstc_service_init (void)
 {
 	NMTstcServiceInfo *info;
-	const char *args[] = { TEST_NM_PYTHON, TEST_NM_SERVICE, NULL };
+	const char *args[] = { TEST_NM_PYTHON, NMTSTC_NM_SERVICE, NULL };
 	GError *error = NULL;
-	int i;
 
 	info = g_malloc0 (sizeof (*info));
 
@@ -96,18 +140,55 @@ nmtstc_service_init (void)
 	 * make sure the service exits if the test program crashes.
 	 */
 	g_spawn_async_with_pipes (NULL, (char **) args, NULL,
-	                          G_SPAWN_SEARCH_PATH,
+	                            G_SPAWN_SEARCH_PATH
+	                          | G_SPAWN_DO_NOT_REAP_CHILD,
 	                          NULL, NULL,
 	                          &info->pid, &info->keepalive_fd, NULL, NULL, &error);
 	g_assert_no_error (error);
 
-	/* Wait until the service is registered on the bus */
-	for (i = 1000; i > 0; i--) {
-		if (name_exists (info->bus, "org.freedesktop.NetworkManager"))
-			break;
-		g_usleep (G_USEC_PER_SEC / 50);
+	{
+		nm_auto_unref_gsource GSource *timeout_source = NULL;
+		nm_auto_unref_gsource GSource *child_source = NULL;
+		GMainContext *context = g_main_context_new ();
+		ServiceInitWaitData data = {
+			.bus = info->bus,
+			.mainloop = g_main_loop_new (context, FALSE),
+		};
+		gboolean had_timeout;
+
+		timeout_source = g_timeout_source_new (50);
+		g_source_set_callback (timeout_source, _service_init_wait_probe_name, &data, NULL);
+		g_source_attach (timeout_source, context);
+
+		child_source = g_child_watch_source_new (info->pid);
+		g_source_set_callback (child_source, (GSourceFunc)(void (*) (void)) _service_init_wait_child_wait, &data, NULL);
+		g_source_attach (child_source, context);
+
+		had_timeout = !nmtst_main_loop_run (data.mainloop, 3000);
+
+		g_source_destroy (timeout_source);
+		g_source_destroy (child_source);
+		g_main_loop_unref (data.mainloop);
+		g_main_context_unref (context);
+
+		if (had_timeout)
+			g_error ("test service %s did not start in time", NMTSTC_NM_SERVICE);
+		if (!data.name_found) {
+			g_assert (data.exited);
+			info->pid = NM_PID_T_INVAL;
+			nmtstc_service_cleanup (info);
+
+			if (   WIFEXITED (data.exit_code)
+			    && WEXITSTATUS (data.exit_code) == 77) {
+				/* If the stub service exited with status 77 it means that it decided
+				 * that it cannot conduct the tests and the test should be (gracefully)
+				 * skip. The likely reason for that, is that libnm is not available
+				 * via pygobject. */
+				return NULL;
+			}
+			g_error ("test service %s exited with error code %d", NMTSTC_NM_SERVICE, data.exit_code);
+		}
 	}
-	g_assert (i > 0);
 
 	/* Grab a proxy to our fake NM service to trigger tests */
 	info->proxy = g_dbus_proxy_new_sync (info->bus,
@@ -121,7 +202,7 @@ nmtstc_service_init (void)
 	                                     NULL, &error);
 	g_assert_no_error (error);
 
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY)
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB
 	info->libdbus.bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
 	g_assert_no_error (error);
 	g_assert (info->libdbus.bus);
@@ -132,32 +213,50 @@ nmtstc_service_init (void)
 void
 nmtstc_service_cleanup (NMTstcServiceInfo *info)
 {
-	int i;
+	int ret;
+	gint64 t;
+	int status;
 
-	g_object_unref (info->proxy);
-	kill (info->pid, SIGTERM);
+	if (!info)
+		return;
 
-	/* Wait until the bus notices the service is gone */
-	for (i = 100; i > 0; i--) {
-		if (!name_exists (info->bus, "org.freedesktop.NetworkManager"))
-			break;
-		g_usleep (G_USEC_PER_SEC / 50);
-	}
-	g_assert (i > 0);
+	nm_close (nm_steal_fd (&info->keepalive_fd));
 
-	g_object_unref (info->bus);
-	nm_close (info->keepalive_fd);
+	g_clear_object (&info->proxy);
 
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY)
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB
 	g_clear_pointer (&info->libdbus.bus, dbus_g_connection_unref);
 #endif
 
+	if (info->pid != NM_PID_T_INVAL) {
+		kill (info->pid, SIGTERM);
+
+		t = g_get_monotonic_time ();
+again_wait:
+		ret = waitpid (info->pid, &status, WNOHANG);
+		if (ret == 0) {
+			if (t + 2000000 < g_get_monotonic_time ()) {
+				kill (info->pid, SIGKILL);
+				g_error ("child process %lld did not exit within timeout", (long long) info->pid);
+			}
+			g_usleep (G_USEC_PER_SEC / 50);
+			goto again_wait;
+		}
+		if (ret == -1 && errno == EINTR)
+			goto again_wait;
+
+		g_assert (ret == info->pid);
+	}
+
+	g_assert (!name_exists (info->bus, "org.freedesktop.NetworkManager"));
+
+	g_clear_object (&info->bus);
+
 	memset (info, 0, sizeof (*info));
 	g_free (info);
 }
 
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB)
-
+#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB)
 typedef struct {
 	GMainLoop *loop;
 	const char *ifname;
@@ -270,8 +369,7 @@ nmtstc_service_add_wired_device (NMTstcServiceInfo *sinfo, NMClient *client,
 {
 	return add_device_common (sinfo, client, "AddWiredDevice", ifname, hwaddr, subchannels);
 }
-
-#endif /* NM_NETWORKMANAGER_COMPILATION_LIB */
+#endif
 
 void
 nmtstc_service_add_connection (NMTstcServiceInfo *sinfo,
@@ -279,7 +377,7 @@ nmtstc_service_add_connection (NMTstcServiceInfo *sinfo,
                                gboolean verify_connection,
                                char **out_path)
 {
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY)
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB
 	gs_unref_hashtable GHashTable *new_settings = NULL;
 	gboolean success;
 	gs_free_error GError *error = NULL;
@@ -353,7 +451,7 @@ nmtstc_service_update_connection (NMTstcServiceInfo *sinfo,
 		path = nm_connection_get_path (connection);
 	g_assert (path);
 
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY)
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB
 	{
 		gs_unref_hashtable GHashTable *new_settings = NULL;
 		gboolean success;
@@ -414,8 +512,7 @@ nmtstc_service_update_connection_variant (NMTstcServiceInfo *sinfo,
 
 /*****************************************************************************/
 
-#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY)
-
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB
 NMClient *
 nmtstc_nm_client_new (void)
 {
@@ -461,7 +558,6 @@ nmtstc_nm_remote_settings_new (void)
 
 	return settings;
 }
-
-#endif /* NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY */
+#endif
 
 /*****************************************************************************/
diff --git a/shared/nm-utils/c-list-util.c b/shared/nm-utils/c-list-util.c
index 070323c6..44ca26a5 100644
--- a/shared/nm-utils/c-list-util.c
+++ b/shared/nm-utils/c-list-util.c
@@ -58,39 +58,35 @@ c_list_relink (CList *lst)
 /*****************************************************************************/
 
 static CList *
-_c_list_sort (CList *ls,
-              CListSortCmp cmp,
-              const void *user_data)
+_c_list_srt_split (CList *ls)
 {
-	CList *ls1, *ls2;
-	CList head;
+	CList *ls2;
 
-	if (!ls->next)
-		return ls;
-
-	/* split list in two halfs @ls1 and @ls2. */
-	ls1 = ls;
 	ls2 = ls;
 	ls = ls->next;
-	while (ls) {
+	if (!ls)
+		return NULL;
+	do {
 		ls = ls->next;
 		if (!ls)
 			break;
 		ls = ls->next;
 		ls2 = ls2->next;
-	}
-	ls = ls2;
-	ls2 = ls->next;
-	ls->next = NULL;
-
-	/* recurse */
-	ls1 = _c_list_sort (ls1, cmp, user_data);
-	if (!ls2)
-		return ls1;
+	} while (ls);
+	ls = ls2->next;
+	ls2->next = NULL;
+	return ls;
+}
 
-	ls2 = _c_list_sort (ls2, cmp, user_data);
+static CList *
+_c_list_srt_merge (CList *ls1,
+                   CList *ls2,
+                   CListSortCmp cmp,
+                   const void *user_data)
+{
+	CList *ls;
+	CList head;
 
-	/* merge */
 	ls = &head;
 	for (;;) {
 		/* while invoking the @cmp function, the list
@@ -115,6 +111,54 @@ _c_list_sort (CList *ls,
 	return head.next;
 }
 
+typedef struct {
+	CList *ls1;
+	CList *ls2;
+	char ls1_sorted;
+} SortStack;
+
+static CList *
+_c_list_sort (CList *ls,
+              CListSortCmp cmp,
+              const void *user_data)
+{
+	/* reserve a huge stack-size. We need roughly log2(n) entries, hence this
+	 * is much more we will ever need. We don't guard for stack-overflow either. */
+	SortStack stack_arr[70];
+	SortStack *stack_head = stack_arr;
+
+	stack_arr[0].ls1 = ls;
+
+	/* A simple top-down, non-recursive, stable merge-sort.
+	 *
+	 * Maybe natural merge-sort would be better, to do better for
+	 * partially sorted lists. */
+_split:
+	stack_head[0].ls2 = _c_list_srt_split (stack_head[0].ls1);
+	if (stack_head[0].ls2) {
+		stack_head[0].ls1_sorted = 0;
+		stack_head[1].ls1 = stack_head[0].ls1;
+		stack_head++;
+		goto _split;
+	}
+
+_backtrack:
+	if (stack_head == stack_arr)
+		return stack_arr[0].ls1;
+
+	stack_head--;
+	if (!stack_head[0].ls1_sorted) {
+		stack_head[0].ls1 = stack_head[1].ls1;
+		stack_head[0].ls1_sorted = 1;
+		stack_head[1].ls1 = stack_head[0].ls2;
+		stack_head++;
+		goto _split;
+	}
+
+	stack_head[0].ls1 = _c_list_srt_merge (stack_head[0].ls1, stack_head[1].ls1, cmp, user_data);
+	goto _backtrack;
+}
+
 /**
  * c_list_sort_headless:
  * @lst: the list.
diff --git a/shared/nm-utils/c-list-util.h b/shared/nm-utils/c-list-util.h
index 199583cf..e87f1c19 100644
--- a/shared/nm-utils/c-list-util.h
+++ b/shared/nm-utils/c-list-util.h
@@ -22,7 +22,7 @@
 #ifndef __C_LIST_UTIL_H__
 #define __C_LIST_UTIL_H__
 
-#include "c-list.h"
+#include "c-list/src/c-list.h"
 
 /*****************************************************************************/
 
diff --git a/shared/nm-utils/nm-c-list.h b/shared/nm-utils/nm-c-list.h
new file mode 100644
index 00000000..b43d1441
--- /dev/null
+++ b/shared/nm-utils/nm-c-list.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2014 Red Hat, Inc.
+ */
+
+#ifndef __NM_C_LIST_H__
+#define __NM_C_LIST_H__
+
+#include "c-list/src/c-list.h"
+
+/*****************************************************************************/
+
+#define nm_c_list_contains_entry(list, what, member) \
+	({ \
+		typeof (what) _what = (what); \
+		\
+		_what && c_list_contains (list, &_what->member); \
+	})
+
+typedef struct {
+	CList lst;
+	void *data;
+} NMCListElem;
+
+static inline NMCListElem *
+nm_c_list_elem_new_stale (void *data)
+{
+	NMCListElem *elem;
+
+	elem = g_slice_new (NMCListElem);
+	elem->data = data;
+	return elem;
+}
+
+static inline void *
+nm_c_list_elem_get (CList *lst)
+{
+	if (!lst)
+		return NULL;
+	return c_list_entry (lst, NMCListElem, lst)->data;
+}
+
+static inline void
+nm_c_list_elem_free (NMCListElem *elem)
+{
+	if (elem) {
+		c_list_unlink_stale (&elem->lst);
+		g_slice_free (NMCListElem, elem);
+	}
+}
+
+static inline void
+nm_c_list_elem_free_all (CList *head, GDestroyNotify free_fcn)
+{
+	NMCListElem *elem;
+
+	while ((elem = c_list_first_entry (head, NMCListElem, lst))) {
+		if (free_fcn)
+			free_fcn (elem->data);
+		c_list_unlink_stale (&elem->lst);
+		g_slice_free (NMCListElem, elem);
+	}
+}
+
+#endif /* __NM_C_LIST_H__ */
diff --git a/shared/nm-utils/nm-compat.c b/shared/nm-utils/nm-compat.c
new file mode 100644
index 00000000..90328c06
--- /dev/null
+++ b/shared/nm-utils/nm-compat.c
@@ -0,0 +1,95 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-compat.h"
+
+/*****************************************************************************/
+
+static void
+_get_keys_cb (const char *key, const char *val, gpointer user_data)
+{
+	GPtrArray *a = user_data;
+
+	g_ptr_array_add (a, g_strdup (key));
+}
+
+static const char **
+_get_keys (NMSettingVpn *setting,
+           gboolean is_secrets,
+           guint *out_length)
+{
+	guint len;
+	const char **keys = NULL;
+	GPtrArray *a;
+
+	nm_assert (NM_IS_SETTING_VPN (setting));
+
+	if (is_secrets)
+		len = nm_setting_vpn_get_num_secrets (setting);
+	else
+		len = nm_setting_vpn_get_num_data_items (setting);
+
+	a = g_ptr_array_sized_new (len + 1);
+
+	if (is_secrets)
+		nm_setting_vpn_foreach_secret (setting, _get_keys_cb, a);
+	else
+		nm_setting_vpn_foreach_data_item (setting, _get_keys_cb, a);
+
+	len = a->len;
+	if (len) {
+		g_ptr_array_sort (a, nm_strcmp_p);
+		g_ptr_array_add (a, NULL);
+		keys = g_memdup (a->pdata, a->len * sizeof (gpointer));
+
+		/* we need to cache the keys *somewhere*. */
+		g_object_set_qdata_full (G_OBJECT (setting),
+		                         is_secrets
+		                         ? NM_CACHED_QUARK ("libnm._nm_setting_vpn_get_secret_keys")
+		                         : NM_CACHED_QUARK ("libnm._nm_setting_vpn_get_data_keys"),
+		                         g_ptr_array_free (a, FALSE),
+		                         (GDestroyNotify) g_strfreev);
+	} else
+		g_ptr_array_free (a, TRUE);
+
+	NM_SET_OUT (out_length, len);
+	return keys;
+}
+
+const char **
+_nm_setting_vpn_get_data_keys (NMSettingVpn *setting,
+                               guint *out_length)
+{
+	g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL);
+
+	return _get_keys (setting, FALSE, out_length);
+}
+
+const char **
+_nm_setting_vpn_get_secret_keys (NMSettingVpn *setting,
+                                 guint *out_length)
+{
+	g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL);
+
+	return _get_keys (setting, TRUE, out_length);
+}
diff --git a/shared/nm-utils/nm-compat.h b/shared/nm-utils/nm-compat.h
new file mode 100644
index 00000000..52341690
--- /dev/null
+++ b/shared/nm-utils/nm-compat.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2017 Red Hat, Inc.
+ */
+
+#ifndef __NM_COMPAT_H__
+#define __NM_COMPAT_H__
+
+#include "nm-setting-vpn.h"
+
+const char **_nm_setting_vpn_get_data_keys (NMSettingVpn *setting,
+                                            guint *out_length);
+
+const char **_nm_setting_vpn_get_secret_keys (NMSettingVpn *setting,
+                                              guint *out_length);
+
+#if NM_CHECK_VERSION (1, 11, 0)
+#define nm_setting_vpn_get_data_keys(setting, out_length) \
+	({ \
+		G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
+		nm_setting_vpn_get_data_keys (setting, out_length); \
+		G_GNUC_END_IGNORE_DEPRECATIONS \
+	})
+#define nm_setting_vpn_get_secret_keys(setting, out_length) \
+	({ \
+		G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
+		nm_setting_vpn_get_secret_keys (setting, out_length); \
+		G_GNUC_END_IGNORE_DEPRECATIONS \
+	})
+#else
+#define nm_setting_vpn_get_data_keys(setting, out_length) \
+	_nm_setting_vpn_get_data_keys (setting, out_length)
+#define nm_setting_vpn_get_secret_keys(setting, out_length) \
+	_nm_setting_vpn_get_secret_keys (setting, out_length)
+#endif
+
+#endif /* __NM_COMPAT_H__ */
diff --git a/shared/nm-utils/nm-dedup-multi.c b/shared/nm-utils/nm-dedup-multi.c
index 59b647ed..fc134e25 100644
--- a/shared/nm-utils/nm-dedup-multi.c
+++ b/shared/nm-utils/nm-dedup-multi.c
@@ -386,10 +386,10 @@ _add (NMDedupMultiIndex *self,
 	head_entry->len++;
 
 	if (   add_head_entry
-	    && !nm_g_hash_table_add (self->idx_entries, head_entry))
+	    && !g_hash_table_add (self->idx_entries, head_entry))
 		nm_assert_not_reached ();
 
-	if (!nm_g_hash_table_add (self->idx_entries, entry))
+	if (!g_hash_table_add (self->idx_entries, entry))
 		nm_assert_not_reached ();
 
 	NM_SET_OUT (out_entry, entry);
@@ -870,7 +870,7 @@ nm_dedup_multi_index_obj_intern (NMDedupMultiIndex *self,
 	nm_assert (obj_new);
 	nm_assert (!obj_new->_multi_idx);
 
-	if (!nm_g_hash_table_add (self->idx_objs, (gpointer) obj_new))
+	if (!g_hash_table_add (self->idx_objs, (gpointer) obj_new))
 		nm_assert_not_reached ();
 
 	((NMDedupMultiObj *) obj_new)->_multi_idx = self;
diff --git a/shared/nm-utils/nm-dedup-multi.h b/shared/nm-utils/nm-dedup-multi.h
index 6286d6a4..8d482de9 100644
--- a/shared/nm-utils/nm-dedup-multi.h
+++ b/shared/nm-utils/nm-dedup-multi.h
@@ -115,7 +115,7 @@ void nm_dedup_multi_index_obj_release (NMDedupMultiIndex *self,
 /* the NMDedupMultiIdxType is an access handle under which you can store and
  * retrieve NMDedupMultiObj instances in NMDedupMultiIndex.
  *
- * The NMDedupMultiIdxTypeClass determines it's behavior, but you can have
+ * The NMDedupMultiIdxTypeClass determines its behavior, but you can have
  * multiple instances (of the same class).
  *
  * For example, NMIP4Config can have idx-type to put there all IPv4 Routes.
diff --git a/shared/nm-utils/nm-enum-utils.c b/shared/nm-utils/nm-enum-utils.c
index 70a8b415..023f8385 100644
--- a/shared/nm-utils/nm-enum-utils.c
+++ b/shared/nm-utils/nm-enum-utils.c
@@ -27,6 +27,62 @@
 
 #define IS_FLAGS_SEPARATOR(ch)  (NM_IN_SET ((ch), ' ', '\t', ',', '\n', '\r'))
 
+static void
+_ASSERT_enum_values_info (GType type,
+                          const NMUtilsEnumValueInfo *value_infos)
+{
+#if NM_MORE_ASSERTS > 5
+	nm_auto_unref_gtypeclass GTypeClass *klass = NULL;
+	gs_unref_hashtable GHashTable *ht = NULL;
+
+	klass = g_type_class_ref (type);
+
+	g_assert (G_IS_ENUM_CLASS (klass) || G_IS_FLAGS_CLASS (klass));
+
+	if (!value_infos)
+		return;
+
+	ht = g_hash_table_new (g_str_hash, g_str_equal);
+
+	for (; value_infos->nick; value_infos++) {
+
+		g_assert (value_infos->nick[0]);
+
+		/* duplicate nicks make no sense!! */
+		g_assert (!g_hash_table_contains (ht, value_infos->nick));
+		g_hash_table_add (ht, (gpointer) value_infos->nick);
+
+		if (G_IS_ENUM_CLASS (klass)) {
+			GEnumValue *enum_value;
+
+			enum_value = g_enum_get_value_by_nick (G_ENUM_CLASS (klass), value_infos->nick);
+			if (enum_value) {
+				/* we do allow specifying the same name via @value_infos and @type.
+				 * That might make sense, if @type comes from a library where older versions
+				 * of the library don't yet support the value. In this case, the caller can
+				 * provide the nick via @value_infos, to support the older library version.
+				 * And then, when actually running against a newer library version where
+				 * @type knows the nick, we have this situation.
+				 *
+				 * However, what never is allowed, is to use a name (nick) to re-number
+				 * the value. That is, if both @value_infos and @type contain a particular
+				 * nick, their numeric values must agree as well.
+				 */
+				g_assert (enum_value->value == value_infos->value);
+			}
+		} else {
+			GFlagsValue *flags_value;
+
+			flags_value = g_flags_get_value_by_nick (G_FLAGS_CLASS (klass), value_infos->nick);
+			if (flags_value) {
+				/* see ENUM case above. */
+				g_assert (flags_value->value == (guint) value_infos->value);
+			}
+		}
+	}
+#endif
+}
+
 static gboolean
 _is_hex_string (const char *str)
 {
@@ -64,36 +120,65 @@ _enum_is_valid_flags_nick (const char *str)
 char *
 _nm_utils_enum_to_str_full (GType type,
                             int value,
-                            const char *flags_separator)
+                            const char *flags_separator,
+                            const NMUtilsEnumValueInfo *value_infos)
 {
-	GTypeClass *class;
-	char *ret;
+	nm_auto_unref_gtypeclass GTypeClass *klass = NULL;
+
+	_ASSERT_enum_values_info (type, value_infos);
 
 	if (   flags_separator
 	    && (   !flags_separator[0]
 	        || NM_STRCHAR_ANY (flags_separator, ch, !IS_FLAGS_SEPARATOR (ch))))
 		g_return_val_if_reached (NULL);
 
-	class = g_type_class_ref (type);
+	klass = g_type_class_ref (type);
 
-	if (G_IS_ENUM_CLASS (class)) {
+	if (G_IS_ENUM_CLASS (klass)) {
 		GEnumValue *enum_value;
 
-		enum_value = g_enum_get_value (G_ENUM_CLASS (class), value);
+		for ( ; value_infos && value_infos->nick; value_infos++) {
+			if (value_infos->value == value)
+				return g_strdup (value_infos->nick);
+		}
+
+		enum_value = g_enum_get_value (G_ENUM_CLASS (klass), value);
 		if (   !enum_value
 		    || !_enum_is_valid_enum_nick (enum_value->value_nick))
-			ret = g_strdup_printf ("%d", value);
+			return g_strdup_printf ("%d", value);
 		else
-			ret = strdup (enum_value->value_nick);
-	} else if (G_IS_FLAGS_CLASS (class)) {
+			return g_strdup (enum_value->value_nick);
+	} else if (G_IS_FLAGS_CLASS (klass)) {
 		GFlagsValue *flags_value;
 		GString *str = g_string_new ("");
 		unsigned uvalue = (unsigned) value;
 
 		flags_separator = flags_separator ?: " ";
 
+		for ( ; value_infos && value_infos->nick; value_infos++) {
+
+			nm_assert (_enum_is_valid_flags_nick (value_infos->nick));
+
+			if (uvalue == 0) {
+				if (value_infos->value != 0)
+					continue;
+			} else {
+				if (!NM_FLAGS_ALL (uvalue, (unsigned) value_infos->value))
+					continue;
+			}
+
+			if (str->len)
+				g_string_append (str, flags_separator);
+			g_string_append (str, value_infos->nick);
+			uvalue &= ~((unsigned) value_infos->value);
+			if (uvalue == 0) {
+				/* we printed all flags. Done. */
+				goto flags_done;
+			}
+		}
+
 		do {
-			flags_value = g_flags_get_first_value (G_FLAGS_CLASS (class), uvalue);
+			flags_value = g_flags_get_first_value (G_FLAGS_CLASS (klass), uvalue);
 			if (str->len)
 				g_string_append (str, flags_separator);
 			if (   !flags_value
@@ -105,12 +190,12 @@ _nm_utils_enum_to_str_full (GType type,
 			g_string_append (str, flags_value->value_nick);
 			uvalue &= ~flags_value->value;
 		} while (uvalue);
-		ret = g_string_free (str, FALSE);
-	} else
-		g_return_val_if_reached (NULL);
 
-	g_type_class_unref (class);
-	return ret;
+flags_done:
+		return g_string_free (str, FALSE);
+	}
+
+	g_return_val_if_reached (NULL);
 }
 
 static const NMUtilsEnumValueInfo *
@@ -132,7 +217,7 @@ _nm_utils_enum_from_str_full (GType type,
                               char **err_token,
                               const NMUtilsEnumValueInfo *value_infos)
 {
-	GTypeClass *class;
+	GTypeClass *klass;
 	gboolean ret = FALSE;
 	int value = 0;
 	gs_free char *str_clone = NULL;
@@ -142,13 +227,15 @@ _nm_utils_enum_from_str_full (GType type,
 
 	g_return_val_if_fail (str, FALSE);
 
+	_ASSERT_enum_values_info (type, value_infos);
+
 	str_clone = strdup (str);
 	s = nm_str_skip_leading_spaces (str_clone);
 	g_strchomp (s);
 
-	class = g_type_class_ref (type);
+	klass = g_type_class_ref (type);
 
-	if (G_IS_ENUM_CLASS (class)) {
+	if (G_IS_ENUM_CLASS (klass)) {
 		GEnumValue *enum_value;
 
 		if (s[0]) {
@@ -164,21 +251,15 @@ _nm_utils_enum_from_str_full (GType type,
 					value = (int) v64;
 					ret = TRUE;
 				}
-			} else {
-				enum_value = g_enum_get_value_by_nick (G_ENUM_CLASS (class), s);
-				if (enum_value) {
-					value = enum_value->value;
-					ret = TRUE;
-				} else {
-					nick = _find_value_info (value_infos, s);
-					if (nick) {
-						value = nick->value;
-						ret = TRUE;
-					}
-				}
+			} else if ((nick = _find_value_info (value_infos, s))) {
+				value = nick->value;
+				ret = TRUE;
+			} else if ((enum_value = g_enum_get_value_by_nick (G_ENUM_CLASS (klass), s))) {
+				value = enum_value->value;
+				ret = TRUE;
 			}
 		}
-	} else if (G_IS_FLAGS_CLASS (class)) {
+	} else if (G_IS_FLAGS_CLASS (klass)) {
 		GFlagsValue *flags_value;
 		unsigned uvalue = 0;
 
@@ -209,19 +290,13 @@ _nm_utils_enum_from_str_full (GType type,
 						break;
 					}
 					uvalue |= (unsigned) v64;
-				} else {
-					flags_value = g_flags_get_value_by_nick (G_FLAGS_CLASS (class), s);
-					if (flags_value)
-						uvalue |= flags_value->value;
-					else {
-						nick = _find_value_info (value_infos, s);
-						if (nick)
-							uvalue = (unsigned) nick->value;
-						else {
-							ret = FALSE;
-							break;
-						}
-					}
+				} else if ((nick = _find_value_info (value_infos, s)))
+					uvalue |= (unsigned) nick->value;
+				else if ((flags_value = g_flags_get_value_by_nick (G_FLAGS_CLASS (klass), s)))
+					uvalue |= flags_value->value;
+				else {
+					ret = FALSE;
+					break;
 				}
 			}
 
@@ -234,23 +309,23 @@ _nm_utils_enum_from_str_full (GType type,
 
 	NM_SET_OUT (err_token, !ret && s[0] ? g_strdup (s) : NULL);
 	NM_SET_OUT (out_value, ret ? value : 0);
-	g_type_class_unref (class);
+	g_type_class_unref (klass);
 	return ret;
 }
 
 const char **
 _nm_utils_enum_get_values (GType type, gint from, gint to)
 {
-	GTypeClass *class;
+	GTypeClass *klass;
 	GPtrArray *array;
 	gint i;
 	char sbuf[64];
 
-	class = g_type_class_ref (type);
+	klass = g_type_class_ref (type);
 	array = g_ptr_array_new ();
 
-	if (G_IS_ENUM_CLASS (class)) {
-		GEnumClass *enum_class = G_ENUM_CLASS (class);
+	if (G_IS_ENUM_CLASS (klass)) {
+		GEnumClass *enum_class = G_ENUM_CLASS (klass);
 		GEnumValue *enum_value;
 
 		for (i = 0; i < enum_class->n_values; i++) {
@@ -262,8 +337,8 @@ _nm_utils_enum_get_values (GType type, gint from, gint to)
 					g_ptr_array_add (array, (gpointer) g_intern_string (nm_sprintf_buf (sbuf, "%d", enum_value->value)));
 			}
 		}
-	} else if (G_IS_FLAGS_CLASS (class)) {
-		GFlagsClass *flags_class = G_FLAGS_CLASS (class);
+	} else if (G_IS_FLAGS_CLASS (klass)) {
+		GFlagsClass *flags_class = G_FLAGS_CLASS (klass);
 		GFlagsValue *flags_value;
 
 		for (i = 0; i < flags_class->n_values; i++) {
@@ -276,12 +351,12 @@ _nm_utils_enum_get_values (GType type, gint from, gint to)
 			}
 		}
 	} else {
-		g_type_class_unref (class);
+		g_type_class_unref (klass);
 		g_ptr_array_free (array, TRUE);
 		g_return_val_if_reached (NULL);
 	}
 
-	g_type_class_unref (class);
+	g_type_class_unref (klass);
 	g_ptr_array_add (array, NULL);
 
 	return (const char **) g_ptr_array_free (array, FALSE);
diff --git a/shared/nm-utils/nm-enum-utils.h b/shared/nm-utils/nm-enum-utils.h
index b78d9191..d6dae859 100644
--- a/shared/nm-utils/nm-enum-utils.h
+++ b/shared/nm-utils/nm-enum-utils.h
@@ -31,7 +31,10 @@ typedef struct _NMUtilsEnumValueInfo {
 	int value;
 } NMUtilsEnumValueInfo;
 
-char *_nm_utils_enum_to_str_full (GType type, int value, const char *sep);
+char *_nm_utils_enum_to_str_full (GType type,
+                                  int value,
+                                  const char *sep,
+                                  const NMUtilsEnumValueInfo *value_infos);
 gboolean _nm_utils_enum_from_str_full (GType type,
                                        const char *str,
                                        int *out_value,
diff --git a/shared/nm-utils/nm-glib.h b/shared/nm-utils/nm-glib.h
index 599890e0..010f1820 100644
--- a/shared/nm-utils/nm-glib.h
+++ b/shared/nm-utils/nm-glib.h
@@ -14,13 +14,12 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright 2008 - 2011 Red Hat, Inc.
+ * Copyright 2008 - 2018 Red Hat, Inc.
  */
 
 #ifndef __NM_GLIB_H__
 #define __NM_GLIB_H__
 
-
 #include <gio/gio.h>
 #include <string.h>
 
@@ -40,84 +39,6 @@
 
 #endif
 
-static inline void
-__g_type_ensure (GType type)
-{
-#if !GLIB_CHECK_VERSION(2,34,0)
-	if (G_UNLIKELY (type == (GType)-1))
-		g_error ("can't happen");
-#else
-	G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
-	g_type_ensure (type);
-	G_GNUC_END_IGNORE_DEPRECATIONS;
-#endif
-}
-#define g_type_ensure __g_type_ensure
-
-#if !GLIB_CHECK_VERSION(2,34,0)
-
-#define g_clear_pointer(pp, destroy) \
-    G_STMT_START {                                                                 \
-        G_STATIC_ASSERT (sizeof *(pp) == sizeof (gpointer));                       \
-        /* Only one access, please */                                              \
-        gpointer *_pp = (gpointer *) (pp);                                         \
-        gpointer _p;                                                               \
-        /* This assignment is needed to avoid a gcc warning */                     \
-        GDestroyNotify _destroy = (GDestroyNotify) (destroy);                      \
-                                                                                   \
-        _p = *_pp;                                                                 \
-        if (_p)                                                                    \
-        {                                                                          \
-            *_pp = NULL;                                                           \
-            _destroy (_p);                                                         \
-        }                                                                          \
-    } G_STMT_END
-
-/* These are used to clean up the output of test programs; we can just let
- * them no-op in older glib.
- */
-#define g_test_expect_message(log_domain, log_level, pattern)
-#define g_test_assert_expected_messages()
-
-#else
-
-/* We build with -DGLIB_MAX_ALLOWED_VERSION set to 2.32 to make sure we don't
- * accidentally use new API that we shouldn't. But we don't want warnings for
- * the APIs that we emulate above.
- */
-
-#define g_test_expect_message(domain, level, format...) \
-	G_STMT_START { \
-		G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
-		g_test_expect_message (domain, level, format); \
-		G_GNUC_END_IGNORE_DEPRECATIONS \
-	} G_STMT_END
-
-#define g_test_assert_expected_messages_internal(domain, file, line, func) \
-	G_STMT_START { \
-		G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
-		g_test_assert_expected_messages_internal (domain, file, line, func); \
-		G_GNUC_END_IGNORE_DEPRECATIONS \
-	} G_STMT_END
-
-#endif
-
-
-#if GLIB_CHECK_VERSION (2, 35, 0)
-/* For glib >= 2.36, g_type_init() is deprecated.
- * But since 2.35.1 (7c42ab23b55c43ab96d0ac2124b550bf1f49c1ec) this function
- * does nothing. Replace the call with empty statement. */
-#define nm_g_type_init()     G_STMT_START { (void) 0; } G_STMT_END
-#else
-#define nm_g_type_init()     G_STMT_START { g_type_init (); } G_STMT_END
-#endif
-
-
-/* g_test_initialized() is only available since glib 2.36. */
-#if !GLIB_CHECK_VERSION (2, 36, 0)
-#define g_test_initialized() (g_test_config_vars->test_initialized)
-#endif
-
 /* g_assert_cmpmem() is only available since glib 2.46. */
 #if !GLIB_CHECK_VERSION (2, 45, 7)
 #define g_assert_cmpmem(m1, l1, m2, l2) G_STMT_START {\
@@ -146,239 +67,6 @@ nm_glib_check_version (guint major, guint minor, guint micro)
 	               && glib_micro_version < micro));
 }
 
-/* g_test_skip() is only available since glib 2.38. Add a compatibility wrapper. */
-static inline void
-__nmtst_g_test_skip (const gchar *msg)
-{
-#if GLIB_CHECK_VERSION (2, 38, 0)
-	G_GNUC_BEGIN_IGNORE_DEPRECATIONS
-	g_test_skip (msg);
-	G_GNUC_END_IGNORE_DEPRECATIONS
-#else
-	g_debug ("%s", msg);
-#endif
-}
-#define g_test_skip __nmtst_g_test_skip
-
-
-/* g_test_add_data_func_full() is only available since glib 2.34. Add a compatibility wrapper. */
-static inline void
-__g_test_add_data_func_full (const char     *testpath,
-                             gpointer        test_data,
-                             GTestDataFunc   test_func,
-                             GDestroyNotify  data_free_func)
-{
-#if GLIB_CHECK_VERSION (2, 34, 0)
-	G_GNUC_BEGIN_IGNORE_DEPRECATIONS
-	g_test_add_data_func_full (testpath, test_data, test_func, data_free_func);
-	G_GNUC_END_IGNORE_DEPRECATIONS
-#else
-	g_return_if_fail (testpath != NULL);
-	g_return_if_fail (testpath[0] == '/');
-	g_return_if_fail (test_func != NULL);
-
-	g_test_add_vtable (testpath, 0, test_data, NULL,
-	                   (GTestFixtureFunc) test_func,
-	                   (GTestFixtureFunc) data_free_func);
-#endif
-}
-#define g_test_add_data_func_full __g_test_add_data_func_full
-
-
-#if !GLIB_CHECK_VERSION (2, 34, 0)
-#define G_DEFINE_QUARK(QN, q_n)               \
-GQuark                                        \
-q_n##_quark (void)                            \
-{                                             \
-	static GQuark q;                          \
-                                              \
-	if G_UNLIKELY (q == 0)                    \
-		q = g_quark_from_static_string (#QN); \
-                                              \
-	return q;                                 \
-}
-#endif
-
-
-static inline gboolean
-nm_g_hash_table_replace (GHashTable *hash, gpointer key, gpointer value)
-{
-	/* glib 2.40 added a return value indicating whether the key already existed
-	 * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */
-#if GLIB_CHECK_VERSION(2, 40, 0)
-	return g_hash_table_replace (hash, key, value);
-#else
-	gboolean contained = g_hash_table_contains (hash, key);
-
-	g_hash_table_replace (hash, key, value);
-	return !contained;
-#endif
-}
-
-static inline gboolean
-nm_g_hash_table_insert (GHashTable *hash, gpointer key, gpointer value)
-{
-	/* glib 2.40 added a return value indicating whether the key already existed
-	 * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */
-#if GLIB_CHECK_VERSION(2, 40, 0)
-	return g_hash_table_insert (hash, key, value);
-#else
-	gboolean contained = g_hash_table_contains (hash, key);
-
-	g_hash_table_insert (hash, key, value);
-	return !contained;
-#endif
-}
-
-static inline gboolean
-nm_g_hash_table_add (GHashTable *hash, gpointer key)
-{
-	/* glib 2.40 added a return value indicating whether the key already existed
-	 * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */
-#if GLIB_CHECK_VERSION(2, 40, 0)
-	return g_hash_table_add (hash, key);
-#else
-	gboolean contained = g_hash_table_contains (hash, key);
-
-	g_hash_table_add (hash, key);
-	return !contained;
-#endif
-}
-
-#if !GLIB_CHECK_VERSION(2, 40, 0) || defined (NM_GLIB_COMPAT_H_TEST)
-static inline void
-_nm_g_ptr_array_insert (GPtrArray *array,
-                        gint       index_,
-                        gpointer   data)
-{
-	g_return_if_fail (array);
-	g_return_if_fail (index_ >= -1);
-	g_return_if_fail (index_ <= (gint) array->len);
-
-	g_ptr_array_add (array, data);
-
-	if (index_ != -1 && index_ != (gint) (array->len - 1)) {
-		memmove (&(array->pdata[index_ + 1]),
-		         &(array->pdata[index_]),
-		         (array->len - index_ - 1) * sizeof (gpointer));
-		array->pdata[index_] = data;
-	}
-}
-#endif
-#if !GLIB_CHECK_VERSION(2, 40, 0)
-#define g_ptr_array_insert(array, index, data) G_STMT_START { _nm_g_ptr_array_insert (array, index, data); } G_STMT_END
-#else
-#define g_ptr_array_insert(array, index, data) \
-	G_STMT_START { \
-		G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
-		g_ptr_array_insert (array, index, data); \
-		G_GNUC_END_IGNORE_DEPRECATIONS \
-	} G_STMT_END
-#endif
-
-
-#if !GLIB_CHECK_VERSION (2, 40, 0)
-static inline gboolean
-_g_key_file_save_to_file (GKeyFile     *key_file,
-                          const gchar  *filename,
-                          GError      **error)
-{
-	gchar *contents;
-	gboolean success;
-	gsize length;
-
-	g_return_val_if_fail (key_file != NULL, FALSE);
-	g_return_val_if_fail (filename != NULL, FALSE);
-	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-	contents = g_key_file_to_data (key_file, &length, NULL);
-	g_assert (contents != NULL);
-
-	success = g_file_set_contents (filename, contents, length, error);
-	g_free (contents);
-
-	return success;
-}
-#define g_key_file_save_to_file(key_file, filename, error) \
-	_g_key_file_save_to_file (key_file, filename, error)
-#else
-#define g_key_file_save_to_file(key_file, filename, error) \
-	({ \
-		gboolean _success; \
-		\
-		G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
-		_success = g_key_file_save_to_file (key_file, filename, error); \
-		G_GNUC_END_IGNORE_DEPRECATIONS \
-		_success; \
-	})
-#endif
-
-
-#if GLIB_CHECK_VERSION (2, 36, 0)
-#define g_credentials_get_unix_pid(creds, error) \
-	({ \
-		G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
-			(g_credentials_get_unix_pid) ((creds), (error)); \
-		G_GNUC_END_IGNORE_DEPRECATIONS \
-	})
-#else
-#define g_credentials_get_unix_pid(creds, error) \
-	({ \
-		struct ucred *native_creds; \
-		 \
-		native_creds = g_credentials_get_native ((creds), G_CREDENTIALS_TYPE_LINUX_UCRED); \
-		g_assert (native_creds); \
-		native_creds->pid; \
-	})
-#endif
-
-
-#if !GLIB_CHECK_VERSION(2, 40, 0) || defined (NM_GLIB_COMPAT_H_TEST)
-static inline gpointer *
-_nm_g_hash_table_get_keys_as_array (GHashTable *hash_table,
-                                    guint      *length)
-{
-	GHashTableIter iter;
-	gpointer key, *ret;
-	guint i = 0;
-
-	g_return_val_if_fail (hash_table, NULL);
-
-	ret = g_new0 (gpointer, g_hash_table_size (hash_table) + 1);
-	g_hash_table_iter_init (&iter, hash_table);
-
-	while (g_hash_table_iter_next (&iter, &key, NULL))
-		ret[i++] = key;
-
-	ret[i] = NULL;
-
-	if (length)
-		*length = i;
-
-	return ret;
-}
-#endif
-#if !GLIB_CHECK_VERSION(2, 40, 0)
-#define g_hash_table_get_keys_as_array(hash_table, length) \
-	({ \
-		_nm_g_hash_table_get_keys_as_array (hash_table, length); \
-	})
-#else
-#define g_hash_table_get_keys_as_array(hash_table, length) \
-	({ \
-		G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
-			(g_hash_table_get_keys_as_array) ((hash_table), (length)); \
-		G_GNUC_END_IGNORE_DEPRECATIONS \
-	})
-#endif
-
-#ifndef g_info
-/* g_info was only added with 2.39.2 */
-#define g_info(...)     g_log (G_LOG_DOMAIN,         \
-                               G_LOG_LEVEL_INFO,     \
-                               __VA_ARGS__)
-#endif
-
 #if !GLIB_CHECK_VERSION(2, 44, 0)
 static inline gpointer
 g_steal_pointer (gpointer pp)
@@ -397,7 +85,6 @@ g_steal_pointer (gpointer pp)
   (0 ? (*(pp)) : (g_steal_pointer) (pp))
 #endif
 
-
 static inline gboolean
 _nm_g_strv_contains (const gchar * const *strv,
                      const gchar         *str)
@@ -420,70 +107,17 @@ _nm_g_strv_contains (const gchar * const *strv,
 }
 #define g_strv_contains _nm_g_strv_contains
 
-static inline GVariant *
-_nm_g_variant_new_take_string (gchar *string)
-{
-#if !GLIB_CHECK_VERSION(2, 36, 0)
-	GVariant *value;
-
-	g_return_val_if_fail (string != NULL, NULL);
-	g_return_val_if_fail (g_utf8_validate (string, -1, NULL), NULL);
-
-	value = g_variant_new_string (string);
-	g_free (string);
-	return value;
-#elif !GLIB_CHECK_VERSION(2, 38, 0)
-	GVariant *value;
-	GBytes *bytes;
-
-	g_return_val_if_fail (string != NULL, NULL);
-	g_return_val_if_fail (g_utf8_validate (string, -1, NULL), NULL);
-
-	bytes = g_bytes_new_take (string, strlen (string) + 1);
-	value = g_variant_new_from_bytes (G_VARIANT_TYPE_STRING, bytes, TRUE);
-	g_bytes_unref (bytes);
-
-	return value;
-#else
-	G_GNUC_BEGIN_IGNORE_DEPRECATIONS
-	return g_variant_new_take_string (string);
-	G_GNUC_END_IGNORE_DEPRECATIONS
-#endif
-}
-#define g_variant_new_take_string _nm_g_variant_new_take_string
-
-#if !GLIB_CHECK_VERSION(2, 38, 0)
-_nm_printf (1, 2)
-static inline GVariant *
-_nm_g_variant_new_printf (const char *format_string, ...)
-{
-	char *string;
-	va_list ap;
-
-	g_return_val_if_fail (format_string, NULL);
-
-	va_start (ap, format_string);
-	string = g_strdup_vprintf (format_string, ap);
-	va_end (ap);
-
-	return g_variant_new_take_string (string);
-}
-#define g_variant_new_printf(...) _nm_g_variant_new_printf(__VA_ARGS__)
-#else
-#define g_variant_new_printf(...) \
-	({ \
-		GVariant *_v; \
-		\
-		G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
-		_v = g_variant_new_printf (__VA_ARGS__); \
-		G_GNUC_END_IGNORE_DEPRECATIONS \
-		_v; \
-	})
-#endif
-
 #if !GLIB_CHECK_VERSION (2, 56, 0)
 #define g_object_ref(Obj)      ((typeof(Obj)) g_object_ref (Obj))
 #define g_object_ref_sink(Obj) ((typeof(Obj)) g_object_ref_sink (Obj))
 #endif
 
+#ifndef g_autofree
+/* we still don't rely on recent glib to provide g_autofree. Hence, we continue
+ * to use our gs_* free macros that we took from libgsystem.
+ *
+ * To ease migration towards g_auto*, add a compat define for g_autofree. */
+#define g_autofree gs_free
+#endif
+
 #endif  /* __NM_GLIB_H__ */
diff --git a/shared/nm-utils/nm-hash-utils.c b/shared/nm-utils/nm-hash-utils.c
index c563140e..4bc12b7c 100644
--- a/shared/nm-utils/nm-hash-utils.c
+++ b/shared/nm-utils/nm-hash-utils.c
@@ -35,33 +35,77 @@
 
 G_STATIC_ASSERT (sizeof (guint) * HASH_KEY_SIZE_GUINT >= HASH_KEY_SIZE);
 
+static const guint8 *volatile global_seed = NULL;
+
 static const guint8 *
-_get_hash_key (void)
+_get_hash_key_init (void)
 {
-	static const guint8 *volatile global_seed = NULL;
+	/* the returned hash is aligned to guin64, hence, it is safe
+	 * to use it as guint* or guint64* pointer. */
+	static union {
+		guint8 v8[HASH_KEY_SIZE];
+	} g_arr _nm_alignas (guint64);
+	static gsize g_lock;
 	const guint8 *g;
+	CSipHash siph_state;
+	uint64_t h;
+	guint *p;
 
 	g = global_seed;
-	if (G_UNLIKELY (g == NULL)) {
-		/* the returned hash is aligned to guin64, hence, it is save
-		 * to use it as guint* or guint64* pointer. */
-		static union {
-			guint8 v8[HASH_KEY_SIZE];
-		} g_arr _nm_alignas (guint64);
-		static gsize g_lock;
-
-		if (g_once_init_enter (&g_lock)) {
-			nm_utils_random_bytes (g_arr.v8, sizeof (g_arr.v8));
-			g_atomic_pointer_compare_and_exchange (&global_seed, NULL, g_arr.v8);
-			g = g_arr.v8;
-			g_once_init_leave (&g_lock, 1);
-		} else {
-			g = global_seed;
-			nm_assert (g);
-		}
+	if (G_LIKELY (g != NULL)) {
+		nm_assert (g == g_arr.v8);
+		return g;
 	}
 
-	return g;
+	if (g_once_init_enter (&g_lock)) {
+
+		nm_utils_random_bytes (g_arr.v8, sizeof (g_arr.v8));
+
+		/* use siphash() of the key-size, to mangle the first guint. Otherwise,
+		 * the first guint has only the entropy that nm_utils_random_bytes()
+		 * generated for the first 4 bytes and relies on a good random generator. */
+		c_siphash_init (&siph_state, g_arr.v8);
+		c_siphash_append (&siph_state, g_arr.v8, sizeof (g_arr.v8));
+		h = c_siphash_finalize (&siph_state);
+		p = (guint *) g_arr.v8;
+		if (sizeof (guint) < sizeof (h))
+			*p = *p ^ ((guint) (h & 0xFFFFFFFFu)) ^ ((guint) (h >> 32));
+		else
+			*p = *p ^ ((guint) (h & 0xFFFFFFFFu));
+
+		g_atomic_pointer_compare_and_exchange (&global_seed, NULL, g_arr.v8);
+		g_once_init_leave (&g_lock, 1);
+	}
+
+	nm_assert (global_seed == g_arr.v8);
+	return g_arr.v8;
+}
+
+#define _get_hash_key() \
+	({ \
+		const guint8 *_g; \
+		\
+		_g = global_seed; \
+		if (G_UNLIKELY (_g == NULL)) \
+			_g = _get_hash_key_init (); \
+		_g; \
+	})
+
+guint
+nm_hash_static (guint static_seed)
+{
+	/* note that we only xor the static_seed with the key.
+	 * We don't use siphash, which would mix the bits better.
+	 * Note that this doesn't matter, because static_seed is not
+	 * supposed to be a value that you are hashing (for that, use
+	 * full siphash).
+	 * Instead, different callers may set a different static_seed
+	 * so that nm_hash_str(NULL) != nm_hash_ptr(NULL).
+	 *
+	 * Also, ensure that we don't return zero.
+	 */
+	return ((*((const guint *) _get_hash_key ())) ^ static_seed)
+	       ?: static_seed ?: 3679500967u;
 }
 
 void
@@ -75,7 +119,7 @@ nm_hash_init (NMHashState *state, guint static_seed)
 	g = _get_hash_key ();
 	memcpy (seed, g, HASH_KEY_SIZE);
 	seed[0] ^= static_seed;
-	siphash24_init (&state->_state, (const guint8 *) seed);
+	c_siphash_init (&state->_state, (const guint8 *) seed);
 }
 
 guint
@@ -83,11 +127,10 @@ nm_hash_str (const char *str)
 {
 	NMHashState h;
 
-	if (str) {
-		nm_hash_init (&h, 1867854211u);
-		nm_hash_update_str (&h, str);
-	} else
-		nm_hash_init (&h, 842995561u);
+	if (!str)
+		return nm_hash_static (1867854211u);
+	nm_hash_init (&h, 1867854211u);
+	nm_hash_update_str (&h, str);
 	return nm_hash_complete (&h);
 }
 
@@ -100,16 +143,13 @@ nm_str_hash (gconstpointer str)
 guint
 nm_hash_ptr (gconstpointer ptr)
 {
-	guint h;
-
-	h = ((const guint *) _get_hash_key ())[0];
-
-	if (sizeof (ptr) <= sizeof (guint))
-		h = h ^ ((guint) ((uintptr_t) ptr));
-	else
-		h = h ^ ((guint) (((guint64) (uintptr_t) ptr) >> 32)) ^ ((guint) ((uintptr_t) ptr));
+	NMHashState h;
 
-	return h ?: 2907677551u;
+	if (!ptr)
+		return nm_hash_static (2907677551u);
+	nm_hash_init (&h, 2907677551u);
+	nm_hash_update (&h, &ptr, sizeof (ptr));
+	return nm_hash_complete (&h);
 }
 
 guint
@@ -117,3 +157,27 @@ nm_direct_hash (gconstpointer ptr)
 {
 	return nm_hash_ptr (ptr);
 }
+
+/*****************************************************************************/
+
+guint
+nm_pstr_hash (gconstpointer p)
+{
+	const char *const*s = p;
+
+	if (!s)
+		return nm_hash_static (101061439u);
+	return nm_hash_str (*s);
+}
+
+gboolean
+nm_pstr_equal (gconstpointer a, gconstpointer b)
+{
+	const char *const*s1 = a;
+	const char *const*s2 = b;
+
+	return    (s1 == s2)
+	       || (   s1
+	           && s2
+	           && nm_streq0 (*s1, *s2));
+}
diff --git a/shared/nm-utils/nm-hash-utils.h b/shared/nm-utils/nm-hash-utils.h
index 276e1ebe..b7742e0f 100644
--- a/shared/nm-utils/nm-hash-utils.h
+++ b/shared/nm-utils/nm-hash-utils.h
@@ -22,15 +22,17 @@
 #ifndef __NM_HASH_UTILS_H__
 #define __NM_HASH_UTILS_H__
 
-#include "siphash24.h"
+#include "c-siphash/src/c-siphash.h"
 #include "nm-macros-internal.h"
 
 struct _NMHashState {
-	struct siphash _state;
+	CSipHash _state;
 };
 
 typedef struct _NMHashState NMHashState;
 
+guint nm_hash_static (guint static_seed);
+
 void nm_hash_init (NMHashState *state, guint static_seed);
 
 static inline guint
@@ -40,7 +42,7 @@ nm_hash_complete (NMHashState *state)
 
 	nm_assert (state);
 
-	h = siphash24_finalize (&state->_state);
+	h = c_siphash_finalize (&state->_state);
 
 	/* we don't ever want to return a zero hash.
 	 *
@@ -55,7 +57,7 @@ nm_hash_update (NMHashState *state, const void *ptr, gsize n)
 	nm_assert (ptr);
 	nm_assert (n > 0);
 
-	siphash24_compress (ptr, n, &state->_state);
+	c_siphash_append (&state->_state, ptr, n);
 }
 
 #define nm_hash_update_val(state, val) \
@@ -166,7 +168,7 @@ nm_hash_update_mem (NMHashState *state, const void *ptr, gsize n)
 	 * instead. */
 	nm_hash_update (state, &n, sizeof (n));
 	if (n > 0)
-		siphash24_compress (ptr, n, &state->_state);
+		c_siphash_append (&state->_state, ptr, n);
 }
 
 static inline void
@@ -207,4 +209,15 @@ guint nm_direct_hash (gconstpointer str);
 guint nm_hash_str (const char *str);
 guint nm_str_hash (gconstpointer str);
 
+/*****************************************************************************/
+
+/* nm_pstr_*() are for hashing keys that are pointers to strings,
+ * that is, "const char *const*" types, using strcmp(). */
+
+guint nm_pstr_hash (gconstpointer p);
+
+gboolean nm_pstr_equal (gconstpointer a, gconstpointer b);
+
+/*****************************************************************************/
+
 #endif /* __NM_HASH_UTILS_H__ */
diff --git a/shared/nm-utils/nm-jansson.h b/shared/nm-utils/nm-jansson.h
new file mode 100644
index 00000000..b00c75c6
--- /dev/null
+++ b/shared/nm-utils/nm-jansson.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_JANSSON_H__
+#define __NM_JANSSON_H__
+
+/* you need to include at least "config.h" first, possibly "nm-default.h". */
+
+#if WITH_JANSSON
+
+#include <jansson.h>
+
+/* Added in Jansson v2.7 */
+#ifndef json_boolean_value
+#define json_boolean_value json_is_true
+#endif
+
+/* Added in Jansson v2.8 */
+#ifndef json_object_foreach_safe
+#define json_object_foreach_safe(object, n, key, value)     \
+    for(key = json_object_iter_key(json_object_iter(object)), \
+            n = json_object_iter_next(object, json_object_key_to_iter(key)); \
+        key && (value = json_object_iter_value(json_object_key_to_iter(key))); \
+        key = json_object_iter_key(n), \
+            n = json_object_iter_next(object, json_object_key_to_iter(key)))
+#endif
+
+#endif /* WITH_JANSON */
+
+#endif  /* __NM_JANSSON_H__ */
diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h
index 29678bb6..908b25fd 100644
--- a/shared/nm-utils/nm-macros-internal.h
+++ b/shared/nm-utils/nm-macros-internal.h
@@ -35,6 +35,12 @@
 #define _nm_alignof(type)    __alignof (type)
 #define _nm_alignas(type)    _nm_align (_nm_alignof (type))
 
+#if __GNUC__ >= 7
+#define _nm_fallthrough      __attribute__ ((fallthrough))
+#else
+#define _nm_fallthrough
+#endif
+
 /*****************************************************************************/
 
 #ifdef thread_local
@@ -70,6 +76,30 @@ static inline int nm_close (int fd);
 GS_DEFINE_CLEANUP_FUNCTION(void*, _nm_auto_free_impl, free)
 
 static inline void
+nm_free_secret (char *secret)
+{
+	if (secret) {
+		memset (secret, 0, strlen (secret));
+		g_free (secret);
+	}
+}
+
+static inline void
+_nm_auto_free_secret_impl (char **v)
+{
+	nm_free_secret (*v);
+}
+
+/**
+ * nm_auto_free_secret:
+ *
+ * Call g_free() on a variable location when it goes out of scope.
+ * Also, previously, calls memset(loc, 0, strlen(loc)) to clear out
+ * the secret.
+ */
+#define nm_auto_free_secret nm_auto(_nm_auto_free_secret_impl)
+
+static inline void
 _nm_auto_unset_gvalue_impl (GValue *v)
 {
 	g_value_unset (v);
@@ -123,6 +153,14 @@ _nm_auto_protect_errno (int *p_saved_errno)
 }
 #define NM_AUTO_PROTECT_ERRNO(errsv_saved) nm_auto(_nm_auto_protect_errno) _nm_unused const int errsv_saved = (errno)
 
+static inline void
+_nm_auto_unref_gsource (GSource **ptr)
+{
+	if (*ptr)
+		g_source_unref (g_steal_pointer (ptr));
+}
+#define nm_auto_unref_gsource nm_auto(_nm_auto_unref_gsource)
+
 /*****************************************************************************/
 
 /* http://stackoverflow.com/a/11172679 */
@@ -241,7 +279,8 @@ NM_G_ERROR_MSG (GError *error)
 		gsize _n = 0; \
 		\
 		if (_array) { \
-			_nm_unused typeof (*(_array[0])) *_array_check = _array[0]; \
+			_nm_unused gconstpointer _type_check_is_pointer = _array[0]; \
+			\
 			while (_array[_n]) \
 				_n++; \
 		} \
@@ -359,6 +398,28 @@ NM_G_ERROR_MSG (GError *error)
 #define NM_CONSTCAST(type, obj, ...) \
 	NM_CONSTCAST_FULL(type, (obj), (obj), ##__VA_ARGS__)
 
+#if _NM_CC_SUPPORT_GENERIC
+#define NM_UNCONST_PTR(type, arg) \
+	_Generic ((arg), \
+	          const type *: ((type *) (arg)), \
+	                type *: ((type *) (arg)))
+#else
+#define NM_UNCONST_PTR(type, arg) \
+	((type *) (arg))
+#endif
+
+#if _NM_CC_SUPPORT_GENERIC
+#define NM_UNCONST_PPTR(type, arg) \
+	_Generic ((arg), \
+	          const type *     *: ((type **) (arg)), \
+	                type *     *: ((type **) (arg)), \
+	          const type *const*: ((type **) (arg)), \
+	                type *const*: ((type **) (arg)))
+#else
+#define NM_UNCONST_PPTR(type, arg) \
+	((type **) (arg))
+#endif
+
 #define NM_GOBJECT_CAST(type, obj, is_check, ...) \
 	({ \
 		const void *_obj = (obj); \
@@ -388,6 +449,41 @@ NM_G_ERROR_MSG (GError *error)
 #endif
 
 #if _NM_CC_SUPPORT_GENERIC
+/* these macros cast (value) to
+ *  - "const char **"      (for "MC", mutable-const)
+ *  - "const char *const*" (for "CC", const-const)
+ * The point is to do this cast, but only accepting pointers
+ * that are compatible already.
+ *
+ * The problem is, if you add a function like g_strdupv(), the input
+ * argument is not modified (CC), but you want to make it work also
+ * for "char **". C doesn't allow this form of casting (for good reasons),
+ * so the function makes a choice like g_strdupv(char**). That means,
+ * every time you want to call ith with a const argument, you need to
+ * explicitly cast it.
+ *
+ * These macros do the cast, but they only accept a compatible input
+ * type, otherwise they will fail compilation.
+ */
+#define NM_CAST_STRV_MC(value) \
+	(_Generic ((value), \
+	           const char *     *: (const char *     *) (value), \
+	                 char *     *: (const char *     *) (value), \
+	                       void *: (const char *     *) (value)))
+#define NM_CAST_STRV_CC(value) \
+	(_Generic ((value), \
+	           const char *const*: (const char *const*) (value), \
+	           const char *     *: (const char *const*) (value), \
+	                 char *const*: (const char *const*) (value), \
+	                 char *     *: (const char *const*) (value), \
+	                 const void *: (const char *const*) (value), \
+	                       void *: (const char *const*) (value)))
+#else
+#define NM_CAST_STRV_MC(value) ((const char *     *) (value))
+#define NM_CAST_STRV_CC(value) ((const char *const*) (value))
+#endif
+
+#if _NM_CC_SUPPORT_GENERIC
 #define NM_PROPAGATE_CONST(test_expr, ptr) \
 	(_Generic ((test_expr), \
 	           const typeof (*(test_expr)) *: ((const typeof (*(ptr)) *) (ptr)), \
@@ -741,6 +837,32 @@ nm_g_object_unref (gpointer obj)
 		_changed; \
 	})
 
+#define nm_clear_pointer(pp, destroy) \
+	({ \
+		typeof (*(pp)) *_pp = (pp); \
+		typeof (*_pp) _p; \
+		gboolean _changed = FALSE; \
+		\
+		if (   _pp \
+		    && (_p = *_pp)) { \
+			_nm_unused gconstpointer _p_check_is_pointer = _p; \
+			\
+			*_pp = NULL; \
+			/* g_clear_pointer() assigns @destroy first to a local variable, so that
+			 * you can call "g_clear_pointer (pp, (GDestroyNotify) destroy);" without
+			 * gcc emitting a warning. We don't do that, hence, you cannot cast
+			 * "destroy" first.
+			 *
+			 * On the upside: you are not supposed to cast fcn, because the pointer
+			 * types are preserved. If you really need a cast, you should cast @pp.
+			 * But that is hardly ever necessary. */ \
+			(destroy) (_p); \
+			\
+			_changed = TRUE; \
+		} \
+		_changed; \
+	})
+
 /* basically, replaces
  *   g_clear_pointer (&location, g_free)
  * with
@@ -751,42 +873,20 @@ nm_g_object_unref (gpointer obj)
  * pointer or points to a const-pointer.
  */
 #define nm_clear_g_free(pp) \
-	({  \
-		typeof (*(pp)) *_pp = (pp); \
-		typeof (**_pp) *_p; \
-		gboolean _changed = FALSE; \
-		\
-		if (  _pp \
-		    && (_p = *_pp)) { \
-			*_pp = NULL; \
-			g_free (_p); \
-			_changed = TRUE; \
-		} \
-		_changed; \
-	})
+	nm_clear_pointer (pp, g_free)
 
 #define nm_clear_g_object(pp) \
-	({ \
-		typeof (*(pp)) *_pp = (pp); \
-		typeof (**_pp) *_p; \
-		gboolean _changed = FALSE; \
-		\
-		if (   _pp \
-		    && (_p = *_pp)) { \
-			nm_assert (G_IS_OBJECT (_p)); \
-			*_pp = NULL; \
-			g_object_unref (_p); \
-			_changed = TRUE; \
-		} \
-		_changed; \
-	})
+	nm_clear_pointer (pp, g_object_unref)
 
 static inline gboolean
 nm_clear_g_source (guint *id)
 {
-	if (id && *id) {
-		g_source_remove (*id);
+	guint v;
+
+	if (   id
+	    && (v = *id)) {
 		*id = 0;
+		g_source_remove (v);
 		return TRUE;
 	}
 	return FALSE;
@@ -795,9 +895,12 @@ nm_clear_g_source (guint *id)
 static inline gboolean
 nm_clear_g_signal_handler (gpointer self, gulong *id)
 {
-	if (id && *id) {
-		g_signal_handler_disconnect (self, *id);
+	gulong v;
+
+	if (   id
+	    && (v = *id)) {
 		*id = 0;
+		g_signal_handler_disconnect (self, v);
 		return TRUE;
 	}
 	return FALSE;
@@ -806,9 +909,12 @@ nm_clear_g_signal_handler (gpointer self, gulong *id)
 static inline gboolean
 nm_clear_g_variant (GVariant **variant)
 {
-	if (variant && *variant) {
-		g_variant_unref (*variant);
+	GVariant *v;
+
+	if (   variant
+	    && (v = *variant)) {
 		*variant = NULL;
+		g_variant_unref (v);
 		return TRUE;
 	}
 	return FALSE;
@@ -817,10 +923,13 @@ nm_clear_g_variant (GVariant **variant)
 static inline gboolean
 nm_clear_g_cancellable (GCancellable **cancellable)
 {
-	if (cancellable && *cancellable) {
-		g_cancellable_cancel (*cancellable);
-		g_object_unref (*cancellable);
+	GCancellable *v;
+
+	if (   cancellable
+	    && (v = *cancellable)) {
 		*cancellable = NULL;
+		g_cancellable_cancel (v);
+		g_object_unref (v);
 		return TRUE;
 	}
 	return FALSE;
@@ -991,35 +1100,6 @@ nm_strcmp_p (gconstpointer a, gconstpointer b)
 	return strcmp (s1, s2);
 }
 
-/* like nm_strcmp_p(), suitable for g_ptr_array_sort_with_data().
- * g_ptr_array_sort() just casts nm_strcmp_p() to a function of different
- * signature. I guess, in glib there are knowledgeable people that ensure
- * that this additional argument doesn't cause problems due to different ABI
- * for every architecture that glib supports.
- * For NetworkManager, we'd rather avoid such stunts.
- **/
-static inline int
-nm_strcmp_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data)
-{
-	const char *s1 = *((const char **) a);
-	const char *s2 = *((const char **) b);
-
-	return strcmp (s1, s2);
-}
-
-static inline int
-nm_cmp_uint32_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data)
-{
-	const guint32 a = *((const guint32 *) p_a);
-	const guint32 b = *((const guint32 *) p_b);
-
-	if (a < b)
-		return -1;
-	if (a > b)
-		return 1;
-	return 0;
-}
-
 /*****************************************************************************/
 
 /* Taken from systemd's UNIQ_T and UNIQ macros. */
@@ -1150,6 +1230,28 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro)
 		_buf; \
 	})
 
+/* aims to alloca() a buffer and fill it with printf(format, name).
+ * Note that format must not contain any format specifier except
+ * "%s".
+ * If the resulting string would be too large for stack allocation,
+ * it allocates a buffer with g_malloc() and assigns it to *p_val_to_free. */
+#define nm_construct_name_a(format, name, p_val_to_free) \
+	({ \
+		const char *const _name = (name); \
+		char **const _p_val_to_free = (p_val_to_free); \
+		const gsize _name_len = strlen (_name); \
+		char *_buf2; \
+		\
+		nm_assert (_p_val_to_free && !*_p_val_to_free); \
+		if (NM_STRLEN (format) + _name_len < 200) \
+			_buf2 = nm_sprintf_bufa (NM_STRLEN (format) + _name_len, format, _name); \
+		else { \
+			_buf2 = g_strdup_printf (format, _name); \
+			*_p_val_to_free = _buf2; \
+		} \
+		(const char *) _buf2; \
+	})
+
 /*****************************************************************************/
 
 /**
@@ -1197,7 +1299,6 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro)
 #define false   0
 #endif
 
-
 #ifdef _G_BOOLEAN_EXPR
 /* g_assert() uses G_LIKELY(), which in turn uses _G_BOOLEAN_EXPR().
  * As glib's implementation uses a local variable _g_boolean_var_,
@@ -1225,6 +1326,25 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro)
 
 /*****************************************************************************/
 
+/**
+ * nm_steal_int:
+ * @p_val: pointer to an int type.
+ *
+ * Returns: *p_val and sets *p_val to zero the same time.
+ *   Accepts %NULL, in which case also numeric 0 will be returned.
+ */
+#define nm_steal_int(p_val) \
+	({ \
+		typeof (p_val) const _p_val = (p_val); \
+		typeof (*_p_val) _val = 0; \
+		\
+		if (   _p_val \
+		    && (_val = *_p_val)) { \
+			*_p_val = 0; \
+		} \
+		_val; \
+	})
+
 static inline int
 nm_steal_fd (int *p_fd)
 {
@@ -1255,4 +1375,6 @@ nm_close (int fd)
 	return r;
 }
 
+#define NM_PID_T_INVAL ((pid_t) -1)
+
 #endif /* __NM_MACROS_INTERNAL_H__ */
diff --git a/shared/nm-utils/nm-obj.h b/shared/nm-utils/nm-obj.h
index 1a9d4868..4edd1f3e 100644
--- a/shared/nm-utils/nm-obj.h
+++ b/shared/nm-utils/nm-obj.h
@@ -56,7 +56,7 @@ struct _NMObjBaseClass {
 	 * Note that it is also an abstract super class of GTypeInstance, that means
 	 * you may implement a NMObjBaseClass as a subtype of GTypeClass.
 	 *
-	 * For that to work, you must properly set the GTypeClass instance (and it's
+	 * For that to work, you must properly set the GTypeClass instance (and its
 	 * GType).
 	 *
 	 * Note that to implement a NMObjBaseClass that is *not* a GTypeClass, you wouldn't
diff --git a/shared/nm-utils/nm-random-utils.c b/shared/nm-utils/nm-random-utils.c
index 65986b3d..3e968a8e 100644
--- a/shared/nm-utils/nm-random-utils.c
+++ b/shared/nm-utils/nm-random-utils.c
@@ -52,7 +52,7 @@
  * value.
  *
  * Note that if calling getrandom() fails because there is not enough
- * entroy (at early boot), the function will read /dev/urandom.
+ * entropy (at early boot), the function will read /dev/urandom.
  * Which of course, still has low entropy, and cause kernel to log
  * a warning.
  */
diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c
index 0b343afd..d0019c11 100644
--- a/shared/nm-utils/nm-shared-utils.c
+++ b/shared/nm-utils/nm-shared-utils.c
@@ -499,6 +499,158 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma
 
 /*****************************************************************************/
 
+/* like nm_strcmp_p(), suitable for g_ptr_array_sort_with_data().
+ * g_ptr_array_sort() just casts nm_strcmp_p() to a function of different
+ * signature. I guess, in glib there are knowledgeable people that ensure
+ * that this additional argument doesn't cause problems due to different ABI
+ * for every architecture that glib supports.
+ * For NetworkManager, we'd rather avoid such stunts.
+ **/
+int
+nm_strcmp_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+	const char *s1 = *((const char **) a);
+	const char *s2 = *((const char **) b);
+
+	return strcmp (s1, s2);
+}
+
+int
+nm_cmp_uint32_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data)
+{
+	const guint32 a = *((const guint32 *) p_a);
+	const guint32 b = *((const guint32 *) p_b);
+
+	if (a < b)
+		return -1;
+	if (a > b)
+		return 1;
+	return 0;
+}
+
+int
+nm_cmp_int2ptr_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data)
+{
+	/* p_a and p_b are two pointers to a pointer, where the pointer is
+	 * interpreted as a integer using GPOINTER_TO_INT().
+	 *
+	 * That is the case of a hash-table that uses GINT_TO_POINTER() to
+	 * convert integers as pointers, and the resulting keys-as-array
+	 * array. */
+	const int a = GPOINTER_TO_INT (*((gconstpointer *) p_a));
+	const int b = GPOINTER_TO_INT (*((gconstpointer *) p_b));
+
+	if (a < b)
+		return -1;
+	if (a > b)
+		return 1;
+	return 0;
+}
+
+/*****************************************************************************/
+
+const char *
+nm_utils_dbus_path_get_last_component (const char *dbus_path)
+{
+	if (dbus_path) {
+		dbus_path = strrchr (dbus_path, '/');
+		if (dbus_path)
+			return dbus_path + 1;
+	}
+	return NULL;
+}
+
+static gint64
+_dbus_path_component_as_num (const char *p)
+{
+	gint64 n;
+
+	/* no odd stuff. No leading zeros, only a non-negative, decimal integer.
+	 *
+	 * Otherwise, there would be multiple ways to encode the same number "10"
+	 * and "010". That is just confusing. A number has no leading zeros,
+	 * if it has, it's not a number (as far as we are concerned here). */
+	if (p[0] == '0') {
+		if (p[1] != '\0')
+			return -1;
+		else
+			return 0;
+	}
+	if (!(p[0] >= '1' && p[0] <= '9'))
+		return -1;
+	if (!NM_STRCHAR_ALL (&p[1], ch, (ch >= '0' && ch <= '9')))
+		return -1;
+	n = _nm_utils_ascii_str_to_int64 (p, 10, 0, G_MAXINT64, -1);
+	nm_assert (n == -1 || nm_streq0 (p, nm_sprintf_bufa (100, "%"G_GINT64_FORMAT, n)));
+	return n;
+}
+
+int
+nm_utils_dbus_path_cmp (const char *dbus_path_a, const char *dbus_path_b)
+{
+	const char *l_a, *l_b;
+	gsize plen;
+	gint64 n_a, n_b;
+
+	/* compare function for two D-Bus paths. It behaves like
+	 * strcmp(), except, if both paths have the same prefix,
+	 * and both end in a (positive) number, then the paths
+	 * will be sorted by number. */
+
+	NM_CMP_SELF (dbus_path_a, dbus_path_b);
+
+	/* if one or both paths have no slash (and no last component)
+	 * compare the full paths directly. */
+	if (   !(l_a = nm_utils_dbus_path_get_last_component (dbus_path_a))
+	    || !(l_b = nm_utils_dbus_path_get_last_component (dbus_path_b)))
+		goto comp_full;
+
+	/* check if both paths have the same prefix (up to the last-component). */
+	plen = l_a - dbus_path_a;
+	if (plen != (l_b - dbus_path_b))
+		goto comp_full;
+	NM_CMP_RETURN (strncmp (dbus_path_a, dbus_path_b, plen));
+
+	n_a = _dbus_path_component_as_num (l_a);
+	n_b = _dbus_path_component_as_num (l_b);
+	if (n_a == -1 && n_b == -1)
+		goto comp_l;
+
+	/* both components must be convertiable to a number. If they are not,
+	 * (and only one of them is), then we must always strictly sort numeric parts
+	 * after non-numeric components. If we wouldn't, we wouldn't have
+	 * a total order.
+	 *
+	 * An example of a not total ordering would be:
+	 *   "8"   < "010"  (numeric)
+	 *   "0x"  < "8"    (lexical)
+	 *   "0x"  > "010"  (lexical)
+	 * We avoid this, by forcing that a non-numeric entry "0x" always sorts
+	 * before numeric entries.
+	 *
+	 * Additionally, _dbus_path_component_as_num() would also reject "010" as
+	 * not a valid number.
+	 */
+	if (n_a == -1)
+		return -1;
+	if (n_b == -1)
+		return 1;
+
+	NM_CMP_DIRECT (n_a, n_b);
+	nm_assert (nm_streq (dbus_path_a, dbus_path_b));
+	return 0;
+
+comp_full:
+	NM_CMP_DIRECT_STRCMP0 (dbus_path_a, dbus_path_b);
+	return 0;
+comp_l:
+	NM_CMP_DIRECT_STRCMP0 (l_a, l_b);
+	nm_assert (nm_streq (dbus_path_a, dbus_path_b));
+	return 0;
+}
+
+/*****************************************************************************/
+
 /**
  * nm_utils_strsplit_set:
  * @str: the string to split.
@@ -1115,3 +1267,235 @@ nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll)
 
 	return 0;
 }
+
+NMUtilsNamedValue *
+nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len)
+{
+	GHashTableIter iter;
+	NMUtilsNamedValue *values;
+	guint i, len;
+
+	if (   !hash
+	    || !(len = g_hash_table_size (hash))) {
+		NM_SET_OUT (out_len, 0);
+		return NULL;
+	}
+
+	i = 0;
+	values = g_new (NMUtilsNamedValue, len + 1);
+	g_hash_table_iter_init (&iter, hash);
+	while (g_hash_table_iter_next (&iter,
+	                               (gpointer *) &values[i].name,
+	                               (gpointer *) &values[i].value_ptr))
+		i++;
+	nm_assert (i == len);
+	values[i].name = NULL;
+	values[i].value_ptr = NULL;
+
+	if (len > 1) {
+		g_qsort_with_data (values, len, sizeof (values[0]),
+		                   nm_utils_named_entry_cmp_with_data, NULL);
+	}
+
+	NM_SET_OUT (out_len, len);
+	return values;
+}
+
+gpointer *
+nm_utils_hash_keys_to_array (GHashTable *hash,
+                             GCompareDataFunc compare_func,
+                             gpointer user_data,
+                             guint *out_len)
+{
+	guint len;
+	gpointer *keys;
+
+	/* by convention, we never return an empty array. In that
+	 * case, always %NULL. */
+	if (   !hash
+	    || g_hash_table_size (hash) == 0) {
+		NM_SET_OUT (out_len, 0);
+		return NULL;
+	}
+
+	keys = g_hash_table_get_keys_as_array (hash, &len);
+	if (   len > 1
+	    && compare_func) {
+		g_qsort_with_data (keys,
+		                   len,
+		                   sizeof (gpointer),
+		                   compare_func,
+		                   user_data);
+	}
+	NM_SET_OUT (out_len, len);
+	return keys;
+}
+
+char **
+nm_utils_strv_make_deep_copied (const char **strv)
+{
+	gsize i;
+
+	/* it takes a strv dictionary, and copies each
+	 * strings. Note that this updates @strv *in-place*
+	 * and returns it. */
+
+	if (!strv)
+		return NULL;
+	for (i = 0; strv[i]; i++)
+		strv[i] = g_strdup (strv[i]);
+
+	return (char **) strv;
+}
+
+/*****************************************************************************/
+
+/**
+ * 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`.
+ * @out_ppid: parent process id
+ *
+ * Originally copied from polkit source (src/polkit/polkitunixprocess.c)
+ * and adjusted.
+ *
+ * Returns: the timestamp when the process started (by parsing /proc/$PID/stat).
+ * If an error occurs (e.g. the process does not exist), 0 is returned.
+ *
+ * The returned start time counts since boot, in the unit HZ (with HZ usually being (1/100) seconds)
+ **/
+guint64
+nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid)
+{
+	guint64 start_time;
+	char filename[256];
+	gs_free gchar *contents = NULL;
+	size_t length;
+	gs_free const char **tokens = NULL;
+	gchar *p;
+	char state = ' ';
+	gint64 ppid = 0;
+
+	start_time = 0;
+	contents = NULL;
+
+	g_return_val_if_fail (pid > 0, 0);
+
+	nm_sprintf_buf (filename, "/proc/%"G_GUINT64_FORMAT"/stat", (guint64) pid);
+
+	if (!g_file_get_contents (filename, &contents, &length, NULL))
+		goto fail;
+
+	/* start time is the token at index 19 after the '(process name)' entry - since only this
+	 * field can contain the ')' character, search backwards for this to avoid malicious
+	 * processes trying to fool us
+	 */
+	p = strrchr (contents, ')');
+	if (!p)
+		goto fail;
+	p += 2; /* skip ') ' */
+	if (p - contents >= (int) length)
+		goto fail;
+
+	state = p[0];
+
+	tokens = nm_utils_strsplit_set (p, " ");
+
+	if (NM_PTRARRAY_LEN (tokens) < 20)
+		goto fail;
+
+	if (out_ppid) {
+		ppid = _nm_utils_ascii_str_to_int64 (tokens[1], 10, 1, G_MAXINT, 0);
+		if (ppid == 0)
+			goto fail;
+	}
+
+	start_time = _nm_utils_ascii_str_to_int64 (tokens[19], 10, 1, G_MAXINT64, 0);
+	if (start_time == 0)
+		goto fail;
+
+	NM_SET_OUT (out_state, state);
+	NM_SET_OUT (out_ppid, ppid);
+	return start_time;
+
+fail:
+	NM_SET_OUT (out_state, ' ');
+	NM_SET_OUT (out_ppid, 0);
+	return 0;
+}
+
+/*****************************************************************************/
+
+/**
+ * _nm_utils_strv_sort:
+ * @strv: pointer containing strings that will be sorted
+ *   in-place, %NULL is allowed, unless @len indicates
+ *   that there are more elements.
+ * @len: the number of elements in strv. If negative,
+ *   strv must be a NULL terminated array and the length
+ *   will be calculated first. If @len is a positive
+ *   number, all first @len elements in @strv must be
+ *   non-NULL, valid strings.
+ *
+ * Ascending sort of the array @strv inplace, using plain strcmp() string
+ * comparison.
+ */
+void
+_nm_utils_strv_sort (const char **strv, gssize len)
+{
+	gsize l;
+
+	l = len < 0 ? (gsize) NM_PTRARRAY_LEN (strv) : (gsize) len;
+
+	if (l <= 1)
+		return;
+
+	nm_assert (l <= (gsize) G_MAXINT);
+
+	g_qsort_with_data (strv,
+	                   l,
+	                   sizeof (const char *),
+	                   nm_strcmp_p_with_data,
+	                   NULL);
+}
+
+/*****************************************************************************/
+
+gpointer
+_nm_utils_user_data_pack (int nargs, gconstpointer *args)
+{
+	int i;
+	gpointer *data;
+
+	nm_assert (nargs > 0);
+	nm_assert (args);
+
+	data = g_slice_alloc (((gsize) nargs) * sizeof (gconstpointer));
+	for (i = 0; i < nargs; i++)
+		data[i] = (gpointer) args[i];
+	return data;
+}
+
+void
+_nm_utils_user_data_unpack (gpointer user_data, int nargs, ...)
+{
+	gpointer *data = user_data;
+	va_list ap;
+	int i;
+
+	nm_assert (data);
+	nm_assert (nargs > 0);
+
+	va_start (ap, nargs);
+	for (i = 0; i < nargs; i++) {
+		gpointer *dst;
+
+		dst = va_arg (ap, gpointer *);
+		nm_assert (dst);
+
+		*dst = data[i];
+	}
+	va_end (ap);
+
+	g_slice_free1 (((gsize) nargs) * sizeof (gconstpointer), user_data);
+}
diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h
index d6d829cd..4b081630 100644
--- a/shared/nm-utils/nm-shared-utils.h
+++ b/shared/nm-utils/nm-shared-utils.h
@@ -113,6 +113,9 @@ nm_ip_addr_set (int addr_family, gpointer dst, const NMIPAddr *src)
 #define NM_CMP_DIRECT_MEMCMP(a, b, size) \
     NM_CMP_RETURN (memcmp ((a), (b), (size)))
 
+#define NM_CMP_DIRECT_STRCMP0(a, b) \
+    NM_CMP_RETURN (g_strcmp0 ((a), (b)))
+
 #define NM_CMP_DIRECT_IN6ADDR(a, b) \
     G_STMT_START { \
         const struct in6_addr *const _a = (a); \
@@ -191,6 +194,18 @@ void nm_utils_strbuf_append_str (char **buf, gsize *len, const char *str);
 
 const char *nm_strquote (char *buf, gsize buf_len, const char *str);
 
+static inline gboolean
+nm_utils_is_separator (const char c)
+{
+	return NM_IN_SET (c, ' ', '\t');
+}
+
+/*****************************************************************************/
+
+const char *nm_utils_dbus_path_get_last_component (const char *dbus_path);
+
+int nm_utils_dbus_path_cmp (const char *dbus_path_a, const char *dbus_path_b);
+
 /*****************************************************************************/
 
 const char **nm_utils_strsplit_set (const char *str, const char *delimiters);
@@ -326,12 +341,18 @@ _nm_g_slice_free_fcn_define (16)
 		/* If mem_size is a compile time constant, the compiler
 		 * will be able to optimize this. Hence, you don't want
 		 * to call this with a non-constant size argument. */ \
-		switch (mem_size) { \
+		G_STATIC_ASSERT_EXPR (   ((mem_size) ==  1) \
+		                      || ((mem_size) ==  2) \
+		                      || ((mem_size) ==  4) \
+		                      || ((mem_size) ==  8) \
+		                      || ((mem_size) == 12) \
+		                      || ((mem_size) == 16)); \
+		switch ((mem_size)) { \
 		case  1: _fcn = _nm_g_slice_free_fcn_1;  break; \
 		case  2: _fcn = _nm_g_slice_free_fcn_2;  break; \
 		case  4: _fcn = _nm_g_slice_free_fcn_4;  break; \
 		case  8: _fcn = _nm_g_slice_free_fcn_8;  break; \
-		case 12: _fcn = _nm_g_slice_free_fcn_12;  break; \
+		case 12: _fcn = _nm_g_slice_free_fcn_12; break; \
 		case 16: _fcn = _nm_g_slice_free_fcn_16; break; \
 		default: g_assert_not_reached (); _fcn = NULL; break; \
 		} \
@@ -415,6 +436,39 @@ char *nm_utils_str_utf8safe_unescape_cp (const char *str);
 
 char *nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags);
 
+static inline void
+nm_g_variant_unref_floating (GVariant *var)
+{
+	/* often a function wants to keep a reference to an input variant.
+	 * It uses g_variant_ref_sink() to either increase the ref-count,
+	 * or take ownership of a possibly floating reference.
+	 *
+	 * If the function doesn't actually want to do anything with the
+	 * input variant, it still must make sure that a passed in floating
+	 * reference is consumed. Hence, this helper which:
+	 *
+	 *   - does nothing if @var is not floating
+	 *   - unrefs (consumes) @var if it is floating. */
+	if (g_variant_is_floating (var))
+		g_variant_unref (var);
+}
+
+/*****************************************************************************/
+
+static inline int
+nm_utf8_collate0 (const char *a, const char *b)
+{
+	if (!a)
+		return !b ? 0 : -1;
+	if (!b)
+		return 1;
+	return g_utf8_collate (a, b);
+}
+
+int nm_strcmp_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data);
+int nm_cmp_uint32_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data);
+int nm_cmp_int2ptr_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data);
+
 /*****************************************************************************/
 
 typedef struct {
@@ -435,6 +489,35 @@ typedef struct {
 #define nm_utils_named_entry_cmp           nm_strcmp_p
 #define nm_utils_named_entry_cmp_with_data nm_strcmp_p_with_data
 
+NMUtilsNamedValue *nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len);
+
+gpointer *nm_utils_hash_keys_to_array (GHashTable *hash,
+                                       GCompareDataFunc compare_func,
+                                       gpointer user_data,
+                                       guint *out_len);
+
+static inline const char **
+nm_utils_strdict_get_keys (const GHashTable *hash,
+                           gboolean sorted,
+                           guint *out_length)
+{
+	return (const char **) nm_utils_hash_keys_to_array ((GHashTable *) hash,
+	                                                    sorted ? nm_strcmp_p_with_data : NULL,
+	                                                    NULL,
+	                                                    out_length);
+}
+
+char **nm_utils_strv_make_deep_copied (const char **strv);
+
+static inline char **
+nm_utils_strv_make_deep_copied_nonnull (const char **strv)
+{
+	return nm_utils_strv_make_deep_copied (strv) ?: g_new0 (char *, 1);
+}
+
+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)
+
 /*****************************************************************************/
 
 #define NM_UTILS_NS_PER_SECOND  ((gint64) 1000000000)
@@ -449,4 +532,120 @@ int nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll)
 
 /*****************************************************************************/
 
+static inline const char *
+nm_utils_dbus_normalize_object_path (const char *path)
+{
+	/* D-Bus does not allow an empty object path. Hence, whenever we mean NULL / no-object
+	 * on D-Bus, it's path is actually "/".
+	 *
+	 * Normalize that away, and return %NULL in that case. */
+	if (path && path[0] == '/' && path[1] == '\0')
+		return NULL;
+	return path;
+}
+
+#define NM_DEFINE_GDBUS_ARG_INFO_FULL(name_, ...) \
+	((GDBusArgInfo *) (&((const GDBusArgInfo) { \
+		.ref_count = -1, \
+		.name = name_, \
+		__VA_ARGS__ \
+	})))
+
+#define NM_DEFINE_GDBUS_ARG_INFO(name_, a_signature) \
+	NM_DEFINE_GDBUS_ARG_INFO_FULL ( \
+		name_, \
+		.signature = a_signature, \
+	)
+
+#define NM_DEFINE_GDBUS_ARG_INFOS(...) \
+	((GDBusArgInfo **) ((const GDBusArgInfo *[]) { \
+		__VA_ARGS__ \
+		NULL, \
+	}))
+
+#define NM_DEFINE_GDBUS_PROPERTY_INFO(name_, ...) \
+	((GDBusPropertyInfo *) (&((const GDBusPropertyInfo) { \
+		.ref_count = -1, \
+		.name = name_, \
+		__VA_ARGS__ \
+	})))
+
+#define NM_DEFINE_GDBUS_PROPERTY_INFO_READABLE(name_, m_signature) \
+	NM_DEFINE_GDBUS_PROPERTY_INFO ( \
+		name_, \
+		.signature = m_signature, \
+		.flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE, \
+	)
+
+#define NM_DEFINE_GDBUS_PROPERTY_INFOS(...) \
+	((GDBusPropertyInfo **) ((const GDBusPropertyInfo *[]) { \
+		__VA_ARGS__ \
+		NULL, \
+	}))
+
+#define NM_DEFINE_GDBUS_SIGNAL_INFO_INIT(name_, ...) \
+	{ \
+		.ref_count = -1, \
+		.name = name_, \
+		__VA_ARGS__ \
+	}
+
+#define NM_DEFINE_GDBUS_SIGNAL_INFO(name_, ...) \
+	((GDBusSignalInfo *) (&((const GDBusSignalInfo) NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (name_, __VA_ARGS__))))
+
+#define NM_DEFINE_GDBUS_SIGNAL_INFOS(...) \
+	((GDBusSignalInfo **) ((const GDBusSignalInfo *[]) { \
+		__VA_ARGS__ \
+		NULL, \
+	}))
+
+#define NM_DEFINE_GDBUS_METHOD_INFO_INIT(name_, ...) \
+	{ \
+		.ref_count = -1, \
+		.name = name_, \
+		__VA_ARGS__ \
+	}
+
+#define NM_DEFINE_GDBUS_METHOD_INFO(name_, ...) \
+	((GDBusMethodInfo *) (&((const GDBusMethodInfo) NM_DEFINE_GDBUS_METHOD_INFO_INIT (name_, __VA_ARGS__))))
+
+#define NM_DEFINE_GDBUS_METHOD_INFOS(...) \
+	((GDBusMethodInfo **) ((const GDBusMethodInfo *[]) { \
+		__VA_ARGS__ \
+		NULL, \
+	}))
+
+#define NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(name_, ...) \
+	{ \
+		.ref_count = -1, \
+		.name = name_, \
+		__VA_ARGS__ \
+	}
+
+#define NM_DEFINE_GDBUS_INTERFACE_INFO(name_, ...) \
+	((GDBusInterfaceInfo *) (&((const GDBusInterfaceInfo) NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (name_, __VA_ARGS__))))
+
+#define NM_DEFINE_GDBUS_INTERFACE_VTABLE(...) \
+	((GDBusInterfaceVTable *) (&((const GDBusInterfaceVTable) { \
+		__VA_ARGS__ \
+	})))
+
+/*****************************************************************************/
+
+guint64 nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid);
+
+/*****************************************************************************/
+
+gpointer _nm_utils_user_data_pack (int nargs, gconstpointer *args);
+
+#define nm_utils_user_data_pack(...) \
+	_nm_utils_user_data_pack(NM_NARG (__VA_ARGS__), (gconstpointer[]) { __VA_ARGS__ })
+
+void _nm_utils_user_data_unpack (gpointer user_data, int nargs, ...);
+
+#define nm_utils_user_data_unpack(user_data, ...) \
+	_nm_utils_user_data_unpack(user_data, NM_NARG (__VA_ARGS__), __VA_ARGS__)
+
+/*****************************************************************************/
+
 #endif /* __NM_SHARED_UTILS_H__ */
diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h
index 126546ec..efbe6c9a 100644
--- a/shared/nm-utils/nm-test-utils.h
+++ b/shared/nm-utils/nm-test-utils.h
@@ -21,6 +21,10 @@
 #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.
  *
@@ -158,6 +162,14 @@
 			g_assert_not_reached (); \
 	} G_STMT_END
 
+#define nmtst_assert_nonnull(command) \
+	({ \
+		typeof (*(command)) *_ptr = (command); \
+		\
+		g_assert (_ptr && (TRUE || (command))); \
+		_ptr; \
+	 })
+
 #define nmtst_assert_success(success, error) \
 	G_STMT_START { \
 		g_assert_no_error (error); \
@@ -199,7 +211,6 @@ _nmtst_exit (void) \
 	nmtst_free (); \
 }
 
-
 static inline gboolean
 nmtst_initialized (void)
 {
@@ -264,7 +275,6 @@ BREAK_INNER_LOOPS:
 	return (char **) g_array_free (result, FALSE);
 }
 
-
 /* free instances allocated by nmtst (especially nmtst_init()) on shutdown
  * to release memory. After nmtst_free(), the test is uninitialized again. */
 static inline void
@@ -328,8 +338,6 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_
 
 	__nmtst_internal.assert_logging = !!assert_logging;
 
-	nm_g_type_init ();
-
 	is_debug = g_test_verbose ();
 
 	nmtst_debug = g_getenv ("NMTST_DEBUG");
@@ -424,6 +432,11 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_
 			g_array_append_val (debug_messages, msg);
 		}
 	} else {
+		/* We're intentionally assigning a value to static variables
+		 * s_tests_x and p_tests_x without using it afterwards, just
+		 * so that valgrind doesn't complain about the leak. */
+		NM_PRAGMA_WARNING_DISABLE("-Wunused-but-set-variable")
+
 		/* g_test_init() is a variadic function, so we cannot pass it
 		 * (variadic) arguments. If you need to pass additional parameters,
 		 * call nmtst_init() with argc==NULL and call g_test_init() yourself. */
@@ -497,6 +510,8 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_
 				s_tests = NULL;
 			}
 		}
+
+		NM_PRAGMA_WARNING_REENABLE
 	}
 
 	if (test_quick_set)
@@ -529,13 +544,8 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_
 		*out_set_logging = TRUE;
 #endif
 		g_assert (success);
-#if GLIB_CHECK_VERSION(2,34,0)
 		if (__nmtst_internal.no_expect_message)
 			g_log_set_always_fatal (G_LOG_FATAL_MASK);
-#else
-		/* g_test_expect_message() is a NOP, so allow any messages */
-		g_log_set_always_fatal (G_LOG_FATAL_MASK);
-#endif
 	} else if (__nmtst_internal.no_expect_message) {
 		/* We have a test that would be assert_logging, but the user specified no_expect_message.
 		 * This transforms g_test_expect_message() into a NOP, but we also have to relax
@@ -555,14 +565,9 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_
 		}
 #endif
 	} else {
-#if GLIB_CHECK_VERSION(2,34,0)
 		/* We were called not to set logging levels. This means, that the user
 		 * expects to assert against (all) messages. Any uncought message is fatal. */
 		g_log_set_always_fatal (G_LOG_LEVEL_MASK);
-#else
-		/* g_test_expect_message() is a NOP, so allow any messages */
-		g_log_set_always_fatal (G_LOG_FATAL_MASK);
-#endif
 	}
 
 	if ((!__nmtst_internal.assert_logging || (__nmtst_internal.assert_logging && __nmtst_internal.no_expect_message)) &&
@@ -629,7 +634,6 @@ nmtst_test_quick (void)
 	return __nmtst_internal.test_quick;
 }
 
-#if GLIB_CHECK_VERSION(2,34,0)
 #undef g_test_expect_message
 #define g_test_expect_message(...) \
 	G_STMT_START { \
@@ -637,9 +641,7 @@ nmtst_test_quick (void)
 		if (__nmtst_internal.assert_logging && __nmtst_internal.no_expect_message) { \
 			g_debug ("nmtst: assert-logging: g_test_expect_message %s", G_STRINGIFY ((__VA_ARGS__))); \
 		} else { \
-			G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
 			g_test_expect_message (__VA_ARGS__); \
-			G_GNUC_END_IGNORE_DEPRECATIONS \
 		} \
 	} G_STMT_END
 #undef g_test_assert_expected_messages_internal
@@ -653,10 +655,21 @@ nmtst_test_quick (void)
 		if (__nmtst_internal.assert_logging && __nmtst_internal.no_expect_message) \
 			g_debug ("nmtst: assert-logging: g_test_assert_expected_messages(%s, %s:%d, %s)", _domain?:"", _file?:"", _line, _func?:""); \
 		\
-		G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
 		g_test_assert_expected_messages_internal (_domain, _file, _line, _func); \
-		G_GNUC_END_IGNORE_DEPRECATIONS \
 	} G_STMT_END
+
+#define NMTST_EXPECT(domain, level, msg)        g_test_expect_message (domain, level, msg)
+
+#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL
+#define NMTST_EXPECT_LIBNM_U(level, msg)        NMTST_EXPECT ("libnm-util", level, msg)
+#define NMTST_EXPECT_LIBNM_G(level, msg)        NMTST_EXPECT ("libnm-glib", level, msg)
+
+#define NMTST_EXPECT_LIBNM_U_CRITICAL(msg)      NMTST_EXPECT_LIBNM_U (G_LOG_LEVEL_CRITICAL, msg)
+#define NMTST_EXPECT_LIBNM_G_CRITICAL(msg)      NMTST_EXPECT_LIBNM_G (G_LOG_LEVEL_CRITICAL, msg)
+#else
+#define NMTST_EXPECT_LIBNM(level, msg)          NMTST_EXPECT ("libnm", level, msg)
+
+#define NMTST_EXPECT_LIBNM_CRITICAL(msg)        NMTST_EXPECT_LIBNM (G_LOG_LEVEL_CRITICAL, msg)
 #endif
 
 /*****************************************************************************/
@@ -801,6 +814,12 @@ nmtst_get_rand_int (void)
 	return g_rand_int (nmtst_get_rand ());
 }
 
+static inline gboolean
+nmtst_get_rand_bool (void)
+{
+	return nmtst_get_rand_int () % 2;
+}
+
 static inline gpointer
 nmtst_rand_buf (GRand *rand, gpointer buffer, gsize buffer_length)
 {
@@ -902,32 +921,28 @@ _nmtst_main_loop_run_timeout (gpointer user_data)
 {
 	GMainLoop **p_loop = user_data;
 
-	g_assert (p_loop);
-	g_assert (*p_loop);
-
-	g_main_loop_quit (*p_loop);
-	*p_loop = NULL;
-
+	g_assert (p_loop && *p_loop);
+	g_main_loop_quit (g_steal_pointer (p_loop));
 	return G_SOURCE_REMOVE;
 }
 
 static inline gboolean
-nmtst_main_loop_run (GMainLoop *loop, int timeout_ms)
+nmtst_main_loop_run (GMainLoop *loop, guint timeout_ms)
 {
-	GSource *source = NULL;
-	guint id = 0;
+	nm_auto_unref_gsource GSource *source = NULL;
 	GMainLoop *loopx = loop;
 
 	if (timeout_ms > 0) {
 		source = g_timeout_source_new (timeout_ms);
 		g_source_set_callback (source, _nmtst_main_loop_run_timeout, &loopx, NULL);
-		id = g_source_attach (source, g_main_loop_get_context (loop));
-		g_assert (id);
-		g_source_unref (source);
+		g_source_attach (source, g_main_loop_get_context (loop));
 	}
 
 	g_main_loop_run (loop);
 
+	if (source)
+		g_source_destroy (source);
+
 	/* if the timeout was reached, return FALSE. */
 	return loopx != NULL;
 }
@@ -1116,7 +1131,7 @@ _nmtst_assert_ip4_address (const char *file, int line, in_addr_t addr, const cha
 		char buf[100];
 
 		g_error ("%s:%d: Unexpected IPv4 address: expected %s, got %s",
-		         file, line, str_expected ? str_expected : "0.0.0.0",
+		         file, line, str_expected ?: "0.0.0.0",
 		         inet_ntop (AF_INET, &addr, buf, sizeof (buf)));
 	}
 }
@@ -1134,7 +1149,7 @@ _nmtst_assert_ip6_address (const char *file, int line, const struct in6_addr *ad
 		char buf[100];
 
 		g_error ("%s:%d: Unexpected IPv6 address: expected %s, got %s",
-		         file, line, str_expected ? str_expected : "::",
+		         file, line, str_expected ?: "::",
 		         inet_ntop (AF_INET6, addr, buf, sizeof (buf)));
 	}
 }
@@ -1499,13 +1514,12 @@ _nmtst_connection_normalize (NMConnection *connection, ...)
 static inline NMConnection *
 _nmtst_connection_duplicate_and_normalize (NMConnection *connection, ...)
 {
-	gboolean was_modified;
 	va_list args;
 
 	connection = nmtst_clone_connection (connection);
 
 	va_start (args, connection);
-	was_modified = _nmtst_connection_normalize_v (connection, args);
+	_nmtst_connection_normalize_v (connection, args);
 	va_end (args);
 
 	return connection;
@@ -1697,7 +1711,7 @@ nmtst_assert_setting_verifies (NMSetting *setting)
 	g_assert (success);
 }
 
-#if defined(__NM_SIMPLE_CONNECTION_H__)
+#if defined(__NM_SIMPLE_CONNECTION_H__) && NM_CHECK_VERSION (1, 10, 0) && (!defined (NM_VERSION_MAX_ALLOWED) || NM_VERSION_MAX_ALLOWED >= NM_VERSION_1_10)
 static inline void
 _nmtst_assert_connection_has_settings (NMConnection *connection, gboolean has_at_least, gboolean has_at_most, ...)
 {
@@ -1715,7 +1729,7 @@ _nmtst_assert_connection_has_settings (NMConnection *connection, gboolean has_at
 
 	va_start (ap, has_at_most);
 	while ((name = va_arg (ap, const char *))) {
-		if (!nm_g_hash_table_add (names, (gpointer) name))
+		if (!g_hash_table_add (names, (gpointer) name))
 			g_assert_not_reached ();
 		g_ptr_array_add (names_arr, (gpointer) name);
 	}
@@ -1751,8 +1765,7 @@ _nmtst_assert_connection_has_settings (NMConnection *connection, gboolean has_at
 #define nmtst_assert_connection_has_settings(connection, ...)          _nmtst_assert_connection_has_settings ((connection), TRUE,  TRUE,  __VA_ARGS__, NULL)
 #define nmtst_assert_connection_has_settings_at_least(connection, ...) _nmtst_assert_connection_has_settings ((connection), TRUE,  FALSE, __VA_ARGS__, NULL)
 #define nmtst_assert_connection_has_settings_at_most(connection, ...)  _nmtst_assert_connection_has_settings ((connection), FALSE, TRUE,  __VA_ARGS__, NULL)
-
-#endif /* __NM_SIMPLE_CONNECTION_H__ */
+#endif
 
 static inline void
 nmtst_assert_setting_verify_fails (NMSetting *setting,
@@ -1809,7 +1822,6 @@ nmtst_assert_hwaddr_equals (gconstpointer hwaddr1, gssize hwaddr1_len, const cha
     nmtst_assert_hwaddr_equals (hwaddr1, hwaddr1_len, expected, __FILE__, __LINE__)
 #endif
 
-
 #if defined(__NM_SIMPLE_CONNECTION_H__) && defined(__NM_SETTING_CONNECTION_H__) && defined(__NM_KEYFILE_INTERNAL_H__)
 
 static inline NMConnection *
diff --git a/shared/nm-utils/siphash24.c b/shared/nm-utils/siphash24.c
deleted file mode 100644
index 3a5a635d..00000000
--- a/shared/nm-utils/siphash24.c
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
-   SipHash reference C implementation
-
-   Written in 2012 by
-   Jean-Philippe Aumasson <jeanphilippe.aumasson@gmail.com>
-   Daniel J. Bernstein <djb@cr.yp.to>
-
-   To the extent possible under law, the author(s) have dedicated all copyright
-   and related and neighboring rights to this software to the public domain
-   worldwide. This software is distributed without any warranty.
-
-   You should have received a copy of the CC0 Public Domain Dedication along with
-   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
-
-   (Minimal changes made by Lennart Poettering, to make clean for inclusion in systemd)
-   (Refactored by Tom Gundersen to split up in several functions and follow systemd
-    coding style)
-*/
-
-#include "nm-default.h"
-
-#define assert(cond) nm_assert (cond)
-
-#include <stdio.h>
-
-#include "siphash24.h"
-#include "unaligned.h"
-
-static inline uint64_t rotate_left(uint64_t x, uint8_t b) {
-        assert(b < 64);
-
-        return (x << b) | (x >> (64 - b));
-}
-
-static inline void sipround(struct siphash *state) {
-        assert(state);
-
-        state->v0 += state->v1;
-        state->v1 = rotate_left(state->v1, 13);
-        state->v1 ^= state->v0;
-        state->v0 = rotate_left(state->v0, 32);
-        state->v2 += state->v3;
-        state->v3 = rotate_left(state->v3, 16);
-        state->v3 ^= state->v2;
-        state->v0 += state->v3;
-        state->v3 = rotate_left(state->v3, 21);
-        state->v3 ^= state->v0;
-        state->v2 += state->v1;
-        state->v1 = rotate_left(state->v1, 17);
-        state->v1 ^= state->v2;
-        state->v2 = rotate_left(state->v2, 32);
-}
-
-void siphash24_init(struct siphash *state, const uint8_t k[16]) {
-        uint64_t k0, k1;
-
-        assert(state);
-        assert(k);
-
-        k0 = unaligned_read_le64(k);
-        k1 = unaligned_read_le64(k + 8);
-
-        *state = (struct siphash) {
-                /* "somepseudorandomlygeneratedbytes" */
-                .v0 = 0x736f6d6570736575ULL ^ k0,
-                .v1 = 0x646f72616e646f6dULL ^ k1,
-                .v2 = 0x6c7967656e657261ULL ^ k0,
-                .v3 = 0x7465646279746573ULL ^ k1,
-                .padding = 0,
-                .inlen = 0,
-        };
-}
-
-void siphash24_compress(const void *_in, size_t inlen, struct siphash *state) {
-
-        const uint8_t *in = _in;
-        const uint8_t *end = in + inlen;
-        size_t left = state->inlen & 7;
-        uint64_t m;
-
-        assert(in);
-        assert(state);
-
-        /* Update total length */
-        state->inlen += inlen;
-
-        /* If padding exists, fill it out */
-        if (left > 0) {
-                for ( ; in < end && left < 8; in ++, left ++)
-                        state->padding |= ((uint64_t) *in) << (left * 8);
-
-                if (in == end && left < 8)
-                        /* We did not have enough input to fill out the padding completely */
-                        return;
-
-#ifdef DEBUG
-                printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0);
-                printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1);
-                printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2);
-                printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3);
-                printf("(%3zu) compress padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t)state->padding);
-#endif
-
-                state->v3 ^= state->padding;
-                sipround(state);
-                sipround(state);
-                state->v0 ^= state->padding;
-
-                state->padding = 0;
-        }
-
-        end -= (state->inlen % sizeof(uint64_t));
-
-        for ( ; in < end; in += 8) {
-                m = unaligned_read_le64(in);
-#ifdef DEBUG
-                printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0);
-                printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1);
-                printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2);
-                printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3);
-                printf("(%3zu) compress %08x %08x\n", state->inlen, (uint32_t) (m >> 32), (uint32_t) m);
-#endif
-                state->v3 ^= m;
-                sipround(state);
-                sipround(state);
-                state->v0 ^= m;
-        }
-
-        left = state->inlen & 7;
-        switch (left) {
-                case 7:
-                        state->padding |= ((uint64_t) in[6]) << 48;
-                        /* fall through */
-                case 6:
-                        state->padding |= ((uint64_t) in[5]) << 40;
-                        /* fall through */
-                case 5:
-                        state->padding |= ((uint64_t) in[4]) << 32;
-                        /* fall through */
-                case 4:
-                        state->padding |= ((uint64_t) in[3]) << 24;
-                        /* fall through */
-                case 3:
-                        state->padding |= ((uint64_t) in[2]) << 16;
-                        /* fall through */
-                case 2:
-                        state->padding |= ((uint64_t) in[1]) <<  8;
-                        /* fall through */
-                case 1:
-                        state->padding |= ((uint64_t) in[0]);
-                        /* fall through */
-                case 0:
-                        break;
-        }
-}
-
-uint64_t siphash24_finalize(struct siphash *state) {
-        uint64_t b;
-
-        assert(state);
-
-        b = state->padding | (((uint64_t) state->inlen) << 56);
-
-#ifdef DEBUG
-        printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0);
-        printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1);
-        printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2);
-        printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3);
-        printf("(%3zu) padding   %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t) state->padding);
-#endif
-
-        state->v3 ^= b;
-        sipround(state);
-        sipround(state);
-        state->v0 ^= b;
-
-#ifdef DEBUG
-        printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0);
-        printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1);
-        printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2);
-        printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3);
-#endif
-        state->v2 ^= 0xff;
-
-        sipround(state);
-        sipround(state);
-        sipround(state);
-        sipround(state);
-
-        return state->v0 ^ state->v1 ^ state->v2  ^ state->v3;
-}
-
-uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]) {
-        struct siphash state;
-
-        assert(in);
-        assert(k);
-
-        siphash24_init(&state, k);
-        siphash24_compress(in, inlen, &state);
-
-        return siphash24_finalize(&state);
-}
diff --git a/shared/nm-utils/siphash24.h b/shared/nm-utils/siphash24.h
deleted file mode 100644
index 54e2420c..00000000
--- a/shared/nm-utils/siphash24.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#include <inttypes.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/types.h>
-
-struct siphash {
-        uint64_t v0;
-        uint64_t v1;
-        uint64_t v2;
-        uint64_t v3;
-        uint64_t padding;
-        size_t inlen;
-};
-
-void siphash24_init(struct siphash *state, const uint8_t k[16]);
-void siphash24_compress(const void *in, size_t inlen, struct siphash *state);
-#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state))
-
-uint64_t siphash24_finalize(struct siphash *state);
-
-uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]);
diff --git a/shared/nm-utils/unaligned.h b/shared/nm-utils/unaligned.h
index 7c847a3c..feddaa91 100644
--- a/shared/nm-utils/unaligned.h
+++ b/shared/nm-utils/unaligned.h
@@ -1,22 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
 /***
   This file is part of systemd.
 
   Copyright 2014 Tom Gundersen
-
-  systemd is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
 #include <endian.h>
@@ -25,89 +13,77 @@
 /* BE */
 
 static inline uint16_t unaligned_read_be16(const void *_u) {
-        const uint8_t *u = _u;
+        const struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u;
 
-        return (((uint16_t) u[0]) << 8) |
-                ((uint16_t) u[1]);
+        return be16toh(u->x);
 }
 
 static inline uint32_t unaligned_read_be32(const void *_u) {
-        const uint8_t *u = _u;
+        const struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u;
 
-        return (((uint32_t) unaligned_read_be16(u)) << 16) |
-                ((uint32_t) unaligned_read_be16(u + 2));
+        return be32toh(u->x);
 }
 
 static inline uint64_t unaligned_read_be64(const void *_u) {
-        const uint8_t *u = _u;
+        const struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u;
 
-        return (((uint64_t) unaligned_read_be32(u)) << 32) |
-                ((uint64_t) unaligned_read_be32(u + 4));
+        return be64toh(u->x);
 }
 
 static inline void unaligned_write_be16(void *_u, uint16_t a) {
-        uint8_t *u = _u;
+        struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u;
 
-        u[0] = (uint8_t) (a >> 8);
-        u[1] = (uint8_t) a;
+        u->x = be16toh(a);
 }
 
 static inline void unaligned_write_be32(void *_u, uint32_t a) {
-        uint8_t *u = _u;
+        struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u;
 
-        unaligned_write_be16(u, (uint16_t) (a >> 16));
-        unaligned_write_be16(u + 2, (uint16_t) a);
+        u->x = be32toh(a);
 }
 
 static inline void unaligned_write_be64(void *_u, uint64_t a) {
-        uint8_t *u = _u;
+        struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u;
 
-        unaligned_write_be32(u, (uint32_t) (a >> 32));
-        unaligned_write_be32(u + 4, (uint32_t) a);
+        u->x = be64toh(a);
 }
 
 /* LE */
 
 static inline uint16_t unaligned_read_le16(const void *_u) {
-        const uint8_t *u = _u;
+        const struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u;
 
-        return (((uint16_t) u[1]) << 8) |
-                ((uint16_t) u[0]);
+        return le16toh(u->x);
 }
 
 static inline uint32_t unaligned_read_le32(const void *_u) {
-        const uint8_t *u = _u;
+        const struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u;
 
-        return (((uint32_t) unaligned_read_le16(u + 2)) << 16) |
-                ((uint32_t) unaligned_read_le16(u));
+        return le32toh(u->x);
 }
 
 static inline uint64_t unaligned_read_le64(const void *_u) {
-        const uint8_t *u = _u;
+        const struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u;
 
-        return (((uint64_t) unaligned_read_le32(u + 4)) << 32) |
-                ((uint64_t) unaligned_read_le32(u));
+        return le64toh(u->x);
 }
 
 static inline void unaligned_write_le16(void *_u, uint16_t a) {
-        uint8_t *u = _u;
+        struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u;
 
-        u[0] = (uint8_t) a;
-        u[1] = (uint8_t) (a >> 8);
+        u->x = le16toh(a);
 }
 
 static inline void unaligned_write_le32(void *_u, uint32_t a) {
-        uint8_t *u = _u;
+        struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u;
 
-        unaligned_write_le16(u, (uint16_t) a);
-        unaligned_write_le16(u + 2, (uint16_t) (a >> 16));
+        u->x = le32toh(a);
 }
 
 static inline void unaligned_write_le64(void *_u, uint64_t a) {
-        uint8_t *u = _u;
+        struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u;
 
-        unaligned_write_le32(u, (uint32_t) a);
-        unaligned_write_le32(u + 4, (uint32_t) (a >> 32));
+        u->x = le64toh(a);
 }
 
 #if __BYTE_ORDER == __BIG_ENDIAN
diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h
index 2ef9960b..2a37c26d 100644
--- a/shared/nm-version-macros.h
+++ b/shared/nm-version-macros.h
@@ -37,7 +37,7 @@
  * Evaluates to the minor version number of NetworkManager which this source
  * is compiled against.
  */
-#define NM_MINOR_VERSION (10)
+#define NM_MINOR_VERSION (11)
 
 /**
  * NM_MICRO_VERSION:
@@ -45,7 +45,7 @@
  * Evaluates to the micro version number of NetworkManager which this source
  * compiled against.
  */
-#define NM_MICRO_VERSION (8)
+#define NM_MICRO_VERSION (4)
 
 /**
  * NM_CHECK_VERSION:
@@ -72,13 +72,25 @@
 #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_10_2 (NM_ENCODE_VERSION (1, 10, 2))
-#define NM_VERSION_1_10_4 (NM_ENCODE_VERSION (1, 10, 4))
-#define NM_VERSION_1_10_6 (NM_ENCODE_VERSION (1, 10, 6))
-#define NM_VERSION_1_10_8 (NM_ENCODE_VERSION (1, 10, 8))
+#define NM_VERSION_1_12   (NM_ENCODE_VERSION (1, 12, 0))
 
-#define NM_VERSION_CUR_STABLE  NM_VERSION_1_10_8
-#define NM_VERSION_NEXT_STABLE NM_VERSION_1_10_8
+/* 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)
 
diff --git a/shared/nm-version-macros.h.in b/shared/nm-version-macros.h.in
index cb1d545a..8d07fc82 100644
--- a/shared/nm-version-macros.h.in
+++ b/shared/nm-version-macros.h.in
@@ -72,13 +72,25 @@
 #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_10_2 (NM_ENCODE_VERSION (1, 10, 2))
-#define NM_VERSION_1_10_4 (NM_ENCODE_VERSION (1, 10, 4))
-#define NM_VERSION_1_10_6 (NM_ENCODE_VERSION (1, 10, 6))
-#define NM_VERSION_1_10_8 (NM_ENCODE_VERSION (1, 10, 8))
+#define NM_VERSION_1_12   (NM_ENCODE_VERSION (1, 12, 0))
 
-#define NM_VERSION_CUR_STABLE  NM_VERSION_1_10_8
-#define NM_VERSION_NEXT_STABLE NM_VERSION_1_10_8
+/* 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)