about summary refs log tree commit diff
path: root/shared/nm-utils
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
committerMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
commit90e8691111889a7b5f3c812f5a41f15a8a058913 (patch)
treef101a879eca27c34a9bfa5f3da52266b22539a36 /shared/nm-utils
parentbdb6eeb0670658255c2a4c3c501c0a27fa8cfe55 (diff)
New upstream version 1.9.90 upstream/1.9.90
Diffstat (limited to 'shared/nm-utils')
-rw-r--r--shared/nm-utils/c-list-util.c165
-rw-r--r--shared/nm-utils/c-list-util.h43
-rw-r--r--shared/nm-utils/c-list.h397
-rw-r--r--shared/nm-utils/nm-dedup-multi.c1123
-rw-r--r--shared/nm-utils/nm-dedup-multi.h437
-rw-r--r--shared/nm-utils/nm-enum-utils.c288
-rw-r--r--shared/nm-utils/nm-enum-utils.h45
-rw-r--r--shared/nm-utils/nm-glib.h29
-rw-r--r--shared/nm-utils/nm-hash-utils.c119
-rw-r--r--shared/nm-utils/nm-hash-utils.h210
-rw-r--r--shared/nm-utils/nm-macros-internal.h276
-rw-r--r--shared/nm-utils/nm-obj.h82
-rw-r--r--shared/nm-utils/nm-random-utils.c165
-rw-r--r--shared/nm-utils/nm-random-utils.h27
-rw-r--r--shared/nm-utils/nm-shared-utils.c587
-rw-r--r--shared/nm-utils/nm-shared-utils.h304
-rw-r--r--shared/nm-utils/nm-test-utils.h139
-rw-r--r--shared/nm-utils/siphash24.c203
-rw-r--r--shared/nm-utils/siphash24.h23
19 files changed, 4626 insertions, 36 deletions
diff --git a/shared/nm-utils/c-list-util.c b/shared/nm-utils/c-list-util.c
new file mode 100644
index 00000000..070323c6
--- /dev/null
+++ b/shared/nm-utils/c-list-util.c
@@ -0,0 +1,165 @@
+/* -*- 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 "c-list-util.h"
+
+/*****************************************************************************/
+
+/**
+ * c_list_relink:
+ * @lst: the head list entry
+ *
+ * Takes an invalid list, that has undefined prev pointers.
+ * Only the next pointers are valid, and the tail's next
+ * pointer points to %NULL instead of the head.
+ *
+ * c_list_relink() fixes the list by updating all prev pointers
+ * and close the circular linking by pointing the tails' next
+ * pointer to @lst.
+ *
+ * The use of this function is to do a bulk update, that lets the
+ * list degredate by not updating the prev pointers. At the end,
+ * the list can be fixed by c_list_relink().
+ */
+void
+c_list_relink (CList *lst)
+{
+	CList *ls, *ls_prev;
+
+	ls_prev = lst;
+	ls = lst->next;
+	do {
+		ls->prev = ls_prev;
+		ls_prev = ls;
+		ls = ls->next;
+	} while (ls);
+	ls_prev->next = lst;
+	lst->prev = ls_prev;
+}
+
+/*****************************************************************************/
+
+static CList *
+_c_list_sort (CList *ls,
+              CListSortCmp cmp,
+              const void *user_data)
+{
+	CList *ls1, *ls2;
+	CList head;
+
+	if (!ls->next)
+		return ls;
+
+	/* split list in two halfs @ls1 and @ls2. */
+	ls1 = ls;
+	ls2 = ls;
+	ls = ls->next;
+	while (ls) {
+		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;
+
+	ls2 = _c_list_sort (ls2, cmp, user_data);
+
+	/* merge */
+	ls = &head;
+	for (;;) {
+		/* while invoking the @cmp function, the list
+		 * elements are not properly linked. Don't try to access
+		 * their next/prev pointers. */
+		if (cmp (ls1, ls2, user_data) <= 0) {
+			ls->next = ls1;
+			ls = ls1;
+			ls1 = ls1->next;
+			if (!ls1)
+				break;
+		} else {
+			ls->next = ls2;
+			ls = ls2;
+			ls2 = ls2->next;
+			if (!ls2)
+				break;
+		}
+	}
+	ls->next = ls1 ?: ls2;
+
+	return head.next;
+}
+
+/**
+ * c_list_sort_headless:
+ * @lst: the list.
+ * @cmp: compare function for sorting. While comparing two
+ *   CList elements, their next/prev pointers are in undefined
+ *   state.
+ * @user_data: user data for @cmp.
+ *
+ * Sorts the list @lst according to @cmp. Contrary to
+ * c_list_sort(), @lst is not the list head but a
+ * valid entry as well. This function returns the new
+ * list head.
+ */
+CList *
+c_list_sort_headless (CList *lst,
+                      CListSortCmp cmp,
+                      const void *user_data)
+{
+	if (!c_list_is_empty (lst)) {
+		lst->prev->next = NULL;
+		lst = _c_list_sort (lst, cmp, user_data);
+		c_list_relink (lst);
+	}
+	return lst;
+}
+
+/**
+ * c_list_sort:
+ * @head: the list head.
+ * @cmp: compare function for sorting. While comparing two
+ *   CList elements, their next/prev pointers are in undefined
+ *   state.
+ * @user_data: user data for @cmp.
+ *
+ * Sorts the list @head according to @cmp.
+ */
+void
+c_list_sort (CList *head,
+             CListSortCmp cmp,
+             const void *user_data)
+{
+	if (   !c_list_is_empty (head)
+	    && head->next->next != head) {
+		head->prev->next = NULL;
+		head->next = _c_list_sort (head->next, cmp, user_data);
+		c_list_relink (head);
+	}
+}
diff --git a/shared/nm-utils/c-list-util.h b/shared/nm-utils/c-list-util.h
new file mode 100644
index 00000000..199583cf
--- /dev/null
+++ b/shared/nm-utils/c-list-util.h
@@ -0,0 +1,43 @@
+/* -*- 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 __C_LIST_UTIL_H__
+#define __C_LIST_UTIL_H__
+
+#include "c-list.h"
+
+/*****************************************************************************/
+
+void c_list_relink (CList *lst);
+
+typedef int (*CListSortCmp) (const CList *a,
+                             const CList *b,
+                             const void *user_data);
+
+CList *c_list_sort_headless (CList *lst,
+                             CListSortCmp cmp,
+                             const void *user_data);
+
+void c_list_sort (CList *head,
+                  CListSortCmp cmp,
+                  const void *user_data);
+
+#endif /* __C_LIST_UTIL_H__ */
diff --git a/shared/nm-utils/c-list.h b/shared/nm-utils/c-list.h
new file mode 100644
index 00000000..557862b6
--- /dev/null
+++ b/shared/nm-utils/c-list.h
@@ -0,0 +1,397 @@
+#pragma once
+
+/*
+ * Circular Double Linked List Implementation in Standard 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
+ * element. Insertion and removal can be done in O(1) due to the double links.
+ * Furthermore, the list is circular, thus allows access to front/tail in O(1)
+ * as well, even if you only have a single head pointer (which is not how the
+ * list is usually operated, though).
+ *
+ * Note that you are free to use the list implementation without a head
+ * pointer. However, usual operation uses a single CList object as head, which
+ * is itself linked in the list and as such must be identified as list head.
+ * This allows very simply list operations and avoids a lot of special cases.
+ * Most importantly, you can unlink entries without requiring a head pointer.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+
+typedef struct CList CList;
+
+/**
+ * struct CList - Entry of a circular double linked list
+ * @next:               next entry
+ * @prev:               previous entry
+ *
+ * Each entry in a list must embed a CList object. This object contains
+ * pointers to its next and previous elements, which can be freely accessed by
+ * the API user at any time. Note that the list is circular, and the list head
+ * is linked in the list as well.
+ *
+ * The list head must be initialized via C_LIST_INIT before use. There is no
+ * reason to initialize entry objects before linking them. However, if you need
+ * a boolean state that tells you whether the entry is linked or not, you should
+ * initialize the entry via C_LIST_INIT as well.
+ */
+struct CList {
+        CList *next;
+        CList *prev;
+};
+
+#define C_LIST_INIT(_var) { .next = &(_var), .prev = &(_var) }
+
+/**
+ * c_list_init() - initialize list entry
+ * @what:               list entry to initialize
+ */
+static inline void c_list_init(CList *what) {
+        *what = (CList)C_LIST_INIT(*what);
+}
+
+/**
+ * c_list_entry() - get parent container of list entry
+ * @_what:              list entry, or NULL
+ * @_t:                 type of parent container
+ * @_m:                 member name of list entry in @_t
+ *
+ * If the list entry @_what is embedded into a surrounding structure, this will
+ * turn the list entry pointer @_what into a pointer to the parent container
+ * (using offsetof(3), or sometimes called container_of(3)).
+ *
+ * If @_what is NULL, this will also return NULL.
+ *
+ * Return: Pointer to parent container, or NULL.
+ */
+#define c_list_entry(_what, _t, _m) \
+        ((_t *)(void *)(((unsigned long)(void *)(_what) ?: \
+                         offsetof(_t, _m)) - offsetof(_t, _m)))
+
+/**
+ * c_list_is_linked() - check whether an entry is linked
+ * @what:               entry to check, or NULL
+ *
+ * Return: True if @what is linked in a list, false if not.
+ */
+static inline _Bool c_list_is_linked(const CList *what) {
+        return what && what->next != what;
+}
+
+/**
+ * c_list_is_empty() - check whether a list is empty
+ * @list:               list to check, or NULL
+ *
+ * Return: True if @list is empty, false if not.
+ */
+static inline _Bool c_list_is_empty(const CList *list) {
+        return !list || !c_list_is_linked(list);
+}
+
+/**
+ * c_list_link_before() - link entry into list
+ * @where:              linked list entry used as anchor
+ * @what:               entry to link
+ *
+ * This links @what directly in front of @where. @where can either be a list
+ * head or any entry in the list.
+ *
+ * If @where points to the list head, this effectively links @what as new tail
+ * element. Hence, the macro c_list_link_tail() is an alias to this.
+ *
+ * @what is not inspected prior to being linked. Hence, it better not be linked
+ * into another list, or the other list will be corrupted.
+ */
+static inline void c_list_link_before(CList *where, CList *what) {
+        CList *prev = where->prev, *next = where;
+
+        next->prev = what;
+        what->next = next;
+        what->prev = prev;
+        prev->next = what;
+}
+#define c_list_link_tail(_list, _what) c_list_link_before((_list), (_what))
+
+/**
+ * c_list_link_after() - link entry into list
+ * @where:              linked list entry used as anchor
+ * @what:               entry to link
+ *
+ * This links @what directly after @where. @where can either be a list head or
+ * any entry in the list.
+ *
+ * If @where points to the list head, this effectively links @what as new front
+ * element. Hence, the macro c_list_link_front() is an alias to this.
+ *
+ * @what is not inspected prior to being linked. Hence, it better not be linked
+ * into another list, or the other list will be corrupted.
+ */
+static inline void c_list_link_after(CList *where, CList *what) {
+        CList *prev = where, *next = where->next;
+
+        next->prev = what;
+        what->next = next;
+        what->prev = prev;
+        prev->next = what;
+}
+#define c_list_link_front(_list, _what) c_list_link_after((_list), (_what))
+
+/**
+ * c_list_unlink() - unlink element from list
+ * @what:               element to unlink
+ *
+ * This unlinks @what. If @what was initialized via C_LIST_INIT(), it has no
+ * effect. If @what was never linked, nor initialized, behavior is undefined.
+ *
+ * Note that this does not modify @what. It just modifies the previous and next
+ * elements in the list to no longer reference @what. If you want to make sure
+ * @what is re-initialized after removal, use c_list_unlink_init().
+ */
+static inline void c_list_unlink(CList *what) {
+        CList *prev = what->prev, *next = what->next;
+
+        next->prev = prev;
+        prev->next = next;
+}
+
+/**
+ * c_list_unlink_init() - unlink element from list and re-initialize
+ * @what:               element to unlink
+ *
+ * This is like c_list_unlink() but re-initializes @what after removal.
+ */
+static inline void c_list_unlink_init(CList *what) {
+        /* condition is not needed, but avoids STOREs in fast-path */
+        if (c_list_is_linked(what)) {
+                c_list_unlink(what);
+                *what = (CList)C_LIST_INIT(*what);
+        }
+}
+
+/**
+ * c_list_swap() - exchange the contents of two lists
+ * @list1:      the list to operate on
+ * @list2:      the list to operate on
+ *
+ * This replaces the contents of the list @list1 with the contents
+ * of @list2, and vice versa.
+ */
+static inline void c_list_swap(CList *list1, CList *list2) {
+        CList t;
+
+        /* make neighbors of list1 point to list2, and vice versa */
+        t = *list1;
+        t.next->prev = list2;
+        t.prev->next = list2;
+        t = *list2;
+        t.next->prev = list1;
+        t.prev->next = list1;
+
+        /* swap list1 and list2 now that their neighbors were fixed up */
+        t = *list1;
+        *list1 = *list2;
+        *list2 = t;
+}
+
+/**
+ * c_list_splice() - splice one list into another
+ * @target:     the list to splice into
+ * @source:     the list to splice
+ *
+ * This removes all the entries from @source and splice them into @target.
+ * The order of the two lists is preserved and the source is appended
+ * to the end of target.
+ *
+ * On return, the source list will be empty.
+ */
+static inline void c_list_splice(CList *target, CList *source) {
+        if (!c_list_is_empty(source)) {
+                /* attach the front of @source to the tail of @target */
+                source->next->prev = target->prev;
+                target->prev->next = source->next;
+
+                /* attach the tail of @source to the front of @target */
+                source->prev->next = target;
+                target->prev = source->prev;
+
+                /* clear source */
+                *source = (CList)C_LIST_INIT(*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
+ *
+ * This returns a pointer to the first element, or NULL if empty. This never
+ * returns a pointer to the list head.
+ *
+ * Return: Pointer to first list element, or NULL if empty.
+ */
+static inline CList *c_list_first(CList *list) {
+        return c_list_is_empty(list) ? NULL : list->next;
+}
+
+/**
+ * c_list_last() - return pointer to last element, or NULL if empty
+ * @list:               list to operate on, or NULL
+ *
+ * This returns a pointer to the last element, or NULL if empty. This never
+ * returns a pointer to the list head.
+ *
+ * Return: Pointer to last list element, or NULL if empty.
+ */
+static inline CList *c_list_last(CList *list) {
+        return c_list_is_empty(list) ? NULL : list->prev;
+}
+
+/**
+ * c_list_first_entry() - return pointer to first entry, or NULL if empty
+ * @_list:              list to operate on, or NULL
+ * @_t:                 type of list entries
+ * @_m:                 name of CList member in @_t
+ *
+ * This is like c_list_first(), but also applies c_list_entry() on the result.
+ *
+ * Return: Pointer to first list entry, or NULL if empty.
+ */
+#define c_list_first_entry(_list, _t, _m) \
+        c_list_entry(c_list_first(_list), _t, _m)
+
+/**
+ * c_list_last_entry() - return pointer to last entry, or NULL if empty
+ * @_list:              list to operate on, or NULL
+ * @_t:                 type of list entries
+ * @_m:                 name of CList member in @_t
+ *
+ * This is like c_list_last(), but also applies c_list_entry() on the result.
+ *
+ * Return: Pointer to last list entry, or NULL if empty.
+ */
+#define c_list_last_entry(_list, _t, _m) \
+        c_list_entry(c_list_last(_list), _t, _m)
+
+/**
+ * c_list_length() - return number of linked entries, excluding the head
+ * @list:               list to operate on
+ *
+ * Returns the number of entries in the list, excluding the list head @list.
+ * That is, for a list that is empty according to c_list_is_empty(), the
+ * returned length is 0. This requires to iterate the list and has thus O(n)
+ * runtime.
+ *
+ * Note that this function is meant for debugging purposes only. If you need
+ * the list size during normal operation, you should maintain a counter
+ * separately.
+ *
+ * Return: Number of items in @list.
+ */
+static inline unsigned long c_list_length(const CList *list) {
+        unsigned long n = 0;
+        const CList *iter;
+
+        c_list_for_each(iter, list)
+                ++n;
+
+        return n;
+}
+
+/**
+ * c_list_contains() - check whether an entry is linked in a certain list
+ * @list:               list to operate on
+ * @what:               entry to look for
+ *
+ * This checks whether @what is linked into @list. This requires a linear
+ * search through the list, as such runs in O(n). Note that the list-head is
+ * considered part of the list, and hence this returns true if @what equals
+ * @list.
+ *
+ * Note that this function is meant for debugging purposes, and consistency
+ * checks. You should always be aware whether your objects are linked in a
+ * specific list.
+ *
+ * Return: True if @what is in @list, false otherwise.
+ */
+static inline _Bool c_list_contains(const CList *list, const CList *what) {
+        const CList *iter;
+
+        c_list_for_each(iter, list)
+                if (what == iter)
+                        return 1;
+
+        return what == list;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/shared/nm-utils/nm-dedup-multi.c b/shared/nm-utils/nm-dedup-multi.c
new file mode 100644
index 00000000..8a59f1c4
--- /dev/null
+++ b/shared/nm-utils/nm-dedup-multi.c
@@ -0,0 +1,1123 @@
+/* -*- 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-dedup-multi.h"
+
+#include "nm-hash-utils.h"
+
+/*****************************************************************************/
+
+typedef struct {
+	/* the stack-allocated lookup entry. It has a compatible
+	 * memory layout with NMDedupMultiEntry and NMDedupMultiHeadEntry.
+	 *
+	 * It is recognizable by having lst_entries_sentinel.next set to NULL.
+	 * Contrary to the other entries, which have lst_entries.next
+	 * always non-NULL.
+	 * */
+	CList lst_entries_sentinel;
+	const NMDedupMultiObj *obj;
+	const NMDedupMultiIdxType *idx_type;
+	bool lookup_head;
+} LookupEntry;
+
+struct _NMDedupMultiIndex {
+	int ref_count;
+	GHashTable *idx_entries;
+	GHashTable *idx_objs;
+};
+
+/*****************************************************************************/
+
+static void
+ASSERT_idx_type (const NMDedupMultiIdxType *idx_type)
+{
+	nm_assert (idx_type);
+#if NM_MORE_ASSERTS > 10
+	nm_assert (idx_type->klass);
+	nm_assert (idx_type->klass->idx_obj_id_hash_update);
+	nm_assert (idx_type->klass->idx_obj_id_equal);
+	nm_assert (!!idx_type->klass->idx_obj_partition_hash_update == !!idx_type->klass->idx_obj_partition_equal);
+	nm_assert (idx_type->lst_idx_head.next);
+#endif
+}
+
+void
+nm_dedup_multi_idx_type_init (NMDedupMultiIdxType *idx_type,
+                              const NMDedupMultiIdxTypeClass *klass)
+{
+	nm_assert (idx_type);
+	nm_assert (klass);
+
+	memset (idx_type, 0, sizeof (*idx_type));
+	idx_type->klass = klass;
+	c_list_init (&idx_type->lst_idx_head);
+
+	ASSERT_idx_type (idx_type);
+}
+
+/*****************************************************************************/
+
+static NMDedupMultiEntry *
+_entry_lookup_obj (const NMDedupMultiIndex *self,
+                   const NMDedupMultiIdxType *idx_type,
+                   const NMDedupMultiObj *obj)
+{
+	const LookupEntry stack_entry = {
+		.obj = obj,
+		.idx_type = idx_type,
+		.lookup_head = FALSE,
+	};
+
+	ASSERT_idx_type (idx_type);
+	return g_hash_table_lookup (self->idx_entries, &stack_entry);
+}
+
+static NMDedupMultiHeadEntry *
+_entry_lookup_head (const NMDedupMultiIndex *self,
+                    const NMDedupMultiIdxType *idx_type,
+                    const NMDedupMultiObj *obj)
+{
+	NMDedupMultiHeadEntry *head_entry;
+	const LookupEntry stack_entry = {
+		.obj = obj,
+		.idx_type = idx_type,
+		.lookup_head = TRUE,
+	};
+
+	ASSERT_idx_type (idx_type);
+
+	if (!idx_type->klass->idx_obj_partition_equal) {
+		if (c_list_is_empty (&idx_type->lst_idx_head))
+			head_entry = NULL;
+		else {
+			nm_assert (c_list_length (&idx_type->lst_idx_head) == 1);
+			head_entry = c_list_entry (idx_type->lst_idx_head.next, NMDedupMultiHeadEntry, lst_idx);
+		}
+		nm_assert (head_entry == g_hash_table_lookup (self->idx_entries, &stack_entry));
+		return head_entry;
+	}
+
+	return g_hash_table_lookup (self->idx_entries, &stack_entry);
+}
+
+static void
+_entry_unpack (const NMDedupMultiEntry *entry,
+               const NMDedupMultiIdxType **out_idx_type,
+               const NMDedupMultiObj **out_obj,
+               gboolean *out_lookup_head)
+{
+	const NMDedupMultiHeadEntry *head_entry;
+	const LookupEntry *lookup_entry;
+
+	nm_assert (entry);
+
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (LookupEntry, lst_entries_sentinel) == G_STRUCT_OFFSET (NMDedupMultiEntry, lst_entries));
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMDedupMultiEntry, lst_entries) == G_STRUCT_OFFSET (NMDedupMultiHeadEntry, lst_entries_head));
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMDedupMultiEntry, obj) == G_STRUCT_OFFSET (NMDedupMultiHeadEntry, idx_type));
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMDedupMultiEntry, is_head) == G_STRUCT_OFFSET (NMDedupMultiHeadEntry, is_head));
+
+	if (!entry->lst_entries.next) {
+		/* the entry is stack-allocated by _entry_lookup(). */
+		lookup_entry = (LookupEntry *) entry;
+		*out_obj = lookup_entry->obj;
+		*out_idx_type = lookup_entry->idx_type;
+		*out_lookup_head = lookup_entry->lookup_head;
+	} else if (entry->is_head) {
+		head_entry = (NMDedupMultiHeadEntry *) entry;
+		nm_assert (!c_list_is_empty (&head_entry->lst_entries_head));
+		*out_obj = c_list_entry (head_entry->lst_entries_head.next, NMDedupMultiEntry, lst_entries)->obj;
+		*out_idx_type = head_entry->idx_type;
+		*out_lookup_head = TRUE;
+	} else {
+		*out_obj = entry->obj;
+		*out_idx_type = entry->head->idx_type;
+		*out_lookup_head = FALSE;
+	}
+
+	nm_assert (NM_IN_SET (*out_lookup_head, FALSE, TRUE));
+	ASSERT_idx_type (*out_idx_type);
+
+	/* for lookup of the head, we allow to omit object, but only
+	 * if the idx_type does not parition the objects. Otherwise, we
+	 * require a obj to compare. */
+	nm_assert (   !*out_lookup_head
+	           || (   *out_obj
+	               || !(*out_idx_type)->klass->idx_obj_partition_equal));
+
+	/* lookup of the object requires always an object. */
+	nm_assert (   *out_lookup_head
+	           || *out_obj);
+}
+
+static guint
+_dict_idx_entries_hash (const NMDedupMultiEntry *entry)
+{
+	const NMDedupMultiIdxType *idx_type;
+	const NMDedupMultiObj *obj;
+	gboolean lookup_head;
+	NMHashState h;
+
+	_entry_unpack (entry, &idx_type, &obj, &lookup_head);
+
+	nm_hash_init (&h, 1914869417u);
+	if (idx_type->klass->idx_obj_partition_hash_update) {
+		nm_assert (obj);
+		idx_type->klass->idx_obj_partition_hash_update (idx_type, obj, &h);
+	}
+
+	if (!lookup_head)
+		idx_type->klass->idx_obj_id_hash_update (idx_type, obj, &h);
+
+	nm_hash_update_val (&h, idx_type);
+	return nm_hash_complete (&h);
+}
+
+static gboolean
+_dict_idx_entries_equal (const NMDedupMultiEntry *entry_a,
+                         const NMDedupMultiEntry *entry_b)
+{
+	const NMDedupMultiIdxType *idx_type_a, *idx_type_b;
+	const NMDedupMultiObj *obj_a, *obj_b;
+	gboolean lookup_head_a, lookup_head_b;
+
+	_entry_unpack (entry_a, &idx_type_a, &obj_a, &lookup_head_a);
+	_entry_unpack (entry_b, &idx_type_b, &obj_b, &lookup_head_b);
+
+	if (   idx_type_a != idx_type_b
+	    || lookup_head_a != lookup_head_b)
+		return FALSE;
+	if (!nm_dedup_multi_idx_type_partition_equal (idx_type_a, obj_a, obj_b))
+		return FALSE;
+	if (   !lookup_head_a
+	    && !nm_dedup_multi_idx_type_id_equal (idx_type_a, obj_a, obj_b))
+		return FALSE;
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+_add (NMDedupMultiIndex *self,
+      NMDedupMultiIdxType *idx_type,
+      const NMDedupMultiObj *obj,
+      NMDedupMultiEntry *entry,
+      NMDedupMultiIdxMode mode,
+      const NMDedupMultiEntry *entry_order,
+      NMDedupMultiHeadEntry *head_existing,
+      const NMDedupMultiEntry **out_entry,
+      const NMDedupMultiObj **out_obj_old)
+{
+	NMDedupMultiHeadEntry *head_entry;
+	const NMDedupMultiObj *obj_new, *obj_old;
+	gboolean add_head_entry = FALSE;
+
+	nm_assert (self);
+	ASSERT_idx_type (idx_type);
+	nm_assert (obj);
+	nm_assert (NM_IN_SET (mode,
+	                      NM_DEDUP_MULTI_IDX_MODE_PREPEND,
+	                      NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE,
+	                      NM_DEDUP_MULTI_IDX_MODE_APPEND,
+	                      NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE));
+	nm_assert (!head_existing || head_existing->idx_type == idx_type);
+	nm_assert (({
+	                const NMDedupMultiHeadEntry *_h;
+	                gboolean _ok = TRUE;
+	                if (head_existing) {
+	                    _h = nm_dedup_multi_index_lookup_head (self, idx_type, obj);
+	                    if (head_existing == NM_DEDUP_MULTI_HEAD_ENTRY_MISSING)
+	                        _ok = (_h == NULL);
+	                    else
+	                        _ok = (_h == head_existing);
+	                }
+	                _ok;
+	            }));
+
+	if (entry) {
+		gboolean changed = FALSE;
+
+		nm_dedup_multi_entry_set_dirty (entry, FALSE);
+
+		nm_assert (!head_existing || entry->head == head_existing);
+
+		if (entry_order) {
+			nm_assert (entry_order->head == entry->head);
+			nm_assert (c_list_contains (&entry->lst_entries, &entry_order->lst_entries));
+			nm_assert (c_list_contains (&entry_order->lst_entries, &entry->lst_entries));
+		}
+
+		switch (mode) {
+		case NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE:
+			if (entry_order) {
+				if (   entry_order != entry
+				    && entry->lst_entries.next != &entry_order->lst_entries) {
+					c_list_unlink (&entry->lst_entries);
+					c_list_link_before ((CList *) &entry_order->lst_entries, &entry->lst_entries);
+					changed = TRUE;
+				}
+			} else {
+				if (entry->lst_entries.prev != &entry->head->lst_entries_head) {
+					c_list_unlink (&entry->lst_entries);
+					c_list_link_front ((CList *) &entry->head->lst_entries_head, &entry->lst_entries);
+					changed = TRUE;
+				}
+			}
+			break;
+		case NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE:
+			if (entry_order) {
+				if (   entry_order != entry
+				    && entry->lst_entries.prev != &entry_order->lst_entries) {
+					c_list_unlink (&entry->lst_entries);
+					c_list_link_after ((CList *) &entry_order->lst_entries, &entry->lst_entries);
+					changed = TRUE;
+				}
+			} else {
+				if (entry->lst_entries.next != &entry->head->lst_entries_head) {
+					c_list_unlink (&entry->lst_entries);
+					c_list_link_tail ((CList *) &entry->head->lst_entries_head, &entry->lst_entries);
+					changed = TRUE;
+				}
+			}
+			break;
+		case NM_DEDUP_MULTI_IDX_MODE_PREPEND:
+		case NM_DEDUP_MULTI_IDX_MODE_APPEND:
+			break;
+		};
+
+		nm_assert (obj->klass == ((const NMDedupMultiObj *) entry->obj)->klass);
+		if (   obj == entry->obj
+		    || obj->klass->obj_full_equal (obj,
+		                                   entry->obj)) {
+			NM_SET_OUT (out_entry, entry);
+			NM_SET_OUT (out_obj_old, nm_dedup_multi_obj_ref (entry->obj));
+			return changed;
+		}
+
+		obj_new = nm_dedup_multi_index_obj_intern (self, obj);
+
+		obj_old = entry->obj;
+		entry->obj = obj_new;
+
+		NM_SET_OUT (out_entry, entry);
+		if (out_obj_old)
+			*out_obj_old = obj_old;
+		else
+			nm_dedup_multi_obj_unref (obj_old);
+		return TRUE;
+	}
+
+	if (    idx_type->klass->idx_obj_partitionable
+	    && !idx_type->klass->idx_obj_partitionable (idx_type, obj)) {
+		/* this object cannot be partitioned by this idx_type. */
+		nm_assert (!head_existing || head_existing == NM_DEDUP_MULTI_HEAD_ENTRY_MISSING);
+		NM_SET_OUT (out_entry, NULL);
+		NM_SET_OUT (out_obj_old, NULL);
+		return FALSE;
+	}
+
+	obj_new = nm_dedup_multi_index_obj_intern (self, obj);
+
+	if (!head_existing)
+		head_entry = _entry_lookup_head (self, idx_type, obj_new);
+	else if (head_existing == NM_DEDUP_MULTI_HEAD_ENTRY_MISSING)
+		head_entry = NULL;
+	else
+		head_entry = head_existing;
+
+	if (!head_entry) {
+		head_entry = g_slice_new0 (NMDedupMultiHeadEntry);
+		head_entry->is_head = TRUE;
+		head_entry->idx_type = idx_type;
+		c_list_init (&head_entry->lst_entries_head);
+		c_list_link_tail (&idx_type->lst_idx_head, &head_entry->lst_idx);
+		add_head_entry = TRUE;
+	} else
+		nm_assert (c_list_contains (&idx_type->lst_idx_head, &head_entry->lst_idx));
+
+	if (entry_order) {
+		nm_assert (!add_head_entry);
+		nm_assert (entry_order->head == head_entry);
+		nm_assert (c_list_contains (&head_entry->lst_entries_head, &entry_order->lst_entries));
+		nm_assert (c_list_contains (&entry_order->lst_entries, &head_entry->lst_entries_head));
+	}
+
+	entry = g_slice_new0 (NMDedupMultiEntry);
+	entry->obj = obj_new;
+	entry->head = head_entry;
+
+	switch (mode) {
+	case NM_DEDUP_MULTI_IDX_MODE_PREPEND:
+	case NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE:
+		if (entry_order)
+			c_list_link_before ((CList *) &entry_order->lst_entries, &entry->lst_entries);
+		else
+			c_list_link_front (&head_entry->lst_entries_head, &entry->lst_entries);
+		break;
+	default:
+		if (entry_order)
+			c_list_link_after ((CList *) &entry_order->lst_entries, &entry->lst_entries);
+		else
+			c_list_link_tail (&head_entry->lst_entries_head, &entry->lst_entries);
+		break;
+	};
+
+	idx_type->len++;
+	head_entry->len++;
+
+	if (   add_head_entry
+	    && !nm_g_hash_table_add (self->idx_entries, head_entry))
+		nm_assert_not_reached ();
+
+	if (!nm_g_hash_table_add (self->idx_entries, entry))
+		nm_assert_not_reached ();
+
+	NM_SET_OUT (out_entry, entry);
+	NM_SET_OUT (out_obj_old, NULL);
+	return TRUE;
+}
+
+gboolean
+nm_dedup_multi_index_add (NMDedupMultiIndex *self,
+                          NMDedupMultiIdxType *idx_type,
+                          /*const NMDedupMultiObj * */ gconstpointer obj,
+                          NMDedupMultiIdxMode mode,
+                          const NMDedupMultiEntry **out_entry,
+                          /* const NMDedupMultiObj ** */ gpointer out_obj_old)
+{
+	NMDedupMultiEntry *entry;
+
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (idx_type, FALSE);
+	g_return_val_if_fail (obj, FALSE);
+	g_return_val_if_fail (NM_IN_SET (mode,
+	                                 NM_DEDUP_MULTI_IDX_MODE_PREPEND,
+	                                 NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE,
+	                                 NM_DEDUP_MULTI_IDX_MODE_APPEND,
+	                                 NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE),
+	                      FALSE);
+
+	entry = _entry_lookup_obj (self, idx_type, obj);
+	return _add (self, idx_type, obj,
+	             entry, mode,
+	             NULL, NULL,
+	             out_entry, out_obj_old);
+}
+
+/* nm_dedup_multi_index_add_full:
+ * @self: the index instance.
+ * @idx_type: the index handle for storing @obj.
+ * @obj: the NMDedupMultiObj instance to add.
+ * @mode: whether to append or prepend the new item. If @entry_order is given,
+ *   the entry will be sorted after/before, instead of appending/prepending to
+ *   the entire list. If a comparable object is already tracked, then it may
+ *   still be resorted by specifying one of the "FORCE" modes.
+ * @entry_order: if not NULL, the new entry will be sorted before or after @entry_order.
+ *   If given, @entry_order MUST be tracked by @self, and the object it points to MUST
+ *   be in the same partition tracked by @idx_type. That is, they must have the same
+ *   head_entry and it means, you must ensure that @entry_order and the created/modified
+ *   entry will share the same head.
+ * @entry_existing: if not NULL, it safes a hash lookup of the entry where the
+ *   object will be placed in. You can omit this, and it will be automatically
+ *   detected (at the expense of an additional hash lookup).
+ *   Basically, this is the result of nm_dedup_multi_index_lookup_obj(),
+ *   with the pecularity that if you know that @obj is not yet tracked,
+ *   you may specify %NM_DEDUP_MULTI_ENTRY_MISSING.
+ * @head_existing: an optional argument to safe a lookup for the head. If specified,
+ *   it must be identical to nm_dedup_multi_index_lookup_head(), with the pecularity
+ *   that if the head is not yet tracked, you may specify %NM_DEDUP_MULTI_HEAD_ENTRY_MISSING
+ * @out_entry: if give, return the added entry. This entry may have already exists (update)
+ *   or be newly created. If @obj is not partitionable according to @idx_type, @obj
+ *   is not to be added and it returns %NULL.
+ * @out_obj_old: if given, return the previously contained object. It only
+ *   returns a  object, if a matching entry was tracked previously, not if a
+ *   new entry was created. Note that when passing @out_obj_old you obtain a reference
+ *   to the boxed object and MUST return it with nm_dedup_multi_obj_unref().
+ *
+ * Adds and object to the index.
+ *
+ * Return: %TRUE if anything changed, %FALSE if nothing changed.
+ */
+gboolean
+nm_dedup_multi_index_add_full (NMDedupMultiIndex *self,
+                               NMDedupMultiIdxType *idx_type,
+                               /*const NMDedupMultiObj * */ gconstpointer obj,
+                               NMDedupMultiIdxMode mode,
+                               const NMDedupMultiEntry *entry_order,
+                               const NMDedupMultiEntry *entry_existing,
+                               const NMDedupMultiHeadEntry *head_existing,
+                               const NMDedupMultiEntry **out_entry,
+                               /* const NMDedupMultiObj ** */ gpointer out_obj_old)
+{
+	NMDedupMultiEntry *entry;
+
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (idx_type, FALSE);
+	g_return_val_if_fail (obj, FALSE);
+	g_return_val_if_fail (NM_IN_SET (mode,
+	                                 NM_DEDUP_MULTI_IDX_MODE_PREPEND,
+	                                 NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE,
+	                                 NM_DEDUP_MULTI_IDX_MODE_APPEND,
+	                                 NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE),
+	                      FALSE);
+
+	if (entry_existing == NULL)
+		entry = _entry_lookup_obj (self, idx_type, obj);
+	else if (entry_existing == NM_DEDUP_MULTI_ENTRY_MISSING) {
+		nm_assert (!_entry_lookup_obj (self, idx_type, obj));
+		entry = NULL;
+	} else {
+		nm_assert (entry_existing == _entry_lookup_obj (self, idx_type, obj));
+		entry = (NMDedupMultiEntry *) entry_existing;
+	}
+	return _add (self, idx_type, obj,
+	             entry,
+	             mode, entry_order,
+	             (NMDedupMultiHeadEntry *) head_existing,
+	             out_entry, out_obj_old);
+}
+
+/*****************************************************************************/
+
+static void
+_remove_entry (NMDedupMultiIndex *self,
+               NMDedupMultiEntry *entry,
+               gboolean *out_head_entry_removed)
+{
+	const NMDedupMultiObj *obj;
+	NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIdxType *idx_type;
+
+	nm_assert (self);
+	nm_assert (entry);
+	nm_assert (entry->obj);
+	nm_assert (entry->head);
+	nm_assert (!c_list_is_empty (&entry->lst_entries));
+	nm_assert (g_hash_table_lookup (self->idx_entries, entry) == entry);
+
+	head_entry = (NMDedupMultiHeadEntry *) entry->head;
+	obj = entry->obj;
+
+	nm_assert (head_entry);
+	nm_assert (head_entry->len > 0);
+	nm_assert (g_hash_table_lookup (self->idx_entries, head_entry) == head_entry);
+
+	idx_type = (NMDedupMultiIdxType *) head_entry->idx_type;
+	ASSERT_idx_type (idx_type);
+
+	nm_assert (idx_type->len >= head_entry->len);
+	if (--head_entry->len > 0) {
+		nm_assert (idx_type->len > 1);
+		idx_type->len--;
+		head_entry = NULL;
+	}
+
+	NM_SET_OUT (out_head_entry_removed, head_entry != NULL);
+
+	if (!g_hash_table_remove (self->idx_entries, entry))
+		nm_assert_not_reached ();
+
+	if (   head_entry
+	    && !g_hash_table_remove (self->idx_entries, head_entry))
+		nm_assert_not_reached ();
+
+	c_list_unlink (&entry->lst_entries);
+	g_slice_free (NMDedupMultiEntry, entry);
+
+	if (head_entry) {
+		nm_assert (c_list_is_empty (&head_entry->lst_entries_head));
+		c_list_unlink (&head_entry->lst_idx);
+		g_slice_free (NMDedupMultiHeadEntry, head_entry);
+	}
+
+	nm_dedup_multi_obj_unref (obj);
+}
+
+static guint
+_remove_head (NMDedupMultiIndex *self,
+              NMDedupMultiHeadEntry *head_entry,
+              gboolean remove_all /* otherwise just dirty ones */,
+              gboolean mark_survivors_dirty)
+{
+	guint n;
+	gboolean head_entry_removed;
+	CList *iter_entry, *iter_entry_safe;
+
+	nm_assert (self);
+	nm_assert (head_entry);
+	nm_assert (head_entry->len > 0);
+	nm_assert (head_entry->len == c_list_length (&head_entry->lst_entries_head));
+	nm_assert (g_hash_table_lookup (self->idx_entries, head_entry) == head_entry);
+
+	n = 0;
+	c_list_for_each_safe (iter_entry, iter_entry_safe, &head_entry->lst_entries_head) {
+		NMDedupMultiEntry *entry;
+
+		entry = c_list_entry (iter_entry, NMDedupMultiEntry, lst_entries);
+		if (   remove_all
+		    || entry->dirty) {
+			_remove_entry (self,
+			               entry,
+			               &head_entry_removed);
+			n++;
+			if (head_entry_removed)
+				break;
+		} else if (mark_survivors_dirty)
+			nm_dedup_multi_entry_set_dirty (entry, TRUE);
+	}
+
+	return n;
+}
+
+static guint
+_remove_idx_entry (NMDedupMultiIndex *self,
+                   NMDedupMultiIdxType *idx_type,
+                   gboolean remove_all /* otherwise just dirty ones */,
+                   gboolean mark_survivors_dirty)
+{
+	guint n;
+	CList *iter_idx, *iter_idx_safe;
+
+	nm_assert (self);
+	ASSERT_idx_type (idx_type);
+
+	n = 0;
+	c_list_for_each_safe (iter_idx, iter_idx_safe, &idx_type->lst_idx_head) {
+		n += _remove_head (self,
+		                   c_list_entry (iter_idx, NMDedupMultiHeadEntry, lst_idx),
+		                   remove_all, mark_survivors_dirty);
+	}
+	return n;
+}
+
+guint
+nm_dedup_multi_index_remove_entry (NMDedupMultiIndex *self,
+                                   gconstpointer entry)
+{
+	g_return_val_if_fail (self, 0);
+
+	nm_assert (entry);
+
+	if (!((NMDedupMultiEntry *) entry)->is_head) {
+		_remove_entry (self, (NMDedupMultiEntry *) entry, NULL);
+		return 1;
+	}
+	return _remove_head (self, (NMDedupMultiHeadEntry *) entry, TRUE, FALSE);
+}
+
+guint
+nm_dedup_multi_index_remove_obj (NMDedupMultiIndex *self,
+                                 NMDedupMultiIdxType *idx_type,
+                                 /*const NMDedupMultiObj * */ gconstpointer obj,
+                                 /*const NMDedupMultiObj ** */ gconstpointer *out_obj)
+{
+	const NMDedupMultiEntry *entry;
+
+	entry = nm_dedup_multi_index_lookup_obj (self, idx_type, obj);
+	if (!entry) {
+		NM_SET_OUT (out_obj, NULL);
+		return 0;
+	}
+
+	/* since we are about to remove the object, we obviously pass
+	 * a reference to @out_obj, the caller MUST unref the object,
+	 * if he chooses to provide @out_obj. */
+	NM_SET_OUT (out_obj, nm_dedup_multi_obj_ref (entry->obj));
+
+	_remove_entry (self, (NMDedupMultiEntry *) entry, NULL);
+	return 1;
+}
+
+guint
+nm_dedup_multi_index_remove_head (NMDedupMultiIndex *self,
+                                  NMDedupMultiIdxType *idx_type,
+                                  /*const NMDedupMultiObj * */ gconstpointer obj)
+{
+	const NMDedupMultiHeadEntry *entry;
+
+	entry = nm_dedup_multi_index_lookup_head (self, idx_type, obj);
+	return entry
+	       ? _remove_head (self, (NMDedupMultiHeadEntry *) entry, TRUE, FALSE)
+	       : 0;
+}
+
+guint
+nm_dedup_multi_index_remove_idx (NMDedupMultiIndex *self,
+                                 NMDedupMultiIdxType *idx_type)
+{
+	g_return_val_if_fail (self, 0);
+	g_return_val_if_fail (idx_type, 0);
+
+	return _remove_idx_entry (self, idx_type, TRUE, FALSE);
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_dedup_multi_index_lookup_obj:
+ * @self: the index cache
+ * @idx_type: the lookup index type
+ * @obj: the object to lookup. This means the match is performed
+ *   according to NMDedupMultiIdxTypeClass's idx_obj_id_equal()
+ *   of @idx_type.
+ *
+ * Returns: the cache entry or %NULL if the entry wasn't found.
+ */
+const NMDedupMultiEntry *
+nm_dedup_multi_index_lookup_obj (const NMDedupMultiIndex *self,
+                                 const NMDedupMultiIdxType *idx_type,
+                                 /*const NMDedupMultiObj * */ gconstpointer obj)
+{
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (idx_type, FALSE);
+	g_return_val_if_fail (obj, FALSE);
+
+	nm_assert (idx_type && idx_type->klass);
+	return _entry_lookup_obj (self, idx_type, obj);
+}
+
+/**
+ * nm_dedup_multi_index_lookup_head:
+ * @self: the index cache
+ * @idx_type: the lookup index type
+ * @obj: the object to lookup, of type "const NMDedupMultiObj *".
+ *   Depending on the idx_type, you *must* also provide a selector
+ *   object, even when looking up the list head. That is, because
+ *   the idx_type implementation may choose to partition the objects
+ *   in distinct list, so you need a selector object to know which
+ *   list head to lookup.
+ *
+ * Returns: the cache entry or %NULL if the entry wasn't found.
+ */
+const NMDedupMultiHeadEntry *
+nm_dedup_multi_index_lookup_head (const NMDedupMultiIndex *self,
+                                  const NMDedupMultiIdxType *idx_type,
+                                  /*const NMDedupMultiObj * */ gconstpointer obj)
+{
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (idx_type, FALSE);
+
+	return _entry_lookup_head (self, idx_type, obj);
+}
+
+/*****************************************************************************/
+
+void
+nm_dedup_multi_index_dirty_set_head (NMDedupMultiIndex *self,
+                                     const NMDedupMultiIdxType *idx_type,
+                                     /*const NMDedupMultiObj * */ gconstpointer obj)
+{
+	NMDedupMultiHeadEntry *head_entry;
+	CList *iter_entry;
+
+	g_return_if_fail (self);
+	g_return_if_fail (idx_type);
+
+	head_entry = _entry_lookup_head (self, idx_type, obj);
+	if (!head_entry)
+		return;
+
+	c_list_for_each (iter_entry, &head_entry->lst_entries_head) {
+		NMDedupMultiEntry *entry;
+
+		entry = c_list_entry (iter_entry, NMDedupMultiEntry, lst_entries);
+		nm_dedup_multi_entry_set_dirty (entry, TRUE);
+	}
+}
+
+void
+nm_dedup_multi_index_dirty_set_idx (NMDedupMultiIndex *self,
+                                    const NMDedupMultiIdxType *idx_type)
+{
+	CList *iter_idx, *iter_entry;
+
+	g_return_if_fail (self);
+	g_return_if_fail (idx_type);
+
+	c_list_for_each (iter_idx, &idx_type->lst_idx_head) {
+		NMDedupMultiHeadEntry *head_entry;
+
+		head_entry = c_list_entry (iter_idx, NMDedupMultiHeadEntry, lst_idx);
+		c_list_for_each (iter_entry, &head_entry->lst_entries_head) {
+			NMDedupMultiEntry *entry;
+
+			entry = c_list_entry (iter_entry, NMDedupMultiEntry, lst_entries);
+			nm_dedup_multi_entry_set_dirty (entry, TRUE);
+		}
+	}
+}
+
+/**
+ * nm_dedup_multi_index_dirty_remove_idx:
+ * @self: the index instance
+ * @idx_type: the index-type to select the objects.
+ * @mark_survivors_dirty: while the function removes all entries that are
+ *   marked as dirty, if @set_dirty is true, the surviving objects
+ *   will be marked dirty right away.
+ *
+ * Deletes all entries for @idx_type that are marked dirty. Only
+ * non-dirty objects survive. If @mark_survivors_dirty is set to TRUE, the survivors
+ * are marked as dirty right away.
+ *
+ * Returns: number of deleted entries.
+ */
+guint
+nm_dedup_multi_index_dirty_remove_idx (NMDedupMultiIndex *self,
+                                       NMDedupMultiIdxType *idx_type,
+                                       gboolean mark_survivors_dirty)
+{
+	g_return_val_if_fail (self, 0);
+	g_return_val_if_fail (idx_type, 0);
+
+	return _remove_idx_entry (self, idx_type, FALSE, mark_survivors_dirty);
+}
+
+/*****************************************************************************/
+
+static guint
+_dict_idx_objs_hash (const NMDedupMultiObj *obj)
+{
+	NMHashState h;
+
+	nm_hash_init (&h, 1748638583u);
+	obj->klass->obj_full_hash_update (obj, &h);
+	return nm_hash_complete (&h);
+}
+
+static gboolean
+_dict_idx_objs_equal (const NMDedupMultiObj *obj_a,
+                      const NMDedupMultiObj *obj_b)
+{
+	return    obj_a == obj_b
+	       || (   obj_a->klass == obj_b->klass
+	           && obj_a->klass->obj_full_equal (obj_a, obj_b));
+}
+
+void
+nm_dedup_multi_index_obj_release (NMDedupMultiIndex *self,
+                                  /* const NMDedupMultiObj * */ gconstpointer obj)
+{
+	nm_assert (self);
+	nm_assert (obj);
+	nm_assert (g_hash_table_lookup (self->idx_objs, obj) == obj);
+	nm_assert (((const NMDedupMultiObj *) obj)->_multi_idx == self);
+
+	((NMDedupMultiObj *) obj)->_multi_idx = NULL;
+	if (!g_hash_table_remove (self->idx_objs, obj))
+		nm_assert_not_reached ();
+}
+
+gconstpointer
+nm_dedup_multi_index_obj_find (NMDedupMultiIndex *self,
+                               /* const NMDedupMultiObj * */ gconstpointer obj)
+{
+	g_return_val_if_fail (self, NULL);
+	g_return_val_if_fail (obj, NULL);
+
+	return g_hash_table_lookup (self->idx_objs, obj);
+}
+
+gconstpointer
+nm_dedup_multi_index_obj_intern (NMDedupMultiIndex *self,
+                                 /* const NMDedupMultiObj * */ gconstpointer obj)
+{
+	const NMDedupMultiObj *obj_new = obj;
+	const NMDedupMultiObj *obj_old;
+
+	nm_assert (self);
+	nm_assert (obj_new);
+
+	if (obj_new->_multi_idx == self) {
+		nm_assert (g_hash_table_lookup (self->idx_objs, obj_new) == obj_new);
+		nm_dedup_multi_obj_ref (obj_new);
+		return obj_new;
+	}
+
+	obj_old = g_hash_table_lookup (self->idx_objs, obj_new);
+	nm_assert (obj_old != obj_new);
+
+	if (obj_old) {
+		nm_assert (obj_old->_multi_idx == self);
+		nm_dedup_multi_obj_ref (obj_old);
+		return obj_old;
+	}
+
+	if (nm_dedup_multi_obj_needs_clone (obj_new))
+		obj_new = nm_dedup_multi_obj_clone (obj_new);
+	else
+		obj_new = nm_dedup_multi_obj_ref (obj_new);
+
+	nm_assert (obj_new);
+	nm_assert (!obj_new->_multi_idx);
+
+	if (!nm_g_hash_table_add (self->idx_objs, (gpointer) obj_new))
+		nm_assert_not_reached ();
+
+	((NMDedupMultiObj *) obj_new)->_multi_idx = self;
+	return obj_new;
+}
+
+const NMDedupMultiObj *
+nm_dedup_multi_obj_unref (const NMDedupMultiObj *obj)
+{
+	if (obj) {
+		nm_assert (obj->_ref_count > 0);
+		nm_assert (obj->_ref_count != NM_OBJ_REF_COUNT_STACKINIT);
+
+again:
+		if (--(((NMDedupMultiObj *) obj)->_ref_count) <= 0) {
+			if (obj->_multi_idx) {
+				/* restore the ref-count to 1 and release the object first
+				 * from the index. Then, retry again to unref. */
+				((NMDedupMultiObj *) obj)->_ref_count++;
+				nm_dedup_multi_index_obj_release (obj->_multi_idx, obj);
+				nm_assert (obj->_ref_count == 1);
+				nm_assert (!obj->_multi_idx);
+				goto again;
+			}
+
+			obj->klass->obj_destroy ((NMDedupMultiObj *) obj);
+		}
+	}
+
+	return NULL;
+}
+
+gboolean
+nm_dedup_multi_obj_needs_clone (const NMDedupMultiObj *obj)
+{
+	nm_assert (obj);
+
+	if (   obj->_multi_idx
+	    || obj->_ref_count == NM_OBJ_REF_COUNT_STACKINIT)
+		return TRUE;
+
+	if (   obj->klass->obj_needs_clone
+	    && obj->klass->obj_needs_clone (obj))
+		return TRUE;
+
+	return FALSE;
+}
+
+const NMDedupMultiObj *
+nm_dedup_multi_obj_clone (const NMDedupMultiObj *obj)
+{
+	const NMDedupMultiObj *o;
+
+	nm_assert (obj);
+
+	o = obj->klass->obj_clone (obj);
+	nm_assert (o);
+	nm_assert (o->_ref_count == 1);
+	return o;
+}
+
+gconstpointer *
+nm_dedup_multi_objs_to_array_head (const NMDedupMultiHeadEntry *head_entry,
+                                   NMDedupMultiFcnSelectPredicate predicate,
+                                   gpointer user_data,
+                                   guint *out_len)
+{
+	gconstpointer *result;
+	CList *iter;
+	guint i;
+
+	if (!head_entry) {
+		NM_SET_OUT (out_len, 0);
+		return NULL;
+	}
+
+	result = g_new (gconstpointer, head_entry->len + 1);
+	i = 0;
+	c_list_for_each (iter, &head_entry->lst_entries_head) {
+		const NMDedupMultiObj *obj = c_list_entry (iter, NMDedupMultiEntry, lst_entries)->obj;
+
+		if (   !predicate
+		    || predicate (obj, user_data)) {
+			nm_assert (i < head_entry->len);
+			result[i++] = obj;
+		}
+	}
+
+	if (i == 0) {
+		g_free (result);
+		NM_SET_OUT (out_len, 0);
+		return NULL;
+	}
+
+	nm_assert (i <= head_entry->len);
+	NM_SET_OUT (out_len, i);
+	result[i++] = NULL;
+	return result;
+}
+
+GPtrArray *
+nm_dedup_multi_objs_to_ptr_array_head (const NMDedupMultiHeadEntry *head_entry,
+                                       NMDedupMultiFcnSelectPredicate predicate,
+                                       gpointer user_data)
+{
+	GPtrArray *result;
+	CList *iter;
+
+	if (!head_entry)
+		return NULL;
+
+	result = g_ptr_array_new_full (head_entry->len,
+	                               (GDestroyNotify) nm_dedup_multi_obj_unref);
+	c_list_for_each (iter, &head_entry->lst_entries_head) {
+		const NMDedupMultiObj *obj = c_list_entry (iter, NMDedupMultiEntry, lst_entries)->obj;
+
+		if (   !predicate
+		    || predicate (obj, user_data))
+			g_ptr_array_add (result, (gpointer) nm_dedup_multi_obj_ref (obj));
+	}
+
+	if (result->len == 0) {
+		g_ptr_array_unref (result);
+		return NULL;
+	}
+	return result;
+}
+
+/**
+ * nm_dedup_multi_entry_reorder:
+ * @entry: the entry to reorder. It must not be NULL (and tracked in an index).
+ * @entry_order: (allow-none): an optional other entry. It MUST be in the same
+ *   list as entry. If given, @entry will be ordered after/before @entry_order.
+ *   If left at %NULL, @entry will be moved to the front/end of the list.
+ * @order_after: if @entry_order is given, %TRUE means to move @entry after
+ *   @entry_order (otherwise before).
+ *   If @entry_order is %NULL, %TRUE means to move @entry to the tail of the list
+ *   (otherwise the beginning). Note that "tail of the list" here means that @entry
+ *   will be linked before the head of the circular list.
+ *
+ * Returns: %TRUE, if anything was changed. Otherwise, @entry was already at the
+ * right place and nothing was done.
+ */
+gboolean
+nm_dedup_multi_entry_reorder (const NMDedupMultiEntry *entry,
+                              const NMDedupMultiEntry *entry_order,
+                              gboolean order_after)
+{
+	nm_assert (entry);
+
+	if (!entry_order) {
+		const NMDedupMultiHeadEntry *head_entry = entry->head;
+
+		nm_assert (c_list_contains (&head_entry->lst_entries_head, &entry->lst_entries));
+		if (order_after) {
+			if (head_entry->lst_entries_head.prev != &entry->lst_entries) {
+				c_list_unlink ((CList *) &entry->lst_entries);
+				c_list_link_tail ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries);
+				return TRUE;
+			}
+		} else {
+			if (head_entry->lst_entries_head.next != &entry->lst_entries) {
+				c_list_unlink ((CList *) &entry->lst_entries);
+				c_list_link_front ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries);
+				return TRUE;
+			}
+		}
+	} else if (entry != entry_order) {
+		if (order_after) {
+			if (entry_order->lst_entries.next != &entry->lst_entries) {
+				c_list_unlink ((CList *) &entry->lst_entries);
+				c_list_link_after ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries);
+				return TRUE;
+			}
+		} else {
+			if (entry_order->lst_entries.prev != &entry->lst_entries) {
+				c_list_unlink ((CList *) &entry->lst_entries);
+				c_list_link_before ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries);
+				return TRUE;
+			}
+		}
+	}
+
+	return FALSE;
+}
+
+/*****************************************************************************/
+
+NMDedupMultiIndex *
+nm_dedup_multi_index_new (void)
+{
+	NMDedupMultiIndex *self;
+
+	self = g_slice_new0 (NMDedupMultiIndex);
+	self->ref_count = 1;
+	self->idx_entries = g_hash_table_new ((GHashFunc) _dict_idx_entries_hash, (GEqualFunc) _dict_idx_entries_equal);
+	self->idx_objs    = g_hash_table_new ((GHashFunc) _dict_idx_objs_hash,    (GEqualFunc) _dict_idx_objs_equal);
+	return self;
+}
+
+NMDedupMultiIndex *
+nm_dedup_multi_index_ref (NMDedupMultiIndex *self)
+{
+	g_return_val_if_fail (self, NULL);
+	g_return_val_if_fail (self->ref_count > 0, NULL);
+
+	self->ref_count++;
+	return self;
+}
+
+NMDedupMultiIndex *
+nm_dedup_multi_index_unref (NMDedupMultiIndex *self)
+{
+	GHashTableIter iter;
+	const NMDedupMultiIdxType *idx_type;
+	NMDedupMultiEntry *entry;
+	const NMDedupMultiObj *obj;
+
+	g_return_val_if_fail (self, NULL);
+	g_return_val_if_fail (self->ref_count > 0, NULL);
+
+	if (--self->ref_count > 0)
+		return NULL;
+
+more:
+	g_hash_table_iter_init (&iter, self->idx_entries);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &entry, NULL)) {
+		if (entry->is_head)
+			idx_type = ((NMDedupMultiHeadEntry *) entry)->idx_type;
+		else
+			idx_type = entry->head->idx_type;
+		_remove_idx_entry (self, (NMDedupMultiIdxType *) idx_type, TRUE, FALSE);
+		goto more;
+	}
+
+	nm_assert (g_hash_table_size (self->idx_entries) == 0);
+
+	g_hash_table_iter_init (&iter, self->idx_objs);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) {
+		nm_assert (obj->_multi_idx == self);
+		((NMDedupMultiObj * )obj)->_multi_idx = NULL;
+	}
+	g_hash_table_remove_all (self->idx_objs);
+
+	g_hash_table_unref (self->idx_entries);
+	g_hash_table_unref (self->idx_objs);
+
+	g_slice_free (NMDedupMultiIndex, self);
+	return NULL;
+}
diff --git a/shared/nm-utils/nm-dedup-multi.h b/shared/nm-utils/nm-dedup-multi.h
new file mode 100644
index 00000000..bebfe43d
--- /dev/null
+++ b/shared/nm-utils/nm-dedup-multi.h
@@ -0,0 +1,437 @@
+/* -*- 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_DEDUP_MULTI_H__
+#define __NM_DEDUP_MULTI_H__
+
+#include "nm-obj.h"
+#include "c-list-util.h"
+
+/*****************************************************************************/
+
+struct _NMHashState;
+
+typedef struct _NMDedupMultiObj             NMDedupMultiObj;
+typedef struct _NMDedupMultiObjClass        NMDedupMultiObjClass;
+typedef struct _NMDedupMultiIdxType         NMDedupMultiIdxType;
+typedef struct _NMDedupMultiIdxTypeClass    NMDedupMultiIdxTypeClass;
+typedef struct _NMDedupMultiEntry           NMDedupMultiEntry;
+typedef struct _NMDedupMultiHeadEntry       NMDedupMultiHeadEntry;
+typedef struct _NMDedupMultiIndex           NMDedupMultiIndex;
+
+typedef enum _NMDedupMultiIdxMode {
+	NM_DEDUP_MULTI_IDX_MODE_PREPEND,
+
+	NM_DEDUP_MULTI_IDX_MODE_PREPEND_FORCE,
+
+	/* append new objects to the end of the list.
+	 * If the object is already in the cache, don't move it. */
+	NM_DEDUP_MULTI_IDX_MODE_APPEND,
+
+	/* like NM_DEDUP_MULTI_IDX_MODE_APPEND, but if the object
+	 * is already in teh cache, move it to the end. */
+	NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE,
+} NMDedupMultiIdxMode;
+
+/*****************************************************************************/
+
+struct _NMDedupMultiObj {
+	union {
+		NMObjBaseInst parent;
+		const NMDedupMultiObjClass *klass;
+	};
+	NMDedupMultiIndex *_multi_idx;
+	guint _ref_count;
+};
+
+struct _NMDedupMultiObjClass {
+	NMObjBaseClass parent;
+
+	const NMDedupMultiObj *(*obj_clone) (const NMDedupMultiObj *obj);
+
+	gboolean (*obj_needs_clone) (const NMDedupMultiObj *obj);
+
+	void (*obj_destroy) (NMDedupMultiObj *obj);
+
+	/* the NMDedupMultiObj can be deduplicated. For that the obj_full_hash_update()
+	 * and obj_full_equal() compare *all* fields of the object, even minor ones. */
+	void (*obj_full_hash_update)  (const NMDedupMultiObj *obj,
+	                               struct _NMHashState *h);
+	gboolean (*obj_full_equal) (const NMDedupMultiObj *obj_a,
+	                            const NMDedupMultiObj *obj_b);
+};
+
+/*****************************************************************************/
+
+static inline const NMDedupMultiObj *
+nm_dedup_multi_obj_ref (const NMDedupMultiObj *obj)
+{
+	/* ref and unref accept const pointers. Objects is supposed to be shared
+	 * and kept immutable. Disallowing to take/retrun a reference to a const
+	 * NMPObject is cumbersome, because callers are precisely expected to
+	 * keep a ref on the otherwise immutable object. */
+
+	nm_assert (obj);
+	nm_assert (obj->_ref_count != NM_OBJ_REF_COUNT_STACKINIT);
+	nm_assert (obj->_ref_count > 0);
+
+	((NMDedupMultiObj *) obj)->_ref_count++;
+	return obj;
+}
+
+const NMDedupMultiObj *nm_dedup_multi_obj_unref       (const NMDedupMultiObj *obj);
+const NMDedupMultiObj *nm_dedup_multi_obj_clone       (const NMDedupMultiObj *obj);
+gboolean               nm_dedup_multi_obj_needs_clone (const NMDedupMultiObj *obj);
+
+gconstpointer nm_dedup_multi_index_obj_intern (NMDedupMultiIndex *self,
+                                               /* const NMDedupMultiObj * */ gconstpointer obj);
+
+void nm_dedup_multi_index_obj_release (NMDedupMultiIndex *self,
+                                       /* const NMDedupMultiObj * */ gconstpointer obj);
+
+/* const NMDedupMultiObj * */ gconstpointer nm_dedup_multi_index_obj_find (NMDedupMultiIndex *self,
+                                                                           /* const NMDedupMultiObj * */ gconstpointer obj);
+
+/*****************************************************************************/
+
+/* 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
+ * multiple instances (of the same class).
+ *
+ * For example, NMIP4Config can have idx-type to put there all IPv4 Routes.
+ * This idx-type instance is private to the NMIP4Config instance. Basically,
+ * the NMIP4Config instance uses the idx-type to maintain an ordered list
+ * of routes in NMDedupMultiIndex.
+ *
+ * However, a NMDedupMultiIdxType may also partition the set of objects
+ * in multiple distinct lists. NMIP4Config doesn't do that (because instead
+ * of creating one idx-type for IPv4 and IPv6 routes, it just cretaes
+ * to distinct idx-types, one for each address family.
+ * This partitioning is used by NMPlatform to maintain a lookup index for
+ * routes by ifindex. As the ifindex is dynamic, it does not create an
+ * idx-type instance for each ifindex. Instead, it has one idx-type for
+ * all routes. But whenever accessing NMDedupMultiIndex with an NMDedupMultiObj,
+ * the partitioning NMDedupMultiIdxType takes into accound the NMDedupMultiObj
+ * instance to associate it with the right list.
+ *
+ * Hence, a NMDedupMultiIdxEntry has a list of possibly multiple NMDedupMultiHeadEntry
+ * instances, which each is the head for a list of NMDedupMultiEntry instances.
+ * In the platform example, the NMDedupMultiHeadEntry parition the indexed objects
+ * by their ifindex. */
+struct _NMDedupMultiIdxType {
+	union {
+		NMObjBaseInst parent;
+		const NMDedupMultiIdxTypeClass *klass;
+	};
+
+	CList lst_idx_head;
+
+	guint len;
+};
+
+void nm_dedup_multi_idx_type_init (NMDedupMultiIdxType *idx_type,
+                                   const NMDedupMultiIdxTypeClass *klass);
+
+struct _NMDedupMultiIdxTypeClass {
+	NMObjBaseClass parent;
+
+	void (*idx_obj_id_hash_update)  (const NMDedupMultiIdxType *idx_type,
+	                                 const NMDedupMultiObj *obj,
+	                                 struct _NMHashState *h);
+	gboolean (*idx_obj_id_equal) (const NMDedupMultiIdxType *idx_type,
+	                              const NMDedupMultiObj *obj_a,
+	                              const NMDedupMultiObj *obj_b);
+
+	/* an NMDedupMultiIdxTypeClass which implements partitioning of the
+	 * tracked objects, must implement the idx_obj_partition*() functions.
+	 *
+	 * idx_obj_partitionable() may return NULL if the object cannot be tracked.
+	 * For example, a index for routes by ifindex, may not want to track any
+	 * routes that don't have a valid ifindex. If the idx-type says that the
+	 * object is not partitionable, it is never added to the NMDedupMultiIndex. */
+	gboolean (*idx_obj_partitionable)   (const NMDedupMultiIdxType *idx_type,
+	                                     const NMDedupMultiObj *obj);
+	void (*idx_obj_partition_hash_update) (const NMDedupMultiIdxType *idx_type,
+	                                       const NMDedupMultiObj *obj,
+	                                       struct _NMHashState *h);
+	gboolean (*idx_obj_partition_equal) (const NMDedupMultiIdxType *idx_type,
+	                                     const NMDedupMultiObj *obj_a,
+	                                     const NMDedupMultiObj *obj_b);
+};
+
+static inline gboolean
+nm_dedup_multi_idx_type_id_equal (const NMDedupMultiIdxType *idx_type,
+                                  /* const NMDedupMultiObj * */ gconstpointer obj_a,
+                                  /* const NMDedupMultiObj * */ gconstpointer obj_b)
+{
+	nm_assert (idx_type);
+	return    obj_a == obj_b
+	       || idx_type->klass->idx_obj_id_equal (idx_type,
+	                                             obj_a,
+	                                             obj_b);
+}
+
+static inline gboolean
+nm_dedup_multi_idx_type_partition_equal (const NMDedupMultiIdxType *idx_type,
+                                         /* const NMDedupMultiObj * */ gconstpointer obj_a,
+                                         /* const NMDedupMultiObj * */ gconstpointer obj_b)
+{
+	nm_assert (idx_type);
+	if (idx_type->klass->idx_obj_partition_equal) {
+		nm_assert (obj_a);
+		nm_assert (obj_b);
+		return    obj_a == obj_b
+		       || idx_type->klass->idx_obj_partition_equal (idx_type,
+		                                                    obj_a,
+		                                                    obj_b);
+	}
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+struct _NMDedupMultiEntry {
+
+	/* this is the list of all entries that share the same head entry.
+	 * All entries compare equal according to idx_obj_partition_equal(). */
+	CList lst_entries;
+
+	/* const NMDedupMultiObj * */ gconstpointer obj;
+
+	bool is_head;
+	bool dirty;
+
+	const NMDedupMultiHeadEntry *head;
+};
+
+struct _NMDedupMultiHeadEntry {
+
+	/* this is the list of all entries that share the same head entry.
+	 * All entries compare equal according to idx_obj_partition_equal(). */
+	CList lst_entries_head;
+
+	const NMDedupMultiIdxType *idx_type;
+
+	bool is_head;
+
+	guint len;
+
+	CList lst_idx;
+};
+
+/*****************************************************************************/
+
+static inline gconstpointer
+nm_dedup_multi_entry_get_obj (const NMDedupMultiEntry *entry)
+{
+	/* convenience method that allows to skip the %NULL check on
+	 * @entry. Think of the NULL-conditional operator ?. of C# */
+	return entry ? entry->obj : NULL;
+}
+
+/*****************************************************************************/
+
+static inline void
+nm_dedup_multi_entry_set_dirty (const NMDedupMultiEntry *entry,
+                                gboolean dirty)
+{
+	/* NMDedupMultiEntry is always exposed as a const object, because it is not
+	 * supposed to be modified outside NMDedupMultiIndex API. Except the "dirty"
+	 * flag. In C++ speak, it is a mutable field.
+	 *
+	 * Add this inline function, to cast-away constness and set the dirty flag. */
+	nm_assert (entry);
+	((NMDedupMultiEntry *) entry)->dirty = dirty;
+}
+
+/*****************************************************************************/
+
+NMDedupMultiIndex *nm_dedup_multi_index_new (void);
+NMDedupMultiIndex *nm_dedup_multi_index_ref (NMDedupMultiIndex *self);
+NMDedupMultiIndex *nm_dedup_multi_index_unref (NMDedupMultiIndex *self);
+
+static inline void
+_nm_auto_unref_dedup_multi_index (NMDedupMultiIndex **v)
+{
+	if (*v)
+		nm_dedup_multi_index_unref (*v);
+}
+#define nm_auto_unref_dedup_multi_index nm_auto(_nm_auto_unref_dedup_multi_index)
+
+#define NM_DEDUP_MULTI_ENTRY_MISSING      ((const NMDedupMultiEntry *)     GUINT_TO_POINTER (1))
+#define NM_DEDUP_MULTI_HEAD_ENTRY_MISSING ((const NMDedupMultiHeadEntry *) GUINT_TO_POINTER (1))
+
+gboolean nm_dedup_multi_index_add_full (NMDedupMultiIndex *self,
+                                        NMDedupMultiIdxType *idx_type,
+                                        /*const NMDedupMultiObj * */ gconstpointer obj,
+                                        NMDedupMultiIdxMode mode,
+                                        const NMDedupMultiEntry *entry_order,
+                                        const NMDedupMultiEntry *entry_existing,
+                                        const NMDedupMultiHeadEntry *head_existing,
+                                        const NMDedupMultiEntry **out_entry,
+                                        /* const NMDedupMultiObj ** */ gpointer out_obj_old);
+
+gboolean nm_dedup_multi_index_add (NMDedupMultiIndex *self,
+                                   NMDedupMultiIdxType *idx_type,
+                                   /*const NMDedupMultiObj * */ gconstpointer obj,
+                                   NMDedupMultiIdxMode mode,
+                                   const NMDedupMultiEntry **out_entry,
+                                   /* const NMDedupMultiObj ** */ gpointer out_obj_old);
+
+const NMDedupMultiEntry *nm_dedup_multi_index_lookup_obj (const NMDedupMultiIndex *self,
+                                                          const NMDedupMultiIdxType *idx_type,
+                                                          /*const NMDedupMultiObj * */ gconstpointer obj);
+
+const NMDedupMultiHeadEntry *nm_dedup_multi_index_lookup_head (const NMDedupMultiIndex *self,
+                                                               const NMDedupMultiIdxType *idx_type,
+                                                               /*const NMDedupMultiObj * */ gconstpointer obj);
+
+guint nm_dedup_multi_index_remove_entry (NMDedupMultiIndex *self,
+                                         gconstpointer entry);
+
+guint nm_dedup_multi_index_remove_obj (NMDedupMultiIndex *self,
+                                       NMDedupMultiIdxType *idx_type,
+                                       /*const NMDedupMultiObj * */ gconstpointer obj,
+                                       /*const NMDedupMultiObj ** */ gconstpointer *out_obj);
+
+guint nm_dedup_multi_index_remove_head (NMDedupMultiIndex *self,
+                                        NMDedupMultiIdxType *idx_type,
+                                        /*const NMDedupMultiObj * */ gconstpointer obj);
+
+guint nm_dedup_multi_index_remove_idx (NMDedupMultiIndex *self,
+                                       NMDedupMultiIdxType *idx_type);
+
+void nm_dedup_multi_index_dirty_set_head (NMDedupMultiIndex *self,
+                                          const NMDedupMultiIdxType *idx_type,
+                                          /*const NMDedupMultiObj * */ gconstpointer obj);
+
+void nm_dedup_multi_index_dirty_set_idx (NMDedupMultiIndex *self,
+                                         const NMDedupMultiIdxType *idx_type);
+
+guint nm_dedup_multi_index_dirty_remove_idx (NMDedupMultiIndex *self,
+                                             NMDedupMultiIdxType *idx_type,
+                                             gboolean mark_survivors_dirty);
+
+/*****************************************************************************/
+
+typedef struct _NMDedupMultiIter {
+	const CList *_head;
+	const CList *_next;
+	const NMDedupMultiEntry *current;
+} NMDedupMultiIter;
+
+static inline void
+nm_dedup_multi_iter_init (NMDedupMultiIter *iter, const NMDedupMultiHeadEntry *head)
+{
+	g_return_if_fail (iter);
+
+	if (head && !c_list_is_empty (&head->lst_entries_head)) {
+		iter->_head = &head->lst_entries_head;
+		iter->_next = head->lst_entries_head.next;
+	} else {
+		iter->_head = NULL;
+		iter->_next = NULL;
+	}
+	iter->current = NULL;
+}
+
+static inline gboolean
+nm_dedup_multi_iter_next (NMDedupMultiIter *iter)
+{
+	g_return_val_if_fail (iter, FALSE);
+
+	if (!iter->_next)
+		return FALSE;
+
+	/* we always look ahead for the next. This way, the user
+	 * may delete the current entry (but no other entries). */
+	iter->current = c_list_entry (iter->_next, NMDedupMultiEntry, lst_entries);
+	if (iter->_next->next == iter->_head)
+		iter->_next = NULL;
+	else
+		iter->_next = iter->_next->next;
+	return TRUE;
+}
+
+#define nm_dedup_multi_iter_for_each(iter, head_entry) \
+	for (nm_dedup_multi_iter_init ((iter), (head_entry)); \
+	     nm_dedup_multi_iter_next ((iter)); \
+	     )
+
+/*****************************************************************************/
+
+typedef gboolean (*NMDedupMultiFcnSelectPredicate) (/* const NMDedupMultiObj * */ gconstpointer obj,
+                                                    gpointer user_data);
+
+gconstpointer *nm_dedup_multi_objs_to_array_head (const NMDedupMultiHeadEntry *head_entry,
+                                                  NMDedupMultiFcnSelectPredicate predicate,
+                                                  gpointer user_data,
+                                                  guint *out_len);
+GPtrArray *nm_dedup_multi_objs_to_ptr_array_head (const NMDedupMultiHeadEntry *head_entry,
+                                                  NMDedupMultiFcnSelectPredicate predicate,
+                                                  gpointer user_data);
+
+static inline const NMDedupMultiEntry *
+nm_dedup_multi_head_entry_get_idx (const NMDedupMultiHeadEntry *head_entry,
+                                   int idx)
+{
+	CList *iter;
+
+	if (head_entry) {
+		if (idx >= 0) {
+			c_list_for_each (iter, &head_entry->lst_entries_head) {
+				if (idx-- == 0)
+					return c_list_entry (iter, NMDedupMultiEntry, lst_entries);
+			}
+		} else {
+			for (iter = head_entry->lst_entries_head.prev;
+			     iter != &head_entry->lst_entries_head;
+			     iter = iter->prev) {
+				if (++idx == 0)
+					return c_list_entry (iter, NMDedupMultiEntry, lst_entries);
+			}
+		}
+	}
+	return NULL;
+}
+
+static inline void
+nm_dedup_multi_head_entry_sort (const NMDedupMultiHeadEntry *head_entry,
+                                CListSortCmp cmp,
+                                gconstpointer user_data)
+{
+	if (head_entry) {
+		/* the head entry can be sorted directly without messing up the
+		 * index to which it belongs. Of course, this does mess up any
+		 * NMDedupMultiIter instances. */
+		c_list_sort ((CList *) &head_entry->lst_entries_head, cmp, user_data);
+	}
+}
+
+gboolean nm_dedup_multi_entry_reorder (const NMDedupMultiEntry *entry,
+                                       const NMDedupMultiEntry *entry_order,
+                                       gboolean order_after);
+
+/*****************************************************************************/
+
+#endif /* __NM_DEDUP_MULTI_H__ */
diff --git a/shared/nm-utils/nm-enum-utils.c b/shared/nm-utils/nm-enum-utils.c
new file mode 100644
index 00000000..70a8b415
--- /dev/null
+++ b/shared/nm-utils/nm-enum-utils.c
@@ -0,0 +1,288 @@
+/* -*- 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-enum-utils.h"
+
+/*****************************************************************************/
+
+#define IS_FLAGS_SEPARATOR(ch)  (NM_IN_SET ((ch), ' ', '\t', ',', '\n', '\r'))
+
+static gboolean
+_is_hex_string (const char *str)
+{
+	return    str[0] == '0'
+	       && str[1] == 'x'
+	       && str[2]
+	       && NM_STRCHAR_ALL (&str[2], ch, g_ascii_isxdigit (ch));
+}
+
+static gboolean
+_is_dec_string (const char *str)
+{
+	return    str[0]
+	       && NM_STRCHAR_ALL (&str[0], ch, g_ascii_isdigit (ch));
+}
+
+static gboolean
+_enum_is_valid_enum_nick (const char *str)
+{
+	return    str[0]
+	       && !NM_STRCHAR_ANY (str, ch, g_ascii_isspace (ch))
+	       && !_is_dec_string (str)
+	       && !_is_hex_string (str);
+}
+
+static gboolean
+_enum_is_valid_flags_nick (const char *str)
+{
+	return    str[0]
+	       && !NM_STRCHAR_ANY (str, ch, IS_FLAGS_SEPARATOR (ch))
+	       && !_is_dec_string (str)
+	       && !_is_hex_string (str);
+}
+
+char *
+_nm_utils_enum_to_str_full (GType type,
+                            int value,
+                            const char *flags_separator)
+{
+	GTypeClass *class;
+	char *ret;
+
+	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);
+
+	if (G_IS_ENUM_CLASS (class)) {
+		GEnumValue *enum_value;
+
+		enum_value = g_enum_get_value (G_ENUM_CLASS (class), value);
+		if (   !enum_value
+		    || !_enum_is_valid_enum_nick (enum_value->value_nick))
+			ret = g_strdup_printf ("%d", value);
+		else
+			ret = strdup (enum_value->value_nick);
+	} else if (G_IS_FLAGS_CLASS (class)) {
+		GFlagsValue *flags_value;
+		GString *str = g_string_new ("");
+		unsigned uvalue = (unsigned) value;
+
+		flags_separator = flags_separator ?: " ";
+
+		do {
+			flags_value = g_flags_get_first_value (G_FLAGS_CLASS (class), uvalue);
+			if (str->len)
+				g_string_append (str, flags_separator);
+			if (   !flags_value
+			    || !_enum_is_valid_flags_nick (flags_value->value_nick)) {
+				if (uvalue)
+					g_string_append_printf (str, "0x%x", uvalue);
+				break;
+			}
+			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;
+}
+
+static const NMUtilsEnumValueInfo *
+_find_value_info (const NMUtilsEnumValueInfo *value_infos, const char *needle)
+{
+	if (value_infos) {
+		for (; value_infos->nick; value_infos++) {
+			if (nm_streq (needle, value_infos->nick))
+				return value_infos;
+		}
+	}
+	return NULL;
+}
+
+gboolean
+_nm_utils_enum_from_str_full (GType type,
+                              const char *str,
+                              int *out_value,
+                              char **err_token,
+                              const NMUtilsEnumValueInfo *value_infos)
+{
+	GTypeClass *class;
+	gboolean ret = FALSE;
+	int value = 0;
+	gs_free char *str_clone = NULL;
+	char *s;
+	gint64 v64;
+	const NMUtilsEnumValueInfo *nick;
+
+	g_return_val_if_fail (str, FALSE);
+
+	str_clone = strdup (str);
+	s = nm_str_skip_leading_spaces (str_clone);
+	g_strchomp (s);
+
+	class = g_type_class_ref (type);
+
+	if (G_IS_ENUM_CLASS (class)) {
+		GEnumValue *enum_value;
+
+		if (s[0]) {
+			if (_is_hex_string (s)) {
+				v64 = _nm_utils_ascii_str_to_int64 (s, 16, 0, G_MAXUINT, -1);
+				if (v64 != -1) {
+					value = (int) v64;
+					ret = TRUE;
+				}
+			} else if (_is_dec_string (s)) {
+				v64 = _nm_utils_ascii_str_to_int64 (s, 10, 0, G_MAXUINT, -1);
+				if (v64 != -1) {
+					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 (G_IS_FLAGS_CLASS (class)) {
+		GFlagsValue *flags_value;
+		unsigned uvalue = 0;
+
+		ret = TRUE;
+		while (s[0]) {
+			char *s_end;
+
+			for (s_end = s; s_end[0]; s_end++) {
+				if (IS_FLAGS_SEPARATOR (s_end[0])) {
+					s_end[0] = '\0';
+					s_end++;
+					break;
+				}
+			}
+
+			if (s[0]) {
+				if (_is_hex_string (s)) {
+					v64 = _nm_utils_ascii_str_to_int64 (&s[2], 16, 0, G_MAXUINT, -1);
+					if (v64 == -1) {
+						ret = FALSE;
+						break;
+					}
+					uvalue |= (unsigned) v64;
+				} else if (_is_dec_string (s)) {
+					v64 = _nm_utils_ascii_str_to_int64 (s, 10, 0, G_MAXUINT, -1);
+					if (v64 == -1) {
+						ret = FALSE;
+						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;
+						}
+					}
+				}
+			}
+
+			s = s_end;
+		}
+
+		value = (int) uvalue;
+	} else
+		g_return_val_if_reached (FALSE);
+
+	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);
+	return ret;
+}
+
+const char **
+_nm_utils_enum_get_values (GType type, gint from, gint to)
+{
+	GTypeClass *class;
+	GPtrArray *array;
+	gint i;
+	char sbuf[64];
+
+	class = g_type_class_ref (type);
+	array = g_ptr_array_new ();
+
+	if (G_IS_ENUM_CLASS (class)) {
+		GEnumClass *enum_class = G_ENUM_CLASS (class);
+		GEnumValue *enum_value;
+
+		for (i = 0; i < enum_class->n_values; i++) {
+			enum_value = &enum_class->values[i];
+			if (enum_value->value >= from && enum_value->value <= to) {
+				if (_enum_is_valid_enum_nick (enum_value->value_nick))
+					g_ptr_array_add (array, (gpointer) enum_value->value_nick);
+				else
+					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);
+		GFlagsValue *flags_value;
+
+		for (i = 0; i < flags_class->n_values; i++) {
+			flags_value = &flags_class->values[i];
+			if (flags_value->value >= (guint) from && flags_value->value <= (guint) to) {
+				if (_enum_is_valid_flags_nick (flags_value->value_nick))
+					g_ptr_array_add (array, (gpointer) flags_value->value_nick);
+				else
+					g_ptr_array_add (array, (gpointer) g_intern_string (nm_sprintf_buf (sbuf, "0x%x", (unsigned) flags_value->value)));
+			}
+		}
+	} else {
+		g_type_class_unref (class);
+		g_ptr_array_free (array, TRUE);
+		g_return_val_if_reached (NULL);
+	}
+
+	g_type_class_unref (class);
+	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
new file mode 100644
index 00000000..b78d9191
--- /dev/null
+++ b/shared/nm-utils/nm-enum-utils.h
@@ -0,0 +1,45 @@
+/* -*- 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_ENUM_UTILS_H__
+#define __NM_ENUM_UTILS_H__
+
+/*****************************************************************************/
+
+typedef struct _NMUtilsEnumValueInfo {
+	/* currently, this is only used for _nm_utils_enum_from_str_full() to
+	 * declare additional aliases for values. */
+	const char *nick;
+	int value;
+} NMUtilsEnumValueInfo;
+
+char *_nm_utils_enum_to_str_full (GType type, int value, const char *sep);
+gboolean _nm_utils_enum_from_str_full (GType type,
+                                       const char *str,
+                                       int *out_value,
+                                       char **err_token,
+                                       const NMUtilsEnumValueInfo *value_infos);
+
+const char **_nm_utils_enum_get_values (GType type, gint from, gint to);
+
+/*****************************************************************************/
+
+#endif /* __NM_ENUM_UTILS_H__ */
diff --git a/shared/nm-utils/nm-glib.h b/shared/nm-utils/nm-glib.h
index dd18756a..4ef538e4 100644
--- a/shared/nm-utils/nm-glib.h
+++ b/shared/nm-utils/nm-glib.h
@@ -452,4 +452,33 @@ _nm_g_variant_new_take_string (gchar *string)
 }
 #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
+
 #endif  /* __NM_GLIB_H__ */
diff --git a/shared/nm-utils/nm-hash-utils.c b/shared/nm-utils/nm-hash-utils.c
new file mode 100644
index 00000000..c563140e
--- /dev/null
+++ b/shared/nm-utils/nm-hash-utils.c
@@ -0,0 +1,119 @@
+/* -*- 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-hash-utils.h"
+
+#include <stdint.h>
+
+#include "nm-shared-utils.h"
+#include "nm-random-utils.h"
+
+/*****************************************************************************/
+
+#define HASH_KEY_SIZE 16u
+#define HASH_KEY_SIZE_GUINT ((HASH_KEY_SIZE + sizeof (guint) - 1) / sizeof (guint))
+
+G_STATIC_ASSERT (sizeof (guint) * HASH_KEY_SIZE_GUINT >= HASH_KEY_SIZE);
+
+static const guint8 *
+_get_hash_key (void)
+{
+	static const guint8 *volatile global_seed = NULL;
+	const guint8 *g;
+
+	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);
+		}
+	}
+
+	return g;
+}
+
+void
+nm_hash_init (NMHashState *state, guint static_seed)
+{
+	const guint8 *g;
+	guint seed[HASH_KEY_SIZE_GUINT];
+
+	nm_assert (state);
+
+	g = _get_hash_key ();
+	memcpy (seed, g, HASH_KEY_SIZE);
+	seed[0] ^= static_seed;
+	siphash24_init (&state->_state, (const guint8 *) seed);
+}
+
+guint
+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);
+	return nm_hash_complete (&h);
+}
+
+guint
+nm_str_hash (gconstpointer str)
+{
+	return nm_hash_str (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));
+
+	return h ?: 2907677551u;
+}
+
+guint
+nm_direct_hash (gconstpointer ptr)
+{
+	return nm_hash_ptr (ptr);
+}
diff --git a/shared/nm-utils/nm-hash-utils.h b/shared/nm-utils/nm-hash-utils.h
new file mode 100644
index 00000000..276e1ebe
--- /dev/null
+++ b/shared/nm-utils/nm-hash-utils.h
@@ -0,0 +1,210 @@
+/* -*- 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_HASH_UTILS_H__
+#define __NM_HASH_UTILS_H__
+
+#include "siphash24.h"
+#include "nm-macros-internal.h"
+
+struct _NMHashState {
+	struct siphash _state;
+};
+
+typedef struct _NMHashState NMHashState;
+
+void nm_hash_init (NMHashState *state, guint static_seed);
+
+static inline guint
+nm_hash_complete (NMHashState *state)
+{
+	guint64 h;
+
+	nm_assert (state);
+
+	h = siphash24_finalize (&state->_state);
+
+	/* we don't ever want to return a zero hash.
+	 *
+	 * NMPObject requires that in _idx_obj_part(), and it's just a good idea. */
+	return (((guint) (h >> 32)) ^ ((guint) h)) ?: 1396707757u;
+}
+
+static inline void
+nm_hash_update (NMHashState *state, const void *ptr, gsize n)
+{
+	nm_assert (state);
+	nm_assert (ptr);
+	nm_assert (n > 0);
+
+	siphash24_compress (ptr, n, &state->_state);
+}
+
+#define nm_hash_update_val(state, val) \
+	G_STMT_START { \
+		typeof (val) _val = (val); \
+		\
+		nm_hash_update ((state), &_val, sizeof (_val)); \
+	} G_STMT_END
+
+static inline void
+nm_hash_update_bool (NMHashState *state, bool val)
+{
+	nm_hash_update (state, &val, sizeof (val));
+}
+
+#define _NM_HASH_COMBINE_BOOLS_x_1( t, y)      ((y) ? ((t) (1ull <<  0)) : ((t) 0ull))
+#define _NM_HASH_COMBINE_BOOLS_x_2( t, y, ...) ((y) ? ((t) (1ull <<  1)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_1  (t, __VA_ARGS__)
+#define _NM_HASH_COMBINE_BOOLS_x_3( t, y, ...) ((y) ? ((t) (1ull <<  2)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_2  (t, __VA_ARGS__)
+#define _NM_HASH_COMBINE_BOOLS_x_4( t, y, ...) ((y) ? ((t) (1ull <<  3)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_3  (t, __VA_ARGS__)
+#define _NM_HASH_COMBINE_BOOLS_x_5( t, y, ...) ((y) ? ((t) (1ull <<  4)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_4  (t, __VA_ARGS__)
+#define _NM_HASH_COMBINE_BOOLS_x_6( t, y, ...) ((y) ? ((t) (1ull <<  5)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_5  (t, __VA_ARGS__)
+#define _NM_HASH_COMBINE_BOOLS_x_7( t, y, ...) ((y) ? ((t) (1ull <<  6)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_6  (t, __VA_ARGS__)
+#define _NM_HASH_COMBINE_BOOLS_x_8( t, y, ...) ((y) ? ((t) (1ull <<  7)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_7  (t, __VA_ARGS__)
+#define _NM_HASH_COMBINE_BOOLS_x_9( t, y, ...) ((y) ? ((t) (1ull <<  8)) : ((t) 0ull)) | (G_STATIC_ASSERT_EXPR (sizeof (t) >= 2), (_NM_HASH_COMBINE_BOOLS_x_8  (t, __VA_ARGS__)))
+#define _NM_HASH_COMBINE_BOOLS_x_10(t, y, ...) ((y) ? ((t) (1ull <<  9)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_9  (t, __VA_ARGS__)
+#define _NM_HASH_COMBINE_BOOLS_x_11(t, y, ...) ((y) ? ((t) (1ull << 10)) : ((t) 0ull)) | _NM_HASH_COMBINE_BOOLS_x_10 (t, __VA_ARGS__)
+#define _NM_HASH_COMBINE_BOOLS_n2(t, n, ...) _NM_HASH_COMBINE_BOOLS_x_##n (t, __VA_ARGS__)
+#define _NM_HASH_COMBINE_BOOLS_n(t, n, ...) _NM_HASH_COMBINE_BOOLS_n2(t, n, __VA_ARGS__)
+
+#define NM_HASH_COMBINE_BOOLS(type, ...) ((type) (_NM_HASH_COMBINE_BOOLS_n(type, NM_NARG (__VA_ARGS__), __VA_ARGS__)))
+
+#define nm_hash_update_bools(state, ...) \
+	nm_hash_update_val (state, NM_HASH_COMBINE_BOOLS (guint8, __VA_ARGS__))
+
+#define _NM_HASH_COMBINE_VALS_typ_x_1( y)       typeof (y) _v1;
+#define _NM_HASH_COMBINE_VALS_typ_x_2( y, ...)  typeof (y) _v2;  _NM_HASH_COMBINE_VALS_typ_x_1  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_3( y, ...)  typeof (y) _v3;  _NM_HASH_COMBINE_VALS_typ_x_2  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_4( y, ...)  typeof (y) _v4;  _NM_HASH_COMBINE_VALS_typ_x_3  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_5( y, ...)  typeof (y) _v5;  _NM_HASH_COMBINE_VALS_typ_x_4  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_6( y, ...)  typeof (y) _v6;  _NM_HASH_COMBINE_VALS_typ_x_5  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_7( y, ...)  typeof (y) _v7;  _NM_HASH_COMBINE_VALS_typ_x_6  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_8( y, ...)  typeof (y) _v8;  _NM_HASH_COMBINE_VALS_typ_x_7  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_9( y, ...)  typeof (y) _v9;  _NM_HASH_COMBINE_VALS_typ_x_8  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_10(y, ...)  typeof (y) _v10; _NM_HASH_COMBINE_VALS_typ_x_9  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_11(y, ...)  typeof (y) _v11; _NM_HASH_COMBINE_VALS_typ_x_10  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_12(y, ...)  typeof (y) _v12; _NM_HASH_COMBINE_VALS_typ_x_11 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_13(y, ...)  typeof (y) _v13; _NM_HASH_COMBINE_VALS_typ_x_12 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_14(y, ...)  typeof (y) _v14; _NM_HASH_COMBINE_VALS_typ_x_13 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_15(y, ...)  typeof (y) _v15; _NM_HASH_COMBINE_VALS_typ_x_14 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_16(y, ...)  typeof (y) _v16; _NM_HASH_COMBINE_VALS_typ_x_15 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_17(y, ...)  typeof (y) _v17; _NM_HASH_COMBINE_VALS_typ_x_16 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_18(y, ...)  typeof (y) _v18; _NM_HASH_COMBINE_VALS_typ_x_17 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_19(y, ...)  typeof (y) _v19; _NM_HASH_COMBINE_VALS_typ_x_18 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_x_20(y, ...)  typeof (y) _v20; _NM_HASH_COMBINE_VALS_typ_x_19 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_n2(n, ...) _NM_HASH_COMBINE_VALS_typ_x_##n (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_typ_n(n, ...) _NM_HASH_COMBINE_VALS_typ_n2(n, __VA_ARGS__)
+
+#define _NM_HASH_COMBINE_VALS_val_x_1( y)       ._v1  = (y),
+#define _NM_HASH_COMBINE_VALS_val_x_2( y, ...)  ._v2  = (y), _NM_HASH_COMBINE_VALS_val_x_1  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_3( y, ...)  ._v3  = (y), _NM_HASH_COMBINE_VALS_val_x_2  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_4( y, ...)  ._v4  = (y), _NM_HASH_COMBINE_VALS_val_x_3  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_5( y, ...)  ._v5  = (y), _NM_HASH_COMBINE_VALS_val_x_4  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_6( y, ...)  ._v6  = (y), _NM_HASH_COMBINE_VALS_val_x_5  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_7( y, ...)  ._v7  = (y), _NM_HASH_COMBINE_VALS_val_x_6  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_8( y, ...)  ._v8  = (y), _NM_HASH_COMBINE_VALS_val_x_7  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_9( y, ...)  ._v9  = (y), _NM_HASH_COMBINE_VALS_val_x_8  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_10(y, ...)  ._v10 = (y), _NM_HASH_COMBINE_VALS_val_x_9  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_11(y, ...)  ._v11 = (y), _NM_HASH_COMBINE_VALS_val_x_10  (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_12(y, ...)  ._v12 = (y), _NM_HASH_COMBINE_VALS_val_x_11 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_13(y, ...)  ._v13 = (y), _NM_HASH_COMBINE_VALS_val_x_12 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_14(y, ...)  ._v14 = (y), _NM_HASH_COMBINE_VALS_val_x_13 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_15(y, ...)  ._v15 = (y), _NM_HASH_COMBINE_VALS_val_x_14 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_16(y, ...)  ._v16 = (y), _NM_HASH_COMBINE_VALS_val_x_15 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_17(y, ...)  ._v17 = (y), _NM_HASH_COMBINE_VALS_val_x_16 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_18(y, ...)  ._v18 = (y), _NM_HASH_COMBINE_VALS_val_x_17 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_19(y, ...)  ._v19 = (y), _NM_HASH_COMBINE_VALS_val_x_18 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_x_20(y, ...)  ._v20 = (y), _NM_HASH_COMBINE_VALS_val_x_19 (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_n2(n, ...) _NM_HASH_COMBINE_VALS_val_x_##n (__VA_ARGS__)
+#define _NM_HASH_COMBINE_VALS_val_n(n, ...) _NM_HASH_COMBINE_VALS_val_n2(n, __VA_ARGS__)
+
+/* NM_HASH_COMBINE_VALS() is faster then nm_hash_update_val() as it combines multiple
+ * calls to nm_hash_update() using a packed structure. */
+#define NM_HASH_COMBINE_VALS(var, ...) \
+	const struct _nm_packed { \
+		_NM_HASH_COMBINE_VALS_typ_n (NM_NARG (__VA_ARGS__), __VA_ARGS__) \
+	} var _nm_alignas (guint64) = { \
+		_NM_HASH_COMBINE_VALS_val_n (NM_NARG (__VA_ARGS__), __VA_ARGS__) \
+	}
+
+/* nm_hash_update_vals() is faster then nm_hash_update_val() as it combines multiple
+ * calls to nm_hash_update() using a packed structure. */
+#define nm_hash_update_vals(state, ...) \
+	G_STMT_START { \
+		NM_HASH_COMBINE_VALS (_val, __VA_ARGS__); \
+		\
+		nm_hash_update ((state), &_val, sizeof (_val)); \
+	} G_STMT_END
+
+static inline void
+nm_hash_update_mem (NMHashState *state, const void *ptr, gsize n)
+{
+	/* This also hashes the length of the data. That means,
+	 * hashing two consecutive binary fields (of arbitrary
+	 * length), will hash differently. That is,
+	 * [[1,1], []] differs from [[1],[1]].
+	 *
+	 * If you have a constant length (sizeof), use nm_hash_update()
+	 * instead. */
+	nm_hash_update (state, &n, sizeof (n));
+	if (n > 0)
+		siphash24_compress (ptr, n, &state->_state);
+}
+
+static inline void
+nm_hash_update_str0 (NMHashState *state, const char *str)
+{
+	if (str)
+		nm_hash_update_mem (state, str, strlen (str));
+	else {
+		gsize n = G_MAXSIZE;
+
+		nm_hash_update (state, &n, sizeof (n));
+	}
+}
+
+static inline void
+nm_hash_update_str (NMHashState *state, const char *str)
+{
+	nm_assert (str);
+	nm_hash_update (state, str, strlen (str) + 1);
+}
+
+#if _NM_CC_SUPPORT_GENERIC
+/* Like nm_hash_update_str(), but restricted to arrays only. nm_hash_update_str() only works
+ * with a @str argument that cannot be NULL. If you have a string pointer, that is never NULL, use
+ * nm_hash_update() instead. */
+#define nm_hash_update_strarr(state, str) \
+	(_Generic (&(str), \
+		const char (*) [sizeof (str)]: nm_hash_update_str ((state), (str)), \
+		char (*) [sizeof (str)]:       nm_hash_update_str ((state), (str))) \
+	)
+#else
+#define nm_hash_update_strarr(state, str) nm_hash_update_str ((state), (str))
+#endif
+
+guint nm_hash_ptr (gconstpointer ptr);
+guint nm_direct_hash (gconstpointer str);
+
+guint nm_hash_str (const char *str);
+guint nm_str_hash (gconstpointer str);
+
+#endif /* __NM_HASH_UTILS_H__ */
diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h
index 5fe4bc50..d9dab83d 100644
--- a/shared/nm-utils/nm-macros-internal.h
+++ b/shared/nm-utils/nm-macros-internal.h
@@ -26,20 +26,41 @@
 #include <stdlib.h>
 #include <errno.h>
 
-#include "nm-glib.h"
+#define _nm_packed           __attribute__ ((packed))
+#define _nm_unused           __attribute__ ((unused))
+#define _nm_pure             __attribute__ ((pure))
+#define _nm_const            __attribute__ ((const))
+#define _nm_printf(a,b)      __attribute__ ((__format__ (__printf__, a, b)))
+#define _nm_align(s)         __attribute__ ((aligned (s)))
+#define _nm_alignof(type)    __alignof (type)
+#define _nm_alignas(type)    _nm_align (_nm_alignof (type))
+
+/*****************************************************************************/
+
+#ifdef thread_local
+#define _nm_thread_local thread_local
+/*
+ * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__
+ * see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769
+ */
+#elif __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16))
+#define _nm_thread_local _Thread_local
+#else
+#define _nm_thread_local __thread
+#endif
 
 /*****************************************************************************/
 
-#define _nm_packed __attribute__ ((packed))
-#define _nm_unused __attribute__ ((unused))
-#define _nm_pure   __attribute__ ((pure))
-#define _nm_const  __attribute__ ((const))
-#define _nm_printf(a,b) __attribute__ ((__format__ (__printf__, a, b)))
+#include "nm-glib.h"
+
+/*****************************************************************************/
 
 #define nm_offsetofend(t,m) (G_STRUCT_OFFSET (t,m) + sizeof (((t *) NULL)->m))
 
 #define nm_auto(fcn) __attribute__ ((cleanup(fcn)))
 
+static inline int nm_close (int fd);
+
 /**
  * nm_auto_free:
  *
@@ -56,6 +77,14 @@ _nm_auto_unset_gvalue_impl (GValue *v)
 #define nm_auto_unset_gvalue nm_auto(_nm_auto_unset_gvalue_impl)
 
 static inline void
+_nm_auto_unref_gtypeclass (gpointer v)
+{
+	if (v && *((gpointer *) v))
+		g_type_class_unref (*((gpointer *) v));
+}
+#define nm_auto_unref_gtypeclass nm_auto(_nm_auto_unref_gtypeclass)
+
+static inline void
 _nm_auto_free_gstring_impl (GString **str)
 {
 	if (*str)
@@ -69,7 +98,7 @@ _nm_auto_close_impl (int *pfd)
 	if (*pfd >= 0) {
 		int errsv = errno;
 
-		(void) close (*pfd);
+		(void) nm_close (*pfd);
 		errno = errsv;
 	}
 }
@@ -201,6 +230,23 @@ NM_G_ERROR_MSG (GError *error)
 /* macro to return strlen() of a compile time string. */
 #define NM_STRLEN(str)     ( sizeof ("" str) - 1 )
 
+/* returns the length of a NULL terminated array of pointers,
+ * like g_strv_length() does. The difference is:
+ *  - it operats on arrays of pointers (of any kind, requiring no cast).
+ *  - it accepts NULL to return zero. */
+#define NM_PTRARRAY_LEN(array) \
+	({ \
+		typeof (*(array)) *const _array = (array); \
+		gsize _n = 0; \
+		\
+		if (_array) { \
+			_nm_unused typeof (*(_array[0])) *_array_check = _array[0]; \
+			while (_array[_n]) \
+				_n++; \
+		} \
+		_n; \
+	})
+
 /* Note: @value is only evaluated when *out_val is present.
  * Thus,
  *    NM_SET_OUT (out_str, g_strdup ("hallo"));
@@ -217,6 +263,47 @@ NM_G_ERROR_MSG (GError *error)
 
 /*****************************************************************************/
 
+#if (defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 9 ))) || (defined (__clang__))
+#define _NM_CC_SUPPORT_GENERIC 1
+#else
+#define _NM_CC_SUPPORT_GENERIC 0
+#endif
+
+#if _NM_CC_SUPPORT_GENERIC
+#define _NM_CONSTCAST(type, obj) \
+	(_Generic ((obj), \
+	           void *           : ((type *) (obj)), \
+	           void *const      : ((type *) (obj)), \
+	           const void *     : ((const type *) (obj)), \
+	           const void *const: ((const type *) (obj)), \
+	           const type *     : (obj), \
+	           const type *const: (obj), \
+	           type *           : (obj), \
+	           type *const      : (obj)))
+#else
+/* _NM_CONSTCAST() is there to preserve constness of a pointer.
+ * It uses C11's _Generic(). If that is not supported, we fall back
+ * to casting away constness. So, with _Generic, we get some additional
+ * static type checking by preserving constness, without, we cast it
+ * to a non-const pointer. */
+#define _NM_CONSTCAST(type, obj) \
+	((type *) (obj))
+#endif
+
+#if _NM_CC_SUPPORT_GENERIC
+/* returns @value, if the type of @value matches @type.
+ * This requires support for C11 _Generic(). If no support is
+ * present, this returns @value directly.
+ *
+ * It's useful to check the let the compiler ensure that @value is
+ * of a certain type. */
+#define _NM_ENSURE_TYPE(type, value) (_Generic ((value), type: (value)))
+#else
+#define _NM_ENSURE_TYPE(type, value) (value)
+#endif
+
+/*****************************************************************************/
+
 #define _NM_IN_SET_EVAL_1( op, _x, y)           (_x == (y))
 #define _NM_IN_SET_EVAL_2( op, _x, y, ...)      (_x == (y)) op _NM_IN_SET_EVAL_1  (op, _x, __VA_ARGS__)
 #define _NM_IN_SET_EVAL_3( op, _x, y, ...)      (_x == (y)) op _NM_IN_SET_EVAL_2  (op, _x, __VA_ARGS__)
@@ -395,6 +482,16 @@ fcn (void) \
 
 /*****************************************************************************/
 
+static inline GString *
+nm_gstring_prepare (GString **l)
+{
+	if (*l)
+		g_string_set_size (*l, 0);
+	else
+		*l = g_string_sized_new (30);
+	return *l;
+}
+
 static inline const char *
 nm_str_not_empty (const char *str)
 {
@@ -487,7 +584,7 @@ _notify (obj_type *obj, _PropertyEnums prop) \
 /* these are implemented as a macro, because they accept self
  * as both (type*) and (const type*), and return a const
  * private pointer accordingly. */
-#define __NM_GET_PRIVATE(self, type, is_check, result_cmd) \
+#define __NM_GET_PRIVATE(self, type, is_check, addrop) \
 	({ \
 		/* preserve the const-ness of self. Unfortunately, that
 		 * way, @self cannot be a void pointer */ \
@@ -497,11 +594,11 @@ _notify (obj_type *obj, _PropertyEnums prop) \
 		_nm_unused const type *const _self2 = (_self); \
 		\
 		nm_assert (is_check (_self)); \
-		( result_cmd ); \
+		( addrop ( _NM_CONSTCAST (type, _self)->_priv) ); \
 	})
 
-#define _NM_GET_PRIVATE(self, type, is_check)     __NM_GET_PRIVATE(self, type, is_check, &_self->_priv)
-#define _NM_GET_PRIVATE_PTR(self, type, is_check) __NM_GET_PRIVATE(self, type, is_check,  _self->_priv)
+#define _NM_GET_PRIVATE(self, type, is_check)     __NM_GET_PRIVATE(self, type, is_check, &)
+#define _NM_GET_PRIVATE_PTR(self, type, is_check) __NM_GET_PRIVATE(self, type, is_check,  )
 
 #define __NM_GET_PRIVATE_VOID(self, type, is_check, result_cmd) \
 	({ \
@@ -540,6 +637,36 @@ nm_g_object_unref (gpointer obj)
 		g_object_unref (obj);
 }
 
+/* Assigns GObject @obj to destination @pdst, and takes an additional ref.
+ * The previous value of @pdst is unrefed.
+ *
+ * It makes sure to first increase the ref-count of @obj, and handles %NULL
+ * @obj correctly.
+ * */
+#define nm_g_object_ref_set(pp, obj) \
+	({ \
+		typeof (*(pp)) *const _pp = (pp); \
+		typeof (**_pp) *const _obj = (obj); \
+		typeof (**_pp) *_p; \
+		gboolean _changed = FALSE; \
+		\
+		if (   _pp \
+		    && ((_p = *_pp) != _obj)) { \
+			if (_obj) { \
+				nm_assert (G_IS_OBJECT (_obj)); \
+				 g_object_ref (_obj); \
+			} \
+			if (_p) { \
+				nm_assert (G_IS_OBJECT (_p)); \
+				*_pp = NULL; \
+				g_object_unref (_p); \
+			} \
+			*_pp = _obj; \
+			_changed = TRUE; \
+		} \
+		_changed; \
+	})
+
 /* basically, replaces
  *   g_clear_pointer (&location, g_free)
  * with
@@ -552,13 +679,32 @@ nm_g_object_unref (gpointer obj)
 #define nm_clear_g_free(pp) \
 	({  \
 		typeof (*(pp)) *_pp = (pp); \
-		typeof (**_pp) *_p = *_pp; \
+		typeof (**_pp) *_p; \
+		gboolean _changed = FALSE; \
 		\
-		if (_p) { \
+		if (  _pp \
+		    && (_p = *_pp)) { \
 			*_pp = NULL; \
 			g_free (_p); \
+			_changed = TRUE; \
+		} \
+		_changed; \
+	})
+
+#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; \
 		} \
-		!!_p; \
+		_changed; \
 	})
 
 static inline gboolean
@@ -620,6 +766,50 @@ nm_clear_g_cancellable (GCancellable **cancellable)
 
 /*****************************************************************************/
 
+#define NM_UTILS_LOOKUP_DEFAULT(v)            return (v)
+#define NM_UTILS_LOOKUP_DEFAULT_WARN(v)       g_return_val_if_reached (v)
+#define NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(v)  { nm_assert_not_reached (); return (v); }
+#define NM_UTILS_LOOKUP_ITEM(v, n)            (void) 0; case v: return (n); (void) 0
+#define NM_UTILS_LOOKUP_STR_ITEM(v, n)        NM_UTILS_LOOKUP_ITEM(v, ""n"")
+#define NM_UTILS_LOOKUP_ITEM_IGNORE(v)        (void) 0; case v: break; (void) 0
+#define NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER()   (void) 0; default: break; (void) 0
+
+#define _NM_UTILS_LOOKUP_DEFINE(scope, fcn_name, lookup_type, result_type, unknown_val, ...) \
+scope result_type \
+fcn_name (lookup_type val) \
+{ \
+	switch (val) { \
+		(void) 0, \
+		__VA_ARGS__ \
+		(void) 0; \
+	}; \
+	{ unknown_val; } \
+}
+
+#define NM_UTILS_LOOKUP_STR_DEFINE(fcn_name, lookup_type, unknown_val, ...) \
+	_NM_UTILS_LOOKUP_DEFINE (, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__)
+#define NM_UTILS_LOOKUP_STR_DEFINE_STATIC(fcn_name, lookup_type, unknown_val, ...) \
+	_NM_UTILS_LOOKUP_DEFINE (static, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__)
+
+/* Call the string-lookup-table function @fcn_name. If the function returns
+ * %NULL, the numeric index is converted to string using a alloca() buffer.
+ * Beware: this macro uses alloca(). */
+#define NM_UTILS_LOOKUP_STR(fcn_name, idx) \
+	({ \
+		typeof (idx) _idx = (idx); \
+		const char *_s; \
+		\
+		_s = fcn_name (_idx); \
+		if (!_s) { \
+			_s = g_alloca (30); \
+			\
+			g_snprintf ((char *) _s, 30, "(%lld)", (long long) _idx); \
+		} \
+		_s; \
+	})
+
+/*****************************************************************************/
+
 /* check if @flags has exactly one flag (@check) set. You should call this
  * only with @check being a compile time constant and a power of two. */
 #define NM_FLAGS_HAS(flags, check)  \
@@ -686,6 +876,33 @@ nm_strstrip (char *str)
 	return str ? g_strstrip (str) : NULL;
 }
 
+static inline const char *
+nm_strstrip_avoid_copy (const char *str, char **str_free)
+{
+	gsize l;
+	char *s;
+
+	nm_assert (str_free && !*str_free);
+
+	if (!str)
+		return NULL;
+
+	str = nm_str_skip_leading_spaces (str);
+	l = strlen (str);
+	if (   l == 0
+	    || !g_ascii_isspace (str[l - 1]))
+		return str;
+	while (   l > 0
+	       && g_ascii_isspace (str[l - 1]))
+		l--;
+
+	s = g_new (char, l + 1);
+	memcpy (s, str, l);
+	s[l] = '\0';
+	*str_free = s;
+	return s;
+}
+
 /* g_ptr_array_sort()'s compare function takes pointers to the
  * value. Thus, you cannot use strcmp directly. You can use
  * nm_strcmp_p().
@@ -934,4 +1151,35 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro)
 
 /*****************************************************************************/
 
+static inline int
+nm_steal_fd (int *p_fd)
+{
+	int fd;
+
+	if (   p_fd
+	    && ((fd = *p_fd) >= 0)) {
+		*p_fd = -1;
+		return fd;
+	}
+	return -1;
+}
+
+/**
+ * nm_close:
+ *
+ * Like close() but throws an assertion if the input fd is
+ * invalid.  Closing an invalid fd is a programming error, so
+ * it's better to catch it early.
+ */
+static inline int
+nm_close (int fd)
+{
+	if (fd >= 0) {
+		if (close (fd) == 0)
+			return 0;
+		nm_assert (errno != EBADF);
+	}
+	return -1;
+}
+
 #endif /* __NM_MACROS_INTERNAL_H__ */
diff --git a/shared/nm-utils/nm-obj.h b/shared/nm-utils/nm-obj.h
new file mode 100644
index 00000000..1a9d4868
--- /dev/null
+++ b/shared/nm-utils/nm-obj.h
@@ -0,0 +1,82 @@
+/* -*- 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_OBJ_H__
+#define __NM_OBJ_H__
+
+/*****************************************************************************/
+
+#define NM_OBJ_REF_COUNT_STACKINIT (G_MAXINT)
+
+typedef struct _NMObjBaseInst  NMObjBaseInst;
+typedef struct _NMObjBaseClass NMObjBaseClass;
+
+struct _NMObjBaseInst {
+	/* The first field of NMObjBaseInst is compatible with GObject.
+	 * Basically, NMObjBaseInst is an abstract base type of GTypeInstance.
+	 *
+	 * If you do it right, you may derive a type of NMObjBaseInst as a proper GTypeInstance.
+	 * That involves allocating a GType for it, which can be inconvenient because
+	 * a GType is dynamically created (and the class can no longer be immutable
+	 * memory).
+	 *
+	 * Even if your implementation of NMObjBaseInst is not a full fledged GType(Instance),
+	 * you still can use GTypeInstances in the same context as you can decide based on the
+	 * NMObjBaseClass with what kind of object you are dealing with.
+	 *
+	 * Basically, the only thing NMObjBaseInst gives you is access to an
+	 * NMObjBaseClass instance.
+	 */
+	union {
+		const NMObjBaseClass *klass;
+		GTypeInstance g_type_instance;
+	};
+};
+
+struct _NMObjBaseClass {
+	/* NMObjBaseClass is the base class of all NMObjBaseInst implementations.
+	 * 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
+	 * GType).
+	 *
+	 * Note that to implement a NMObjBaseClass that is *not* a GTypeClass, you wouldn't
+	 * set the GType. Hence, this field is only useful for type implementations that actually
+	 * extend GTypeClass.
+	 *
+	 * In a way it is wrong that NMObjBaseClass has the GType member, because it is
+	 * a base class of GTypeClass and doesn't necessarily use the GType. However,
+	 * it is here so that G_TYPE_CHECK_INSTANCE_TYPE() and friends work correctly
+	 * on any NMObjectClass. That means, while not necessary, it is convenient that
+	 * a NMObjBaseClass has all members of GTypeClass.
+	 * Also note that usually you have only one instance of a certain type, so this
+	 * wastes just a few bytes for the unneeded GType.
+	 */
+	union {
+		GType g_type;
+		GTypeClass g_type_class;
+	};
+};
+
+/*****************************************************************************/
+
+#endif /* __NM_OBJ_H__ */
diff --git a/shared/nm-utils/nm-random-utils.c b/shared/nm-utils/nm-random-utils.c
new file mode 100644
index 00000000..5d9e29da
--- /dev/null
+++ b/shared/nm-utils/nm-random-utils.c
@@ -0,0 +1,165 @@
+/* -*- 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-random-utils.h"
+
+#include <fcntl.h>
+
+#if USE_SYS_RANDOM_H
+#include <sys/random.h>
+#else
+#include <linux/random.h>
+#endif
+
+#include "nm-shared-utils.h"
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_random_bytes:
+ * @p: the buffer to fill
+ * @n: the number of bytes to write to @p.
+ *
+ * Uses getrandom() or reads /dev/urandom to fill the buffer
+ * with random data. If all fails, as last fallback it uses
+ * GRand to fill the buffer with pseudo random numbers.
+ * The function always succeeds in writing some random numbers
+ * to the buffer. The return value of FALSE indicates that the
+ * obtained bytes are probably not of good randomness.
+ *
+ * Returns: whether the written bytes are good. If you
+ * don't require good randomness, you can ignore the return
+ * value.
+ *
+ * Note that if calling getrandom() fails because there is not enough
+ * entroy (at early boot), the function will read /dev/urandom.
+ * Which of course, still has low entropy, and cause kernel to log
+ * a warning.
+ */
+gboolean
+nm_utils_random_bytes (void *p, size_t n)
+{
+	int fd;
+	int r;
+	gboolean has_high_quality = TRUE;
+	gboolean urandom_success;
+	guint8 *buf = p;
+	gboolean avoid_urandom = FALSE;
+
+	g_return_val_if_fail (p, FALSE);
+	g_return_val_if_fail (n > 0, FALSE);
+
+#if HAVE_GETRANDOM
+	{
+		static gboolean have_syscall = TRUE;
+
+		if (have_syscall) {
+			r = getrandom (buf, n, GRND_NONBLOCK);
+			if (r > 0) {
+				if ((size_t) r == n)
+					return TRUE;
+
+				/* no or partial read. There is not enough entropy.
+				 * Fill the rest reading from urandom, and remember that
+				 * some bits are not hight quality. */
+				nm_assert (r < n);
+				buf += r;
+				n -= r;
+				has_high_quality = FALSE;
+
+				/* At this point, we don't want to read /dev/urandom, because
+				 * the entropy pool is low (early boot?), and asking for more
+				 * entropy causes kernel messages to be logged.
+				 *
+				 * We use our fallback via GRand. Note that g_rand_new() also
+				 * tries to seed itself with data from /dev/urandom, but since
+				 * we reuse the instance, it shouldn't matter. */
+				avoid_urandom = TRUE;
+			} else {
+				if (errno == ENOSYS) {
+					/* no support for getrandom(). We don't know whether
+					 * we urandom will give us good quality. Assume yes. */
+					have_syscall = FALSE;
+				} else {
+					/* unknown error. We'll read urandom below, but we don't have
+					 * high-quality randomness. */
+					has_high_quality = FALSE;
+				}
+			}
+		}
+	}
+#endif
+
+	urandom_success = FALSE;
+	if (!avoid_urandom) {
+fd_open:
+		fd = open ("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY);
+		if (fd < 0) {
+			r = errno;
+			if (r == EINTR)
+				goto fd_open;
+		} else {
+			r = nm_utils_fd_read_loop_exact (fd, buf, n, TRUE);
+			close (fd);
+			if (r >= 0)
+				urandom_success = TRUE;
+		}
+	}
+
+	if (!urandom_success) {
+		static _nm_thread_local GRand *rand = NULL;
+		gsize i;
+		int j;
+
+		/* we failed to fill the bytes reading from urandom.
+		 * Fill the bits using GRand pseudo random numbers.
+		 *
+		 * We don't have good quality.
+		 */
+		has_high_quality = FALSE;
+
+		if (G_UNLIKELY (!rand))
+			rand = g_rand_new ();
+
+		nm_assert (n > 0);
+		i = 0;
+		for (;;) {
+			const union {
+				guint32 v32;
+				guint8 v8[4];
+			} v = {
+				.v32 = g_rand_int (rand),
+			};
+
+			for (j = 0; j < 4; ) {
+				buf[i++] = v.v8[j++];
+				if (i >= n)
+					goto done;
+			}
+		}
+done:
+		;
+	}
+
+	return has_high_quality;
+}
diff --git a/shared/nm-utils/nm-random-utils.h b/shared/nm-utils/nm-random-utils.h
new file mode 100644
index 00000000..15a118d3
--- /dev/null
+++ b/shared/nm-utils/nm-random-utils.h
@@ -0,0 +1,27 @@
+/* -*- 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_RANDOM_UTILS_H__
+#define __NM_RANDOM_UTILS_H__
+
+gboolean nm_utils_random_bytes (void *p, size_t n);
+
+#endif /* __NM_RANDOM_UTILS_H__ */
diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c
index e7f31cbc..cea60edf 100644
--- a/shared/nm-utils/nm-shared-utils.c
+++ b/shared/nm-utils/nm-shared-utils.c
@@ -24,6 +24,17 @@
 #include "nm-shared-utils.h"
 
 #include <errno.h>
+#include <arpa/inet.h>
+#include <poll.h>
+#include <fcntl.h>
+
+/*****************************************************************************/
+
+const void *const _NM_PTRARRAY_EMPTY[1] = { NULL };
+
+/*****************************************************************************/
+
+const NMIPAddr nm_ip_addr_zero = { 0 };
 
 /*****************************************************************************/
 
@@ -106,6 +117,336 @@ nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...)
 
 /*****************************************************************************/
 
+/**
+ * nm_strquote:
+ * @buf: the output buffer of where to write the quoted @str argument.
+ * @buf_len: the size of @buf.
+ * @str: (allow-none): the string to quote.
+ *
+ * Writes @str to @buf with quoting. The resulting buffer
+ * is always NUL terminated, unless @buf_len is zero.
+ * If @str is %NULL, it writes "(null)".
+ *
+ * If @str needs to be truncated, the closing quote is '^' instead
+ * of '"'.
+ *
+ * This is similar to nm_strquote_a(), which however uses alloca()
+ * to allocate a new buffer. Also, here @buf_len is the size of @buf,
+ * while nm_strquote_a() has the number of characters to print. The latter
+ * doesn't include the quoting.
+ *
+ * Returns: the input buffer with the quoted string.
+ */
+const char *
+nm_strquote (char *buf, gsize buf_len, const char *str)
+{
+	const char *const buf0 = buf;
+
+	if (!str) {
+		nm_utils_strbuf_append_str (&buf, &buf_len, "(null)");
+		goto out;
+	}
+
+	if (G_UNLIKELY (buf_len <= 2)) {
+		switch (buf_len) {
+		case 2:
+			*(buf++) = '^';
+			/* fall-through*/
+		case 1:
+			*(buf++) = '\0';
+			break;
+		}
+		goto out;
+	}
+
+	*(buf++) = '"';
+	buf_len--;
+
+	nm_utils_strbuf_append_str (&buf, &buf_len, str);
+
+	/* if the string was too long we indicate truncation with a
+	 * '^' instead of a closing quote. */
+	if (G_UNLIKELY (buf_len <= 1)) {
+		switch (buf_len) {
+		case 1:
+			buf[-1] = '^';
+			break;
+		case 0:
+			buf[-2] = '^';
+			break;
+		default:
+			nm_assert_not_reached ();
+			break;
+		}
+	} else {
+		nm_assert (buf_len >= 2);
+		*(buf++) = '"';
+		*(buf++) = '\0';
+	}
+
+out:
+	return buf0;
+}
+
+/*****************************************************************************/
+
+char _nm_utils_to_string_buffer[];
+
+void
+nm_utils_to_string_buffer_init (char **buf, gsize *len)
+{
+	if (!*buf) {
+		*buf = _nm_utils_to_string_buffer;
+		*len = sizeof (_nm_utils_to_string_buffer);
+	}
+}
+
+gboolean
+nm_utils_to_string_buffer_init_null (gconstpointer obj, char **buf, gsize *len)
+{
+	nm_utils_to_string_buffer_init (buf, len);
+	if (!obj) {
+		g_strlcpy (*buf, "(null)", *len);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+const char *
+nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs,
+                    gsize n_descs,
+                    unsigned flags,
+                    char *buf,
+                    gsize len)
+{
+	gsize i;
+	char *p;
+
+#if NM_MORE_ASSERTS > 10
+	nm_assert (descs);
+	nm_assert (n_descs > 0);
+	for (i = 0; i < n_descs; i++) {
+		gsize j;
+
+		nm_assert (descs[i].name && descs[i].name[0]);
+		for (j = 0; j < i; j++)
+			nm_assert (descs[j].flag != descs[i].flag);
+	}
+#endif
+
+	nm_utils_to_string_buffer_init (&buf, &len);
+
+	if (!len)
+		return buf;
+
+	buf[0] = '\0';
+	p = buf;
+	if (!flags) {
+		for (i = 0; i < n_descs; i++) {
+			if (!descs[i].flag) {
+				nm_utils_strbuf_append_str (&p, &len, descs[i].name);
+				break;
+			}
+		}
+		return buf;
+	}
+
+	for (i = 0; flags && i < n_descs; i++) {
+		if (   descs[i].flag
+		    && NM_FLAGS_ALL (flags, descs[i].flag)) {
+			flags &= ~descs[i].flag;
+
+			if (buf[0] != '\0')
+				nm_utils_strbuf_append_c (&p, &len, ',');
+			nm_utils_strbuf_append_str (&p, &len, descs[i].name);
+		}
+	}
+	if (flags) {
+		if (buf[0] != '\0')
+			nm_utils_strbuf_append_c (&p, &len, ',');
+		nm_utils_strbuf_append (&p, &len, "0x%x", flags);
+	}
+	return buf;
+};
+
+/*****************************************************************************/
+
+/**
+ * _nm_utils_ip4_prefix_to_netmask:
+ * @prefix: a CIDR prefix
+ *
+ * Returns: the netmask represented by the prefix, in network byte order
+ **/
+guint32
+_nm_utils_ip4_prefix_to_netmask (guint32 prefix)
+{
+	return prefix < 32 ? ~htonl(0xFFFFFFFF >> prefix) : 0xFFFFFFFF;
+}
+
+/**
+ * _nm_utils_ip4_get_default_prefix:
+ * @ip: an IPv4 address (in network byte order)
+ *
+ * When the Internet was originally set up, various ranges of IP addresses were
+ * segmented into three network classes: A, B, and C.  This function will return
+ * a prefix that is associated with the IP address specified defining where it
+ * falls in the predefined classes.
+ *
+ * Returns: the default class prefix for the given IP
+ **/
+/* The function is originally from ipcalc.c of Red Hat's initscripts. */
+guint32
+_nm_utils_ip4_get_default_prefix (guint32 ip)
+{
+	if (((ntohl (ip) & 0xFF000000) >> 24) <= 127)
+		return 8;  /* Class A - 255.0.0.0 */
+	else if (((ntohl (ip) & 0xFF000000) >> 24) <= 191)
+		return 16;  /* Class B - 255.255.0.0 */
+
+	return 24;  /* Class C - 255.255.255.0 */
+}
+
+gboolean
+nm_utils_ip_is_site_local (int addr_family,
+                           const void *address)
+{
+	in_addr_t addr4;
+
+	switch (addr_family) {
+	case AF_INET:
+		/* RFC1918 private addresses
+		 * 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 */
+		addr4 = ntohl (*((const in_addr_t *) address));
+		return    (addr4 & 0xff000000) == 0x0a000000
+		       || (addr4 & 0xfff00000) == 0xac100000
+		       || (addr4 & 0xffff0000) == 0xc0a80000;
+	case AF_INET6:
+		return IN6_IS_ADDR_SITELOCAL (address);
+	default:
+		g_return_val_if_reached (FALSE);
+	}
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_utils_parse_inaddr_bin (int addr_family,
+                           const char *text,
+                           gpointer out_addr)
+{
+	NMIPAddr addrbin;
+
+	g_return_val_if_fail (text, FALSE);
+
+	if (addr_family == AF_UNSPEC)
+		addr_family = strchr (text, ':') ? AF_INET6 : AF_INET;
+	else
+		g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), FALSE);
+
+	/* use a temporary variable @addrbin, to guarantee that @out_addr
+	 * is only modified on success. */
+	if (inet_pton (addr_family, text, &addrbin) != 1)
+		return FALSE;
+
+	if (out_addr) {
+		switch (addr_family) {
+		case AF_INET:
+			*((in_addr_t *) out_addr) = addrbin.addr4;
+			break;
+		case AF_INET6:
+			*((struct in6_addr *) out_addr) = addrbin.addr6;
+			break;
+		default:
+			nm_assert_not_reached ();
+		}
+	}
+	return TRUE;
+}
+
+gboolean
+nm_utils_parse_inaddr (int addr_family,
+                       const char *text,
+                       char **out_addr)
+{
+	NMIPAddr addrbin;
+	char addrstr_buf[MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
+
+	nm_assert (!out_addr || !*out_addr);
+
+	if (!nm_utils_parse_inaddr_bin (addr_family, text, &addrbin))
+		return FALSE;
+	NM_SET_OUT (out_addr, g_strdup (inet_ntop (addr_family, &addrbin, addrstr_buf, sizeof (addrstr_buf))));
+	return TRUE;
+}
+
+gboolean
+nm_utils_parse_inaddr_prefix_bin (int addr_family,
+                                  const char *text,
+                                  gpointer out_addr,
+                                  int *out_prefix)
+{
+	gs_free char *addrstr_free = NULL;
+	int prefix = -1;
+	const char *slash;
+	const char *addrstr;
+	NMIPAddr addrbin;
+	int addr_len;
+
+	g_return_val_if_fail (text, FALSE);
+
+	if (addr_family == AF_UNSPEC)
+		addr_family = strchr (text, ':') ? AF_INET6 : AF_INET;
+
+	if (addr_family == AF_INET)
+		addr_len = sizeof (in_addr_t);
+	else if (addr_family == AF_INET6)
+		addr_len = sizeof (struct in6_addr);
+	else
+		g_return_val_if_reached (FALSE);
+
+	slash = strchr (text, '/');
+	if (slash)
+		addrstr = addrstr_free = g_strndup (text, slash - text);
+	else
+		addrstr = text;
+
+	if (inet_pton (addr_family, addrstr, &addrbin) != 1)
+		return FALSE;
+
+	if (slash) {
+		prefix = _nm_utils_ascii_str_to_int64 (slash + 1, 10,
+		                                       0,
+		                                       addr_family == AF_INET ? 32 : 128,
+		                                       -1);
+		if (prefix == -1)
+			return FALSE;
+	}
+
+	if (out_addr)
+		memcpy (out_addr, &addrbin, addr_len);
+	NM_SET_OUT (out_prefix, prefix);
+	return TRUE;
+}
+
+gboolean
+nm_utils_parse_inaddr_prefix (int addr_family,
+                              const char *text,
+                              char **out_addr,
+                              int *out_prefix)
+{
+	NMIPAddr addrbin;
+	char addrstr_buf[MAX (INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
+
+	if (!nm_utils_parse_inaddr_prefix_bin (addr_family, text, &addrbin, out_prefix))
+		return FALSE;
+	NM_SET_OUT (out_addr, g_strdup (inet_ntop (addr_family, &addrbin, addrstr_buf, sizeof (addrstr_buf))));
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 /* _nm_utils_ascii_str_to_int64:
  *
  * A wrapper for g_ascii_strtoll, that checks whether the whole string
@@ -159,6 +500,118 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma
 /*****************************************************************************/
 
 /**
+ * nm_utils_strsplit_set:
+ * @str: the string to split.
+ * @delimiters: the set of delimiters. If %NULL, defaults to " \t\n",
+ *   like bash's $IFS.
+ *
+ * This is a replacement for g_strsplit_set() which avoids copying
+ * each word once (the entire strv array), but instead copies it once
+ * and all words point into that internal copy.
+ *
+ * Another difference from g_strsplit_set() is that this never returns
+ * empty words. Multiple delimiters are combined and treated as one.
+ *
+ * Returns: %NULL if @str is %NULL or contains only delimiters.
+ *   Otherwise, a %NULL terminated strv array containing non-empty
+ *   words, split at the delimiter characters (delimiter characters
+ *   are removed).
+ *   The strings to which the result strv array points to are allocated
+ *   after the returned result itself. Don't free the strings themself,
+ *   but free everything with g_free().
+ */
+const char **
+nm_utils_strsplit_set (const char *str, const char *delimiters)
+{
+	const char **ptr, **ptr0;
+	gsize alloc_size, plen, i;
+	gsize str_len;
+	char *s0;
+	char *s;
+	guint8 delimiters_table[256];
+
+	if (!str)
+		return NULL;
+
+	/* initialize lookup table for delimiter */
+	if (!delimiters)
+		delimiters = " \t\n";
+	memset (delimiters_table, 0, sizeof (delimiters_table));
+	for (i = 0; delimiters[i]; i++)
+		delimiters_table[(guint8) delimiters[i]] = 1;
+
+#define _is_delimiter(ch, delimiters_table) \
+	((delimiters_table)[(guint8) (ch)] != 0)
+
+	/* skip initial delimiters, and return of the remaining string is
+	 * empty. */
+	while (_is_delimiter (str[0], delimiters_table))
+		str++;
+	if (!str[0])
+		return NULL;
+
+	str_len = strlen (str) + 1;
+	alloc_size = 8;
+
+	/* we allocate the buffer larger, so to copy @str at the
+	 * end of it as @s0. */
+	ptr0 = g_malloc ((sizeof (const char *) * (alloc_size + 1)) + str_len);
+	s0 = (char *) &ptr0[alloc_size + 1];
+	memcpy (s0, str, str_len);
+
+	plen = 0;
+	s = s0;
+	ptr = ptr0;
+
+	while (TRUE) {
+		if (plen >= alloc_size) {
+			const char **ptr_old = ptr;
+
+			/* reallocate the buffer. Note that for now the string
+			 * continues to be in ptr0/s0. We fix that at the end. */
+			alloc_size += 2;
+			ptr = g_malloc ((sizeof (const char *) * (alloc_size + 1)) + str_len);
+			memcpy (ptr, ptr_old, sizeof (const char *) * plen);
+			if (ptr_old != ptr0)
+				g_free (ptr_old);
+		}
+
+		ptr[plen++] = s;
+
+		nm_assert (s[0] && !_is_delimiter (s[0], delimiters_table));
+
+		while (TRUE) {
+			s++;
+			if (_is_delimiter (s[0], delimiters_table))
+				break;
+			if (s[0] == '\0')
+				goto done;
+		}
+
+		s[0] = '\0';
+		s++;
+		while (_is_delimiter (s[0], delimiters_table))
+			s++;
+		if (s[0] == '\0')
+			break;
+	}
+done:
+	ptr[plen] = NULL;
+
+	if (ptr != ptr0) {
+		/* we reallocated the buffer. We must copy over the
+		 * string @s0 and adjust the pointers. */
+		s = (char *) &ptr[alloc_size + 1];
+		memcpy (s, s0, str_len);
+		for (i = 0; i < plen; i++)
+			ptr[i] = &s[ptr[i] - s0];
+		g_free (ptr0);
+	}
+
+	return ptr;
+}
+
+/**
  * nm_utils_strv_find_first:
  * @list: the strv list to search
  * @len: the length of the list, or a negative value if @list is %NULL terminated.
@@ -204,6 +657,35 @@ nm_utils_strv_find_first (char **list, gssize len, const char *needle)
 	return -1;
 }
 
+char **
+_nm_utils_strv_cleanup (char **strv,
+                        gboolean strip_whitespace,
+                        gboolean skip_empty,
+                        gboolean skip_repeated)
+{
+	guint i, j;
+
+	if (!strv || !*strv)
+		return strv;
+
+	if (strip_whitespace) {
+		for (i = 0; strv[i]; i++)
+			g_strstrip (strv[i]);
+	}
+	if (!skip_empty && !skip_repeated)
+		return strv;
+	j = 0;
+	for (i = 0; strv[i]; i++) {
+		if (   (skip_empty && !*strv[i])
+		    || (skip_repeated && nm_utils_strv_find_first (strv, j, strv[i]) >= 0))
+			g_free (strv[i]);
+		else
+			strv[j++] = strv[i];
+	}
+	strv[j] = NULL;
+	return strv;
+}
+
 /*****************************************************************************/
 
 gint
@@ -363,6 +845,16 @@ nm_g_object_set_property (GObject *object,
 	return TRUE;
 }
 
+GParamSpec *
+nm_g_object_class_find_property_from_gtype (GType gtype,
+                                            const char *property_name)
+{
+	nm_auto_unref_gtypeclass GObjectClass *gclass = NULL;
+
+	gclass = g_type_class_ref (gtype);
+	return g_object_class_find_property (gclass, property_name);
+}
+
 /*****************************************************************************/
 
 static void
@@ -502,3 +994,98 @@ nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags)
 	}
 	return str;
 }
+
+/*****************************************************************************/
+
+/* taken from systemd's fd_wait_for_event(). Note that the timeout
+ * is here in nano-seconds, not micro-seconds. */
+int
+nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns)
+{
+	struct pollfd pollfd = {
+		.fd = fd,
+		.events = event,
+	};
+	struct timespec ts, *pts;
+	int r;
+
+	if (timeout_ns < 0)
+		pts = NULL;
+	else {
+		ts.tv_sec = (time_t) (timeout_ns / NM_UTILS_NS_PER_SECOND);
+		ts.tv_nsec = (long int) (timeout_ns % NM_UTILS_NS_PER_SECOND);
+		pts = &ts;
+	}
+
+	r = ppoll (&pollfd, 1, pts, NULL);
+	if (r < 0)
+		return -errno;
+	if (r == 0)
+		return 0;
+	return pollfd.revents;
+}
+
+/* taken from systemd's loop_read() */
+ssize_t
+nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll)
+{
+	uint8_t *p = buf;
+	ssize_t n = 0;
+
+	g_return_val_if_fail (fd >= 0, -EINVAL);
+	g_return_val_if_fail (buf, -EINVAL);
+
+	/* If called with nbytes == 0, let's call read() at least
+	 * once, to validate the operation */
+
+	if (nbytes > (size_t) SSIZE_MAX)
+		return -EINVAL;
+
+	do {
+		ssize_t k;
+
+		k = read (fd, p, nbytes);
+		if (k < 0) {
+			if (errno == EINTR)
+				continue;
+
+			if (errno == EAGAIN && do_poll) {
+
+				/* We knowingly ignore any return value here,
+				 * and expect that any error/EOF is reported
+				 * via read() */
+
+				(void) nm_utils_fd_wait_for_event (fd, POLLIN, -1);
+				continue;
+			}
+
+			return n > 0 ? n : -errno;
+		}
+
+		if (k == 0)
+			return n;
+
+		g_assert ((size_t) k <= nbytes);
+
+		p += k;
+		nbytes -= k;
+		n += k;
+	} while (nbytes > 0);
+
+	return n;
+}
+
+/* taken from systemd's loop_read_exact() */
+int
+nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll)
+{
+	ssize_t n;
+
+	n = nm_utils_fd_read_loop (fd, buf, nbytes, do_poll);
+	if (n < 0)
+		return (int) n;
+	if ((size_t) n != nbytes)
+		return -EIO;
+
+	return 0;
+}
diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h
index 69f9533d..96aab534 100644
--- a/shared/nm-utils/nm-shared-utils.h
+++ b/shared/nm-utils/nm-shared-utils.h
@@ -22,8 +22,143 @@
 #ifndef __NM_SHARED_UTILS_H__
 #define __NM_SHARED_UTILS_H__
 
+#include <netinet/in.h>
+
+/*****************************************************************************/
+
+typedef struct {
+	union {
+		guint8 addr_ptr[1];
+		in_addr_t addr4;
+		struct in6_addr addr6;
+
+		/* NMIPAddr is really a union for IP addresses.
+		 * However, as ethernet addresses fit in here nicely, use
+		 * it also for an ethernet MAC address. */
+		guint8 addr_eth[6 /*ETH_ALEN*/];
+	};
+} NMIPAddr;
+
+extern const NMIPAddr nm_ip_addr_zero;
+
+/*****************************************************************************/
+
+static inline char
+nm_utils_addr_family_to_char (int addr_family)
+{
+	switch (addr_family) {
+	case AF_INET:  return '4';
+	case AF_INET6: return '6';
+	}
+	g_return_val_if_reached ('?');
+}
+
+static inline gsize
+nm_utils_addr_family_to_size (int addr_family)
+{
+	switch (addr_family) {
+	case AF_INET:  return sizeof (in_addr_t);
+	case AF_INET6: return sizeof (struct in6_addr);
+	}
+	g_return_val_if_reached (0);
+}
+
+#define nm_assert_addr_family(addr_family) \
+	nm_assert (NM_IN_SET ((addr_family), AF_INET, AF_INET6))
+
+/*****************************************************************************/
+
+#define NM_CMP_RETURN(c) \
+    G_STMT_START { \
+        const int _cc = (c); \
+        if (_cc) \
+            return _cc < 0 ? -1 : 1; \
+    } G_STMT_END
+
+#define NM_CMP_SELF(a, b) \
+    G_STMT_START { \
+        typeof (a) _a = (a); \
+        typeof (b) _b = (b); \
+        \
+        if (_a == _b) \
+            return 0; \
+        if (!_a) \
+            return -1; \
+        if (!_b) \
+            return 1; \
+    } G_STMT_END
+
+#define NM_CMP_DIRECT(a, b) \
+    G_STMT_START { \
+        typeof (a) _a = (a); \
+        typeof (b) _b = (b); \
+        \
+        if (_a != _b) \
+            return (_a < _b) ? -1 : 1; \
+    } G_STMT_END
+
+#define NM_CMP_DIRECT_MEMCMP(a, b, size) \
+    NM_CMP_RETURN (memcmp ((a), (b), (size)))
+
+#define NM_CMP_DIRECT_IN6ADDR(a, b) \
+    G_STMT_START { \
+        const struct in6_addr *const _a = (a); \
+        const struct in6_addr *const _b = (b); \
+        NM_CMP_RETURN (memcmp (_a, _b, sizeof (struct in6_addr))); \
+    } G_STMT_END
+
+#define NM_CMP_FIELD(a, b, field) \
+    NM_CMP_DIRECT (((a)->field), ((b)->field))
+
+#define NM_CMP_FIELD_UNSAFE(a, b, field) \
+    G_STMT_START { \
+        /* it's unsafe, because it evaluates the arguments more then once.
+         * This is necessary for bitfields, for which typeof() doesn't work. */ \
+        if (((a)->field) != ((b)->field)) \
+            return ((a)->field < ((b)->field)) ? -1 : 1; \
+    } G_STMT_END
+
+#define NM_CMP_FIELD_BOOL(a, b, field) \
+    NM_CMP_DIRECT (!!((a)->field), !!((b)->field))
+
+#define NM_CMP_FIELD_STR(a, b, field) \
+    NM_CMP_RETURN (strcmp (((a)->field), ((b)->field)))
+
+#define NM_CMP_FIELD_STR_INTERNED(a, b, field) \
+    G_STMT_START { \
+        const char *_a = ((a)->field); \
+        const char *_b = ((b)->field); \
+        \
+        if (_a != _b) { \
+            NM_CMP_RETURN (g_strcmp0 (_a, _b)); \
+        } \
+    } G_STMT_END
+
+#define NM_CMP_FIELD_STR0(a, b, field) \
+    NM_CMP_RETURN (g_strcmp0 (((a)->field), ((b)->field)))
+
+#define NM_CMP_FIELD_MEMCMP_LEN(a, b, field, len) \
+    NM_CMP_RETURN (memcmp (&((a)->field), &((b)->field), \
+                           MIN (len, sizeof ((a)->field))))
+
+#define NM_CMP_FIELD_MEMCMP(a, b, field) \
+    NM_CMP_RETURN (memcmp (&((a)->field), \
+                           &((b)->field), \
+                           sizeof ((a)->field)))
+
+#define NM_CMP_FIELD_IN6ADDR(a, b, field) \
+    G_STMT_START { \
+        const struct in6_addr *const _a = &((a)->field); \
+        const struct in6_addr *const _b = &((b)->field); \
+        NM_CMP_RETURN (memcmp (_a, _b, sizeof (struct in6_addr))); \
+    } G_STMT_END
+
 /*****************************************************************************/
 
+extern const void *const _NM_PTRARRAY_EMPTY[1];
+
+#define NM_PTRARRAY_EMPTY(type) ((type const*) _NM_PTRARRAY_EMPTY)
+
 static inline void
 _nm_utils_strbuf_init (char *buf, gsize len, char **p_buf_ptr, gsize *p_buf_len)
 {
@@ -41,12 +176,47 @@ void nm_utils_strbuf_append (char **buf, gsize *len, const char *format, ...) _n
 void nm_utils_strbuf_append_c (char **buf, gsize *len, char c);
 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);
+
 /*****************************************************************************/
 
+const char **nm_utils_strsplit_set (const char *str, const char *delimiters);
+
 gssize nm_utils_strv_find_first (char **list, gssize len, const char *needle);
 
+char **_nm_utils_strv_cleanup (char **strv,
+                               gboolean strip_whitespace,
+                               gboolean skip_empty,
+                               gboolean skip_repeated);
+
+/*****************************************************************************/
+
+guint32 _nm_utils_ip4_prefix_to_netmask (guint32 prefix);
+guint32 _nm_utils_ip4_get_default_prefix (guint32 ip);
+
+gboolean nm_utils_ip_is_site_local (int addr_family,
+                                    const void *address);
+
 /*****************************************************************************/
 
+gboolean nm_utils_parse_inaddr_bin  (int addr_family,
+                                     const char *text,
+                                     gpointer out_addr);
+
+gboolean nm_utils_parse_inaddr (int addr_family,
+                                const char *text,
+                                char **out_addr);
+
+gboolean nm_utils_parse_inaddr_prefix_bin (int addr_family,
+                                           const char *text,
+                                           gpointer out_addr,
+                                           int *out_prefix);
+
+gboolean nm_utils_parse_inaddr_prefix (int addr_family,
+                                       const char *text,
+                                       char **out_addr,
+                                       int *out_prefix);
+
 gint64 _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max, gint64 fallback);
 
 gint _nm_utils_ascii_str_to_bool (const char *str,
@@ -54,6 +224,123 @@ gint _nm_utils_ascii_str_to_bool (const char *str,
 
 /*****************************************************************************/
 
+extern char _nm_utils_to_string_buffer[2096];
+
+void     nm_utils_to_string_buffer_init (char **buf, gsize *len);
+gboolean nm_utils_to_string_buffer_init_null (gconstpointer obj, char **buf, gsize *len);
+
+/*****************************************************************************/
+
+typedef struct {
+	unsigned flag;
+	const char *name;
+} NMUtilsFlags2StrDesc;
+
+#define NM_UTILS_FLAGS2STR(f, n) { .flag = f, .name = ""n, }
+
+#define _NM_UTILS_FLAGS2STR_DEFINE(scope, fcn_name, flags_type, ...) \
+scope const char * \
+fcn_name (flags_type flags, char *buf, gsize len) \
+{ \
+	static const NMUtilsFlags2StrDesc descs[] = { \
+		__VA_ARGS__ \
+	}; \
+	G_STATIC_ASSERT (sizeof (flags_type) <= sizeof (unsigned)); \
+	return nm_utils_flags2str (descs, G_N_ELEMENTS (descs), flags, buf, len); \
+};
+
+#define NM_UTILS_FLAGS2STR_DEFINE(fcn_name, flags_type, ...) \
+	_NM_UTILS_FLAGS2STR_DEFINE (, fcn_name, flags_type, __VA_ARGS__)
+#define NM_UTILS_FLAGS2STR_DEFINE_STATIC(fcn_name, flags_type, ...) \
+	_NM_UTILS_FLAGS2STR_DEFINE (static, fcn_name, flags_type, __VA_ARGS__)
+
+const char *nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs,
+                                gsize n_descs,
+                                unsigned flags,
+                                char *buf,
+                                gsize len);
+
+/*****************************************************************************/
+
+#define NM_UTILS_ENUM2STR(v, n)     (void) 0; case v: s = ""n""; break; (void) 0
+#define NM_UTILS_ENUM2STR_IGNORE(v) (void) 0; case v: break; (void) 0
+
+#define _NM_UTILS_ENUM2STR_DEFINE(scope, fcn_name, lookup_type, int_fmt, ...) \
+scope const char * \
+fcn_name (lookup_type val, char *buf, gsize len) \
+{ \
+	nm_utils_to_string_buffer_init (&buf, &len); \
+	if (len) { \
+		const char *s = NULL; \
+		switch (val) { \
+			(void) 0, \
+			__VA_ARGS__ \
+			(void) 0; \
+		}; \
+		if (s) \
+			g_strlcpy (buf, s, len); \
+		else \
+			g_snprintf (buf, len, "(%"int_fmt")", val); \
+	} \
+	return buf; \
+}
+
+#define NM_UTILS_ENUM2STR_DEFINE(fcn_name, lookup_type, ...) \
+	_NM_UTILS_ENUM2STR_DEFINE (, fcn_name, lookup_type, "d", __VA_ARGS__)
+#define NM_UTILS_ENUM2STR_DEFINE_STATIC(fcn_name, lookup_type, ...) \
+	_NM_UTILS_ENUM2STR_DEFINE (static, fcn_name, lookup_type, "d", __VA_ARGS__)
+
+/*****************************************************************************/
+
+#define _nm_g_slice_free_fcn_define(mem_size) \
+static inline void \
+_nm_g_slice_free_fcn_##mem_size (gpointer mem_block) \
+{ \
+	g_slice_free1 (mem_size, mem_block); \
+}
+
+_nm_g_slice_free_fcn_define (1)
+_nm_g_slice_free_fcn_define (2)
+_nm_g_slice_free_fcn_define (4)
+_nm_g_slice_free_fcn_define (8)
+_nm_g_slice_free_fcn_define (12)
+_nm_g_slice_free_fcn_define (16)
+
+#define _nm_g_slice_free_fcn1(mem_size) \
+	({ \
+		void (*_fcn) (gpointer); \
+		\
+		/* 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) { \
+		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 16: _fcn = _nm_g_slice_free_fcn_16; break; \
+		default: g_assert_not_reached (); _fcn = NULL; break; \
+		} \
+		_fcn; \
+	})
+
+/**
+ * nm_g_slice_free_fcn:
+ * @type: type argument for sizeof() operator that you would
+ *   pass to g_slice_new().
+ *
+ * Returns: a function pointer with GDestroyNotify signature
+ *   for g_slice_free(type,*).
+ *
+ * Only certain types are implemented. You'll get an assertion
+ * using the wrong type. */
+#define nm_g_slice_free_fcn(type) (_nm_g_slice_free_fcn1 (sizeof (type)))
+
+#define nm_g_slice_free_fcn_gint64 (nm_g_slice_free_fcn (gint64))
+
+/*****************************************************************************/
+
 /**
  * NMUtilsError:
  * @NM_UTILS_ERROR_UNKNOWN: unknown or unclassified error
@@ -62,10 +349,12 @@ gint _nm_utils_ascii_str_to_bool (const char *str,
  *   error reason. Depending on the usage, this might indicate a bug because
  *   usually the target object should stay alive as long as there are pending
  *   operations.
+ * @NM_UTILS_ERROR_INVALID_ARGUMENT: invalid argument.
  */
 typedef enum {
 	NM_UTILS_ERROR_UNKNOWN = 0,                 /*< nick=Unknown >*/
 	NM_UTILS_ERROR_CANCELLED_DISPOSING,         /*< nick=CancelledDisposing >*/
+	NM_UTILS_ERROR_INVALID_ARGUMENT,            /*< nick=InvalidArgument >*/
 } NMUtilsError;
 
 #define NM_UTILS_ERROR (nm_utils_error_quark ())
@@ -84,6 +373,9 @@ gboolean nm_g_object_set_property (GObject *object,
                                    const GValue *value,
                                    GError **error);
 
+GParamSpec *nm_g_object_class_find_property_from_gtype (GType gtype,
+                                                        const char *property_name);
+
 /*****************************************************************************/
 
 typedef enum {
@@ -102,4 +394,16 @@ char *nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flag
 
 /*****************************************************************************/
 
+#define NM_UTILS_NS_PER_SECOND  ((gint64) 1000000000)
+#define NM_UTILS_NS_PER_MSEC    ((gint64) 1000000)
+#define NM_UTILS_NS_TO_MSEC_CEIL(nsec)      (((nsec) + (NM_UTILS_NS_PER_MSEC - 1)) / NM_UTILS_NS_PER_MSEC)
+
+/*****************************************************************************/
+
+int nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns);
+ssize_t nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll);
+int nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll);
+
+/*****************************************************************************/
+
 #endif /* __NM_SHARED_UTILS_H__ */
diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h
index bc521131..126546ec 100644
--- a/shared/nm-utils/nm-test-utils.h
+++ b/shared/nm-utils/nm-test-utils.h
@@ -41,7 +41,7 @@
  * NMTST_SEED_RAND environment variable:
  *   Tests that use random numbers from nmtst_get_rand() get seeded randomly at each start.
  *   You can specify the seed by setting NMTST_SEED_RAND. Also, tests will print the seed
- *   to stdout, so that you know the choosen seed.
+ *   to stdout, so that you know the chosen seed.
  *
  *
  * NMTST_DEBUG environment variable:
@@ -82,7 +82,8 @@
  *   Whether long-running tests are enabled is determined as follows (highest priority first):
  *     - specifying the value in NMTST_DEBUG has highest priority
  *     - respect g_test_quick(), if the command line contains '-mslow', '-mquick', '-mthorough'.
- *     - use compile time default
+ *     - use compile time default (CFLAGS=-DNMTST_TEST_QUICK=TRUE)
+ *     - enable slow tests by default
  *
  * "p=PATH"|"s=PATH": passes the path to g_test_init() as "-p" and "-s", respectively.
  *   Unfortunately, these options conflict with "--tap" which our makefile passes to the
@@ -137,12 +138,13 @@
 #define NMTST_WAIT(max_wait_ms, wait) \
 	({ \
 		gboolean _not_expired = TRUE; \
-		gint64 _nmtst_end, _nmtst_max_wait_us = (max_wait_ms) * 1000L; \
+		const gint64 nmtst_wait_start_us = g_get_monotonic_time (); \
+		const gint64 nmtst_wait_duration_us = (max_wait_ms) * 1000L; \
+		const gint64 nmtst_wait_end_us = nmtst_wait_start_us + nmtst_wait_duration_us; \
 		\
-		_nmtst_end = g_get_monotonic_time () + _nmtst_max_wait_us; \
 		while (TRUE) { \
 			{ wait }; \
-			if (g_get_monotonic_time () > _nmtst_end) { \
+			if (g_get_monotonic_time () > nmtst_wait_end_us) { \
 				_not_expired = FALSE; \
 				break; \
 			} \
@@ -281,6 +283,15 @@ nmtst_free (void)
 }
 
 static inline void
+_nmtst_log_handler (const gchar   *log_domain,
+                    GLogLevelFlags log_level,
+                    const gchar   *message,
+                    gpointer       user_data)
+{
+	g_print ("%s\n", message);
+}
+
+static inline void
 __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_level, const char *log_domains, gboolean *out_set_logging)
 {
 	const char *nmtst_debug;
@@ -589,6 +600,11 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_
 		g_assert_no_error (error);
 	}
 #endif
+
+	g_log_set_handler (G_LOG_DOMAIN,
+	                   G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
+	                   _nmtst_log_handler,
+	                   NULL);
 }
 
 #ifndef _NMTST_INSIDE_CORE
@@ -647,13 +663,18 @@ nmtst_test_quick (void)
 
 typedef struct _NmtstTestData NmtstTestData;
 
-typedef void (*NmtstTestDataRelease) (const NmtstTestData *test_data);
+typedef void (*NmtstTestHandler) (const NmtstTestData *test_data);
 
 struct _NmtstTestData {
-	const char *testpath;
-	NmtstTestDataRelease fcn_release;
+	union {
+		const char *testpath;
+		char *_testpath;
+	};
 	gsize n_args;
-	gpointer args[1];
+	gpointer *args;
+	NmtstTestHandler _func_setup;
+	GTestDataFunc _func_test;
+	NmtstTestHandler _func_teardown;
 };
 
 static inline void
@@ -670,8 +691,8 @@ _nmtst_test_data_unpack (const NmtstTestData *test_data, gsize n_args, ...)
 	for (i = 0; i < n_args; i++) {
 		p = va_arg (ap, gpointer *);
 
-		g_assert (p);
-		*p = test_data->args[i];
+		if (p)
+			*p = test_data->args[i];
 	}
 	va_end (ap);
 }
@@ -684,25 +705,42 @@ _nmtst_test_data_free (gpointer data)
 
 	g_assert (test_data);
 
-	if (test_data->fcn_release)
-		test_data->fcn_release (test_data);
-
-	g_free ((gpointer) test_data->testpath);
+	g_free (test_data->_testpath);
 	g_free (test_data);
 }
 
 static inline void
-_nmtst_add_test_func_full (const char *testpath, GTestDataFunc test_func, NmtstTestDataRelease fcn_release, gsize n_args, ...)
+_nmtst_test_run (gconstpointer data)
+{
+	const NmtstTestData *test_data = data;
+
+	if (test_data->_func_setup)
+		test_data->_func_setup (test_data);
+
+	test_data->_func_test (test_data);
+
+	if (test_data->_func_teardown)
+		test_data->_func_teardown (test_data);
+}
+
+static inline void
+_nmtst_add_test_func_full (const char *testpath, GTestDataFunc func_test, NmtstTestHandler func_setup, NmtstTestHandler func_teardown, gsize n_args, ...)
 {
 	gsize i;
 	NmtstTestData *data;
 	va_list ap;
 
-	data = g_malloc (G_STRUCT_OFFSET (NmtstTestData, args) + sizeof (gpointer) * (n_args + 1));
+	g_assert (testpath && testpath[0]);
+	g_assert (func_test);
+
+	data = g_malloc0 (sizeof (NmtstTestData) + (sizeof (gpointer) * (n_args + 1)));
 
-	data->testpath = g_strdup (testpath);
-	data->fcn_release = fcn_release;
+	data->_testpath = g_strdup (testpath);
+	data->_func_test = func_test;
+	data->_func_setup = func_setup;
+	data->_func_teardown = func_teardown;
 	data->n_args = n_args;
+	data->args = (gpointer) &data[1];
 	va_start (ap, n_args);
 	for (i = 0; i < n_args; i++)
 		data->args[i] = va_arg (ap, gpointer);
@@ -711,11 +749,11 @@ _nmtst_add_test_func_full (const char *testpath, GTestDataFunc test_func, NmtstT
 
 	g_test_add_data_func_full (testpath,
 	                           data,
-	                           test_func,
+	                           _nmtst_test_run,
 	                           _nmtst_test_data_free);
 }
-#define nmtst_add_test_func_full(testpath, test_func, fcn_release, ...) _nmtst_add_test_func_full(testpath, test_func, fcn_release, NM_NARG (__VA_ARGS__), ##__VA_ARGS__)
-#define nmtst_add_test_func(testpath, test_func, ...) nmtst_add_test_func_full(testpath, test_func, NULL, ##__VA_ARGS__)
+#define nmtst_add_test_func_full(testpath, func_test, func_setup, func_teardown, ...) _nmtst_add_test_func_full(testpath, func_test, func_setup, func_teardown, NM_NARG (__VA_ARGS__), ##__VA_ARGS__)
+#define nmtst_add_test_func(testpath, func_test, ...) nmtst_add_test_func_full(testpath, func_test, NULL, NULL, ##__VA_ARGS__)
 
 /*****************************************************************************/
 
@@ -1659,6 +1697,63 @@ nmtst_assert_setting_verifies (NMSetting *setting)
 	g_assert (success);
 }
 
+#if defined(__NM_SIMPLE_CONNECTION_H__)
+static inline void
+_nmtst_assert_connection_has_settings (NMConnection *connection, gboolean has_at_least, gboolean has_at_most, ...)
+{
+	gs_unref_hashtable GHashTable *names = NULL;
+	gs_free NMSetting **settings = NULL;
+	va_list ap;
+	const char *name;
+	guint i, len;
+	gs_unref_ptrarray GPtrArray *names_arr = NULL;
+
+	g_assert (NM_IS_CONNECTION (connection));
+
+	names = g_hash_table_new (g_str_hash, g_str_equal);
+	names_arr = g_ptr_array_new ();
+
+	va_start (ap, has_at_most);
+	while ((name = va_arg (ap, const char *))) {
+		if (!nm_g_hash_table_add (names, (gpointer) name))
+			g_assert_not_reached ();
+		g_ptr_array_add (names_arr, (gpointer) name);
+	}
+	va_end (ap);
+
+	g_ptr_array_add (names_arr, NULL);
+
+	settings = nm_connection_get_settings (connection, &len);
+	for (i = 0; i < len; i++) {
+		if (   !g_hash_table_remove (names, nm_setting_get_name (settings[i]))
+		    && has_at_most) {
+			g_error ("nmtst_assert_connection_has_settings(): has setting \"%s\" which is not expected",
+			         nm_setting_get_name (settings[i]));
+		}
+	}
+	if (   g_hash_table_size (names) > 0
+	    && has_at_least) {
+		gs_free char *expected_str = g_strjoinv (" ", (char **) names_arr->pdata);
+		gs_free const char **settings_names = NULL;
+		gs_free char *has_str = NULL;
+
+		settings_names = g_new0 (const char *, len + 1);
+		for (i = 0; i < len; i++)
+			settings_names[i] = nm_setting_get_name (settings[i]);
+		has_str = g_strjoinv (" ", (char **) settings_names);
+
+		g_error ("nmtst_assert_connection_has_settings(): the setting lacks %u expected settings (expected: [%s] vs. has: [%s])",
+		         g_hash_table_size (names),
+		         expected_str,
+		         has_str);
+	}
+}
+#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__ */
+
 static inline void
 nmtst_assert_setting_verify_fails (NMSetting *setting,
                                    GQuark expect_error_domain,
diff --git a/shared/nm-utils/siphash24.c b/shared/nm-utils/siphash24.c
new file mode 100644
index 00000000..3a5a635d
--- /dev/null
+++ b/shared/nm-utils/siphash24.c
@@ -0,0 +1,203 @@
+/*
+   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
new file mode 100644
index 00000000..54e2420c
--- /dev/null
+++ b/shared/nm-utils/siphash24.h
@@ -0,0 +1,23 @@
+#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]);