about summary refs log tree commit diff
path: root/src/NetworkManagerUtils.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2015-01-22 00:29:39 +0100
committerMichael Biebl <biebl@debian.org>2015-01-22 00:29:39 +0100
commit2c032d8f1c6292c1338a615e6ec40252889ba85c (patch)
tree1f77182220b2b0264288ba4a476ab47e5bc48716 /src/NetworkManagerUtils.c
parent33491bc4279481db8ae47213e34a6d695a0e8830 (diff)
Imported Upstream version 1.0.0 upstream/1.0.0
Diffstat (limited to 'src/NetworkManagerUtils.c')
-rw-r--r--src/NetworkManagerUtils.c1580
1 files changed, 1458 insertions, 122 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index 0aaa344f..2c291cdb 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -15,20 +15,30 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2004 - 2012 Red Hat, Inc.
- * Copyright (C) 2005 - 2008 Novell, Inc.
+ * Copyright 2004 - 2014 Red Hat, Inc.
+ * Copyright 2005 - 2008 Novell, Inc.
  */
 
+#include "config.h"
+
 #include <glib.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <resolv.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <linux/if.h>
+#include <linux/if_infiniband.h>
 
 #include "NetworkManagerUtils.h"
+#include "nm-platform.h"
 #include "nm-utils.h"
+#include "nm-core-internal.h"
 #include "nm-logging.h"
 #include "nm-device.h"
 #include "nm-setting-connection.h"
@@ -36,40 +46,60 @@
 #include "nm-setting-ip6-config.h"
 #include "nm-setting-wireless.h"
 #include "nm-setting-wireless-security.h"
-#include "nm-manager-auth.h"
+#include "nm-auth-utils.h"
 #include "nm-posix-signals.h"
+#include "nm-dbus-glib-types.h"
+
+/*
+ * Some toolchains (E.G. uClibc 0.9.33 and earlier) don't export
+ * CLOCK_BOOTTIME even though the kernel supports it, so provide a
+ * local definition
+ */
+#ifndef CLOCK_BOOTTIME
+#define CLOCK_BOOTTIME 7
+#endif
 
 /*
- * nm_ethernet_address_is_valid
+ * nm_ethernet_address_is_valid:
+ * @addr: pointer to a binary or ASCII Ethernet address
+ * @len: length of @addr, or -1 if @addr is ASCII
  *
  * Compares an Ethernet address against known invalid addresses.
- *
+
+ * Returns: %TRUE if @addr is a valid Ethernet address, %FALSE if it is not.
  */
 gboolean
-nm_ethernet_address_is_valid (const struct ether_addr *test_addr)
+nm_ethernet_address_is_valid (gconstpointer addr, gssize len)
 {
 	guint8 invalid_addr1[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
 	guint8 invalid_addr2[ETH_ALEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
 	guint8 invalid_addr3[ETH_ALEN] = {0x44, 0x44, 0x44, 0x44, 0x44, 0x44};
 	guint8 invalid_addr4[ETH_ALEN] = {0x00, 0x30, 0xb4, 0x00, 0x00, 0x00}; /* prism54 dummy MAC */
+	guchar first_octet;
 
-	g_return_val_if_fail (test_addr != NULL, FALSE);
+	g_return_val_if_fail (addr != NULL, FALSE);
+	g_return_val_if_fail (len == ETH_ALEN || len == -1, FALSE);
 
 	/* Compare the AP address the card has with invalid ethernet MAC addresses. */
-	if (!memcmp (test_addr->ether_addr_octet, &invalid_addr1, ETH_ALEN))
+	if (nm_utils_hwaddr_matches (addr, len, invalid_addr1, ETH_ALEN))
 		return FALSE;
 
-	if (!memcmp (test_addr->ether_addr_octet, &invalid_addr2, ETH_ALEN))
+	if (nm_utils_hwaddr_matches (addr, len, invalid_addr2, ETH_ALEN))
 		return FALSE;
 
-	if (!memcmp (test_addr->ether_addr_octet, &invalid_addr3, ETH_ALEN))
+	if (nm_utils_hwaddr_matches (addr, len, invalid_addr3, ETH_ALEN))
 		return FALSE;
 
-	if (!memcmp (test_addr->ether_addr_octet, &invalid_addr4, ETH_ALEN))
+	if (nm_utils_hwaddr_matches (addr, len, invalid_addr4, ETH_ALEN))
 		return FALSE;
 
-	if (test_addr->ether_addr_octet[0] & 1)			/* Multicast addresses */
-		return FALSE;
+	/* Check for multicast address */
+	if (len == -1)
+		first_octet = strtoul (addr, NULL, 16);
+	else
+		first_octet = ((guint8 *)addr)[0];
+	if (first_octet & 0x01)
+			return FALSE;
 	
 	return TRUE;
 }
@@ -92,15 +122,15 @@ nm_utils_ip4_address_clear_host_address (in_addr_t addr, guint8 plen)
  * @src: source ip6 address
  * @plen: prefix length of network
  *
- * Note: this function is self assignment save, to update @src inplace, set both
+ * Note: this function is self assignment safe, to update @src inplace, set both
  * @dst and @src to the same destination.
  */
-void
+const struct in6_addr *
 nm_utils_ip6_address_clear_host_address (struct in6_addr *dst, const struct in6_addr *src, guint8 plen)
 {
-	g_return_if_fail (plen <= 128);
-	g_return_if_fail (src);
-	g_return_if_fail (dst);
+	g_return_val_if_fail (plen <= 128, NULL);
+	g_return_val_if_fail (src, NULL);
+	g_return_val_if_fail (dst, NULL);
 
 	if (plen < 128) {
 		guint nbytes = plen / 8;
@@ -116,6 +146,8 @@ nm_utils_ip6_address_clear_host_address (struct in6_addr *dst, const struct in6_
 			memset (&dst->s6_addr[nbytes], 0, 16 - nbytes);
 	} else if (src != dst)
 		*dst = *src;
+
+	return dst;
 }
 
 
@@ -144,6 +176,657 @@ nm_spawn_process (const char *args)
 	return status;
 }
 
+/**
+ * nm_utils_get_start_time_for_pid:
+ * @pid: the process identifier
+ *
+ * Originally copied from polkit source (src/polkit/polkitunixprocess.c)
+ * and adjusted.
+ *
+ * Returns: the timestamp when the process started (by parsing /proc/$PID/stat).
+ * If an error occurs (e.g. the process does not exist), 0 is returned.
+ *
+ * The returned start time counts since boot, in the unit HZ (with HZ usually being (1/100) seconds)
+ **/
+guint64
+nm_utils_get_start_time_for_pid (pid_t pid)
+{
+	guint64 start_time;
+	gchar *filename;
+	gchar *contents;
+	size_t length;
+	gchar **tokens;
+	guint num_tokens;
+	gchar *p;
+	gchar *endp;
+
+	start_time = 0;
+	contents = NULL;
+
+	g_return_val_if_fail (pid > 0, 0);
+
+	filename = g_strdup_printf ("/proc/%"G_GUINT64_FORMAT"/stat", (guint64) pid);
+
+	if (!g_file_get_contents (filename, &contents, &length, NULL))
+		goto out;
+
+	/* start time is the token at index 19 after the '(process name)' entry - since only this
+	 * field can contain the ')' character, search backwards for this to avoid malicious
+	 * processes trying to fool us
+	 */
+	p = strrchr (contents, ')');
+	if (p == NULL)
+		goto out;
+	p += 2; /* skip ') ' */
+	if (p - contents >= (int) length)
+		goto out;
+
+	tokens = g_strsplit (p, " ", 0);
+
+	num_tokens = g_strv_length (tokens);
+
+	if (num_tokens < 20)
+		goto out;
+
+	start_time = strtoull (tokens[19], &endp, 10);
+	if (endp == tokens[19])
+		goto out;
+
+	g_strfreev (tokens);
+
+ out:
+	g_free (filename);
+	g_free (contents);
+
+	return start_time;
+}
+
+/******************************************************************************************/
+
+typedef struct {
+	pid_t pid;
+	guint64 log_domain;
+	union {
+		struct {
+			gint64 wait_start_us;
+			guint source_timeout_kill_id;
+		} async;
+		struct {
+			gboolean success;
+			int child_status;
+		} sync;
+	};
+	NMUtilsKillChildAsyncCb callback;
+	void *user_data;
+
+	char log_name[1]; /* variable-length object, must be last element!! */
+} KillChildAsyncData;
+
+#define LOG_NAME_FMT "kill child process '%s' (%ld)"
+#define LOG_NAME_PROCESS_FMT "kill process '%s' (%ld)"
+#define LOG_NAME_ARGS log_name,(long)pid
+
+static KillChildAsyncData *
+_kc_async_data_alloc (pid_t pid, guint64 log_domain, const char *log_name, NMUtilsKillChildAsyncCb callback, void *user_data)
+{
+	KillChildAsyncData *data;
+	size_t log_name_len;
+
+	/* append the name at the end of our KillChildAsyncData. */
+	log_name_len = strlen (LOG_NAME_FMT) + 20 + strlen (log_name);
+	data = g_malloc (sizeof (KillChildAsyncData) - 1 + log_name_len);
+	g_snprintf (data->log_name, log_name_len, LOG_NAME_FMT, LOG_NAME_ARGS);
+
+	data->pid = pid;
+	data->user_data = user_data;
+	data->callback = callback;
+	data->log_domain = log_domain;
+
+	return data;
+}
+
+#define KC_EXIT_TO_STRING_BUF_SIZE 128
+static const char *
+_kc_exit_to_string (char *buf, int exit)
+#define _kc_exit_to_string(buf, exit) ( G_STATIC_ASSERT_EXPR(sizeof (buf) == KC_EXIT_TO_STRING_BUF_SIZE && sizeof ((buf)[0]) == 1), _kc_exit_to_string (buf, exit) )
+{
+	if (WIFEXITED (exit))
+		g_snprintf (buf, KC_EXIT_TO_STRING_BUF_SIZE, "normally with status %d", WEXITSTATUS (exit));
+	else if (WIFSIGNALED (exit))
+		g_snprintf (buf, KC_EXIT_TO_STRING_BUF_SIZE, "by signal %d", WTERMSIG (exit));
+	else
+		g_snprintf (buf, KC_EXIT_TO_STRING_BUF_SIZE, "with unexpected status %d", exit);
+	return buf;
+}
+
+static const char *
+_kc_signal_to_string (int sig)
+{
+	switch (sig) {
+	case 0:  return "no signal (0)";
+	case SIGKILL:  return "SIGKILL (" G_STRINGIFY (SIGKILL) ")";
+	case SIGTERM:  return "SIGTERM (" G_STRINGIFY (SIGTERM) ")";
+	default:
+		return "Unexpected signal";
+	}
+}
+
+#define KC_WAITED_TO_STRING 100
+static const char *
+_kc_waited_to_string (char *buf, gint64 wait_start_us)
+#define _kc_waited_to_string(buf, wait_start_us) ( G_STATIC_ASSERT_EXPR(sizeof (buf) == KC_WAITED_TO_STRING && sizeof ((buf)[0]) == 1), _kc_waited_to_string (buf, wait_start_us) )
+{
+	g_snprintf (buf, KC_WAITED_TO_STRING, " (%ld usec elapsed)", (long) (nm_utils_get_monotonic_timestamp_us () - wait_start_us));
+	return buf;
+}
+
+static void
+_kc_cb_watch_child (GPid pid, gint status, gpointer user_data)
+{
+	KillChildAsyncData *data = user_data;
+	char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE], buf_wait[KC_WAITED_TO_STRING];
+
+	if (data->async.source_timeout_kill_id)
+		g_source_remove (data->async.source_timeout_kill_id);
+
+	nm_log_dbg (data->log_domain, "%s: terminated %s%s",
+	            data->log_name, _kc_exit_to_string (buf_exit, status),
+	            _kc_waited_to_string (buf_wait, data->async.wait_start_us));
+
+	if (data->callback)
+		data->callback (pid, TRUE, status, data->user_data);
+
+	g_free (data);
+}
+
+static gboolean
+_kc_cb_timeout_grace_period (void *user_data)
+{
+	KillChildAsyncData *data = user_data;
+	int ret, errsv;
+
+	data->async.source_timeout_kill_id = 0;
+
+	if ((ret = kill (data->pid, SIGKILL)) != 0) {
+		errsv = errno;
+		/* ESRCH means, process does not exist or is already a zombie. */
+		if (errsv != ESRCH) {
+			nm_log_err (LOGD_CORE | data->log_domain, "%s: kill(SIGKILL) returned unexpected return value %d: (%s, %d)",
+			            data->log_name, ret, strerror (errsv), errsv);
+		}
+	} else {
+		nm_log_dbg (data->log_domain, "%s: process not terminated after %ld usec. Sending SIGKILL signal",
+		            data->log_name, (long) (nm_utils_get_monotonic_timestamp_us () - data->async.wait_start_us));
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_kc_invoke_callback_idle (gpointer user_data)
+{
+	KillChildAsyncData *data = user_data;
+
+	if (data->sync.success) {
+		char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE];
+
+		nm_log_dbg (data->log_domain, "%s: invoke callback: terminated %s",
+		            data->log_name, _kc_exit_to_string (buf_exit, data->sync.child_status));
+	} else
+		nm_log_dbg (data->log_domain, "%s: invoke callback: killing child failed", data->log_name);
+
+	data->callback (data->pid, data->sync.success, data->sync.child_status, data->user_data);
+	g_free (data);
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_kc_invoke_callback (pid_t pid, guint64 log_domain, const char *log_name, NMUtilsKillChildAsyncCb callback, void *user_data, gboolean success, int child_status)
+{
+	KillChildAsyncData *data;
+
+	if (!callback)
+		return;
+
+	data = _kc_async_data_alloc (pid, log_domain, log_name, callback, user_data);
+	data->sync.success = success;
+	data->sync.child_status = child_status;
+
+	g_idle_add (_kc_invoke_callback_idle, data);
+}
+
+/* nm_utils_kill_child_async:
+ * @pid: the process id of the process to kill
+ * @sig: signal to send initially. Set to 0 to send not signal.
+ * @log_domain: the logging domain used for logging (LOGD_NONE to suppress logging)
+ * @log_name: for logging, the name of the processes to kill
+ * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value
+ * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter is ignored.
+ * @callback: (allow-none): callback after the child terminated. This function will always
+ *   be invoked asynchronously.
+ * @user_data: passed on to callback
+ *
+ * Uses g_child_watch_add(), so note the glib comment: if you obtain pid from g_spawn_async() or
+ * g_spawn_async_with_pipes() you will need to pass %G_SPAWN_DO_NOT_REAP_CHILD as flag to the spawn
+ * function for the child watching to work.
+ * Also note, that you must g_source_remove() any other child watchers for @pid because glib
+ * supports only one watcher per child.
+ **/
+void
+nm_utils_kill_child_async (pid_t pid, int sig, guint64 log_domain,
+                           const char *log_name, guint32 wait_before_kill_msec,
+                           NMUtilsKillChildAsyncCb callback, void *user_data)
+{
+	int status = 0, errsv;
+	pid_t ret;
+	KillChildAsyncData *data;
+	char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE];
+
+	g_return_if_fail (pid > 0);
+	g_return_if_fail (log_name != NULL);
+
+	/* let's see if the child already terminated... */
+	ret = waitpid (pid, &status, WNOHANG);
+	if (ret > 0) {
+		nm_log_dbg (log_domain, LOG_NAME_FMT ": process %ld already terminated %s",
+		            LOG_NAME_ARGS, (long) ret, _kc_exit_to_string (buf_exit, status));
+		_kc_invoke_callback (pid, log_domain, log_name, callback, user_data, TRUE, status);
+		return;
+	} else if (ret != 0) {
+		errsv = errno;
+		/* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */
+		if (errsv != ECHILD) {
+			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)",
+			            LOG_NAME_ARGS, strerror (errsv), errsv);
+			_kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1);
+			return;
+		}
+	}
+
+	/* send the first signal. */
+	if (kill (pid, sig) != 0) {
+		errsv = errno;
+		/* ESRCH means, process does not exist or is already a zombie. */
+		if (errsv != ESRCH) {
+			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error sending %s: %s (%d)",
+			            LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+			_kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1);
+			return;
+		}
+
+		/* let's try again with waitpid, probably there was a race... */
+		ret = waitpid (pid, &status, 0);
+		if (ret > 0) {
+			nm_log_dbg (log_domain, LOG_NAME_FMT ": process %ld already terminated %s",
+			            LOG_NAME_ARGS, (long) ret, _kc_exit_to_string (buf_exit, status));
+			_kc_invoke_callback (pid, log_domain, log_name, callback, user_data, TRUE, status);
+		} else {
+			errsv = errno;
+			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed due to unexpected return value %ld by waitpid (%s, %d) after sending %s",
+			            LOG_NAME_ARGS, (long) ret, strerror (errsv), errsv, _kc_signal_to_string (sig));
+			_kc_invoke_callback (pid, log_domain, log_name, callback, user_data, FALSE, -1);
+		}
+		return;
+	}
+
+	data = _kc_async_data_alloc (pid, log_domain, log_name, callback, user_data);
+	data->async.wait_start_us = nm_utils_get_monotonic_timestamp_us ();
+
+	if (sig != SIGKILL && wait_before_kill_msec > 0) {
+		data->async.source_timeout_kill_id = g_timeout_add (wait_before_kill_msec, _kc_cb_timeout_grace_period, data);
+		nm_log_dbg (log_domain, "%s: wait for process to terminate after sending %s (send SIGKILL in %ld milliseconds)...",
+		            data->log_name,  _kc_signal_to_string (sig), (long) wait_before_kill_msec);
+	} else {
+		data->async.source_timeout_kill_id = 0;
+		nm_log_dbg (log_domain, "%s: wait for process to terminate after sending %s...",
+		            data->log_name, _kc_signal_to_string (sig));
+	}
+
+	g_child_watch_add (pid, _kc_cb_watch_child, data);
+}
+
+static inline gulong
+_sleep_duration_convert_ms_to_us (guint32 sleep_duration_msec)
+{
+	if (sleep_duration_msec > 0) {
+		guint64 x = (gint64) sleep_duration_msec * (guint64) 1000L;
+
+		return x < G_MAXULONG ? (gulong) x : G_MAXULONG;
+	}
+	return G_USEC_PER_SEC / 20;
+}
+
+/* nm_utils_kill_child_sync:
+ * @pid: process id to kill
+ * @sig: signal to sent initially. If 0, no signal is sent. If %SIGKILL, the
+ * second %SIGKILL signal is not sent after @wait_before_kill_msec milliseconds.
+ * @log_domain: log debug information for this domain. Errors and warnings are logged both
+ * as %LOGD_CORE and @log_domain.
+ * @log_name: name of the process to kill for logging.
+ * @child_status: (out) (allow-none): return the exit status of the child, if no error occured.
+ * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value
+ * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter has not effect.
+ * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate.
+ * Set to zero, to use the default (meaning 20 wakeups per seconds).
+ *
+ * Kill a child process synchronously and wait. The function first checks if the child already terminated
+ * and if it did, return the exit status. Otherwise send one @sig signal. @sig  will always be
+ * sent unless the child already exited. If the child does not exit within @wait_before_kill_msec milliseconds,
+ * the function will send %SIGKILL and waits for the child indefinitly. If @wait_before_kill_msec is zero, no
+ * %SIGKILL signal will be sent.
+ **/
+gboolean
+nm_utils_kill_child_sync (pid_t pid, int sig, guint64 log_domain, const char *log_name,
+                          int *child_status, guint32 wait_before_kill_msec,
+                          guint32 sleep_duration_msec)
+{
+	int status = 0, errsv;
+	pid_t ret;
+	gboolean success = FALSE;
+	gboolean was_waiting = FALSE, send_kill = FALSE;
+	char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE];
+	char buf_wait[KC_WAITED_TO_STRING];
+	gint64 wait_start_us;
+
+	g_return_val_if_fail (pid > 0, FALSE);
+	g_return_val_if_fail (log_name != NULL, FALSE);
+
+	/* check if the child process already terminated... */
+	ret = waitpid (pid, &status, WNOHANG);
+	if (ret > 0) {
+		nm_log_dbg (log_domain, LOG_NAME_FMT ": process %ld already terminated %s",
+		            LOG_NAME_ARGS, (long) ret, _kc_exit_to_string (buf_exit, status));
+		success = TRUE;
+		goto out;
+	} else if (ret != 0) {
+		errsv = errno;
+		/* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */
+		if (errsv != ECHILD) {
+			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)",
+			            LOG_NAME_ARGS, strerror (errsv), errsv);
+			goto out;
+		}
+	}
+
+	/* send first signal @sig */
+	if (kill (pid, sig) != 0) {
+		errsv = errno;
+		/* ESRCH means, process does not exist or is already a zombie. */
+		if (errsv != ESRCH) {
+			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed to send %s: %s (%d)",
+			            LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+		} else {
+			/* let's try again with waitpid, probably there was a race... */
+			ret = waitpid (pid, &status, 0);
+			if (ret > 0) {
+				nm_log_dbg (log_domain, LOG_NAME_FMT ": process %ld already terminated %s",
+				            LOG_NAME_ARGS, (long) ret, _kc_exit_to_string (buf_exit, status));
+				success = TRUE;
+			} else {
+				errsv = errno;
+				nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed due to unexpected return value %ld by waitpid (%s, %d) after sending %s",
+				            LOG_NAME_ARGS, (long) ret, strerror (errsv), errsv, _kc_signal_to_string (sig));
+			}
+		}
+		goto out;
+	}
+
+	wait_start_us = nm_utils_get_monotonic_timestamp_us ();
+
+	/* wait for the process to terminated... */
+	if (sig != SIGKILL) {
+		gint64 wait_until, now;
+		gulong sleep_time, sleep_duration_usec;
+		int loop_count = 0;
+
+		sleep_duration_usec = _sleep_duration_convert_ms_to_us (sleep_duration_msec);
+		wait_until = wait_before_kill_msec <= 0 ? 0 : wait_start_us + (((gint64) wait_before_kill_msec) * 1000L);
+
+		while (TRUE) {
+			ret = waitpid (pid, &status, WNOHANG);
+			if (ret > 0) {
+				nm_log_dbg (log_domain, LOG_NAME_FMT ": after sending %s, process %ld exited %s%s",
+				            LOG_NAME_ARGS, _kc_signal_to_string (sig), (long) ret, _kc_exit_to_string (buf_exit, status),
+				            was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : "");
+				success = TRUE;
+				goto out;
+			}
+			if (ret == -1) {
+				errsv = errno;
+				/* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */
+				if (errsv != ECHILD) {
+					nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": after sending %s, waitpid failed with %s (%d)%s",
+					            LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv,
+					           was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : "");
+					goto out;
+				}
+			}
+
+			if (!wait_until)
+				break;
+
+			now = nm_utils_get_monotonic_timestamp_us ();
+			if (now >= wait_until)
+				break;
+
+			if (!was_waiting) {
+				nm_log_dbg (log_domain, LOG_NAME_FMT ": waiting up to %ld milliseconds for process to terminate normally after sending %s...",
+				            LOG_NAME_ARGS, (long) MAX (wait_before_kill_msec, 0), _kc_signal_to_string (sig));
+				was_waiting = TRUE;
+			}
+
+			sleep_time = MIN (wait_until - now, sleep_duration_usec);
+			if (loop_count < 20) {
+				/* At the beginning we expect the process to die fast.
+				 * Limit the sleep time, the limit doubles with every iteration. */
+				sleep_time = MIN (sleep_time, (((guint64) 1) << loop_count) * G_USEC_PER_SEC / 2000);
+				loop_count++;
+			}
+			g_usleep (sleep_time);
+		}
+
+		/* send SIGKILL, if called with @wait_before_kill_msec > 0 */
+		if (wait_until) {
+			nm_log_dbg (log_domain, LOG_NAME_FMT ": sending SIGKILL...", LOG_NAME_ARGS);
+
+			send_kill = TRUE;
+			if (kill (pid, SIGKILL) != 0) {
+				errsv = errno;
+				/* ESRCH means, process does not exist or is already a zombie. */
+				if (errsv != ESRCH) {
+					nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": failed to send SIGKILL (after sending %s), %s (%d)",
+								LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+					goto out;
+				}
+			}
+		}
+	}
+
+	if (!was_waiting) {
+		nm_log_dbg (log_domain, LOG_NAME_FMT ": waiting for process to terminate after sending %s%s...",
+		            LOG_NAME_ARGS, _kc_signal_to_string (sig), send_kill ? " and SIGKILL" : "");
+	}
+
+	/* block until the child terminates. */
+	while ((ret = waitpid (pid, &status, 0)) <= 0) {
+		errsv = errno;
+
+		if (errsv != EINTR) {
+			nm_log_err (LOGD_CORE | log_domain, LOG_NAME_FMT ": after sending %s%s, waitpid failed with %s (%d)%s",
+			            LOG_NAME_ARGS, _kc_signal_to_string (sig), send_kill ? " and SIGKILL" : "", strerror (errsv), errsv,
+			            _kc_waited_to_string (buf_wait, wait_start_us));
+			goto out;
+		}
+	}
+
+	nm_log_dbg (log_domain, LOG_NAME_FMT ": after sending %s%s, process %ld exited %s%s",
+	            LOG_NAME_ARGS, _kc_signal_to_string (sig), send_kill ? " and SIGKILL" : "", (long) ret,
+	            _kc_exit_to_string (buf_exit, status), _kc_waited_to_string (buf_wait, wait_start_us));
+	success = TRUE;
+out:
+	if (child_status)
+		*child_status = success ? status : -1;
+	return success;
+}
+
+/* nm_utils_kill_process_sync:
+ * @pid: process id to kill
+ * @start_time: the start time of the process to kill (as obtained by nm_utils_get_start_time_for_pid()).
+ *   This is an optional argument, to avoid (somewhat) killing the wrong process as @pid
+ *   might get recycled. You can pass 0, to not provide this parameter.
+ * @sig: signal to sent initially. If 0, no signal is sent. If %SIGKILL, the
+ *   second %SIGKILL signal is not sent after @wait_before_kill_msec milliseconds.
+ * @log_domain: log debug information for this domain. Errors and warnings are logged both
+ *   as %LOGD_CORE and @log_domain.
+ * @log_name: name of the process to kill for logging.
+ * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value
+ *   to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter has no effect.
+ * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate.
+ *   Set to zero, to use the default (meaning 20 wakeups per seconds).
+ *
+ * Kill a non-child process synchronously and wait. This function will not return before the
+ * process with PID @pid is gone.
+ **/
+void
+nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, guint64 log_domain,
+                            const char *log_name, guint32 wait_before_kill_msec,
+                            guint32 sleep_duration_msec)
+{
+	int errsv;
+	guint64 start_time0;
+	gint64 wait_until, now, wait_start_us;
+	gulong sleep_time, sleep_duration_usec;
+	int loop_count = 0;
+	gboolean was_waiting = FALSE;
+	char buf_wait[KC_WAITED_TO_STRING];
+
+	g_return_if_fail (pid > 0);
+	g_return_if_fail (log_name != NULL);
+	g_return_if_fail (wait_before_kill_msec > 0);
+
+	start_time0 = nm_utils_get_start_time_for_pid (pid);
+	if (start_time0 == 0) {
+		nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": cannot kill process %ld because it seems already gone",
+		            LOG_NAME_ARGS, (long int) pid);
+		return;
+	}
+	if (start_time != 0 && start_time != start_time0) {
+		nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": don't kill process %ld because the start_time is unexpectedly %lu instead of %ld",
+		            LOG_NAME_ARGS, (long int) pid, (long unsigned) start_time0, (long unsigned) start_time);
+		return;
+	}
+
+	if (kill (pid, sig) != 0) {
+		errsv = errno;
+		/* ESRCH means, process does not exist or is already a zombie. */
+		if (errsv == ESRCH) {
+			nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": failed to send %s because process seems gone",
+			            LOG_NAME_ARGS, _kc_signal_to_string (sig));
+		} else {
+			nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to send %s: %s (%d)",
+			             LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv);
+		}
+		return;
+	}
+
+	/* wait for the process to terminated... */
+
+	wait_start_us = nm_utils_get_monotonic_timestamp_us ();
+
+	sleep_duration_usec = _sleep_duration_convert_ms_to_us (sleep_duration_msec);
+	wait_until = wait_start_us + (((gint64) wait_before_kill_msec) * 1000L);
+
+	while (TRUE) {
+		start_time = nm_utils_get_start_time_for_pid (pid);
+
+		if (start_time != start_time0) {
+			nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": process is gone after sending signal %s%s",
+			            LOG_NAME_ARGS, _kc_signal_to_string (sig),
+			            was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : "");
+			return;
+		}
+
+		if (kill (pid, 0) != 0) {
+			errsv = errno;
+			/* ESRCH means, process does not exist or is already a zombie. */
+			if (errsv == ESRCH) {
+				nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": process is gone or a zombie after sending signal %s%s",
+				            LOG_NAME_ARGS, _kc_signal_to_string (sig),
+				            was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : "");
+			} else {
+				nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to kill(%ld, 0): %s (%d)%s",
+				             LOG_NAME_ARGS, (long int) pid, strerror (errsv), errsv,
+				             was_waiting ? _kc_waited_to_string (buf_wait, wait_start_us) : "");
+			}
+			return;
+		}
+
+		sleep_time = sleep_duration_usec;
+		if (wait_until != 0) {
+			now = nm_utils_get_monotonic_timestamp_us ();
+			if (sig != SIGKILL && now >= wait_until) {
+				/* Still not dead. SIGKILL now... */
+				nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": sending SIGKILL", LOG_NAME_ARGS);
+				if (kill (pid, SIGKILL) != 0) {
+					errsv = errno;
+					/* ESRCH means, process does not exist or is already a zombie. */
+					if (errsv != ESRCH) {
+						nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": process is gone or a zombie%s",
+						            LOG_NAME_ARGS, _kc_waited_to_string (buf_wait, wait_start_us));
+					} else {
+						nm_log_warn (LOGD_CORE | log_domain, LOG_NAME_PROCESS_FMT ": failed to send SIGKILL (after sending %s), %s (%d)%s",
+						             LOG_NAME_ARGS, _kc_signal_to_string (sig), strerror (errsv), errsv,
+						             _kc_waited_to_string (buf_wait, wait_start_us));
+					}
+					return;
+				}
+				sig = SIGKILL;
+				was_waiting = TRUE;
+				wait_until = 0;
+				loop_count = 0; /* reset the loop_count. Now we really expect the process to die quickly. */
+			} else {
+				if (!was_waiting) {
+					nm_log_dbg (log_domain, LOG_NAME_PROCESS_FMT ": waiting up to %ld milliseconds for process to disappear after sending %s...",
+					            LOG_NAME_ARGS, (long) wait_before_kill_msec, _kc_signal_to_string (sig));
+					was_waiting = TRUE;
+				}
+				sleep_time = MIN (wait_until - now, sleep_duration_usec);
+			}
+		}
+
+		if (loop_count < 20) {
+			/* At the beginning we expect the process to die fast.
+			 * Limit the sleep time, the limit doubles with every iteration. */
+			sleep_time = MIN (sleep_time, (((guint64) 1) << loop_count) * G_USEC_PER_SEC / 2000);
+			loop_count++;
+		}
+		g_usleep (sleep_time);
+	}
+}
+#undef LOG_NAME_FMT
+#undef LOG_NAME_PROCESS_FMT
+#undef LOG_NAME_ARGS
+
+const char *const NM_PATHS_DEFAULT[] = {
+	PREFIX "/sbin/",
+	PREFIX "/bin/",
+	"/sbin/",
+	"/usr/sbin/",
+	"/usr/local/sbin/",
+	"/usr/bin/",
+	"/usr/local/bin/",
+	NULL,
+};
+
+const char *
+nm_utils_find_helper(const char *progname, const char *try_first, GError **error)
+{
+	return nm_utils_file_search_in_paths (progname, try_first, NM_PATHS_DEFAULT, G_FILE_TEST_IS_EXECUTABLE, NULL, NULL, error);
+}
+
+/******************************************************************************************/
+
 gboolean
 nm_match_spec_string (const GSList *specs, const char *match)
 {
@@ -160,15 +843,22 @@ nm_match_spec_string (const GSList *specs, const char *match)
 gboolean
 nm_match_spec_hwaddr (const GSList *specs, const char *hwaddr)
 {
-	char *hwaddr_match;
-	gboolean matched;
+	const GSList *iter;
 
 	g_return_val_if_fail (hwaddr != NULL, FALSE);
 
-	hwaddr_match = g_strdup_printf ("mac:%s", hwaddr);
-	matched = nm_match_spec_string (specs, hwaddr_match);
-	g_free (hwaddr_match);
-	return matched;
+	for (iter = specs; iter; iter = g_slist_next (iter)) {
+		const char *spec_str = iter->data;
+
+		if (   !g_ascii_strncasecmp (spec_str, "mac:", 4)
+		    && nm_utils_hwaddr_matches (spec_str + 4, -1, hwaddr, -1))
+			return TRUE;
+
+		if (nm_utils_hwaddr_matches (spec_str, -1, hwaddr, -1))
+			return TRUE;
+	}
+
+	return FALSE;
 }
 
 gboolean
@@ -179,6 +869,9 @@ nm_match_spec_interface_name (const GSList *specs, const char *interface_name)
 
 	g_return_val_if_fail (interface_name != NULL, FALSE);
 
+	if (nm_match_spec_string (specs, interface_name))
+		return TRUE;
+
 	iface_match = g_strdup_printf ("interface-name:%s", interface_name);
 	matched = nm_match_spec_string (specs, iface_match);
 	g_free (iface_match);
@@ -402,8 +1095,8 @@ value_hash_add_object_property (GHashTable *hash,
 
 static char *
 get_new_connection_name (const GSList *existing,
-                         const char *format,
-                         const char *preferred)
+                         const char *preferred,
+                         const char *fallback_prefix)
 {
 	GSList *names = NULL;
 	const GSList *iter;
@@ -411,6 +1104,8 @@ get_new_connection_name (const GSList *existing,
 	int i = 0;
 	gboolean preferred_found = FALSE;
 
+	g_assert (fallback_prefix);
+
 	for (iter = existing; iter; iter = g_slist_next (iter)) {
 		NMConnection *candidate = NM_CONNECTION (iter->data);
 		const char *id;
@@ -436,7 +1131,12 @@ get_new_connection_name (const GSList *existing,
 		char *temp;
 		gboolean found = FALSE;
 
-		temp = g_strdup_printf (format, i);
+		/* Translators: the first %s is a prefix for the connection id, such
+		 * as "Wired Connection" or "VPN Connection". The %d is a number
+		 * that is combined with the first argument to create a unique
+		 * connection id. */
+		temp = g_strdup_printf (C_("connection id fallback", "%s %d"),
+		                        fallback_prefix, i);
 		for (iter = names; iter; iter = g_slist_next (iter)) {
 			if (!strcmp (iter->data, temp)) {
 				found = TRUE;
@@ -453,68 +1153,38 @@ get_new_connection_name (const GSList *existing,
 	return cname;
 }
 
-void
-nm_utils_normalize_connection (NMConnection *connection,
-                               gboolean default_enable_ipv6)
-{
-	NMSettingConnection *s_con = nm_connection_get_setting_connection (connection);
-	const char *default_ip4_method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
-	const char *default_ip6_method =
-		default_enable_ipv6 ? NM_SETTING_IP6_CONFIG_METHOD_AUTO : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
-	NMSettingIP4Config *s_ip4;
-	NMSettingIP6Config *s_ip6;
-	NMSetting *setting;
-	const char *method;
+static char *
+get_new_connection_ifname (const GSList *existing,
+                           const char *prefix)
+{
+	int i;
+	char *name;
+	const GSList *iter;
+	gboolean found;
 
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
+	for (i = 0; i < 500; i++) {
+		name = g_strdup_printf ("%s%d", prefix, i);
 
-	if (nm_setting_connection_get_master (s_con)) {
-		/* Slave connections don't have IP configuration. */
+		if (nm_platform_link_exists (name))
+			goto next;
 
-		if (s_ip4) {
-			method = nm_setting_ip4_config_get_method (s_ip4);
-			if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) != 0) {
-				nm_log_warn (LOGD_SETTINGS, "ignoring IP4 config on slave '%s'",
-				             nm_connection_get_id (connection));
-			}
-			nm_connection_remove_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
-			s_ip4 = NULL;
-		}
+		for (iter = existing, found = FALSE; iter; iter = g_slist_next (iter)) {
+			NMConnection *candidate = iter->data;
 
-		if (s_ip6) {
-			method = nm_setting_ip6_config_get_method (s_ip6);
-			if (g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) != 0) {
-				nm_log_warn (LOGD_SETTINGS, "ignoring IP6 config on slave '%s'",
-				             nm_connection_get_id (connection));
+			if (g_strcmp0 (nm_connection_get_interface_name (candidate), name) == 0) {
+				found = TRUE;
+				break;
 			}
-			nm_connection_remove_setting (connection, NM_TYPE_SETTING_IP6_CONFIG);
-			s_ip6 = NULL;
 		}
-	} else {
-		/* Ensure all non-slave connections have IP4 and IP6 settings objects. If no
-		 * IP6 setting was specified, then assume that means IP6 config is allowed
-		 * to fail. But if no IP4 setting was specified, assume the caller was just
-		 * being lazy.
-		 */
-		if (!s_ip4) {
-			setting = nm_setting_ip4_config_new ();
-			nm_connection_add_setting (connection, setting);
 
-			g_object_set (setting,
-			              NM_SETTING_IP4_CONFIG_METHOD, default_ip4_method,
-			              NULL);
-		}
-		if (!s_ip6) {
-			setting = nm_setting_ip6_config_new ();
-			nm_connection_add_setting (connection, setting);
-
-			g_object_set (setting,
-			              NM_SETTING_IP6_CONFIG_METHOD, default_ip6_method,
-			              NM_SETTING_IP6_CONFIG_MAY_FAIL, TRUE,
-			              NULL);
-		}
+		if (!found)
+			return name;
+
+	next:
+		g_free (name);
 	}
+
+	return NULL;
 }
 
 const char *
@@ -522,8 +1192,7 @@ nm_utils_get_ip_config_method (NMConnection *connection,
                                GType         ip_setting_type)
 {
 	NMSettingConnection *s_con;
-	NMSettingIP4Config *s_ip4;
-	NMSettingIP6Config *s_ip6;
+	NMSettingIPConfig *s_ip4, *s_ip6;
 	const char *method;
 
 	s_con = nm_connection_get_setting_connection (connection);
@@ -536,7 +1205,7 @@ nm_utils_get_ip_config_method (NMConnection *connection,
 		else {
 			s_ip4 = nm_connection_get_setting_ip4_config (connection);
 			g_return_val_if_fail (s_ip4 != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
-			method = nm_setting_ip4_config_get_method (s_ip4);
+			method = nm_setting_ip_config_get_method (s_ip4);
 			g_return_val_if_fail (method != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
 			return method;
@@ -550,7 +1219,7 @@ nm_utils_get_ip_config_method (NMConnection *connection,
 		else {
 			s_ip6 = nm_connection_get_setting_ip6_config (connection);
 			g_return_val_if_fail (s_ip6 != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
-			method = nm_setting_ip6_config_get_method (s_ip6);
+			method = nm_setting_ip_config_get_method (s_ip6);
 			g_return_val_if_fail (method != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
 
 			return method;
@@ -564,12 +1233,16 @@ void
 nm_utils_complete_generic (NMConnection *connection,
                            const char *ctype,
                            const GSList *existing,
-                           const char *format,
-                           const char *preferred,
+                           const char *preferred_id,
+                           const char *fallback_id_prefix,
+                           const char *ifname_prefix,
                            gboolean default_enable_ipv6)
 {
 	NMSettingConnection *s_con;
-	char *id, *uuid;
+	char *id, *uuid, *ifname;
+	GHashTable *parameters;
+
+	g_assert (fallback_id_prefix);
 
 	s_con = nm_connection_get_setting_connection (connection);
 	if (!s_con) {
@@ -586,13 +1259,24 @@ nm_utils_complete_generic (NMConnection *connection,
 
 	/* Add a connection ID if absent */
 	if (!nm_setting_connection_get_id (s_con)) {
-		id = get_new_connection_name (existing, format, preferred);
+		id = get_new_connection_name (existing, preferred_id, fallback_id_prefix);
 		g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_ID, id, NULL);
 		g_free (id);
 	}
 
+	/* Add an interface name, if requested */
+	if (ifname_prefix && !nm_setting_connection_get_interface_name (s_con)) {
+		ifname = get_new_connection_ifname (existing, ifname_prefix);
+		g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, NULL);
+		g_free (ifname);
+	}
+
 	/* Normalize */
-	nm_utils_normalize_connection (connection, default_enable_ipv6);
+	parameters = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_insert (parameters, NM_CONNECTION_NORMALIZE_PARAM_IP6_CONFIG_METHOD,
+	                     default_enable_ipv6 ? NM_SETTING_IP6_CONFIG_METHOD_AUTO : NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
+	nm_connection_normalize (connection, parameters, NULL, NULL);
+	g_hash_table_destroy (parameters);
 }
 
 char *
@@ -682,12 +1366,12 @@ check_ip6_method (NMConnection *orig,
 {
 	GHashTable *props;
 	const char *orig_ip6_method, *candidate_ip6_method;
-	NMSettingIP6Config *candidate_ip6;
+	NMSettingIPConfig *candidate_ip6;
 	gboolean allow = FALSE;
 
 	props = check_property_in_hash (settings,
 	                                NM_SETTING_IP6_CONFIG_SETTING_NAME,
-	                                NM_SETTING_IP6_CONFIG_METHOD);
+	                                NM_SETTING_IP_CONFIG_METHOD);
 	if (!props)
 		return TRUE;
 
@@ -703,7 +1387,7 @@ check_ip6_method (NMConnection *orig,
 
 	if (   strcmp (orig_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0
 	    && strcmp (candidate_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
-	    && (!candidate_ip6 || nm_setting_ip6_config_get_may_fail (candidate_ip6))) {
+	    && (!candidate_ip6 || nm_setting_ip_config_get_may_fail (candidate_ip6))) {
 		allow = TRUE;
 	}
 
@@ -720,7 +1404,7 @@ check_ip6_method (NMConnection *orig,
 	if (allow) {
 		remove_from_hash (settings, props,
 		                  NM_SETTING_IP6_CONFIG_SETTING_NAME,
-		                  NM_SETTING_IP6_CONFIG_METHOD);
+		                  NM_SETTING_IP_CONFIG_METHOD);
 	}
 	return allow;
 }
@@ -733,11 +1417,11 @@ check_ip4_method (NMConnection *orig,
 {
 	GHashTable *props;
 	const char *orig_ip4_method, *candidate_ip4_method;
-	NMSettingIP4Config *candidate_ip4;
+	NMSettingIPConfig *candidate_ip4;
 
 	props = check_property_in_hash (settings,
 	                                NM_SETTING_IP4_CONFIG_SETTING_NAME,
-	                                NM_SETTING_IP4_CONFIG_METHOD);
+	                                NM_SETTING_IP_CONFIG_METHOD);
 	if (!props)
 		return TRUE;
 
@@ -752,11 +1436,11 @@ check_ip4_method (NMConnection *orig,
 
 	if (   strcmp (orig_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
 	    && strcmp (candidate_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0
-	    && (!candidate_ip4 || nm_setting_ip4_config_get_may_fail (candidate_ip4))
+	    && (!candidate_ip4 || nm_setting_ip_config_get_may_fail (candidate_ip4))
 	    && (device_has_carrier == FALSE)) {
 		remove_from_hash (settings, props,
 		                  NM_SETTING_IP4_CONFIG_SETTING_NAME,
-		                  NM_SETTING_IP4_CONFIG_METHOD);
+		                  NM_SETTING_IP_CONFIG_METHOD);
 		return TRUE;
 	}
 	return FALSE;
@@ -798,7 +1482,7 @@ check_connection_mac_address (NMConnection *orig,
                               GHashTable *settings)
 {
 	GHashTable *props;
-	const GByteArray *orig_mac = NULL, *cand_mac = NULL;
+	const char *orig_mac = NULL, *cand_mac = NULL;
 	NMSettingWired *s_wired_orig, *s_wired_cand;
 
 	props = check_property_in_hash (settings,
@@ -933,6 +1617,31 @@ nm_utils_match_connection (GSList *connections,
 	return best_match;
 }
 
+int
+nm_utils_cmp_connection_by_autoconnect_priority (NMConnection **a, NMConnection **b)
+{
+	NMSettingConnection *a_s_con, *b_s_con;
+	gboolean a_ac, b_ac;
+	gint a_ap, b_ap;
+
+	a_s_con = nm_connection_get_setting_connection (*a);
+	b_s_con = nm_connection_get_setting_connection (*b);
+
+	a_ac = !!nm_setting_connection_get_autoconnect (a_s_con);
+	b_ac = !!nm_setting_connection_get_autoconnect (b_s_con);
+	if (a_ac != b_ac)
+		return ((int) b_ac) - ((int) a_ac);
+	if (!a_ac)
+		return 0;
+
+	a_ap = nm_setting_connection_get_autoconnect_priority (a_s_con);
+	b_ap = nm_setting_connection_get_autoconnect_priority (b_s_con);
+	if (a_ap != b_ap)
+		return (a_ap > b_ap) ? -1 : 1;
+
+	return 0;
+}
+
 /* nm_utils_ascii_str_to_int64:
  *
  * A wrapper for g_ascii_strtoll, that checks whether the whole string
@@ -1008,21 +1717,85 @@ nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 max
 	return v;
 }
 
+/**
+ * nm_utils_uuid_generate_from_strings:
+ * @string1: a variadic list of strings. Must be NULL terminated.
+ *
+ * Returns a variant3 UUID based on the concatenated C strings.
+ * It does not simply concatenate them, but also includes the
+ * terminating '\0' character. For example "a", "b", gives
+ * "a\0b\0\0".
+ *
+ * This has the advantage, that the following invocations
+ * all give different UUIDs: (""), ("",""), ("","a"), ("a",""),
+ * ("aa"), ("aa", ""), ("", "aa"), ...
+ */
+char *
+nm_utils_uuid_generate_from_strings (const char *string1, ...)
+{
+	GString *str;
+	va_list args;
+	const char *s;
+	char *uuid;
+
+	if (!string1)
+		return nm_utils_uuid_generate_from_string (NULL, 0, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS);
+
+	str = g_string_sized_new (120); /* effectively allocates power of 2 (128)*/
+
+	g_string_append_len (str, string1, strlen (string1) + 1);
+
+	va_start (args, string1);
+	s = va_arg (args, const char *);
+	while (s) {
+		g_string_append_len (str, s, strlen (s) + 1);
+		s = va_arg (args, const char *);
+	}
+	va_end (args);
+
+	uuid = nm_utils_uuid_generate_from_string (str->str, str->len, NM_UTILS_UUID_TYPE_VARIANT3, NM_UTILS_UUID_NS);
+
+	g_string_free (str, TRUE);
+	return uuid;
+}
+
+/**************************************************************************/
 
 static gint64 monotonic_timestamp_offset_sec;
 
 static void
 monotonic_timestamp_get (struct timespec *tp)
 {
-	static gboolean initialized = FALSE;
-	int err;
-
-	err = clock_gettime (CLOCK_BOOTTIME, tp);
+	static int clock_mode = 0;
+	gboolean first_time = FALSE;
+	int err = 0;
+
+	switch (clock_mode) {
+	case 0:
+		/* the clock is not yet initialized (first run) */
+		err = clock_gettime (CLOCK_BOOTTIME, tp);
+		if (err == -1 && errno == EINVAL) {
+			clock_mode = 2;
+			err = clock_gettime (CLOCK_MONOTONIC, tp);
+		} else
+			clock_mode = 1;
+		first_time = TRUE;
+		break;
+	case 1:
+		/* default, return CLOCK_BOOTTIME */
+		err = clock_gettime (CLOCK_BOOTTIME, tp);
+		break;
+	case 2:
+		/* fallback, return CLOCK_MONOTONIC. Kernels prior to 2.6.39
+		 * don't support CLOCK_BOOTTIME. */
+		err = clock_gettime (CLOCK_MONOTONIC, tp);
+		break;
+	}
 
 	g_assert (err == 0); (void)err;
 	g_assert (tp->tv_nsec >= 0 && tp->tv_nsec < NM_UTILS_NS_PER_SECOND);
 
-	if (G_LIKELY (initialized))
+	if (G_LIKELY (!first_time))
 		return;
 
 	/* Calculate an offset for the time stamp.
@@ -1039,7 +1812,6 @@ monotonic_timestamp_get (struct timespec *tp)
 	 * wraps (~68 years).
 	 **/
 	monotonic_timestamp_offset_sec = (- ((gint64) tp->tv_sec)) + 1;
-	initialized = TRUE;
 
 	if (nm_logging_enabled (LOGL_DEBUG, LOGD_CORE)) {
 		time_t now = time (NULL);
@@ -1048,8 +1820,9 @@ monotonic_timestamp_get (struct timespec *tp)
 
 		strftime (s, sizeof (s), "%Y-%m-%d %H:%M:%S", localtime_r (&now, &tm));
 		nm_log_dbg (LOGD_CORE, "monotonic timestamp started counting 1.%09ld seconds ago with "
-		                       "an offset of %lld.0 seconds to CLOCK_BOOTTIME (local time is %s)",
-		                       tp->tv_nsec, (long long) -monotonic_timestamp_offset_sec, s);
+		                       "an offset of %lld.0 seconds to %s (local time is %s)",
+		                       tp->tv_nsec, (long long) -monotonic_timestamp_offset_sec,
+		                       clock_mode == 1 ? "CLOCK_BOOTTIME" : "CLOCK_MONOTONIC", s);
 	}
 }
 
@@ -1068,7 +1841,7 @@ monotonic_timestamp_get (struct timespec *tp)
 gint64
 nm_utils_get_monotonic_timestamp_ns (void)
 {
-	struct timespec tp;
+	struct timespec tp = { 0 };
 
 	monotonic_timestamp_get (&tp);
 
@@ -1095,7 +1868,7 @@ nm_utils_get_monotonic_timestamp_ns (void)
 gint64
 nm_utils_get_monotonic_timestamp_us (void)
 {
-	struct timespec tp;
+	struct timespec tp = { 0 };
 
 	monotonic_timestamp_get (&tp);
 
@@ -1122,7 +1895,7 @@ nm_utils_get_monotonic_timestamp_us (void)
 gint64
 nm_utils_get_monotonic_timestamp_ms (void)
 {
-	struct timespec tp;
+	struct timespec tp = { 0 };
 
 	monotonic_timestamp_get (&tp);
 
@@ -1149,12 +1922,286 @@ nm_utils_get_monotonic_timestamp_ms (void)
 gint32
 nm_utils_get_monotonic_timestamp_s (void)
 {
-	struct timespec tp;
+	struct timespec tp = { 0 };
 
 	monotonic_timestamp_get (&tp);
 	return (((gint64) tp.tv_sec) + monotonic_timestamp_offset_sec);
 }
 
+typedef struct
+{
+	const char *name;
+	NMSetting *setting;
+	NMSetting *diff_base_setting;
+	GHashTable *setting_diff;
+} LogConnectionSettingData;
+
+typedef struct
+{
+	const char *item_name;
+	NMSettingDiffResult diff_result;
+} LogConnectionSettingItem;
+
+static gint
+_log_connection_sort_hashes_fcn (gconstpointer a, gconstpointer b)
+{
+	const LogConnectionSettingData *v1 = a;
+	const LogConnectionSettingData *v2 = b;
+	guint32 p1, p2;
+	NMSetting *s1, *s2;
+
+	s1 = v1->setting ? v1->setting : v1->diff_base_setting;
+	s2 = v2->setting ? v2->setting : v2->diff_base_setting;
+
+	g_assert (s1 && s2);
+
+	p1 = _nm_setting_get_setting_priority (s1);
+	p2 = _nm_setting_get_setting_priority (s2);
+
+	if (p1 != p2)
+		return p1 > p2 ? 1 : -1;
+
+	return strcmp (v1->name, v2->name);
+}
+
+static GArray *
+_log_connection_sort_hashes (NMConnection *connection, NMConnection *diff_base, GHashTable *connection_diff)
+{
+	GHashTableIter iter;
+	GArray *sorted_hashes;
+	LogConnectionSettingData setting_data;
+
+	sorted_hashes = g_array_sized_new (TRUE, FALSE, sizeof (LogConnectionSettingData), g_hash_table_size (connection_diff));
+
+	g_hash_table_iter_init (&iter, connection_diff);
+	while (g_hash_table_iter_next (&iter, (gpointer) &setting_data.name, (gpointer) &setting_data.setting_diff)) {
+		setting_data.setting = nm_connection_get_setting_by_name (connection, setting_data.name);
+		setting_data.diff_base_setting = diff_base ? nm_connection_get_setting_by_name (diff_base, setting_data.name) : NULL;
+		g_assert (setting_data.setting || setting_data.diff_base_setting);
+		g_array_append_val (sorted_hashes, setting_data);
+	}
+
+	g_array_sort (sorted_hashes, _log_connection_sort_hashes_fcn);
+	return sorted_hashes;
+}
+
+static gint
+_log_connection_sort_names_fcn (gconstpointer a, gconstpointer b)
+{
+	const LogConnectionSettingItem *v1 = a;
+	const LogConnectionSettingItem *v2 = b;
+
+	/* we want to first show the items, that disappeared, then the one that changed and
+	 * then the ones that were added. */
+
+	if ((v1->diff_result & NM_SETTING_DIFF_RESULT_IN_A) != (v2->diff_result & NM_SETTING_DIFF_RESULT_IN_A))
+		return (v1->diff_result & NM_SETTING_DIFF_RESULT_IN_A) ? -1 : 1;
+	if ((v1->diff_result & NM_SETTING_DIFF_RESULT_IN_B) != (v2->diff_result & NM_SETTING_DIFF_RESULT_IN_B))
+		return (v1->diff_result & NM_SETTING_DIFF_RESULT_IN_B) ? 1 : -1;
+	return strcmp (v1->item_name, v2->item_name);
+}
+
+static char *
+_log_connection_get_property (NMSetting *setting, const char *name)
+{
+	GValue val = G_VALUE_INIT;
+	char *s;
+
+	g_return_val_if_fail (setting, NULL);
+
+	if (   !NM_IS_SETTING_VPN (setting)
+	    && nm_setting_get_secret_flags (setting, name, NULL, NULL))
+		return g_strdup ("****");
+
+	if (!_nm_setting_get_property (setting, name, &val))
+		g_return_val_if_reached (FALSE);
+
+	if (G_VALUE_HOLDS_STRING (&val)) {
+		const char *val_s;
+
+		val_s = g_value_get_string (&val);
+		if (!val_s) {
+			/* for NULL, we want to return the unquoted string "NULL". */
+			s = g_strdup ("NULL");
+		} else {
+			char *escaped = g_strescape (val_s, "'");
+
+			s = g_strdup_printf ("'%s'", escaped);
+			g_free (escaped);
+		}
+	} else {
+		s = g_strdup_value_contents (&val);
+		if (s == NULL)
+			s = g_strdup ("NULL");
+		else {
+			char *escaped = g_strescape (s, "'");
+
+			g_free (s);
+			s = escaped;
+		}
+	}
+	g_value_unset(&val);
+	return s;
+}
+
+static void
+_log_connection_sort_names (LogConnectionSettingData *setting_data, GArray *sorted_names)
+{
+	GHashTableIter iter;
+	LogConnectionSettingItem item;
+	gpointer p;
+
+	g_array_set_size (sorted_names, 0);
+
+	g_hash_table_iter_init (&iter, setting_data->setting_diff);
+	while (g_hash_table_iter_next (&iter, (gpointer) &item.item_name, &p)) {
+		item.diff_result = GPOINTER_TO_UINT (p);
+		g_array_append_val (sorted_names, item);
+	}
+
+	g_array_sort (sorted_names, _log_connection_sort_names_fcn);
+}
+
+void
+nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base, guint32 level, guint64 domain, const char *name, const char *prefix)
+{
+	GHashTable *connection_diff = NULL;
+	GArray *sorted_hashes;
+	GArray *sorted_names = NULL;
+	int i, j;
+	gboolean connection_diff_are_same;
+	gboolean print_header = TRUE;
+	gboolean print_setting_header;
+	GString *str1;
+
+	g_return_if_fail (NM_IS_CONNECTION (connection));
+	g_return_if_fail (!diff_base || (NM_IS_CONNECTION (diff_base) && diff_base != connection));
+
+	/* For VPN setting types, this is broken, because we cannot (generically) print the content of data/secrets. Bummer... */
+
+	if (!nm_logging_enabled (level, domain))
+		return;
+
+	if (!prefix)
+		prefix = "";
+	if (!name)
+		name = "";
+
+	connection_diff_are_same = nm_connection_diff (connection, diff_base, NM_SETTING_COMPARE_FLAG_EXACT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT, &connection_diff);
+	if (connection_diff_are_same) {
+		if (diff_base)
+			nm_log (level, domain, "%sconnection '%s' (%p and %p): no difference", prefix, name, connection, diff_base);
+		else
+			nm_log (level, domain, "%sconnection '%s' (%p): no properties set", prefix, name, connection);
+		g_assert (!connection_diff);
+		return;
+	}
+
+	/* FIXME: it doesn't nicely show the content of NMSettingVpn, becuase nm_connection_diff() does not
+	 * expand the hash values. */
+
+	sorted_hashes = _log_connection_sort_hashes (connection, diff_base, connection_diff);
+	if (sorted_hashes->len <= 0)
+		goto out;
+
+	sorted_names = g_array_new (FALSE, FALSE, sizeof (LogConnectionSettingItem));
+	str1 = g_string_new (NULL);
+
+	for (i = 0; i < sorted_hashes->len; i++) {
+		LogConnectionSettingData *setting_data = &g_array_index (sorted_hashes, LogConnectionSettingData, i);
+
+		_log_connection_sort_names (setting_data, sorted_names);
+		print_setting_header = TRUE;
+		for (j = 0; j < sorted_names->len; j++) {
+			char *str_conn, *str_diff;
+			LogConnectionSettingItem *item = &g_array_index (sorted_names, LogConnectionSettingItem, j);
+
+			str_conn = (item->diff_result & NM_SETTING_DIFF_RESULT_IN_A)
+			           ? _log_connection_get_property (setting_data->setting, item->item_name)
+			           : NULL;
+			str_diff = (item->diff_result & NM_SETTING_DIFF_RESULT_IN_B)
+			           ? _log_connection_get_property (setting_data->diff_base_setting, item->item_name)
+			           : NULL;
+
+			if (print_header) {
+				GError *err_verify = NULL;
+
+				if (diff_base)
+					nm_log (level, domain, "%sconnection '%s' (%p < %p):", prefix, name, connection, diff_base);
+				else
+					nm_log (level, domain, "%sconnection '%s' (%p):", prefix, name, connection);
+				print_header = FALSE;
+
+				if (!nm_connection_verify (connection, &err_verify)) {
+					nm_log (level, domain, "%sconnection %p does not verify: %s", prefix, connection, err_verify->message);
+					g_clear_error (&err_verify);
+				}
+			}
+#define _NM_LOG_ALIGN "-25"
+			if (print_setting_header) {
+				if (diff_base) {
+					if (setting_data->setting && setting_data->diff_base_setting)
+						g_string_printf (str1, "%p < %p", setting_data->setting, setting_data->diff_base_setting);
+					else if (setting_data->diff_base_setting)
+						g_string_printf (str1, "*missing* < %p", setting_data->diff_base_setting);
+					else
+						g_string_printf (str1, "%p < *missing*", setting_data->setting);
+					nm_log (level, domain, "%s%"_NM_LOG_ALIGN"s [ %s ]", prefix, setting_data->name, str1->str);
+				} else
+					nm_log (level, domain, "%s%"_NM_LOG_ALIGN"s [ %p ]", prefix, setting_data->name, setting_data->setting);
+				print_setting_header = FALSE;
+			}
+			g_string_printf (str1, "%s.%s", setting_data->name, item->item_name);
+			switch (item->diff_result & (NM_SETTING_DIFF_RESULT_IN_A | NM_SETTING_DIFF_RESULT_IN_B)) {
+				case NM_SETTING_DIFF_RESULT_IN_B:
+					nm_log (level, domain, "%s%"_NM_LOG_ALIGN"s < %s", prefix, str1->str, str_diff ? str_diff : "NULL");
+					break;
+				case NM_SETTING_DIFF_RESULT_IN_A:
+					nm_log (level, domain, "%s%"_NM_LOG_ALIGN"s = %s", prefix, str1->str, str_conn ? str_conn : "NULL");
+					break;
+				default:
+					nm_log (level, domain, "%s%"_NM_LOG_ALIGN"s = %s < %s", prefix, str1->str, str_conn ? str_conn : "NULL", str_diff ? str_diff : "NULL");
+					break;
+#undef _NM_LOG_ALIGN
+			}
+			g_free (str_conn);
+			g_free (str_diff);
+		}
+	}
+
+	g_array_free (sorted_names, TRUE);
+	g_string_free (str1, TRUE);
+out:
+	g_hash_table_destroy (connection_diff);
+	g_array_free (sorted_hashes, TRUE);
+}
+
+
+#define IPV6_PROPERTY_DIR "/proc/sys/net/ipv6/conf/"
+#define IPV4_PROPERTY_DIR "/proc/sys/net/ipv4/conf/"
+G_STATIC_ASSERT (sizeof (IPV4_PROPERTY_DIR) == sizeof (IPV6_PROPERTY_DIR));
+
+static const char *
+_get_property_path (const char *ifname,
+                    const char *property,
+                    gboolean ipv6)
+{
+	static char path[sizeof (IPV6_PROPERTY_DIR) + IFNAMSIZ + 32];
+	int len;
+
+	ifname = ASSERT_VALID_PATH_COMPONENT (ifname);
+	property = ASSERT_VALID_PATH_COMPONENT (property);
+
+	len = g_snprintf (path,
+	                  sizeof (path),
+	                  "%s%s/%s",
+	                  ipv6 ? IPV6_PROPERTY_DIR : IPV4_PROPERTY_DIR,
+	                  ifname,
+	                  property);
+	g_assert (len < sizeof (path) - 1);
+
+	return path;
+}
 
 /**
  * nm_utils_ip6_property_path:
@@ -1167,18 +2214,21 @@ nm_utils_get_monotonic_timestamp_s (void)
 const char *
 nm_utils_ip6_property_path (const char *ifname, const char *property)
 {
-#define IPV6_PROPERTY_DIR "/proc/sys/net/ipv6/conf/"
-	static char path[sizeof (IPV6_PROPERTY_DIR) + IFNAMSIZ + 32];
-	int len;
-
-	ifname = ASSERT_VALID_PATH_COMPONENT (ifname);
-	property = ASSERT_VALID_PATH_COMPONENT (property);
-
-	len = g_snprintf (path, sizeof (path), IPV6_PROPERTY_DIR "%s/%s",
-	                  ifname, property);
-	g_assert (len < sizeof (path) - 1);
+	return _get_property_path (ifname, property, TRUE);
+}
 
-	return path;
+/**
+ * nm_utils_ip4_property_path:
+ * @ifname: an interface name
+ * @property: a property name
+ *
+ * Returns the path to IPv4 property @property on @ifname. Note that
+ * this uses a static buffer.
+ */
+const char *
+nm_utils_ip4_property_path (const char *ifname, const char *property)
+{
+	return _get_property_path (ifname, property, FALSE);
 }
 
 const char *
@@ -1208,5 +2258,291 @@ fail:
 	else
 		nm_log_err (LOGD_CORE, "Failed asserting path component: \"%s\"", name);
 	g_error ("FATAL: Failed asserting path component: %s", name ? name : "(null)");
+	g_assert_not_reached ();
+}
+
+gboolean
+nm_utils_is_specific_hostname (const char *name)
+{
+	if (!name)
+		return FALSE;
+	if (   strcmp (name, "(none)")
+	    && strcmp (name, "localhost")
+	    && strcmp (name, "localhost6")
+	    && strcmp (name, "localhost.localdomain")
+	    && strcmp (name, "localhost6.localdomain6"))
+		return TRUE;
+	return FALSE;
+}
+
+/******************************************************************/
+
+/* Returns the "u" (universal/local) bit value for a Modified EUI-64 */
+static gboolean
+get_gre_eui64_u_bit (guint32 addr)
+{
+	static const struct {
+		guint32 mask;
+		guint32 result;
+	} items[] = {
+		{ 0xff000000 }, { 0x7f000000 },  /* IPv4 loopback */
+		{ 0xf0000000 }, { 0xe0000000 },  /* IPv4 multicast */
+		{ 0xffffff00 }, { 0xe0000000 },  /* IPv4 local multicast */
+		{ 0xffffffff }, { INADDR_BROADCAST },  /* limited broadcast */
+		{ 0xff000000 }, { 0x00000000 },  /* zero net */
+		{ 0xff000000 }, { 0x0a000000 },  /* private 10 (RFC3330) */
+		{ 0xfff00000 }, { 0xac100000 },  /* private 172 */
+		{ 0xffff0000 }, { 0xc0a80000 },  /* private 192 */
+		{ 0xffff0000 }, { 0xa9fe0000 },  /* IPv4 link-local */
+		{ 0xffffff00 }, { 0xc0586300 },  /* anycast 6-to-4 */
+		{ 0xffffff00 }, { 0xc0000200 },  /* test 192 */
+		{ 0xfffe0000 }, { 0xc6120000 },  /* test 198 */
+	};
+	guint i;
+
+	for (i = 0; i < G_N_ELEMENTS (items); i++) {
+		if ((addr & htonl (items[i].mask)) == htonl (items[i].result))
+			return 0x00; /* "local" scope */
+	}
+	return 0x02; /* "universal" scope */
+}
+
+/**
+ * nm_utils_get_ipv6_interface_identifier:
+ * @link_type: the hardware link type
+ * @hwaddr: the hardware address of the interface
+ * @hwaddr_len: the length (in bytes) of @hwaddr
+ * @out_iid: on success, filled with the interface identifier; on failure
+ * zeroed out
+ *
+ * Constructs an interface identifier in "Modified EUI-64" format which is
+ * suitable for constructing IPv6 addresses.  Note that the identifier is
+ * not obscured in any way (eg, RFC3041).
+ *
+ * Returns: %TRUE if the interface identifier could be constructed, %FALSE if
+ * if could not be constructed.
+ */
+gboolean
+nm_utils_get_ipv6_interface_identifier (NMLinkType link_type,
+                                        const guint8 *hwaddr,
+                                        guint hwaddr_len,
+                                        NMUtilsIPv6IfaceId *out_iid)
+{
+	guint32 addr;
+
+	g_return_val_if_fail (hwaddr != NULL, FALSE);
+	g_return_val_if_fail (hwaddr_len > 0, FALSE);
+	g_return_val_if_fail (out_iid != NULL, FALSE);
+
+	out_iid->id = 0;
+
+	switch (link_type) {
+	case NM_LINK_TYPE_INFINIBAND:
+		/* Use the port GUID per http://tools.ietf.org/html/rfc4391#section-8,
+		 * making sure to set the 'u' bit to 1.  The GUID is the lower 64 bits
+		 * of the IPoIB interface's hardware address.
+		 */
+		g_return_val_if_fail (hwaddr_len == INFINIBAND_ALEN, FALSE);
+		memcpy (out_iid->id_u8, hwaddr + INFINIBAND_ALEN - 8, 8);
+		out_iid->id_u8[0] |= 0x02;
+		return TRUE;
+	case NM_LINK_TYPE_GRE:
+	case NM_LINK_TYPE_GRETAP:
+		/* Hardware address is the network-endian IPv4 address */
+		g_return_val_if_fail (hwaddr_len == 4, FALSE);
+		addr = * (guint32 *) hwaddr;
+		out_iid->id_u8[0] = get_gre_eui64_u_bit (addr);
+		out_iid->id_u8[1] = 0x00;
+		out_iid->id_u8[2] = 0x5E;
+		out_iid->id_u8[3] = 0xFE;
+		memcpy (out_iid->id_u8 + 4, &addr, 4);
+		return TRUE;
+	default:
+		if (hwaddr_len == ETH_ALEN) {
+			/* Translate 48-bit MAC address to a 64-bit Modified EUI-64.  See
+			 * http://tools.ietf.org/html/rfc4291#appendix-A
+			 */
+			out_iid->id_u8[0] = hwaddr[0] ^ 0x02;
+			out_iid->id_u8[1] = hwaddr[1];
+			out_iid->id_u8[2] = hwaddr[2];
+			out_iid->id_u8[3] = 0xff;
+			out_iid->id_u8[4] = 0xfe;
+			out_iid->id_u8[5] = hwaddr[3];
+			out_iid->id_u8[6] = hwaddr[4];
+			out_iid->id_u8[7] = hwaddr[5];
+			return TRUE;
+		}
+		break;
+	}
+	return FALSE;
+}
+
+void
+nm_utils_ipv6_addr_set_interface_identfier (struct in6_addr *addr,
+                                            const NMUtilsIPv6IfaceId iid)
+{
+	memcpy (addr->s6_addr + 8, &iid.id_u8, 8);
+}
+
+void
+nm_utils_ipv6_interface_identfier_get_from_addr (NMUtilsIPv6IfaceId *iid,
+                                                 const struct in6_addr *addr)
+{
+	memcpy (iid, addr->s6_addr + 8, 8);
+}
+
+/**
+ * nm_utils_connection_hash_to_dict:
+ * @hash: a hashed #NMConnection
+ *
+ * Returns: a (floating) #GVariant equivalent to @hash.
+ */
+GVariant *
+nm_utils_connection_hash_to_dict (GHashTable *hash)
+{
+	GValue val = { 0, };
+	GVariant *variant;
+
+	if (!hash)
+		return NULL;
+
+	g_value_init (&val, DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT);
+	g_value_set_boxed (&val, hash);
+	variant = dbus_g_value_build_g_variant (&val);
+	g_value_unset (&val);
+
+	return variant;
+}
+
+/**
+ * nm_utils_connection_dict_to_hash:
+ * @dict: a #GVariant-serialized #NMConnection
+ *
+ * Returns: a #GHashTable equivalent to @dict.
+ */
+GHashTable *
+nm_utils_connection_dict_to_hash (GVariant *dict)
+{
+	GValue val = { 0, };
+
+	if (!dict)
+		return NULL;
+
+	dbus_g_value_parse_g_variant (dict, &val);
+	return g_value_get_boxed (&val);
 }
 
+GSList *
+nm_utils_ip4_routes_from_gvalue (const GValue *value)
+{
+	GPtrArray *routes;
+	int i;
+	GSList *list = NULL;
+
+	routes = (GPtrArray *) g_value_get_boxed (value);
+	for (i = 0; routes && (i < routes->len); i++) {
+		GArray *array = (GArray *) g_ptr_array_index (routes, i);
+		guint32 *array_val = (guint32 *) array->data;
+		NMIPRoute *route;
+		GError *error = NULL;
+
+		if (array->len < 4) {
+			g_warning ("Ignoring invalid IP4 route");
+			continue;
+		}
+
+		route = nm_ip_route_new_binary (AF_INET,
+		                                &array_val[0], array_val[1],
+		                                &array_val[2], array_val[3],
+		                                &error);
+		if (route)
+			list = g_slist_prepend (list, route);
+		else {
+			g_warning ("Ignoring invalid IP4 route: %s", error->message);
+			g_clear_error (&error);
+		}
+	}
+
+	return g_slist_reverse (list);
+}
+
+static gboolean
+_nm_utils_gvalue_array_validate (GValueArray *elements, guint n_expected, ...)
+{
+	va_list args;
+	GValue *tmp;
+	int i;
+	gboolean valid = FALSE;
+
+	if (n_expected != elements->n_values)
+		return FALSE;
+
+	va_start (args, n_expected);
+	for (i = 0; i < n_expected; i++) {
+		tmp = g_value_array_get_nth (elements, i);
+		if (G_VALUE_TYPE (tmp) != va_arg (args, GType))
+			goto done;
+	}
+	valid = TRUE;
+
+done:
+	va_end (args);
+	return valid;
+}
+
+GSList *
+nm_utils_ip6_routes_from_gvalue (const GValue *value)
+{
+	GPtrArray *routes;
+	int i;
+	GSList *list = NULL;
+
+	routes = (GPtrArray *) g_value_get_boxed (value);
+	for (i = 0; routes && (i < routes->len); i++) {
+		GValueArray *route_values = (GValueArray *) g_ptr_array_index (routes, i);
+		GByteArray *dest, *next_hop;
+		guint prefix, metric;
+		NMIPRoute *route;
+		GError *error = NULL;
+
+		if (!_nm_utils_gvalue_array_validate (route_values, 4,
+		                                      DBUS_TYPE_G_UCHAR_ARRAY,
+		                                      G_TYPE_UINT,
+		                                      DBUS_TYPE_G_UCHAR_ARRAY,
+		                                      G_TYPE_UINT)) {
+			g_warning ("Ignoring invalid IP6 route");
+			continue;
+		}
+
+		dest = g_value_get_boxed (g_value_array_get_nth (route_values, 0));
+		if (dest->len != 16) {
+			g_warning ("%s: ignoring invalid IP6 dest address of length %d",
+			           __func__, dest->len);
+			continue;
+		}
+
+		prefix = g_value_get_uint (g_value_array_get_nth (route_values, 1));
+
+		next_hop = g_value_get_boxed (g_value_array_get_nth (route_values, 2));
+		if (next_hop->len != 16) {
+			g_warning ("%s: ignoring invalid IP6 next_hop address of length %d",
+			           __func__, next_hop->len);
+			continue;
+		}
+
+		metric = g_value_get_uint (g_value_array_get_nth (route_values, 3));
+
+		route = nm_ip_route_new_binary (AF_INET6,
+		                                dest->data, prefix,
+		                                next_hop->data, metric,
+		                                &error);
+		if (route)
+			list = g_slist_prepend (list, route);
+		else {
+			g_warning ("Ignoring invalid IP6 route: %s", error->message);
+			g_clear_error (&error);
+		}
+	}
+
+	return g_slist_reverse (list);
+}