about summary refs log tree commit diff
path: root/shared/nm-utils
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2019-03-12 15:16:42 +0100
committerSebastien Bacher <seb128@ubuntu.com>2019-03-12 15:16:42 +0100
commitcc4ab276f923ded9f415c1c2bf192994367ab0bb (patch)
treeac3a7775665992b27d07eb44186d38ff961a8cd7 /shared/nm-utils
parentbb1cf58350bb34463e9ffc5f96ac4f9b6bf46d28 (diff)
parentdd428301eb6f02542015121d7b08d9997f137e50 (diff)
Update upstream source from tag 'upstream/1.15.91'
Update to upstream version '1.15.91'
with Debian dir 74de38245314cab529c6c94cd9d0b874ce0c2994
Diffstat (limited to 'shared/nm-utils')
-rw-r--r--shared/nm-utils/nm-c-list.h36
-rw-r--r--shared/nm-utils/nm-dedup-multi.c55
-rw-r--r--shared/nm-utils/nm-errno.c177
-rw-r--r--shared/nm-utils/nm-errno.h138
-rw-r--r--shared/nm-utils/nm-glib.h8
-rw-r--r--shared/nm-utils/nm-hash-utils.c2
-rw-r--r--shared/nm-utils/nm-hash-utils.h3
-rw-r--r--shared/nm-utils/nm-io-utils.c75
-rw-r--r--shared/nm-utils/nm-jansson.h13
-rw-r--r--shared/nm-utils/nm-logging-fwd.h8
-rw-r--r--shared/nm-utils/nm-macros-internal.h107
-rw-r--r--shared/nm-utils/nm-secret-utils.c27
-rw-r--r--shared/nm-utils/nm-secret-utils.h27
-rw-r--r--shared/nm-utils/nm-shared-utils.c376
-rw-r--r--shared/nm-utils/nm-shared-utils.h92
-rw-r--r--shared/nm-utils/nm-test-utils.h8
-rw-r--r--shared/nm-utils/tests/test-shared-general.c46
17 files changed, 1009 insertions, 189 deletions
diff --git a/shared/nm-utils/nm-c-list.h b/shared/nm-utils/nm-c-list.h
index b43d1441..5c73f574 100644
--- a/shared/nm-utils/nm-c-list.h
+++ b/shared/nm-utils/nm-c-list.h
@@ -78,4 +78,40 @@ nm_c_list_elem_free_all (CList *head, GDestroyNotify free_fcn)
 	}
 }
 
+/*****************************************************************************/
+
+static inline gboolean
+nm_c_list_move_before (CList *lst, CList *elem)
+{
+	nm_assert (lst);
+	nm_assert (elem);
+	nm_assert (c_list_contains (lst, elem));
+
+	if (   lst != elem
+	    && lst->prev != elem) {
+		c_list_unlink_stale (elem);
+		c_list_link_before (lst, elem);
+		return TRUE;
+	}
+	return FALSE;
+}
+#define nm_c_list_move_tail(lst, elem) nm_c_list_move_before (lst, elem)
+
+static inline gboolean
+nm_c_list_move_after (CList *lst, CList *elem)
+{
+	nm_assert (lst);
+	nm_assert (elem);
+	nm_assert (c_list_contains (lst, elem));
+
+	if (   lst != elem
+	    && lst->next != elem) {
+		c_list_unlink_stale (elem);
+		c_list_link_after (lst, elem);
+		return TRUE;
+	}
+	return FALSE;
+}
+#define nm_c_list_move_front(lst, elem) nm_c_list_move_after (lst, elem)
+
 #endif /* __NM_C_LIST_H__ */
diff --git a/shared/nm-utils/nm-dedup-multi.c b/shared/nm-utils/nm-dedup-multi.c
index 852c207c..5bdc3e3c 100644
--- a/shared/nm-utils/nm-dedup-multi.c
+++ b/shared/nm-utils/nm-dedup-multi.c
@@ -24,6 +24,7 @@
 #include "nm-dedup-multi.h"
 
 #include "nm-hash-utils.h"
+#include "nm-c-list.h"
 
 /*****************************************************************************/
 
@@ -260,44 +261,27 @@ _add (NMDedupMultiIndex *self,
 		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));
-		}
+		nm_assert (!entry_order || entry_order->head == entry->head);
+		nm_assert (!entry_order || c_list_contains (&entry->lst_entries, &entry_order->lst_entries));
+		nm_assert (!entry_order || 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_stale (&entry->lst_entries);
-					c_list_link_before ((CList *) &entry_order->lst_entries, &entry->lst_entries);
+				if (nm_c_list_move_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_stale (&entry->lst_entries);
-					c_list_link_front ((CList *) &entry->head->lst_entries_head, &entry->lst_entries);
+				if (nm_c_list_move_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_stale (&entry->lst_entries);
-					c_list_link_after ((CList *) &entry_order->lst_entries, &entry->lst_entries);
+				if (nm_c_list_move_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_stale (&entry->lst_entries);
-					c_list_link_tail ((CList *) &entry->head->lst_entries_head, &entry->lst_entries);
+				if (nm_c_list_move_tail ((CList *) &entry->head->lst_entries_head, &entry->lst_entries))
 					changed = TRUE;
-				}
 			}
 			break;
 		case NM_DEDUP_MULTI_IDX_MODE_PREPEND:
@@ -1022,33 +1006,20 @@ nm_dedup_multi_entry_reorder (const NMDedupMultiEntry *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_stale ((CList *) &entry->lst_entries);
-				c_list_link_tail ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries);
+			if (nm_c_list_move_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_stale ((CList *) &entry->lst_entries);
-				c_list_link_front ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries);
+			if (nm_c_list_move_front ((CList *) &head_entry->lst_entries_head, (CList *) &entry->lst_entries))
 				return TRUE;
-			}
 		}
-	} else if (entry != entry_order) {
+	} else {
 		if (order_after) {
-			if (entry_order->lst_entries.next != &entry->lst_entries) {
-				c_list_unlink_stale ((CList *) &entry->lst_entries);
-				c_list_link_after ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries);
+			if (nm_c_list_move_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_stale ((CList *) &entry->lst_entries);
-				c_list_link_before ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries);
+			if (nm_c_list_move_before ((CList *) &entry_order->lst_entries, (CList *) &entry->lst_entries))
 				return TRUE;
-			}
 		}
 	}
 
diff --git a/shared/nm-utils/nm-errno.c b/shared/nm-utils/nm-errno.c
index c87f0b48..30eb9a8e 100644
--- a/shared/nm-utils/nm-errno.c
+++ b/shared/nm-utils/nm-errno.c
@@ -22,33 +22,61 @@
 
 #include "nm-errno.h"
 
+#include <pthread.h>
+
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror, int,
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_geterror,
+#if 0
+	enum _NMErrno,
+#else
+	int,
+#endif
 	NM_UTILS_LOOKUP_DEFAULT (NULL),
 
-	NM_UTILS_LOOKUP_STR_ITEM (NME_UNSPEC,          "NME_UNSPEC"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_BUG,             "NME_BUG"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_NATIVE_ERRNO,    "NME_NATIVE_ERRNO"),
-
-	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_ATTRSIZE,     "NME_NL_ATTRSIZE"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_BAD_SOCK,     "NME_NL_BAD_SOCK"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_DUMP_INTR,    "NME_NL_DUMP_INTR"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_MSG_OVERFLOW, "NME_NL_MSG_OVERFLOW"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_MSG_TOOSHORT, "NME_NL_MSG_TOOSHORT"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_MSG_TRUNC,    "NME_NL_MSG_TRUNC"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_SEQ_MISMATCH, "NME_NL_SEQ_MISMATCH"),
-
-	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NOT_FOUND,    "not-found"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_EXISTS,       "exists"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_WRONG_TYPE,   "wrong-type"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NOT_SLAVE,    "not-slave"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NO_FIRMWARE,  "no-firmware"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_OPNOTSUPP,    "not-supported"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NETLINK,      "netlink"),
-	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_CANT_SET_MTU, "cant-set-mtu"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_ERRNO_SUCCESS,      "NME_ERRNO_SUCCESS"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_ERRNO_OUT_OF_RANGE, "NME_ERRNO_OUT_OF_RANGE"),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NME_UNSPEC,             "NME_UNSPEC"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_BUG,                "NME_BUG"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NATIVE_ERRNO,       "NME_NATIVE_ERRNO"),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_ATTRSIZE,        "NME_NL_ATTRSIZE"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_BAD_SOCK,        "NME_NL_BAD_SOCK"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_DUMP_INTR,       "NME_NL_DUMP_INTR"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_MSG_OVERFLOW,    "NME_NL_MSG_OVERFLOW"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_MSG_TOOSHORT,    "NME_NL_MSG_TOOSHORT"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_MSG_TRUNC,       "NME_NL_MSG_TRUNC"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_SEQ_MISMATCH,    "NME_NL_SEQ_MISMATCH"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_NL_NOADDR,          "NME_NL_NOADDR"),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NOT_FOUND,       "not-found"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_EXISTS,          "exists"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_WRONG_TYPE,      "wrong-type"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NOT_SLAVE,       "not-slave"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NO_FIRMWARE,     "no-firmware"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_OPNOTSUPP,       "not-supported"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_NETLINK,         "netlink"),
+	NM_UTILS_LOOKUP_STR_ITEM (NME_PL_CANT_SET_MTU,    "cant-set-mtu"),
+
+	NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_ERRNO_MININT),
+	NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_ERRNO_RESERVED_LAST_PLUS_1),
 );
 
+/**
+ * nm_strerror():
+ * @nmerr: the NetworkManager specific errno to be converted
+ *   to string.
+ *
+ * NetworkManager specific error numbers reserve a range in "errno.h" with
+ * our own defines. For numbers that don't fall into this range, the numbers
+ * are identical to the common error numbers.
+ *
+ * Idential to strerror(), g_strerror(), nm_strerror_native() for error numbers
+ * that are not in the reserved range of NetworkManager specific errors.
+ *
+ * Returns: (transfer none): the string representation of the error number.
+ */
 const char *
 nm_strerror (int nmerr)
 {
@@ -61,5 +89,110 @@ nm_strerror (int nmerr)
 		if (s)
 			return s;
 	}
-	return g_strerror (nmerr);
+	return nm_strerror_native (nmerr);
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_strerror_native_r:
+ * @errsv: the errno to convert to string.
+ * @buf: the output buffer where to write the string to.
+ * @buf_size: the length of buffer.
+ *
+ * This is like strerror_r(), with one difference: depending on the
+ * locale, the returned string is guaranteed to be valid UTF-8.
+ * Also, there is some confusion as to whether to use glibc's
+ * strerror_r() or the POXIX/XSI variant. This is abstracted
+ * by the function.
+ *
+ * Note that the returned buffer may also be a statically allocated
+ * buffer, and not the input buffer @buf. Consequently, the returned
+ * string may be longer than @buf_size.
+ *
+ * Returns: (transfer none): a NUL terminated error message. This is either a static
+ *   string (that is never freed), or the provided @buf argumnt.
+ */
+const char *
+nm_strerror_native_r (int errsv, char *buf, gsize buf_size)
+{
+	char *buf2;
+
+	nm_assert (buf);
+	nm_assert (buf_size > 0);
+
+#if (_POSIX_C_SOURCE >= 200112L) && !  _GNU_SOURCE
+	/* XSI-compliant */
+	{
+		int errno_saved = errno;
+
+		if (strerror_r (errsv, buf, buf_size) != 0) {
+			g_snprintf (buf, buf_size, "Unspecified errno %d", errsv);
+			errno = errno_saved;
+		}
+		buf2 = buf;
+	}
+#else
+	/* GNU-specific */
+	buf2 = strerror_r (errsv, buf, buf_size);
+#endif
+
+	/* like g_strerror(), ensure that the error message is UTF-8. */
+	if (   !g_get_charset (NULL)
+	    && !g_utf8_validate (buf2, -1, NULL)) {
+		gs_free char *msg = NULL;
+
+		msg = g_locale_to_utf8 (buf2, -1, NULL, NULL, NULL);
+		if (msg) {
+			g_strlcpy (buf, msg, buf_size);
+			buf2 = buf;
+		}
+	}
+
+	return buf2;
+}
+
+/**
+ * nm_strerror_native:
+ * @errsv: the errno integer from <errno.h>
+ *
+ * Like strerror(), but strerror() is not thread-safe and not guaranteed
+ * to be UTF-8.
+ *
+ * g_strerror() is a thread-safe variant of strerror(), however it caches
+ * all returned strings in a dictionary. That means, using this on untrusted
+ * error numbers can result in this cache to grow without limits.
+ *
+ * Instead, return a tread-local buffer. This way, it's thread-safe.
+ *
+ * There is a downside to this: subsequent calls of nm_strerror_native()
+ * overwrite the error message.
+ *
+ * Returns: (transfer none): the text representation of the error number.
+ */
+const char *
+nm_strerror_native (int errsv)
+{
+	static _nm_thread_local char *buf_static = NULL;
+	char *buf;
+
+	buf = buf_static;
+	if (G_UNLIKELY (!buf)) {
+		int errno_saved = errno;
+		pthread_key_t key;
+
+		buf = g_malloc (NM_STRERROR_BUFSIZE);
+		buf_static = buf;
+
+		if (   pthread_key_create (&key, g_free) != 0
+		    || pthread_setspecific (key, buf) != 0) {
+			/* Failure. We will leak the buffer when the thread exits.
+			 *
+			 * Nothing we can do about it really. For Debug builds we fail with an assertion. */
+			nm_assert_not_reached ();
+		}
+		errno = errno_saved;
+	}
+
+	return nm_strerror_native_r (errsv, buf, NM_STRERROR_BUFSIZE);
 }
diff --git a/shared/nm-utils/nm-errno.h b/shared/nm-utils/nm-errno.h
index c3008f1f..d77735a7 100644
--- a/shared/nm-utils/nm-errno.h
+++ b/shared/nm-utils/nm-errno.h
@@ -25,11 +25,23 @@
 
 /*****************************************************************************/
 
-enum {
+enum _NMErrno {
 	_NM_ERRNO_MININT         = G_MININT,
 	_NM_ERRNO_MAXINT         = G_MAXINT,
 	_NM_ERRNO_RESERVED_FIRST = 100000,
 
+
+	/* when we cannot represent a number as positive number, we resort to this
+	 * number. Basically, the values G_MININT, -NME_ERRNO_SUCCESS, NME_ERRNO_SUCCESS
+	 * and G_MAXINT all map to the same value. */
+	NME_ERRNO_OUT_OF_RANGE   = G_MAXINT,
+
+	/* Indicate that the original errno was zero. Zero denotes *no error*, but we know something
+	 * went wrong and we want to report some error. This is a placeholder to mean, something
+	 * was wrong, but errno was zero. */
+	NME_ERRNO_SUCCESS        = G_MAXINT - 1,
+
+
 	/* an unspecified error. */
 	NME_UNSPEC = _NM_ERRNO_RESERVED_FIRST,
 
@@ -68,68 +80,106 @@ enum {
 
 /*****************************************************************************/
 
+/* When we receive an errno from a system function, we can safely assume
+ * that the error number is not negative. We rely on that, and possibly just
+ * "return -errsv;" to signal an error. We also rely on that, because libc
+ * is our trusted base: meaning, if it cannot even succeed at setting errno
+ * according to specification, all bets are off.
+ *
+ * This macro returns the input argument, and asserts that the error variable
+ * is positive.
+ *
+ * In a sense, the macro is related to nm_errno_native() function, but the difference
+ * is that this macro asserts that @errsv is positive, while nm_errno_native() coerces
+ * negative values to be non-negative. */
+#define NM_ERRNO_NATIVE(errsv) \
+	({ \
+		const int _errsv_x = (errsv); \
+		\
+		nm_assert (_errsv_x > 0); \
+		_errsv_x; \
+	})
+
+/* Normalize native errno.
+ *
+ * Our API may return native error codes (<errno.h>) as negative values. This function
+ * takes such an errno, and normalizes it to their positive value.
+ *
+ * The special values G_MININT and zero are coerced to NME_ERRNO_OUT_OF_RANGE and NME_ERRNO_SUCCESS
+ * respectively.
+ * Other values are coerced to their inverse.
+ * Other positive values are returned unchanged.
+ *
+ * Basically, this normalizes errsv to be positive (taking care of two pathological cases).
+ */
 static inline int
 nm_errno_native (int errsv)
 {
-	/* several API returns negative errno values as errors. Normalize
-	 * negative values to positive values.
-	 *
-	 * As a special case, map G_MININT to G_MAXINT. If you care about the
-	 * distinction, then check for G_MININT before.
-	 *
-	 * Basically, this normalizes a plain errno to be non-negative. */
-	return errsv >= 0
-	       ? errsv
-	       : ((errsv == G_MININT) ? G_MAXINT : -errsv);
+	switch (errsv) {
+	case 0:                  return NME_ERRNO_SUCCESS;
+	case G_MININT:           return NME_ERRNO_OUT_OF_RANGE;
+	default:
+		return errsv >= 0 ? errsv : -errsv;
+	}
 }
 
+/* Normalizes an nm-error to be positive.
+ *
+ * Various API returns negative error codes, and this function converts the negative
+ * value to its positive.
+ *
+ * Note that @nmerr is on the domain of NetworkManager specific error numbers,
+ * which is not the same as the native error numbers (errsv from <errno.h>). But
+ * as far as normalizing goes, nm_errno() does exactly the same remapping as
+ * nm_errno_native(). */
 static inline int
 nm_errno (int nmerr)
 {
-	/* Normalizes an nm-error to be positive. Various API returns negative
-	 * error codes, and this function converts the negative value to its
-	 * positive.
-	 *
-	 * It's very similar to nm_errno_native(), but not exactly. The difference is that
-	 * nm_errno_native() is for plain errno, while nm_errno() is for nm-error numbers.
-	 * Yes, nm-error number are ~almost~ the same as errno, except that a particular
-	 * range (_NM_ERRNO_RESERVED_FIRST, _NM_ERRNO_RESERVED_LAST) is reserved. The difference
-	 * between the two functions is only how G_MININT is mapped.
-	 *
-	 * See also nm_errno_from_native() below. */
-	return nmerr >= 0
-	       ? nmerr
-	       : ((nmerr == G_MININT) ? NME_BUG : -nmerr);
+	return nm_errno_native (nmerr);
 }
 
+/* this maps a native errno to a (always non-negative) nm-error number.
+ *
+ * Note that nm-error numbers are embedded into the range of regular
+ * errno. The only difference is, that nm-error numbers reserve a
+ * range (_NM_ERRNO_RESERVED_FIRST, _NM_ERRNO_RESERVED_LAST) for their
+ * own purpose.
+ *
+ * That means, converting an errno to nm-error number means in
+ * most cases just returning itself.
+ * Only pathological cases need special handling:
+ *
+ *  - 0 is mapped to NME_ERRNO_SUCCESS;
+ *  - G_MININT is mapped to NME_ERRNO_OUT_OF_RANGE;
+ *  - values in the range of (+/-) [_NM_ERRNO_RESERVED_FIRST, _NM_ERRNO_RESERVED_LAST]
+ *    are mapped to NME_NATIVE_ERRNO
+ *  - all other values are their (positive) absolute value.
+ */
 static inline int
 nm_errno_from_native (int errsv)
 {
-	/* this maps a native errno to a (always non-negative) nm-error number.
-	 *
-	 * Note that nm-error numbers are embedded into the range of regular
-	 * errno. The only difference is, that nm-error numbers reserve a
-	 * range (_NM_ERRNO_RESERVED_FIRST, _NM_ERRNO_RESERVED_LAST) for their
-	 * own purpose.
-	 *
-	 * That means, converting an errno to nm-error number means in
-	 * most cases just returning itself (negative values are normalized
-	 * to be positive). Only values G_MININT and [_NM_ERRNO_RESERVED_FIRST, _NM_ERRNO_RESERVED_LAST]
-	 * are coerced to the special value NME_NATIVE_ERRNO, as they cannot
-	 * otherwise be represented in nm-error number domain. */
-	if (errsv < 0) {
-		return   G_UNLIKELY (errsv == G_MININT)
+	switch (errsv) {
+	case 0:                  return NME_ERRNO_SUCCESS;
+	case G_MININT:           return NME_ERRNO_OUT_OF_RANGE;
+	default:
+		if (errsv < 0)
+			errsv = -errsv;
+		return   G_UNLIKELY (   errsv >= _NM_ERRNO_RESERVED_FIRST
+		                     && errsv <= _NM_ERRNO_RESERVED_LAST)
 		       ? NME_NATIVE_ERRNO
-		       : -errsv;
+		       : errsv;
 	}
-	return   G_UNLIKELY (   errsv >= _NM_ERRNO_RESERVED_FIRST
-	                     && errsv <= _NM_ERRNO_RESERVED_LAST)
-	       ? NME_NATIVE_ERRNO
-	       : errsv;
 }
 
 const char *nm_strerror (int nmerr);
 
 /*****************************************************************************/
 
+#define NM_STRERROR_BUFSIZE 1024
+
+const char *nm_strerror_native_r (int errsv, char *buf, gsize buf_size);
+const char *nm_strerror_native (int errsv);
+
+/*****************************************************************************/
+
 #endif /* __NM_ERRNO_H__ */
diff --git a/shared/nm-utils/nm-glib.h b/shared/nm-utils/nm-glib.h
index b7534edd..e941e067 100644
--- a/shared/nm-utils/nm-glib.h
+++ b/shared/nm-utils/nm-glib.h
@@ -424,11 +424,13 @@ g_steal_pointer (gpointer pp)
 
 	return ref;
 }
+#endif
 
-/* type safety */
-#define g_steal_pointer(pp) \
-  (0 ? (*(pp)) : (g_steal_pointer) (pp))
+#ifdef g_steal_pointer
+#undef g_steal_pointer
 #endif
+#define g_steal_pointer(pp) \
+	((typeof (*(pp))) g_steal_pointer (pp))
 
 /*****************************************************************************/
 
diff --git a/shared/nm-utils/nm-hash-utils.c b/shared/nm-utils/nm-hash-utils.c
index 80387c71..6e728e6b 100644
--- a/shared/nm-utils/nm-hash-utils.c
+++ b/shared/nm-utils/nm-hash-utils.c
@@ -71,7 +71,7 @@ again:
 		 * the first guint has only the entropy that nm_utils_random_bytes()
 		 * generated for the first 4 bytes and relies on a good random generator.
 		 *
-		 * The first int is especially intersting for nm_hash_static() below, and we
+		 * The first int is especially interesting for nm_hash_static() below, and we
 		 * want to have it all the entropy of t_arr. */
 		c_siphash_init (&siph_state, t_arr.v8);
 		c_siphash_append (&siph_state, (const guint8 *) &t_arr, sizeof (t_arr));
diff --git a/shared/nm-utils/nm-hash-utils.h b/shared/nm-utils/nm-hash-utils.h
index cf71a7e9..1a1e44f5 100644
--- a/shared/nm-utils/nm-hash-utils.h
+++ b/shared/nm-utils/nm-hash-utils.h
@@ -122,6 +122,9 @@ nm_hash_update (NMHashState *state, const void *ptr, gsize n)
 		nm_hash_update ((state), &_val, sizeof (_val)); \
 	} G_STMT_END
 
+#define nm_hash_update_valp(state, val) \
+	nm_hash_update ((state), (val), sizeof (*(val))) \
+
 static inline void
 nm_hash_update_bool (NMHashState *state, bool val)
 {
diff --git a/shared/nm-utils/nm-io-utils.c b/shared/nm-utils/nm-io-utils.c
index 06f756c4..51312748 100644
--- a/shared/nm-utils/nm-io-utils.c
+++ b/shared/nm-utils/nm-io-utils.c
@@ -29,6 +29,7 @@
 
 #include "nm-shared-utils.h"
 #include "nm-secret-utils.h"
+#include "nm-errno.h"
 
 /*****************************************************************************/
 
@@ -36,14 +37,12 @@ _nm_printf (3, 4)
 static int
 _get_contents_error (GError **error, int errsv, const char *format, ...)
 {
-	if (errsv < 0)
-		errsv = -errsv;
-	else if (!errsv)
-		errsv = errno;
+	nm_assert (NM_ERRNO_NATIVE (errsv));
 
 	if (error) {
-		char *msg;
+		gs_free char *msg = NULL;
 		va_list args;
+		char bstrerr[NM_STRERROR_BUFSIZE];
 
 		va_start (args, format);
 		msg = g_strdup_vprintf (format, args);
@@ -52,11 +51,17 @@ _get_contents_error (GError **error, int errsv, const char *format, ...)
 		             G_FILE_ERROR,
 		             g_file_error_from_errno (errsv),
 		             "%s: %s",
-		             msg, g_strerror (errsv));
-		g_free (msg);
+		             msg,
+		             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
 	}
 	return -errsv;
 }
+#define _get_contents_error_errno(error, ...) \
+	({ \
+		int _errsv = (errno); \
+		\
+		_get_contents_error (error, _errsv, __VA_ARGS__); \
+	})
 
 static char *
 _mem_realloc (char *old, gboolean do_bzero_mem, gsize cur_len, gsize new_len)
@@ -127,13 +132,14 @@ nm_utils_fd_get_contents (int fd,
 	struct stat stat_buf;
 	gs_free char *str = NULL;
 	const bool do_bzero_mem = NM_FLAGS_HAS (flags, NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET);
+	int errsv;
 
 	g_return_val_if_fail (fd >= 0, -EINVAL);
 	g_return_val_if_fail (contents, -EINVAL);
 	g_return_val_if_fail (!error || !*error, -EINVAL);
 
 	if (fstat (fd, &stat_buf) < 0)
-		return _get_contents_error (error, 0, "failure during fstat");
+		return _get_contents_error_errno (error, "failure during fstat");
 
 	if (!max_length) {
 		/* default to a very large size, but not extreme */
@@ -156,7 +162,7 @@ nm_utils_fd_get_contents (int fd,
 		if (n_read < 0) {
 			if (do_bzero_mem)
 				nm_explicit_bzero (str, n_stat);
-			return _get_contents_error (error, n_read, "error reading %zu bytes from file descriptor", n_stat);
+			return _get_contents_error (error, -n_read, "error reading %zu bytes from file descriptor", n_stat);
 		}
 		str[n_read] = '\0';
 
@@ -176,19 +182,19 @@ nm_utils_fd_get_contents (int fd,
 		else {
 			fd2 = fcntl (fd, F_DUPFD_CLOEXEC, 0);
 			if (fd2 < 0)
-				return _get_contents_error (error, 0, "error during dup");
+				return _get_contents_error_errno (error, "error during dup");
 		}
 
 		if (!(f = fdopen (fd2, "r"))) {
+			errsv = errno;
 			nm_close (fd2);
-			return _get_contents_error (error, 0, "failure during fdopen");
+			return _get_contents_error (error, errsv, "failure during fdopen");
 		}
 
 		n_have = 0;
 		n_alloc = 0;
 
 		while (!feof (f)) {
-			int errsv;
 			gsize n_read;
 
 			n_read = fread (buf, 1, sizeof (buf), f);
@@ -262,8 +268,8 @@ nm_utils_fd_get_contents (int fd,
  * @flags: %NMUtilsFileGetContentsFlags for reading the file.
  * @contents: the output buffer with the file read. It is always
  *   NUL terminated. The buffer is at most @max_length long, including
- *  the NUL byte. That is, it reads only files up to a length of
- *  @max_length - 1 bytes.
+ *   the NUL byte. That is, it reads only files up to a length of
+ *   @max_length - 1 bytes.
  * @length: optional output argument of the read file size.
  *
  * A reimplementation of g_file_get_contents() with a few differences:
@@ -284,6 +290,7 @@ nm_utils_file_get_contents (int dirfd,
 {
 	int fd;
 	int errsv;
+	char bstrerr[NM_STRERROR_BUFSIZE];
 
 	g_return_val_if_fail (filename && filename[0], -EINVAL);
 
@@ -297,8 +304,8 @@ nm_utils_file_get_contents (int dirfd,
 			             g_file_error_from_errno (errsv),
 			             "Failed to open file \"%s\" with openat: %s",
 			             filename,
-			             g_strerror (errsv));
-			return -errsv;
+			             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
+			return -NM_ERRNO_NATIVE (errsv);
 		}
 	} else {
 		fd = open (filename, O_RDONLY | O_CLOEXEC);
@@ -310,8 +317,8 @@ nm_utils_file_get_contents (int dirfd,
 			             g_file_error_from_errno (errsv),
 			             "Failed to open file \"%s\": %s",
 			             filename,
-			             g_strerror (errsv));
-			return -errsv;
+			             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
+			return -NM_ERRNO_NATIVE (errsv);
 		}
 	}
 	return nm_utils_fd_get_contents (fd,
@@ -341,6 +348,7 @@ nm_utils_file_set_contents (const char *filename,
 	int errsv;
 	gssize s;
 	int fd;
+	char bstrerr[NM_STRERROR_BUFSIZE];
 
 	g_return_val_if_fail (filename, FALSE);
 	g_return_val_if_fail (contents || !length, FALSE);
@@ -359,7 +367,7 @@ nm_utils_file_set_contents (const char *filename,
 		             g_file_error_from_errno (errsv),
 		             "failed to create file %s: %s",
 		             tmp_name,
-		             g_strerror (errsv));
+		             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
 		return FALSE;
 	}
 
@@ -378,7 +386,7 @@ nm_utils_file_set_contents (const char *filename,
 			             g_file_error_from_errno (errsv),
 			             "failed to write to file %s: %s",
 			             tmp_name,
-			             g_strerror (errsv));
+			             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
 			return FALSE;
 		}
 
@@ -395,20 +403,21 @@ nm_utils_file_set_contents (const char *filename,
 	 * guarantee the data is written to the disk before the metadata.)
 	 */
 	if (   lstat (filename, &statbuf) == 0
-	    && statbuf.st_size > 0
-	    && fsync (fd) != 0) {
-		errsv = errno;
+	    && statbuf.st_size > 0) {
+		if (fsync (fd) != 0) {
+			errsv = errno;
 
-		nm_close (fd);
-		unlink (tmp_name);
+			nm_close (fd);
+			unlink (tmp_name);
 
-		g_set_error (error,
-		             G_FILE_ERROR,
-		             g_file_error_from_errno (errsv),
-		             "failed to fsync %s: %s",
-		             tmp_name,
-		             g_strerror (errsv));
-		return FALSE;
+			g_set_error (error,
+			             G_FILE_ERROR,
+			             g_file_error_from_errno (errsv),
+			             "failed to fsync %s: %s",
+			             tmp_name,
+			             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
+			return FALSE;
+		}
 	}
 
 	nm_close (fd);
@@ -422,7 +431,7 @@ nm_utils_file_set_contents (const char *filename,
 		             "failed to rename %s to %s: %s",
 		             tmp_name,
 		             filename,
-		             g_strerror (errsv));
+		             nm_strerror_native_r (errsv, bstrerr, sizeof (bstrerr)));
 		return FALSE;
 	}
 
diff --git a/shared/nm-utils/nm-jansson.h b/shared/nm-utils/nm-jansson.h
index b00c75c6..5a73231f 100644
--- a/shared/nm-utils/nm-jansson.h
+++ b/shared/nm-utils/nm-jansson.h
@@ -34,13 +34,16 @@
 /* Added in Jansson v2.8 */
 #ifndef json_object_foreach_safe
 #define json_object_foreach_safe(object, n, key, value)     \
-    for(key = json_object_iter_key(json_object_iter(object)), \
-            n = json_object_iter_next(object, json_object_key_to_iter(key)); \
-        key && (value = json_object_iter_value(json_object_key_to_iter(key))); \
-        key = json_object_iter_key(n), \
-            n = json_object_iter_next(object, json_object_key_to_iter(key)))
+    for (key = json_object_iter_key(json_object_iter(object)), \
+             n = json_object_iter_next(object, json_object_key_to_iter(key)); \
+         key && (value = json_object_iter_value(json_object_key_to_iter(key))); \
+         key = json_object_iter_key(n), \
+             n = json_object_iter_next(object, json_object_key_to_iter(key)))
 #endif
 
+NM_AUTO_DEFINE_FCN0 (json_t *, _nm_auto_decref_json, json_decref)
+#define nm_auto_decref_json nm_auto(_nm_auto_decref_json)
+
 #endif /* WITH_JANSON */
 
 #endif  /* __NM_JANSSON_H__ */
diff --git a/shared/nm-utils/nm-logging-fwd.h b/shared/nm-utils/nm-logging-fwd.h
index 303d5951..900dfff8 100644
--- a/shared/nm-utils/nm-logging-fwd.h
+++ b/shared/nm-utils/nm-logging-fwd.h
@@ -94,18 +94,20 @@ typedef enum  { /*< skip >*/
 	_LOGL_N, /* the number of logging levels including "OFF" */
 } NMLogLevel;
 
-gboolean _nm_log_enabled (NMLogLevel level,
-                          NMLogDomain domain);
+gboolean _nm_log_enabled_impl (gboolean mt_require_locking,
+                               NMLogLevel level,
+                               NMLogDomain domain);
 
 void _nm_log_impl (const char *file,
                    guint line,
                    const char *func,
+                   gboolean mt_require_locking,
                    NMLogLevel level,
                    NMLogDomain domain,
                    int error,
                    const char *ifname,
                    const char *con_uuid,
                    const char *fmt,
-                   ...) _nm_printf (9, 10);
+                   ...) _nm_printf (10, 11);
 
 #endif /* __NM_LOGGING_DEFINES_H__ */
diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h
index 1f3970fe..42299c96 100644
--- a/shared/nm-utils/nm-macros-internal.h
+++ b/shared/nm-utils/nm-macros-internal.h
@@ -45,6 +45,18 @@
 #define nm_auto(fcn)         __attribute__ ((__cleanup__(fcn)))
 
 
+/* This is required to make LTO working.
+ *
+ * See https://gitlab.freedesktop.org/NetworkManager/NetworkManager/merge_requests/76#note_112694
+ *     https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48200#c28
+ */
+#ifndef __clang__
+#define _nm_externally_visible __attribute__ ((__externally_visible__))
+#else
+#define _nm_externally_visible
+#endif
+
+
 #if __GNUC__ >= 7
 #define _nm_fallthrough      __attribute__ ((__fallthrough__))
 #else
@@ -67,6 +79,28 @@
 
 /*****************************************************************************/
 
+/* most of our code is single-threaded with a mainloop. Hence, we usually don't need
+ * any thread-safety. Sometimes, we do need thread-safety (nm-logging), but we can
+ * avoid locking if we are on the main-thread by:
+ *
+ *   - modifications of shared data is done infrequently and only from the
+ *     main-thread (nm_logging_setup())
+ *   - read-only access is done frequently (nm_logging_enabled())
+ *     - from the main-thread, we can do that without locking (because
+ *       all modifications are also done on the main thread.
+ *     - from other threads, we need locking. But this is expected to be
+ *       done infrequently too. Important is the lock-free fast-path on the
+ *       main-thread.
+ *
+ * By defining NM_THREAD_SAFE_ON_MAIN_THREAD you indicate that this code runs
+ * on the main-thread. It is by default defined to "1". If you have code that
+ * is also used on another thread, redefine the define to 0 (to opt in into
+ * the slow-path).
+ */
+#define NM_THREAD_SAFE_ON_MAIN_THREAD 1
+
+/*****************************************************************************/
+
 #define NM_AUTO_DEFINE_FCN_VOID(CastType, name, func) \
 static inline void name (void *v) \
 { \
@@ -415,7 +449,7 @@ NM_G_ERROR_MSG (GError *error)
 /*****************************************************************************/
 
 /* macro to return strlen() of a compile time string. */
-#define NM_STRLEN(str)     ( sizeof ("" str) - 1 )
+#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:
@@ -826,12 +860,33 @@ fcn (void) \
 
 /*****************************************************************************/
 
-#define nm_streq(s1, s2)  (strcmp (s1, s2) == 0)
-#define nm_streq0(s1, s2) (g_strcmp0 (s1, s2) == 0)
+static inline gboolean
+nm_streq (const char *s1, const char *s2)
+{
+	return strcmp (s1, s2) == 0;
+}
+
+static inline gboolean
+nm_streq0 (const char *s1, const char *s2)
+{
+	return    (s1 == s2)
+	       || (s1 && s2 && strcmp (s1, s2) == 0);
+}
 
 #define NM_STR_HAS_PREFIX(str, prefix) \
 	(strncmp ((str), ""prefix"", NM_STRLEN (prefix)) == 0)
 
+#define NM_STR_HAS_SUFFIX(str, suffix) \
+	({ \
+		const char *_str = (str); \
+		gsize _l = strlen (_str); \
+		\
+		(   (_l >= NM_STRLEN (suffix)) \
+		 && (memcmp (&_str[_l - NM_STRLEN (suffix)], \
+		             ""suffix"", \
+		             NM_STRLEN (suffix)) == 0)); \
+	})
+
 /*****************************************************************************/
 
 static inline GString *
@@ -1127,6 +1182,28 @@ nm_clear_g_cancellable (GCancellable **cancellable)
 	return FALSE;
 }
 
+/* If @cancellable_id is not 0, clear it and call g_cancellable_disconnect().
+ * @cancellable may be %NULL, if there is nothing to disconnect.
+ *
+ * It's like nm_clear_g_signal_handler(), except that it uses g_cancellable_disconnect()
+ * instead of g_signal_handler_disconnect().
+ *
+ * Note the warning in glib documentation about dead-lock and what g_cancellable_disconnect()
+ * actually does. */
+static inline gboolean
+nm_clear_g_cancellable_disconnect (GCancellable *cancellable, gulong *cancellable_id)
+{
+	gulong id;
+
+	if (   cancellable_id
+	    && (id = *cancellable_id) != 0) {
+		*cancellable_id = 0;
+		g_cancellable_disconnect (cancellable, id);
+		return TRUE;
+	}
+	return FALSE;
+}
+
 /*****************************************************************************/
 
 static inline GVariant *
@@ -1243,17 +1320,17 @@ fcn_name (lookup_type val) \
 
 /*****************************************************************************/
 
-#define _NM_BACKPORT_SYMBOL_IMPL(VERSION, RETURN_TYPE, ORIG_FUNC, VERSIONED_FUNC, ARGS_TYPED, ARGS) \
-RETURN_TYPE VERSIONED_FUNC ARGS_TYPED; \
-RETURN_TYPE VERSIONED_FUNC ARGS_TYPED \
+#define _NM_BACKPORT_SYMBOL_IMPL(version, return_type, orig_func, versioned_func, args_typed, args) \
+return_type versioned_func args_typed; \
+_nm_externally_visible return_type versioned_func args_typed \
 { \
-    return ORIG_FUNC ARGS; \
+    return orig_func args; \
 } \
-RETURN_TYPE ORIG_FUNC ARGS_TYPED; \
-__asm__(".symver "G_STRINGIFY(VERSIONED_FUNC)", "G_STRINGIFY(ORIG_FUNC)"@"G_STRINGIFY(VERSION))
+return_type orig_func args_typed; \
+__asm__(".symver "G_STRINGIFY(versioned_func)", "G_STRINGIFY(orig_func)"@"G_STRINGIFY(version))
 
-#define NM_BACKPORT_SYMBOL(VERSION, RETURN_TYPE, FUNC, ARGS_TYPED, ARGS) \
-_NM_BACKPORT_SYMBOL_IMPL(VERSION, RETURN_TYPE, FUNC, _##FUNC##_##VERSION, ARGS_TYPED, ARGS)
+#define NM_BACKPORT_SYMBOL(version, return_type, func, args_typed, args) \
+_NM_BACKPORT_SYMBOL_IMPL(version, return_type, func, _##func##_##version, args_typed, args)
 
 /*****************************************************************************/
 
@@ -1371,6 +1448,14 @@ nm_strcmp_p (gconstpointer a, gconstpointer b)
 		 : _b); \
 	})
 
+/* evaluates to (void) if _A or _B are not constant or of different types */
+#define NM_CONST_MAX(_A, _B) \
+	(__builtin_choose_expr ((   __builtin_constant_p (_A) \
+	                         && __builtin_constant_p (_B) \
+	                         && __builtin_types_compatible_p (typeof (_A), typeof (_B))), \
+	                        ((_A) > (_B)) ? (_A) : (_B),                            \
+	                        ((void)  0)))
+
 /*****************************************************************************/
 
 static inline guint
diff --git a/shared/nm-utils/nm-secret-utils.c b/shared/nm-utils/nm-secret-utils.c
index 65f99c65..ec5cc6b1 100644
--- a/shared/nm-utils/nm-secret-utils.c
+++ b/shared/nm-utils/nm-secret-utils.c
@@ -17,6 +17,7 @@
  * Boston, MA 02110-1301 USA.
  *
  * (C) Copyright 2018 Red Hat, Inc.
+ * (C) Copyright 2015 - 2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
  */
 
 #include "nm-default.h"
@@ -132,3 +133,29 @@ nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len)
 	                                   _secret_buf_free,
 	                                   secret);
 }
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_memeqzero_secret:
+ * @data: the data pointer to check (may be %NULL if @length is zero).
+ * @length: the number of bytes to check.
+ *
+ * Checks that all bytes are zero. This always takes the same amount
+ * of time to prevent timing attacks.
+ *
+ * Returns: whether all bytes are zero.
+ */
+gboolean
+nm_utils_memeqzero_secret (gconstpointer data, gsize length)
+{
+	const guint8 *const key = data;
+	volatile guint8 acc = 0;
+	gsize i;
+
+	for (i = 0; i < length; i++) {
+		acc |= key[i];
+		asm volatile("" : "=r"(acc) : "0"(acc));
+	}
+	return 1 & ((acc - 1) >> 8);
+}
diff --git a/shared/nm-utils/nm-secret-utils.h b/shared/nm-utils/nm-secret-utils.h
index 9df31afe..034ef7bd 100644
--- a/shared/nm-utils/nm-secret-utils.h
+++ b/shared/nm-utils/nm-secret-utils.h
@@ -76,6 +76,19 @@ typedef struct {
 } NMSecretPtr;
 
 static inline void
+nm_secret_ptr_bzero (NMSecretPtr *secret)
+{
+	if (secret) {
+		if (secret->len > 0) {
+			if (secret->ptr)
+				nm_explicit_bzero (secret->ptr, secret->len);
+		}
+	}
+}
+
+#define nm_auto_bzero_secret_ptr nm_auto(nm_secret_ptr_bzero)
+
+static inline void
 nm_secret_ptr_clear (NMSecretPtr *secret)
 {
 	if (secret) {
@@ -90,12 +103,24 @@ nm_secret_ptr_clear (NMSecretPtr *secret)
 
 #define nm_auto_clear_secret_ptr nm_auto(nm_secret_ptr_clear)
 
+#define NM_SECRET_PTR_INIT() \
+	((const NMSecretPtr) { \
+		.len = 0, \
+		.ptr = NULL, \
+	})
+
 #define NM_SECRET_PTR_STATIC(_len) \
 	((const NMSecretPtr) { \
 		.len = _len, \
 		.ptr = ((guint8 [_len]) { }), \
 	})
 
+#define NM_SECRET_PTR_ARRAY(_arr) \
+	((const NMSecretPtr) { \
+		.len = G_N_ELEMENTS (_arr) * sizeof ((_arr)[0]), \
+		.ptr = &((_arr)[0]), \
+	})
+
 static inline void
 nm_secret_ptr_clear_static (const NMSecretPtr *secret)
 {
@@ -148,4 +173,6 @@ GBytes *nm_secret_buf_to_gbytes_take (NMSecretBuf *secret, gssize actual_len);
 
 /*****************************************************************************/
 
+gboolean nm_utils_memeqzero_secret (gconstpointer data, gsize length);
+
 #endif /* __NM_SECRET_UTILS_H__ */
diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c
index c9ef89e9..6a43c670 100644
--- a/shared/nm-utils/nm-shared-utils.c
+++ b/shared/nm-utils/nm-shared-utils.c
@@ -23,10 +23,12 @@
 
 #include "nm-shared-utils.h"
 
-#include <errno.h>
 #include <arpa/inet.h>
 #include <poll.h>
 #include <fcntl.h>
+#include <sys/syscall.h>
+
+#include "nm-errno.h"
 
 /*****************************************************************************/
 
@@ -34,7 +36,116 @@ const void *const _NM_PTRARRAY_EMPTY[1] = { NULL };
 
 /*****************************************************************************/
 
-const NMIPAddr nm_ip_addr_zero = { 0 };
+const NMIPAddr nm_ip_addr_zero = { };
+
+/* this initializes a struct in_addr/in6_addr and allows for untrusted
+ * arguments (like unsuitable @addr_family or @src_len). It's almost safe
+ * in the sense that it verifies input arguments strictly. Also, it
+ * uses memcpy() to access @src, so alignment is not an issue.
+ *
+ * Only potential pitfalls:
+ *
+ * - it allows for @addr_family to be AF_UNSPEC. If that is the case (and the
+ *   caller allows for that), the caller MUST provide @out_addr_family.
+ * - when setting @dst to an IPv4 address, the trailing bytes are not touched.
+ *   Meaning, if @dst is an NMIPAddr union, only the first bytes will be set.
+ *   If that matter to you, clear @dst before. */
+gboolean
+nm_ip_addr_set_from_untrusted (int addr_family,
+                               gpointer dst,
+                               gconstpointer src,
+                               gsize src_len,
+                               int *out_addr_family)
+{
+	nm_assert (dst);
+
+	switch (addr_family) {
+	case AF_UNSPEC:
+		if (!out_addr_family) {
+			/* when the callers allow undefined @addr_family, they must provide
+			 * an @out_addr_family argument. */
+			nm_assert_not_reached ();
+			return FALSE;
+		}
+		switch (src_len) {
+		case sizeof (struct in_addr):  addr_family = AF_INET;  break;
+		case sizeof (struct in6_addr): addr_family = AF_INET6; break;
+		default:
+			return FALSE;
+		}
+		break;
+	case AF_INET:
+		if (src_len != sizeof (struct in_addr))
+			return FALSE;
+		break;
+	case AF_INET6:
+		if (src_len != sizeof (struct in6_addr))
+			return FALSE;
+		break;
+	default:
+		/* when the callers allow undefined @addr_family, they must provide
+		 * an @out_addr_family argument. */
+		nm_assert (out_addr_family);
+		return FALSE;
+	}
+
+	nm_assert (src);
+
+	memcpy (dst, src, src_len);
+	NM_SET_OUT (out_addr_family, addr_family);
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+pid_t
+nm_utils_gettid (void)
+{
+	return (pid_t) syscall (SYS_gettid);
+}
+
+/* Used for asserting that this function is called on the main-thread.
+ * The main-thread is determined by remembering the thread-id
+ * of when the function was called the first time.
+ *
+ * When forking, the thread-id is again reset upon first call. */
+gboolean
+_nm_assert_on_main_thread (void)
+{
+	G_LOCK_DEFINE_STATIC (lock);
+	static pid_t seen_tid;
+	static pid_t seen_pid;
+	pid_t tid;
+	pid_t pid;
+	gboolean success = FALSE;
+
+	tid = nm_utils_gettid ();
+	nm_assert (tid != 0);
+
+	G_LOCK (lock);
+
+	if (G_LIKELY (tid == seen_tid)) {
+		/* we don't care about false positives (when the process forked, and the thread-id
+		 * is accidentally re-used) . It's for assertions only. */
+		success = TRUE;
+	} else {
+		pid = getpid ();
+		nm_assert (pid != 0);
+
+		if (   seen_tid == 0
+			|| seen_pid != pid) {
+			/* either this is the first time we call the function, or the process
+			 * forked. In both cases, remember the thread-id. */
+			seen_tid = tid;
+			seen_pid = pid;
+			success = TRUE;
+		}
+	}
+
+	G_UNLOCK (lock);
+
+	return success;
+}
 
 /*****************************************************************************/
 
@@ -561,6 +672,8 @@ nm_utils_parse_inaddr_prefix_bin (int addr_family,
 		return FALSE;
 
 	if (slash) {
+		/* For IPv4, `ip addr add` supports the prefix-length as a netmask. We don't
+		 * do that. */
 		prefix = _nm_utils_ascii_str_to_int64 (slash + 1, 10,
 		                                       0,
 		                                       addr_family == AF_INET ? 32 : 128,
@@ -1360,6 +1473,53 @@ nm_g_object_class_find_property_from_gtype (GType gtype,
 
 /*****************************************************************************/
 
+/**
+ * nm_g_type_find_implementing_class_for_property:
+ * @gtype: the GObject type which has a property @pname
+ * @pname: the name of the property to look up
+ *
+ * This is only a helper function for printf debugging. It's not
+ * used in actual code. Hence, the function just asserts that
+ * @pname and @gtype arguments are suitable. It cannot fail.
+ *
+ * Returns: the most ancestor type of @gtype, that
+ *   implements the property @pname. It means, it
+ *   searches the type hierarchy to find the type
+ *   that added @pname.
+ */
+GType
+nm_g_type_find_implementing_class_for_property (GType gtype,
+                                                const char *pname)
+{
+	nm_auto_unref_gtypeclass GObjectClass *klass = NULL;
+	GParamSpec *pspec;
+
+	g_return_val_if_fail (pname, G_TYPE_INVALID);
+
+	klass = g_type_class_ref (gtype);
+	g_return_val_if_fail (G_IS_OBJECT_CLASS (klass), G_TYPE_INVALID);
+
+	pspec = g_object_class_find_property (klass, pname);
+	g_return_val_if_fail (pspec, G_TYPE_INVALID);
+
+	gtype = G_TYPE_FROM_CLASS (klass);
+
+	while (TRUE) {
+		nm_auto_unref_gtypeclass GObjectClass *k = NULL;
+
+		k = g_type_class_ref (g_type_parent (gtype));
+
+		g_return_val_if_fail (G_IS_OBJECT_CLASS (k), G_TYPE_INVALID);
+
+		if (g_object_class_find_property (k, pname) != pspec)
+			return gtype;
+
+		gtype = G_TYPE_FROM_CLASS (k);
+	}
+}
+
+/*****************************************************************************/
+
 static void
 _str_append_escape (GString *s, char ch)
 {
@@ -1691,7 +1851,7 @@ nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns)
 
 	r = ppoll (&pollfd, 1, pts, NULL);
 	if (r < 0)
-		return -errno;
+		return -NM_ERRNO_NATIVE (errno);
 	if (r == 0)
 		return 0;
 	return pollfd.revents;
@@ -1718,10 +1878,12 @@ nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll)
 
 		k = read (fd, p, nbytes);
 		if (k < 0) {
-			if (errno == EINTR)
+			int errsv = errno;
+
+			if (errsv == EINTR)
 				continue;
 
-			if (errno == EAGAIN && do_poll) {
+			if (errsv == EAGAIN && do_poll) {
 
 				/* We knowingly ignore any return value here,
 				 * and expect that any error/EOF is reported
@@ -1731,7 +1893,7 @@ nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll)
 				continue;
 			}
 
-			return n > 0 ? n : -errno;
+			return n > 0 ? n : -NM_ERRNO_NATIVE (errsv);
 		}
 
 		if (k == 0)
@@ -2375,3 +2537,205 @@ nm_utils_memeqzero (gconstpointer data, gsize length)
 	/* Now we know that's zero, memcmp with self. */
 	return memcmp (data, p, length) == 0;
 }
+
+/**
+ * nm_utils_bin2hexstr_full:
+ * @addr: pointer of @length bytes. If @length is zero, this may
+ *   also be %NULL.
+ * @length: number of bytes in @addr. May also be zero, in which
+ *   case this will return an empty string.
+ * @delimiter: either '\0', otherwise the output string will have the
+ *   given delimiter character between each two hex numbers.
+ * @upper_case: if TRUE, use upper case ASCII characters for hex.
+ * @out: if %NULL, the function will allocate a new buffer of
+ *   either (@length*2+1) or (@length*3) bytes, depending on whether
+ *   a @delimiter is specified. In that case, the allocated buffer will
+ *   be returned and must be freed by the caller.
+ *   If not %NULL, the buffer must already be preallocated and contain
+ *   at least (@length*2+1) or (@length*3) bytes, depending on the delimiter.
+ *
+ * Returns: the binary value converted to a hex string. If @out is given,
+ *   this always returns @out. If @out is %NULL, a newly allocated string
+ *   is returned.
+ */
+char *
+nm_utils_bin2hexstr_full (gconstpointer addr,
+                          gsize length,
+                          char delimiter,
+                          gboolean upper_case,
+                          char *out)
+{
+	const guint8 *in = addr;
+	const char *LOOKUP = upper_case ? "0123456789ABCDEF" : "0123456789abcdef";
+	char *out0;
+
+	if (out)
+		out0 = out;
+	else {
+		out0 = out = g_new (char, delimiter == '\0'
+		                          ? length * 2 + 1
+		                          : length * 3);
+	}
+
+	/* @out must contain at least @length*3 bytes if @delimiter is set,
+	 * otherwise, @length*2+1. */
+
+	if (length > 0) {
+		nm_assert (in);
+		for (;;) {
+			const guint8 v = *in++;
+
+			*out++ = LOOKUP[v >> 4];
+			*out++ = LOOKUP[v & 0x0F];
+			length--;
+			if (!length)
+				break;
+			if (delimiter)
+				*out++ = delimiter;
+		}
+	}
+
+	*out = '\0';
+	return out0;
+}
+
+guint8 *
+nm_utils_hexstr2bin_full (const char *hexstr,
+                          gboolean allow_0x_prefix,
+                          gboolean delimiter_required,
+                          const char *delimiter_candidates,
+                          gsize required_len,
+                          guint8 *buffer,
+                          gsize buffer_len,
+                          gsize *out_len)
+{
+	const char *in = hexstr;
+	guint8 *out = buffer;
+	gboolean delimiter_has = TRUE;
+	guint8 delimiter = '\0';
+	gsize len;
+
+	nm_assert (hexstr);
+	nm_assert (buffer);
+	nm_assert (required_len > 0 || out_len);
+
+	if (   allow_0x_prefix
+	    && in[0] == '0'
+	    && in[1] == 'x')
+		in += 2;
+
+	while (TRUE) {
+		const guint8 d1 = in[0];
+		guint8 d2;
+		int i1, i2;
+
+		i1 = nm_utils_hexchar_to_int (d1);
+		if (i1 < 0)
+			goto fail;
+
+		/* If there's no leading zero (ie "aa:b:cc") then fake it */
+		d2 = in[1];
+		if (   d2
+		    && (i2 = nm_utils_hexchar_to_int (d2)) >= 0) {
+			*out++ = (i1 << 4) + i2;
+			d2 = in[2];
+			if (!d2)
+				break;
+			in += 2;
+		} else {
+			/* Fake leading zero */
+			*out++ = i1;
+			if (!d2) {
+				if (!delimiter_has) {
+					/* when using no delimiter, there must be pairs of hex chars */
+					goto fail;
+				}
+				break;
+			}
+			in += 1;
+		}
+
+		if (--buffer_len == 0)
+			goto fail;
+
+		if (delimiter_has) {
+			if (d2 != delimiter) {
+				if (delimiter)
+					goto fail;
+				if (delimiter_candidates) {
+					while (delimiter_candidates[0]) {
+						if (delimiter_candidates++[0] == d2)
+							delimiter = d2;
+					}
+				}
+				if (!delimiter) {
+					if (delimiter_required)
+						goto fail;
+					delimiter_has = FALSE;
+					continue;
+				}
+			}
+			in++;
+		}
+	}
+
+	len = out - buffer;
+	if (   required_len == 0
+	    || len == required_len) {
+		NM_SET_OUT (out_len, len);
+		return buffer;
+	}
+
+fail:
+	NM_SET_OUT (out_len, 0);
+	return NULL;
+}
+
+guint8 *
+nm_utils_hexstr2bin_alloc (const char *hexstr,
+                           gboolean allow_0x_prefix,
+                           gboolean delimiter_required,
+                           const char *delimiter_candidates,
+                           gsize required_len,
+                           gsize *out_len)
+{
+	guint8 *buffer;
+	gsize buffer_len, len;
+
+	g_return_val_if_fail (hexstr, NULL);
+
+	nm_assert (required_len > 0 || out_len);
+
+	if (   allow_0x_prefix
+	    && hexstr[0] == '0'
+	    && hexstr[1] == 'x')
+		hexstr += 2;
+
+	if (!hexstr[0])
+		goto fail;
+
+	if (required_len > 0)
+		buffer_len = required_len;
+	else
+		buffer_len = strlen (hexstr) / 2 + 3;
+
+	buffer = g_malloc (buffer_len);
+
+	if (nm_utils_hexstr2bin_full (hexstr,
+	                              FALSE,
+	                              delimiter_required,
+	                              delimiter_candidates,
+	                              required_len,
+	                              buffer,
+	                              buffer_len,
+	                              &len)) {
+		NM_SET_OUT (out_len, len);
+		return buffer;
+	}
+
+	g_free (buffer);
+
+fail:
+	NM_SET_OUT (out_len, 0);
+	return NULL;
+}
diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h
index e28a5fb2..65e34959 100644
--- a/shared/nm-utils/nm-shared-utils.h
+++ b/shared/nm-utils/nm-shared-utils.h
@@ -26,6 +26,18 @@
 
 /*****************************************************************************/
 
+pid_t nm_utils_gettid (void);
+
+gboolean _nm_assert_on_main_thread (void);
+
+#if NM_MORE_ASSERTS > 5
+#define NM_ASSERT_ON_MAIN_THREAD() G_STMT_START { nm_assert (_nm_assert_on_main_thread ()); } G_STMT_END
+#else
+#define NM_ASSERT_ON_MAIN_THREAD() G_STMT_START {                                         ; } G_STMT_END
+#endif
+
+/*****************************************************************************/
+
 static inline gboolean
 _NM_INT_NOT_NEGATIVE (gssize val)
 {
@@ -128,6 +140,18 @@ nm_ip_addr_set (int addr_family, gpointer dst, gconstpointer src)
 	          : sizeof (struct in6_addr));
 }
 
+gboolean nm_ip_addr_set_from_untrusted (int addr_family,
+                                        gpointer dst,
+                                        gconstpointer src,
+                                        gsize src_len,
+                                        int *out_addr_family);
+
+static inline gboolean
+nm_ip4_addr_is_localhost (in_addr_t addr4)
+{
+	return (addr4 & htonl (0xFF000000u)) == htonl (0x7F000000u);
+}
+
 /*****************************************************************************/
 
 #define NM_CMP_RETURN(c) \
@@ -305,7 +329,7 @@ _nm_strndup_a_step (char *s, const char *str, gsize len)
  *
  * Usually, an inline function nm_strdup_int64() would be enough. However,
  * that cannot be used for guint64. So, we would also need nm_strdup_uint64().
- * This causes suble error potential, because the caller needs to ensure to
+ * This causes subtle error potential, because the caller needs to ensure to
  * use the right one (and compiler isn't going to help as it silently casts).
  *
  * Instead, this generic macro is supposed to handle all integers correctly. */
@@ -688,20 +712,26 @@ nm_utils_error_set_literal (GError **error, int error_code, const char *literal)
 	g_set_error ((error), NM_UTILS_ERROR, error_code, __VA_ARGS__)
 
 #define nm_utils_error_set_errno(error, errsv, fmt, ...) \
-	g_set_error ((error), \
-	             NM_UTILS_ERROR, \
-	             NM_UTILS_ERROR_UNKNOWN, \
-	             fmt, \
-	             ##__VA_ARGS__, \
-	             g_strerror (({ \
-	                            const int _errsv = (errsv); \
-	                            \
-	                            (  _errsv >= 0 \
-	                             ? _errsv \
-	                             : (  (_errsv == G_MININT) \
-	                                ? G_MAXINT \
-	                                : -errsv)); \
-	                          })))
+	G_STMT_START { \
+		char _bstrerr[NM_STRERROR_BUFSIZE]; \
+		\
+		g_set_error ((error), \
+		             NM_UTILS_ERROR, \
+		             NM_UTILS_ERROR_UNKNOWN, \
+		             fmt, \
+		             ##__VA_ARGS__, \
+		             nm_strerror_native_r (({ \
+		                                      const int _errsv = (errsv); \
+		                                      \
+		                                      (  _errsv >= 0 \
+		                                       ? _errsv \
+		                                       : (  G_UNLIKELY (_errsv == G_MININT) \
+		                                          ? G_MAXINT \
+		                                          : -errsv)); \
+		                                   }), \
+		                                   _bstrerr, \
+		                                   sizeof (_bstrerr))); \
+	} G_STMT_END
 
 /*****************************************************************************/
 
@@ -777,6 +807,11 @@ GParamSpec *nm_g_object_class_find_property_from_gtype (GType gtype,
 
 /*****************************************************************************/
 
+GType nm_g_type_find_implementing_class_for_property (GType gtype,
+                                                      const char *pname);
+
+/*****************************************************************************/
+
 typedef enum {
 	NM_UTILS_STR_UTF8_SAFE_FLAG_NONE                = 0,
 	NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL         = 0x0001,
@@ -1093,4 +1128,31 @@ nm_strv_ptrarray_take_gstring (GPtrArray *cmd,
 
 int nm_utils_getpagesize (void);
 
+/*****************************************************************************/
+
+char *nm_utils_bin2hexstr_full (gconstpointer addr,
+                                gsize length,
+                                char delimiter,
+                                gboolean upper_case,
+                                char *out);
+
+guint8 *nm_utils_hexstr2bin_full (const char *hexstr,
+                                  gboolean allow_0x_prefix,
+                                  gboolean delimiter_required,
+                                  const char *delimiter_candidates,
+                                  gsize required_len,
+                                  guint8 *buffer,
+                                  gsize buffer_len,
+                                  gsize *out_len);
+
+#define nm_utils_hexstr2bin_buf(hexstr, allow_0x_prefix, delimiter_required, delimiter_candidates, buffer) \
+    nm_utils_hexstr2bin_full ((hexstr), (allow_0x_prefix), (delimiter_required), (delimiter_candidates), G_N_ELEMENTS (buffer), (buffer), G_N_ELEMENTS (buffer), NULL)
+
+guint8 *nm_utils_hexstr2bin_alloc (const char *hexstr,
+                                   gboolean allow_0x_prefix,
+                                   gboolean delimiter_required,
+                                   const char *delimiter_candidates,
+                                   gsize required_len,
+                                   gsize *out_len);
+
 #endif /* __NM_SHARED_UTILS_H__ */
diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h
index c235d93d..c5ea5e3f 100644
--- a/shared/nm-utils/nm-test-utils.h
+++ b/shared/nm-utils/nm-test-utils.h
@@ -1060,7 +1060,7 @@ nmtst_reexec_sudo (void)
 	execvp (__nmtst_internal.sudo_cmd, argv);
 
 	errsv = errno;
-	g_error (">> exec %s failed: %d - %s", __nmtst_internal.sudo_cmd, errsv, strerror (errsv));
+	g_error (">> exec %s failed: %d - %s", __nmtst_internal.sudo_cmd, errsv, nm_strerror_native (errsv));
 }
 
 /*****************************************************************************/
@@ -1214,7 +1214,7 @@ nmtst_inet_from_string (int addr_family, const char *str)
 static inline const char *
 nmtst_inet_to_string (int addr_family, gconstpointer addr)
 {
-	static char buf[MAX (INET6_ADDRSTRLEN, INET_ADDRSTRLEN)];
+	static char buf[NM_CONST_MAX (INET6_ADDRSTRLEN, INET_ADDRSTRLEN)];
 
 	g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 	g_assert (addr);
@@ -1370,7 +1370,7 @@ nmtst_file_unlink_if_exists (const char *name)
 	if (unlink (name) != 0) {
 		errsv = errno;
 		if (errsv != ENOENT)
-			g_error ("nmtst_file_unlink_if_exists(%s): failed with %s", name, strerror (errsv));
+			g_error ("nmtst_file_unlink_if_exists(%s): failed with %s", name, nm_strerror_native (errsv));
 	}
 }
 
@@ -1383,7 +1383,7 @@ nmtst_file_unlink (const char *name)
 
 	if (unlink (name) != 0) {
 		errsv = errno;
-		g_error ("nmtst_file_unlink(%s): failed with %s", name, strerror (errsv));
+		g_error ("nmtst_file_unlink(%s): failed with %s", name, nm_strerror_native (errsv));
 	}
 }
 
diff --git a/shared/nm-utils/tests/test-shared-general.c b/shared/nm-utils/tests/test-shared-general.c
index 7d22e56d..d53b21d9 100644
--- a/shared/nm-utils/tests/test-shared-general.c
+++ b/shared/nm-utils/tests/test-shared-general.c
@@ -23,6 +23,7 @@
 
 #include "nm-utils/nm-time-utils.h"
 #include "nm-utils/nm-random-utils.h"
+#include "nm-utils/unaligned.h"
 
 #include "nm-utils/nm-test-utils.h"
 
@@ -204,6 +205,49 @@ test_nm_strndup_a (void)
 
 /*****************************************************************************/
 
+static void
+test_nm_ip4_addr_is_localhost (void)
+{
+	g_assert ( nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("127.0.0.0")));
+	g_assert ( nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("127.0.0.1")));
+	g_assert ( nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("127.5.0.1")));
+	g_assert (!nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("126.5.0.1")));
+	g_assert (!nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("128.5.0.1")));
+	g_assert (!nm_ip4_addr_is_localhost (nmtst_inet4_from_string ("129.5.0.1")));
+}
+
+/*****************************************************************************/
+
+static void
+test_unaligned (void)
+{
+	int shift;
+
+	for (shift = 0; shift <= 32; shift++) {
+		guint8 buf[100] = { };
+		guint8 val = 0;
+
+		while (val == 0)
+			val = nmtst_get_rand_int () % 256;
+
+		buf[shift] = val;
+
+		g_assert_cmpint (unaligned_read_le64 (&buf[shift]), ==, (guint64) val);
+		g_assert_cmpint (unaligned_read_be64 (&buf[shift]), ==, ((guint64) val) << 56);
+		g_assert_cmpint (unaligned_read_ne64 (&buf[shift]), !=, 0);
+
+		g_assert_cmpint (unaligned_read_le32 (&buf[shift]), ==, (guint32) val);
+		g_assert_cmpint (unaligned_read_be32 (&buf[shift]), ==, ((guint32) val) << 24);
+		g_assert_cmpint (unaligned_read_ne32 (&buf[shift]), !=, 0);
+
+		g_assert_cmpint (unaligned_read_le16 (&buf[shift]), ==, (guint16) val);
+		g_assert_cmpint (unaligned_read_be16 (&buf[shift]), ==, ((guint16) val) << 8);
+		g_assert_cmpint (unaligned_read_ne16 (&buf[shift]), !=, 0);
+	}
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int main (int argc, char **argv)
@@ -215,6 +259,8 @@ int main (int argc, char **argv)
 	g_test_add_func ("/general/test_nm_make_strv", test_make_strv);
 	g_test_add_func ("/general/test_nm_strdup_int", test_nm_strdup_int);
 	g_test_add_func ("/general/test_nm_strndup_a", test_nm_strndup_a);
+	g_test_add_func ("/general/test_nm_ip4_addr_is_localhost", test_nm_ip4_addr_is_localhost);
+	g_test_add_func ("/general/test_unaligned", test_unaligned);
 
 	return g_test_run ();
 }