summary refs log tree commit diff
path: root/src/dhcp
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2017-01-17 20:25:09 +0100
committerMichael Biebl <biebl@debian.org>2017-01-17 20:25:09 +0100
commit58f8be580039b0575b197b9573a1c92745d96d30 (patch)
tree2c226233f623a0dcb529be0eb8cdf97e4a2ae0c0 /src/dhcp
parent45cb5bb3c0e6edb887cf69b417fcaf7053814a9b (diff)
New upstream version 1.5.90 upstream/1.5.90
Diffstat (limited to 'src/dhcp')
-rw-r--r--src/dhcp/nm-dhcp-client-logging.h77
-rw-r--r--src/dhcp/nm-dhcp-client.c997
-rw-r--r--src/dhcp/nm-dhcp-client.h185
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c775
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h54
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c706
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c265
-rw-r--r--src/dhcp/nm-dhcp-helper-api.h36
-rw-r--r--src/dhcp/nm-dhcp-helper.c227
-rw-r--r--src/dhcp/nm-dhcp-listener.c345
-rw-r--r--src/dhcp/nm-dhcp-listener.h36
-rw-r--r--src/dhcp/nm-dhcp-manager.c442
-rw-r--r--src/dhcp/nm-dhcp-manager.h89
-rw-r--r--src/dhcp/nm-dhcp-systemd.c1070
-rw-r--r--src/dhcp/nm-dhcp-utils.c779
-rw-r--r--src/dhcp/nm-dhcp-utils.h45
-rw-r--r--src/dhcp/tests/leases/basic.leases31
-rw-r--r--src/dhcp/tests/leases/malformed1.leases15
-rw-r--r--src/dhcp/tests/leases/malformed2.leases15
-rw-r--r--src/dhcp/tests/leases/malformed3.leases15
-rw-r--r--src/dhcp/tests/test-dhclient-commented-duid.leases2
-rw-r--r--src/dhcp/tests/test-dhclient-duid.leases2
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c915
-rw-r--r--src/dhcp/tests/test-dhcp-utils.c748
24 files changed, 7871 insertions, 0 deletions
diff --git a/src/dhcp/nm-dhcp-client-logging.h b/src/dhcp/nm-dhcp-client-logging.h
new file mode 100644
index 00000000..8dd18bf2
--- /dev/null
+++ b/src/dhcp/nm-dhcp-client-logging.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2016 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DHCP_CLIENT_LOGGING_H__
+#define __NETWORKMANAGER_DHCP_CLIENT_LOGGING_H__
+
+#include "nm-dhcp-client.h"
+
+#define _NMLOG_PREFIX_NAME    "dhcp"
+#define _NMLOG_DOMAIN         LOGD_DHCP
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        const NMLogLevel _level = (level); \
+        \
+        /* we check first for LOGD_DHCP instead of the correct domain.
+         * In the worst case, we guess wrong and enter the block.
+         *
+         * Same for the _NMLOG_ENABLED() macro. Probably it would be more
+         * expensive to determine the correct value then what we could
+         * safe. */ \
+        if (nm_logging_enabled (_level, _NMLOG_DOMAIN)) { \
+            NMDhcpClient *_self = (NMDhcpClient *) (self); \
+            const char *__ifname = _self ? nm_dhcp_client_get_iface (_self) : NULL; \
+            const NMLogDomain _domain = !_self \
+                                            ? LOGD_DHCP \
+                                            : (nm_dhcp_client_get_ipv6 (_self) ? LOGD_DHCP6 : LOGD_DHCP4); \
+            \
+            nm_log (_level, _domain, \
+                    "%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                    _NMLOG_PREFIX_NAME, \
+                    (_domain == LOGD_DHCP4 ? "4" : (_domain == LOGD_DHCP6 ? "6" : "")), \
+                    NM_PRINT_FMT_QUOTED (__ifname, " (", __ifname, ")", "") \
+                    _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+#define _NMLOG2(level, domain, ifname, ...) \
+    G_STMT_START { \
+        const NMLogLevel _level = (level); \
+        const NMLogDomain _domain = (domain); \
+        \
+        /* we check first for LOGD_DHCP instead of the correct domain.
+         * In the worst case, we guess wrong and enter the block.
+         *
+         * Same for the _NMLOG_ENABLED() macro. Probably it would be more
+         * expensive to determine the correct value then what we could
+         * safe. */ \
+        if (nm_logging_enabled (_level, _domain)) { \
+            const char *__ifname = (ifname); \
+            \
+            nm_log (_level, _domain, \
+                    "%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                    _NMLOG_PREFIX_NAME, \
+                    (_domain == LOGD_DHCP4 ? "4" : (_domain == LOGD_DHCP6 ? "6" : "")), \
+                    NM_PRINT_FMT_QUOTED (__ifname, " (", __ifname, ")", "") \
+                    _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+#endif /* __NETWORKMANAGER_DHCP_CLIENT_LOGGING_H__ */
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
new file mode 100644
index 00000000..ba7c6dbf
--- /dev/null
+++ b/src/dhcp/nm-dhcp-client.c
@@ -0,0 +1,997 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2010 Red Hat, Inc.
+ *
+ */
+
+#include "nm-default.h"
+
+#include "nm-dhcp-client.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <uuid/uuid.h>
+
+#include "NetworkManagerUtils.h"
+#include "nm-utils.h"
+#include "nm-dhcp-utils.h"
+#include "platform/nm-platform.h"
+
+#include "nm-dhcp-client-logging.h"
+
+/*****************************************************************************/
+
+enum {
+	SIGNAL_STATE_CHANGED,
+	SIGNAL_PREFIX_DELEGATED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+	PROP_IFACE,
+	PROP_IFINDEX,
+	PROP_HWADDR,
+	PROP_IPV6,
+	PROP_UUID,
+	PROP_PRIORITY,
+	PROP_TIMEOUT,
+);
+
+typedef struct _NMDhcpClientPrivate {
+	char *       iface;
+	int          ifindex;
+	GByteArray * hwaddr;
+	gboolean     ipv6;
+	char *       uuid;
+	guint32      priority;
+	guint32      timeout;
+	GByteArray * duid;
+	GBytes *     client_id;
+	char *       hostname;
+	char *       fqdn;
+
+	NMDhcpState  state;
+	pid_t        pid;
+	guint        timeout_id;
+	guint        watch_id;
+	gboolean     info_only;
+} NMDhcpClientPrivate;
+
+G_DEFINE_TYPE_EXTENDED (NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT, {})
+
+#define NM_DHCP_CLIENT_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMDhcpClient, NM_IS_DHCP_CLIENT)
+
+/*****************************************************************************/
+
+pid_t
+nm_dhcp_client_get_pid (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), -1);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->pid;
+}
+
+const char *
+nm_dhcp_client_get_iface (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->iface;
+}
+
+int
+nm_dhcp_client_get_ifindex (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), -1);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->ifindex;
+}
+
+gboolean
+nm_dhcp_client_get_ipv6 (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->ipv6;
+}
+
+const char *
+nm_dhcp_client_get_uuid (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->uuid;
+}
+
+const GByteArray *
+nm_dhcp_client_get_duid (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->duid;
+}
+
+const GByteArray *
+nm_dhcp_client_get_hw_addr (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->hwaddr;
+}
+
+guint32
+nm_dhcp_client_get_priority (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), G_MAXUINT32);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->priority;
+}
+
+GBytes *
+nm_dhcp_client_get_client_id (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->client_id;
+}
+
+void
+nm_dhcp_client_set_client_id (NMDhcpClient *self, GBytes *client_id)
+{
+	NMDhcpClientPrivate *priv;
+
+	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
+
+	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	if (priv->client_id && client_id && g_bytes_equal (priv->client_id, client_id))
+		return;
+	g_clear_pointer (&priv->client_id, g_bytes_unref);
+	priv->client_id = client_id ? g_bytes_ref (client_id) : NULL;
+}
+
+const char *
+nm_dhcp_client_get_hostname (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->hostname;
+}
+
+const char *
+nm_dhcp_client_get_fqdn (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->fqdn;
+}
+
+/*****************************************************************************/
+
+static const char *state_table[NM_DHCP_STATE_MAX + 1] = {
+	[NM_DHCP_STATE_UNKNOWN]  = "unknown",
+	[NM_DHCP_STATE_BOUND]    = "bound",
+	[NM_DHCP_STATE_TIMEOUT]  = "timeout",
+	[NM_DHCP_STATE_EXPIRE]   = "expire",
+	[NM_DHCP_STATE_DONE]     = "done",
+	[NM_DHCP_STATE_FAIL]     = "fail",
+};
+
+static const char *
+state_to_string (NMDhcpState state)
+{
+	if ((gsize) state < G_N_ELEMENTS (state_table))
+		return state_table[state];
+	return NULL;
+}
+
+static NMDhcpState
+reason_to_state (NMDhcpClient *self, const char *iface, const char *reason)
+{
+	if (g_ascii_strcasecmp (reason, "bound") == 0 ||
+	    g_ascii_strcasecmp (reason, "bound6") == 0 ||
+	    g_ascii_strcasecmp (reason, "renew") == 0 ||
+	    g_ascii_strcasecmp (reason, "renew6") == 0 ||
+	    g_ascii_strcasecmp (reason, "reboot") == 0 ||
+	    g_ascii_strcasecmp (reason, "rebind") == 0 ||
+	    g_ascii_strcasecmp (reason, "rebind6") == 0)
+		return NM_DHCP_STATE_BOUND;
+	else if (g_ascii_strcasecmp (reason, "timeout") == 0)
+		return NM_DHCP_STATE_TIMEOUT;
+	else if (g_ascii_strcasecmp (reason, "nak") == 0 ||
+	         g_ascii_strcasecmp (reason, "expire") == 0 ||
+	         g_ascii_strcasecmp (reason, "expire6") == 0)
+		return NM_DHCP_STATE_EXPIRE;
+	else if (g_ascii_strcasecmp (reason, "end") == 0)
+		return NM_DHCP_STATE_DONE;
+	else if (g_ascii_strcasecmp (reason, "fail") == 0 ||
+	         g_ascii_strcasecmp (reason, "abend") == 0)
+		return NM_DHCP_STATE_FAIL;
+
+	_LOGD ("unmapped DHCP state '%s'", reason);
+	return NM_DHCP_STATE_UNKNOWN;
+}
+
+/*****************************************************************************/
+
+static void
+timeout_cleanup (NMDhcpClient *self)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->timeout_id);
+}
+
+static void
+watch_cleanup (NMDhcpClient *self)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->watch_id);
+}
+
+void
+nm_dhcp_client_stop_pid (pid_t pid, const char *iface)
+{
+	char *name = iface ? g_strdup_printf ("dhcp-client-%s", iface) : NULL;
+
+	g_return_if_fail (pid > 1);
+
+	nm_utils_kill_child_sync (pid, SIGTERM, LOGD_DHCP, name ? name : "dhcp-client", NULL,
+	                          1000 / 2, 1000 / 20);
+	g_free (name);
+}
+
+static void
+stop (NMDhcpClient *self, gboolean release, const GByteArray *duid)
+{
+	NMDhcpClientPrivate *priv;
+
+	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
+
+	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	if (priv->pid > 0) {
+		/* Clean up the watch handler since we're explicitly killing the daemon */
+		watch_cleanup (self);
+		nm_dhcp_client_stop_pid (priv->pid, priv->iface);
+	}
+	priv->pid = -1;
+	priv->info_only = FALSE;
+}
+
+void
+nm_dhcp_client_set_state (NMDhcpClient *self,
+                          NMDhcpState new_state,
+                          GObject *ip_config,
+                          GHashTable *options)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+	gs_free char *event_id = NULL;
+
+	if (new_state >= NM_DHCP_STATE_BOUND)
+		timeout_cleanup (self);
+	if (new_state >= NM_DHCP_STATE_TIMEOUT)
+		watch_cleanup (self);
+
+	if (new_state == NM_DHCP_STATE_BOUND) {
+		g_assert (   (priv->ipv6 && NM_IS_IP6_CONFIG (ip_config))
+		          || (!priv->ipv6 && NM_IS_IP4_CONFIG (ip_config)));
+		g_assert (options);
+		g_assert_cmpint (g_hash_table_size (options), >, 0);
+	} else {
+		g_assert (ip_config == NULL);
+		g_assert (options == NULL);
+	}
+
+	/* The client may send same-state transitions for RENEW/REBIND events and
+	 * the lease may have changed, so handle same-state transitions for the
+	 * BOUND state.  Ignore same-state transitions for other events since
+	 * the lease won't have changed and the state was already handled.
+	 */
+	if ((priv->state == new_state) && (new_state != NM_DHCP_STATE_BOUND))
+		return;
+
+	if (priv->ipv6 && new_state == NM_DHCP_STATE_BOUND) {
+		char *start, *iaid;
+
+		iaid = g_hash_table_lookup (options, "iaid");
+		start = g_hash_table_lookup (options, "life_starts");
+		if (iaid && start)
+			event_id = g_strdup_printf ("%s|%s", iaid, start);
+	}
+
+	_LOGI ("state changed %s -> %s%s%s%s",
+	       state_to_string (priv->state),
+	       state_to_string (new_state),
+	       NM_PRINT_FMT_QUOTED (event_id, ", event ID=\"", event_id, "\"", ""));
+
+	priv->state = new_state;
+	g_signal_emit (G_OBJECT (self),
+	               signals[SIGNAL_STATE_CHANGED], 0,
+	               new_state,
+	               ip_config,
+	               options,
+	               event_id);
+}
+
+static gboolean
+transaction_timeout (gpointer user_data)
+{
+	NMDhcpClient *self = NM_DHCP_CLIENT (user_data);
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	priv->timeout_id = 0;
+	_LOGW ("request timed out");
+	nm_dhcp_client_set_state (self, NM_DHCP_STATE_TIMEOUT, NULL, NULL);
+	return G_SOURCE_REMOVE;
+}
+
+static void
+daemon_watch_cb (GPid pid, gint status, gpointer user_data)
+{
+	NMDhcpClient *self = NM_DHCP_CLIENT (user_data);
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+	NMDhcpState new_state;
+
+	g_return_if_fail (priv->watch_id);
+	priv->watch_id = 0;
+
+	if (WIFEXITED (status))
+		_LOGI ("client pid %d exited with status %d", pid, WEXITSTATUS (status));
+	else if (WIFSIGNALED (status))
+		_LOGI ("client pid %d killed by signal %d", pid, WTERMSIG (status));
+	else if (WIFSTOPPED(status))
+		_LOGI ("client pid %d stopped by signal %d", pid, WSTOPSIG (status));
+	else if (WIFCONTINUED (status))
+		_LOGI ("client pid %d resumed (by SIGCONT)", pid);
+	else
+		_LOGW ("client died abnormally");
+
+	if (!WIFEXITED (status))
+		new_state = NM_DHCP_STATE_FAIL;
+	else
+		new_state = NM_DHCP_STATE_DONE;
+
+	priv->pid = -1;
+
+	nm_dhcp_client_set_state (self, new_state, NULL, NULL);
+}
+
+void
+nm_dhcp_client_start_timeout (NMDhcpClient *self)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	/* Set up a timeout on the transaction to kill it after the timeout */
+	g_assert (priv->timeout_id == 0);
+	priv->timeout_id = g_timeout_add_seconds (priv->timeout,
+	                                          transaction_timeout,
+	                                          self);
+}
+
+void
+nm_dhcp_client_watch_child (NMDhcpClient *self, pid_t pid)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->pid == -1);
+	priv->pid = pid;
+
+	nm_dhcp_client_start_timeout (self);
+
+	g_return_if_fail (priv->watch_id == 0);
+	priv->watch_id = g_child_watch_add (pid, daemon_watch_cb, self);
+}
+
+gboolean
+nm_dhcp_client_start_ip4 (NMDhcpClient *self,
+                          const char *dhcp_client_id,
+                          const char *dhcp_anycast_addr,
+                          const char *hostname,
+                          const char *fqdn,
+                          const char *last_ip4_address)
+{
+	NMDhcpClientPrivate *priv;
+	gs_unref_bytes GBytes *tmp = NULL;
+
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+
+	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+	g_return_val_if_fail (priv->pid == -1, FALSE);
+	g_return_val_if_fail (priv->ipv6 == FALSE, FALSE);
+	g_return_val_if_fail (priv->uuid != NULL, FALSE);
+
+	_LOGI ("activation: beginning transaction (timeout in %d seconds)", priv->timeout);
+
+	if (dhcp_client_id)
+		tmp = nm_dhcp_utils_client_id_string_to_bytes (dhcp_client_id);
+	nm_dhcp_client_set_client_id (self, tmp);
+
+	g_clear_pointer (&priv->hostname, g_free);
+	priv->hostname = g_strdup (hostname);
+	g_free (priv->fqdn);
+	priv->fqdn = g_strdup (fqdn);
+
+	return NM_DHCP_CLIENT_GET_CLASS (self)->ip4_start (self, dhcp_anycast_addr, last_ip4_address);
+}
+
+static GByteArray *
+generate_duid_from_machine_id (void)
+{
+	GByteArray *duid;
+	GChecksum *sum;
+	guint8 buffer[32]; /* SHA256 digest size */
+	gsize sumlen = sizeof (buffer);
+	const guint16 duid_type = g_htons (4);
+	uuid_t uuid;
+	GRand *generator;
+	guint i;
+	gs_free char *machine_id_s = NULL;
+	gs_free char *str = NULL;
+
+	machine_id_s = nm_utils_machine_id_read ();
+	if (nm_utils_machine_id_parse (machine_id_s, uuid)) {
+		/* Hash the machine ID so it's not leaked to the network */
+		sum = g_checksum_new (G_CHECKSUM_SHA256);
+		g_checksum_update (sum, (const guchar *) &uuid, sizeof (uuid));
+		g_checksum_get_digest (sum, buffer, &sumlen);
+		g_checksum_free (sum);
+	} else {
+		nm_log_warn (LOGD_DHCP, "dhcp: failed to read " SYSCONFDIR "/machine-id "
+		             "or " LOCALSTATEDIR "/lib/dbus/machine-id to generate "
+		             "DHCPv6 DUID; creating non-persistent random DUID.");
+
+		generator = g_rand_new ();
+		for (i = 0; i < sizeof (buffer) / sizeof (guint32); i++)
+			((guint32 *) buffer)[i] = g_rand_int (generator);
+		g_rand_free (generator);
+	}
+
+	/* Generate a DHCP Unique Identifier for DHCPv6 using the
+	 * DUID-UUID method (see RFC 6355 section 4).  Format is:
+	 *
+	 * u16: type (DUID-UUID = 4)
+	 * u8[16]: UUID bytes
+	 */
+	duid = g_byte_array_sized_new (18);
+	g_byte_array_append (duid, (guint8 *) &duid_type, sizeof (duid_type));
+
+	/* Since SHA256 is 256 bits, but UUID is 128 bits, we just take the first
+	 * 128 bits of the SHA256 as the DUID-UUID.
+	 */
+	g_byte_array_append (duid, buffer, 16);
+
+	nm_log_dbg (LOGD_DHCP, "dhcp: generated DUID %s",
+	            (str = nm_dhcp_utils_duid_to_string (duid)));
+	return duid;
+}
+
+static GByteArray *
+get_duid (NMDhcpClient *self)
+{
+	static GByteArray *duid = NULL;
+	GByteArray *copy = NULL;
+
+	if (G_UNLIKELY (duid == NULL)) {
+		duid = generate_duid_from_machine_id ();
+		g_assert (duid);
+	}
+
+	if (G_LIKELY (duid)) {
+		copy = g_byte_array_sized_new (duid->len);
+		g_byte_array_append (copy, duid->data, duid->len);
+	}
+
+	return copy;
+}
+
+gboolean
+nm_dhcp_client_start_ip6 (NMDhcpClient *self,
+                          const char *dhcp_anycast_addr,
+                          const struct in6_addr *ll_addr,
+                          const char *hostname,
+                          gboolean info_only,
+                          NMSettingIP6ConfigPrivacy privacy,
+                          guint needed_prefixes)
+{
+	NMDhcpClientPrivate *priv;
+	gs_free char *str = NULL;
+
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+
+	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+	g_return_val_if_fail (priv->pid == -1, FALSE);
+	g_return_val_if_fail (priv->ipv6 == TRUE, FALSE);
+	g_return_val_if_fail (priv->uuid != NULL, FALSE);
+
+	/* If we don't have one yet, read the default DUID for this DHCPv6 client
+	 * from the client-specific persistent configuration.
+	 */
+	if (!priv->duid)
+		priv->duid = NM_DHCP_CLIENT_GET_CLASS (self)->get_duid (self);
+
+	_LOGD ("DUID is '%s'", (str = nm_dhcp_utils_duid_to_string (priv->duid)));
+
+	g_clear_pointer (&priv->hostname, g_free);
+	priv->hostname = g_strdup (hostname);
+
+	priv->info_only = info_only;
+
+	_LOGI ("activation: beginning transaction (timeout in %d seconds)",
+	       priv->timeout);
+
+	return NM_DHCP_CLIENT_GET_CLASS (self)->ip6_start (self,
+	                                                   dhcp_anycast_addr,
+	                                                   ll_addr,
+	                                                   info_only,
+	                                                   privacy,
+	                                                   priv->duid,
+	                                                   needed_prefixes);
+}
+
+void
+nm_dhcp_client_stop_existing (const char *pid_file, const char *binary_name)
+{
+	guint64 start_time;
+	pid_t pid, ppid;
+	const char *exe;
+	char proc_path[NM_STRLEN ("/proc/%lu/cmdline") + 100];
+	gs_free char *pid_contents = NULL, *proc_contents = NULL;
+
+	/* Check for an existing instance and stop it */
+	if (!g_file_get_contents (pid_file, &pid_contents, NULL, NULL))
+		return;
+
+	pid = _nm_utils_ascii_str_to_int64 (pid_contents, 10, 1, G_MAXINT64, 0);
+	if (pid <= 0)
+		goto out;
+
+	start_time = nm_utils_get_start_time_for_pid (pid, NULL, &ppid);
+	if (start_time == 0)
+		goto out;
+
+	nm_sprintf_buf (proc_path, "/proc/%lu/cmdline", (long unsigned) pid);
+	if (!g_file_get_contents (proc_path, &proc_contents, NULL, NULL))
+		goto out;
+
+	exe = strrchr (proc_contents, '/');
+	if (exe)
+		exe++;
+	else
+		exe = proc_contents;
+	if (!nm_streq0 (exe, binary_name))
+		goto out;
+
+	if (ppid == getpid ()) {
+		/* the process is our own child. */
+		nm_utils_kill_child_sync (pid, SIGTERM, LOGD_DHCP, "dhcp-client", NULL, 1000 / 2, 1000 / 20);
+	} else {
+		nm_utils_kill_process_sync (pid, start_time, SIGTERM, LOGD_DHCP,
+		                            "dhcp-client", 1000 / 2, 1000 / 20, 2000);
+	}
+
+out:
+	if (remove (pid_file) == -1) {
+		nm_log_dbg (LOGD_DHCP, "dhcp: could not remove pid file \"%s\": %d (%s)",
+		            pid_file, errno, g_strerror (errno));
+	}
+}
+
+void
+nm_dhcp_client_stop (NMDhcpClient *self, gboolean release)
+{
+	NMDhcpClientPrivate *priv;
+	pid_t old_pid = 0;
+
+	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
+
+	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	/* Kill the DHCP client */
+	old_pid = priv->pid;
+	NM_DHCP_CLIENT_GET_CLASS (self)->stop (self, release, priv->duid);
+	if (old_pid > 0)
+		_LOGI ("canceled DHCP transaction, DHCP client pid %d", old_pid);
+	else
+		_LOGI ("canceled DHCP transaction");
+	g_assert (priv->pid == -1);
+
+	nm_dhcp_client_set_state (self, NM_DHCP_STATE_DONE, NULL, NULL);
+}
+
+/*****************************************************************************/
+
+static char *
+bytearray_variant_to_string (NMDhcpClient *self, GVariant *value, const char *key)
+{
+	const guint8 *array;
+	gsize length;
+	GString *str;
+	int i;
+	unsigned char c;
+	char *converted = NULL;
+
+	g_return_val_if_fail (value != NULL, NULL);
+
+	array = g_variant_get_fixed_array (value, &length, 1);
+
+	/* Since the DHCP options come through environment variables, they should
+	 * already be UTF-8 safe, but just make sure.
+	 */
+	str = g_string_sized_new (length);
+	for (i = 0; i < length; i++) {
+		c = array[i];
+
+		/* Convert NULLs to spaces and non-ASCII characters to ? */
+		if (c == '\0')
+			c = ' ';
+		else if (c > 127)
+			c = '?';
+		str = g_string_append_c (str, c);
+	}
+	str = g_string_append_c (str, '\0');
+
+	converted = str->str;
+	if (!g_utf8_validate (converted, -1, NULL))
+		_LOGW ("option '%s' couldn't be converted to UTF-8", key);
+	g_string_free (str, FALSE);
+	return converted;
+}
+
+#define OLD_TAG "old_"
+#define NEW_TAG "new_"
+
+static void
+maybe_add_option (NMDhcpClient *self,
+                  GHashTable *hash,
+                  const char *key,
+                  GVariant *value)
+{
+	char *str_value = NULL;
+	const char **p;
+	static const char *ignored_keys[] = {
+		"interface",
+		"pid",
+		"reason",
+		"dhcp_message_type",
+		NULL
+	};
+
+	g_return_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE_BYTESTRING));
+
+	if (g_str_has_prefix (key, OLD_TAG))
+		return;
+
+	/* Filter out stuff that's not actually new DHCP options */
+	for (p = ignored_keys; *p; p++) {
+		if (!strcmp (*p, key))
+			return;
+	}
+
+	if (g_str_has_prefix (key, NEW_TAG))
+		key += NM_STRLEN (NEW_TAG);
+	if (!key[0])
+		return;
+
+	str_value = bytearray_variant_to_string (self, value, key);
+	if (str_value)
+		g_hash_table_insert (hash, g_strdup (key), str_value);
+}
+
+gboolean
+nm_dhcp_client_handle_event (gpointer unused,
+                             const char *iface,
+                             gint pid,
+                             GVariant *options,
+                             const char *reason,
+                             NMDhcpClient *self)
+{
+	NMDhcpClientPrivate *priv;
+	guint32 old_state;
+	guint32 new_state;
+	GHashTable *str_options = NULL;
+	GObject *ip_config = NULL;
+	NMPlatformIP6Address prefix = { 0, };
+
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+	g_return_val_if_fail (iface != NULL, FALSE);
+	g_return_val_if_fail (pid > 0, FALSE);
+	g_return_val_if_fail (g_variant_is_of_type (options, G_VARIANT_TYPE_VARDICT), FALSE);
+	g_return_val_if_fail (reason != NULL, FALSE);
+
+	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	if (g_strcmp0 (priv->iface, iface) != 0)
+		return FALSE;
+	if (priv->pid != pid)
+		return FALSE;
+
+	old_state = priv->state;
+	new_state = reason_to_state (self, priv->iface, reason);
+	_LOGD ("DHCP reason '%s' -> state '%s'",
+	       reason, state_to_string (new_state));
+
+	if (new_state == NM_DHCP_STATE_BOUND) {
+		GVariantIter iter;
+		const char *name;
+		GVariant *value;
+
+		/* Copy options */
+		str_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+		g_variant_iter_init (&iter, options);
+		while (g_variant_iter_next (&iter, "{&sv}", &name, &value)) {
+			maybe_add_option (self, str_options, name, value);
+			g_variant_unref (value);
+		}
+
+		if (nm_logging_enabled (LOGL_DEBUG, LOGD_DHCP6)) {
+			GHashTableIter hash_iter;
+			gpointer key, val;
+
+			g_hash_table_iter_init (&hash_iter, str_options);
+			while (g_hash_table_iter_next (&hash_iter, &key, &val))
+				_LOGD ("option '%s'=>'%s'", (const char *) key, (const char *) val);
+		}
+
+		/* Create the IP config */
+		g_warn_if_fail (g_hash_table_size (str_options));
+		if (g_hash_table_size (str_options)) {
+			if (priv->ipv6) {
+				prefix = nm_dhcp_utils_ip6_prefix_from_options (str_options);
+				ip_config = (GObject *) nm_dhcp_utils_ip6_config_from_options (priv->ifindex,
+				                                                               priv->iface,
+				                                                               str_options,
+				                                                               priv->priority,
+				                                                               priv->info_only);
+			} else {
+				ip_config = (GObject *) nm_dhcp_utils_ip4_config_from_options (priv->ifindex,
+				                                                               priv->iface,
+				                                                               str_options,
+				                                                               priv->priority);
+			}
+		}
+	}
+
+	if (!IN6_IS_ADDR_UNSPECIFIED (&prefix.address)) {
+		/* If we got an IPv6 prefix to delegate, we don't change the state
+		 * of the DHCP client instance. Instead, we just signal the prefix
+		 * to the device. */
+		g_signal_emit (G_OBJECT (self),
+		               signals[SIGNAL_PREFIX_DELEGATED], 0,
+		               &prefix);
+	} else {
+		/* Fail if no valid IP config was received */
+		if (new_state == NM_DHCP_STATE_BOUND && ip_config == NULL) {
+			_LOGW ("client bound but IP config not received");
+			new_state = NM_DHCP_STATE_FAIL;
+			g_clear_pointer (&str_options, g_hash_table_unref);
+		}
+
+		nm_dhcp_client_set_state (self, new_state, ip_config, str_options);
+	}
+
+	if (str_options)
+		g_hash_table_destroy (str_options);
+	g_clear_object (&ip_config);
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE ((NMDhcpClient *) object);
+
+	switch (prop_id) {
+	case PROP_IFACE:
+		g_value_set_string (value, priv->iface);
+		break;
+	case PROP_IFINDEX:
+		g_value_set_int (value, priv->ifindex);
+		break;
+	case PROP_HWADDR:
+		g_value_set_boxed (value, priv->hwaddr);
+		break;
+	case PROP_IPV6:
+		g_value_set_boolean (value, priv->ipv6);
+		break;
+	case PROP_UUID:
+		g_value_set_string (value, priv->uuid);
+		break;
+	case PROP_PRIORITY:
+		g_value_set_uint (value, priv->priority);
+		break;
+	case PROP_TIMEOUT:
+		g_value_set_uint (value, priv->timeout);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+              const GValue *value, GParamSpec *pspec)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE ((NMDhcpClient *) object);
+
+	switch (prop_id) {
+	case PROP_IFACE:
+		/* construct-only */
+		priv->iface = g_value_dup_string (value);
+		break;
+	case PROP_IFINDEX:
+		/* construct-only */
+		priv->ifindex = g_value_get_int (value);
+		g_warn_if_fail (priv->ifindex > 0);
+		break;
+	case PROP_HWADDR:
+		/* construct-only */
+		priv->hwaddr = g_value_dup_boxed (value);
+		break;
+	case PROP_IPV6:
+		/* construct-only */
+		priv->ipv6 = g_value_get_boolean (value);
+		break;
+	case PROP_UUID:
+		/* construct-only */
+		priv->uuid = g_value_dup_string (value);
+		break;
+	case PROP_PRIORITY:
+		/* construct-only */
+		priv->priority = g_value_get_uint (value);
+		break;
+	case PROP_TIMEOUT:
+		priv->timeout = g_value_get_uint (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_dhcp_client_init (NMDhcpClient *self)
+{
+	NMDhcpClientPrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_DHCP_CLIENT, NMDhcpClientPrivate);
+	self->_priv = priv;
+
+	priv->pid = -1;
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDhcpClient *self = NM_DHCP_CLIENT (object);
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	/* Stopping the client is left up to the controlling device
+	 * explicitly since we may want to quit NetworkManager but not terminate
+	 * the DHCP client.
+	 */
+
+	watch_cleanup (self);
+	timeout_cleanup (self);
+
+	g_clear_pointer (&priv->iface, g_free);
+	g_clear_pointer (&priv->hostname, g_free);
+	g_clear_pointer (&priv->fqdn, g_free);
+	g_clear_pointer (&priv->uuid, g_free);
+	g_clear_pointer (&priv->client_id, g_bytes_unref);
+
+	if (priv->hwaddr) {
+		g_byte_array_free (priv->hwaddr, TRUE);
+		priv->hwaddr = NULL;
+	}
+
+	if (priv->duid) {
+		g_byte_array_free (priv->duid, TRUE);
+		priv->duid = NULL;
+	}
+
+	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
+}
+
+static void
+nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (client_class);
+
+	g_type_class_add_private (client_class, sizeof (NMDhcpClientPrivate));
+
+	object_class->dispose = dispose;
+	object_class->get_property = get_property;
+	object_class->set_property = set_property;
+
+	client_class->stop = stop;
+	client_class->get_duid = get_duid;
+
+	obj_properties[PROP_IFACE] =
+	    g_param_spec_string (NM_DHCP_CLIENT_INTERFACE, "", "",
+	                         NULL,
+	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                         G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_IFINDEX] =
+	    g_param_spec_int (NM_DHCP_CLIENT_IFINDEX, "", "",
+	                      -1, G_MAXINT, -1,
+	                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_HWADDR] =
+	    g_param_spec_boxed (NM_DHCP_CLIENT_HWADDR, "", "",
+	                        G_TYPE_BYTE_ARRAY,
+	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                        G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_IPV6] =
+	    g_param_spec_boolean (NM_DHCP_CLIENT_IPV6, "", "",
+	                          FALSE,
+	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_UUID] =
+	    g_param_spec_string (NM_DHCP_CLIENT_UUID, "", "",
+	                         NULL,
+	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                         G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_PRIORITY] =
+	    g_param_spec_uint (NM_DHCP_CLIENT_PRIORITY, "", "",
+	                       0, G_MAXUINT32, 0,
+	                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_TIMEOUT] =
+	    g_param_spec_uint (NM_DHCP_CLIENT_TIMEOUT, "", "",
+	                       0, G_MAXUINT, 45,
+	                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+	signals[SIGNAL_STATE_CHANGED] =
+	    g_signal_new (NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  G_STRUCT_OFFSET (NMDhcpClientClass, state_changed),
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_OBJECT, G_TYPE_HASH_TABLE, G_TYPE_STRING);
+
+	signals[SIGNAL_PREFIX_DELEGATED] =
+	    g_signal_new (NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  G_STRUCT_OFFSET (NMDhcpClientClass, state_changed),
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
new file mode 100644
index 00000000..7a083ae7
--- /dev/null
+++ b/src/dhcp/nm-dhcp-client.h
@@ -0,0 +1,185 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2010 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DHCP_CLIENT_H__
+#define __NETWORKMANAGER_DHCP_CLIENT_H__
+
+#include <nm-setting-ip4-config.h>
+#include <nm-setting-ip6-config.h>
+#include <nm-ip4-config.h>
+#include <nm-ip6-config.h>
+
+#define NM_TYPE_DHCP_CLIENT            (nm_dhcp_client_get_type ())
+#define NM_DHCP_CLIENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClient))
+#define NM_DHCP_CLIENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_CLIENT, NMDhcpClientClass))
+#define NM_IS_DHCP_CLIENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_CLIENT))
+#define NM_IS_DHCP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_CLIENT))
+#define NM_DHCP_CLIENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClientClass))
+
+#define NM_DHCP_CLIENT_INTERFACE "iface"
+#define NM_DHCP_CLIENT_IFINDEX   "ifindex"
+#define NM_DHCP_CLIENT_HWADDR    "hwaddr"
+#define NM_DHCP_CLIENT_IPV6      "ipv6"
+#define NM_DHCP_CLIENT_UUID      "uuid"
+#define NM_DHCP_CLIENT_PRIORITY  "priority"
+#define NM_DHCP_CLIENT_TIMEOUT   "timeout"
+
+#define NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED "state-changed"
+#define NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED "prefix-delegated"
+
+typedef enum {
+	NM_DHCP_STATE_UNKNOWN = 0,
+	NM_DHCP_STATE_BOUND,        /* new lease or lease changed */
+	NM_DHCP_STATE_TIMEOUT,      /* timed out contacting server */
+	NM_DHCP_STATE_DONE,         /* client quit or stopped */
+	NM_DHCP_STATE_EXPIRE,       /* lease expired or NAKed */
+	NM_DHCP_STATE_FAIL,         /* failed for some reason */
+	__NM_DHCP_STATE_MAX,
+	NM_DHCP_STATE_MAX = __NM_DHCP_STATE_MAX - 1,
+} NMDhcpState;
+
+struct _NMDhcpClientPrivate;
+
+typedef struct {
+	GObject parent;
+	struct _NMDhcpClientPrivate *_priv;
+} NMDhcpClient;
+
+typedef struct {
+	GObjectClass parent;
+
+	/* Methods */
+
+	gboolean (*ip4_start)     (NMDhcpClient *self,
+	                           const char *anycast_addr,
+	                           const char *last_ip4_address);
+
+	gboolean (*ip6_start)     (NMDhcpClient *self,
+	                           const char *anycast_addr,
+	                           const struct in6_addr *ll_addr,
+	                           gboolean info_only,
+	                           NMSettingIP6ConfigPrivacy privacy,
+	                           const GByteArray *duid,
+	                           guint needed_prefixes);
+
+	void (*stop)              (NMDhcpClient *self,
+	                           gboolean release,
+	                           const GByteArray *duid);
+
+	/**
+	 * get_duid:
+	 * @self: the #NMDhcpClient
+	 *
+	 * Attempts to find an existing DHCPv6 DUID for this client in the DHCP
+	 * client's persistent configuration.  Returned DUID should be the binary
+	 * representation of the DUID.  If no DUID is found, %NULL should be
+	 * returned.
+	 */
+	GByteArray * (*get_duid) (NMDhcpClient *self);
+
+	/* Signals */
+	void (*state_changed) (NMDhcpClient *self,
+	                       NMDhcpState state,
+	                       GObject *ip_config,
+	                       GHashTable *options);
+} NMDhcpClientClass;
+
+GType nm_dhcp_client_get_type (void);
+
+pid_t nm_dhcp_client_get_pid (NMDhcpClient *self);
+
+const char *nm_dhcp_client_get_iface (NMDhcpClient *self);
+
+int         nm_dhcp_client_get_ifindex (NMDhcpClient *self);
+
+gboolean nm_dhcp_client_get_ipv6 (NMDhcpClient *self);
+
+const char *nm_dhcp_client_get_uuid (NMDhcpClient *self);
+
+const GByteArray *nm_dhcp_client_get_duid (NMDhcpClient *self);
+
+const GByteArray *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
+
+guint32 nm_dhcp_client_get_priority (NMDhcpClient *self);
+
+GBytes *nm_dhcp_client_get_client_id (NMDhcpClient *self);
+
+const char *nm_dhcp_client_get_hostname (NMDhcpClient *self);
+
+const char *nm_dhcp_client_get_fqdn (NMDhcpClient *self);
+
+gboolean nm_dhcp_client_start_ip4 (NMDhcpClient *self,
+                                   const char *dhcp_client_id,
+                                   const char *dhcp_anycast_addr,
+                                   const char *hostname,
+                                   const char *fqdn,
+                                   const char *last_ip4_address);
+
+gboolean nm_dhcp_client_start_ip6 (NMDhcpClient *self,
+                                   const char *dhcp_anycast_addr,
+                                   const struct in6_addr *ll_addr,
+                                   const char *hostname,
+                                   gboolean info_only,
+                                   NMSettingIP6ConfigPrivacy privacy,
+                                   guint needed_prefixes);
+
+void nm_dhcp_client_stop (NMDhcpClient *self, gboolean release);
+
+/* Backend helpers for subclasses */
+void nm_dhcp_client_stop_existing (const char *pid_file, const char *binary_name);
+
+void nm_dhcp_client_stop_pid (pid_t pid, const char *iface);
+
+void nm_dhcp_client_start_timeout (NMDhcpClient *self);
+
+void nm_dhcp_client_watch_child (NMDhcpClient *self, pid_t pid);
+
+void nm_dhcp_client_set_state (NMDhcpClient *self,
+                               NMDhcpState new_state,
+                               GObject *ip_config,   /* NMIP4Config or NMIP6Config */
+                               GHashTable *options); /* str:str hash */
+
+gboolean nm_dhcp_client_handle_event (gpointer unused,
+                                      const char *iface,
+                                      gint pid,
+                                      GVariant *options,
+                                      const char *reason,
+                                      NMDhcpClient *self);
+
+void nm_dhcp_client_set_client_id (NMDhcpClient *self, GBytes *client_id);
+
+/*****************************************************************************
+ * Client data
+ *****************************************************************************/
+
+typedef struct {
+	GType (*get_type)(void);
+	const char *name;
+	const char *(*get_path) (void);
+	GSList *(*get_lease_ip_configs) (const char *iface,
+	                                 int ifindex,
+	                                 const char *uuid,
+	                                 gboolean ipv6,
+	                                 guint32 default_route_metric);
+} NMDhcpClientFactory;
+
+extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient;
+extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcd;
+extern const NMDhcpClientFactory _nm_dhcp_client_factory_internal;
+
+#endif /* __NETWORKMANAGER_DHCP_CLIENT_H__ */
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
new file mode 100644
index 00000000..8cc25ab5
--- /dev/null
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -0,0 +1,775 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2011 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-dhcp-dhclient-utils.h"
+
+#include <string.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+
+#include "nm-dhcp-utils.h"
+#include "nm-ip4-config.h"
+#include "nm-utils.h"
+#include "platform/nm-platform.h"
+#include "NetworkManagerUtils.h"
+
+#define CLIENTID_TAG            "send dhcp-client-identifier"
+
+#define HOSTNAME4_TAG    "send host-name"
+#define HOSTNAME4_FORMAT HOSTNAME4_TAG " \"%s\"; # added by NetworkManager"
+
+#define FQDN_TAG_PREFIX  "send fqdn."
+#define FQDN_TAG         FQDN_TAG_PREFIX "fqdn"
+#define FQDN_FORMAT      FQDN_TAG " \"%s\"; # added by NetworkManager"
+
+#define ALSOREQ_TAG "also request "
+#define REQ_TAG "request "
+
+static void
+add_request (GPtrArray *array, const char *item)
+{
+	int i;
+
+	for (i = 0; i < array->len; i++) {
+		if (!strcmp (g_ptr_array_index (array, i), item))
+			return;
+	}
+	g_ptr_array_add (array, g_strdup (item));
+}
+
+static gboolean
+grab_request_options (GPtrArray *store, const char* line)
+{
+	char **areq, **aiter;
+	gboolean end = FALSE;
+
+	/* Grab each 'request' or 'also request'  option and save for later */
+	areq = g_strsplit_set (line, "\t ,", -1);
+	for (aiter = areq; aiter && *aiter; aiter++) {
+		if (!strlen (g_strstrip (*aiter)))
+			continue;
+
+		if (*aiter[0] == ';') {
+			/* all done */
+			end = TRUE;
+			break;
+		}
+
+		if (!g_ascii_isalnum ((*aiter)[0]))
+			continue;
+
+		if ((*aiter)[strlen (*aiter) - 1] == ';') {
+			/* Remove the EOL marker */
+			(*aiter)[strlen (*aiter) - 1] = '\0';
+			end = TRUE;
+		}
+
+		add_request (store, *aiter);
+	}
+
+	if (areq)
+		g_strfreev (areq);
+
+	return end;
+}
+
+
+static void
+add_hostname4 (GString *str, const char *hostname, const char *fqdn)
+{
+	char *plain_hostname, *dot;
+
+	if (fqdn) {
+		g_string_append_printf (str, FQDN_FORMAT "\n", fqdn);
+		g_string_append (str,
+		                 "send fqdn.encoded on;\n"
+		                 "send fqdn.server-update on;\n");
+	} else if (hostname) {
+		plain_hostname = g_strdup (hostname);
+		dot = strchr (plain_hostname, '.');
+		/* get rid of the domain */
+		if (dot)
+			*dot = '\0';
+
+		g_string_append_printf (str, HOSTNAME4_FORMAT "\n", plain_hostname);
+		g_free (plain_hostname);
+	}
+}
+
+static void
+add_ip4_config (GString *str, GBytes *client_id, const char *hostname, const char *fqdn)
+{
+	if (client_id) {
+		const char *p;
+		gsize l;
+		guint i;
+
+		p = g_bytes_get_data (client_id, &l);
+		g_assert (p);
+
+		/* Allow type 0 (non-hardware address) to be represented as a string
+		 * as long as all the characters are printable.
+		 */
+		for (i = 1; (p[0] == 0) && i < l; i++) {
+			if (!g_ascii_isprint (p[i]))
+				break;
+		}
+
+		g_string_append (str, CLIENTID_TAG " ");
+		if (i < l) {
+			/* Unprintable; convert to a hex string */
+			for (i = 0; i < l; i++) {
+				if (i > 0)
+					g_string_append_c (str, ':');
+				g_string_append_printf (str, "%02x", (guint8) p[i]);
+			}
+		} else {
+			/* Printable; just add to the line minus the 'type' */
+			g_string_append_c (str, '"');
+			g_string_append_len (str, p + 1, l - 1);
+			g_string_append_c (str, '"');
+		}
+		g_string_append (str, "; # added by NetworkManager\n");
+	}
+
+	add_hostname4 (str, hostname, fqdn);
+
+	g_string_append_c (str, '\n');
+
+	/* Define options for classless static routes */
+	g_string_append (str,
+	                 "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n");
+	g_string_append (str,
+	                 "option ms-classless-static-routes code 249 = array of unsigned integer 8;\n");
+	/* Web Proxy Auto-Discovery option (bgo #368423) */
+	g_string_append (str, "option wpad code 252 = string;\n");
+
+	g_string_append_c (str, '\n');
+}
+
+static void
+add_hostname6 (GString *str, const char *hostname)
+{
+	/* dhclient only supports the fqdn.fqdn for DHCPv6 and requires a fully-
+	 * qualified name for this option, so we must require one here too.
+	 */
+	if (hostname && strchr (hostname, '.')) {
+		g_string_append_printf (str, FQDN_FORMAT "\n", hostname);
+		g_string_append (str,
+		                 "send fqdn.encoded on;\n"
+		                 "send fqdn.server-update on;\n");
+		g_string_append_c (str, '\n');
+	}
+}
+
+static GBytes *
+read_client_id (const char *str)
+{
+	gs_free char *s = NULL;
+	char *p;
+
+	g_assert (!strncmp (str, CLIENTID_TAG, NM_STRLEN (CLIENTID_TAG)));
+
+	str += NM_STRLEN (CLIENTID_TAG);
+	while (g_ascii_isspace (*str))
+		str++;
+
+	if (*str == '"') {
+		s = g_strdup (str + 1);
+		p = strrchr (s, '"');
+		if (p)
+			*p = '\0';
+		else
+			return NULL;
+	} else
+		s = g_strdup (str);
+
+	g_strchomp (s);
+	if (s[strlen (s) - 1] == ';')
+		s[strlen (s) - 1] = '\0';
+
+	return nm_dhcp_utils_client_id_string_to_bytes (s);
+}
+
+GBytes *
+nm_dhcp_dhclient_get_client_id_from_config_file (const char *path)
+{
+	gs_free char *contents = NULL;
+	gs_strfreev char **lines = NULL;
+	char **line;
+
+	g_return_val_if_fail (path != NULL, NULL);
+
+	if (!g_file_test (path, G_FILE_TEST_EXISTS))
+		return NULL;
+
+	if (!g_file_get_contents (path, &contents, NULL, NULL))
+		return NULL;
+
+	lines = g_strsplit_set (contents, "\n\r", 0);
+	for (line = lines; lines && *line; line++) {
+		if (!strncmp (*line, CLIENTID_TAG, NM_STRLEN (CLIENTID_TAG)))
+			return read_client_id (*line);
+	}
+	return NULL;
+}
+
+char *
+nm_dhcp_dhclient_create_config (const char *interface,
+                                gboolean is_ip6,
+                                GBytes *client_id,
+                                const char *anycast_addr,
+                                const char *hostname,
+                                const char *fqdn,
+                                const char *orig_path,
+                                const char *orig_contents,
+                                GBytes **out_new_client_id)
+{
+	GString *new_contents;
+	GPtrArray *fqdn_opts, *reqs;
+	int i;
+
+	g_return_val_if_fail (!anycast_addr || nm_utils_hwaddr_valid (anycast_addr, ETH_ALEN), NULL);
+
+	new_contents = g_string_new (_("# Created by NetworkManager\n"));
+	fqdn_opts = g_ptr_array_sized_new (5);
+	reqs = g_ptr_array_new_full (5, g_free);
+
+	if (orig_contents) {
+		char **lines, **line;
+		gboolean in_alsoreq = FALSE;
+		gboolean in_req = FALSE;
+
+		g_string_append_printf (new_contents, _("# Merged from %s\n\n"), orig_path);
+
+		lines = g_strsplit_set (orig_contents, "\n\r", 0);
+		for (line = lines; lines && *line; line++) {
+			char *p = *line;
+
+			if (!strlen (g_strstrip (p)))
+				continue;
+
+			if (!strncmp (p, CLIENTID_TAG, strlen (CLIENTID_TAG))) {
+				/* Override config file "dhcp-client-id" and use one from the connection */
+				if (client_id)
+					continue;
+
+				/* Otherwise capture and return the existing client id */
+				if (out_new_client_id)
+					*out_new_client_id = read_client_id (p);
+			}
+
+			/* Override config file hostname and use one from the connection */
+			if (hostname || fqdn) {
+				if (strncmp (p, HOSTNAME4_TAG, strlen (HOSTNAME4_TAG)) == 0)
+					continue;
+				if (strncmp (p, FQDN_TAG, strlen (FQDN_TAG)) == 0)
+					continue;
+			}
+
+			/* To let user's FQDN options (except "fqdn.fqdn") override the
+			 * default ones set by NM, add them later
+			 */
+			if (!strncmp (p, FQDN_TAG_PREFIX, NM_STRLEN (FQDN_TAG_PREFIX))) {
+				g_ptr_array_add (fqdn_opts, g_strdup (p + NM_STRLEN (FQDN_TAG_PREFIX)));
+				continue;
+			}
+
+			/* Ignore 'script' since we pass our own */
+			if (g_str_has_prefix (p, "script "))
+				continue;
+
+			/* Check for "request" */
+			if (!strncmp (p, REQ_TAG, strlen (REQ_TAG))) {
+				in_req = TRUE;
+				p += strlen (REQ_TAG);
+				g_ptr_array_set_size (reqs, 0);
+			}
+
+			/* Save all request options for later use */
+			if (in_req) {
+				in_req = !grab_request_options (reqs, p);
+				continue;
+			}
+
+			/* Check for "also require" */
+			if (!strncmp (p, ALSOREQ_TAG, strlen (ALSOREQ_TAG))) {
+				in_alsoreq = TRUE;
+				p += strlen (ALSOREQ_TAG);
+			}
+
+			if (in_alsoreq) {
+				in_alsoreq = !grab_request_options (reqs, p);
+				continue;
+			}
+
+			/* Existing configuration line is OK, add it to new configuration */
+			g_string_append (new_contents, *line);
+			g_string_append_c (new_contents, '\n');
+		}
+
+		if (lines)
+			g_strfreev (lines);
+	} else
+		g_string_append_c (new_contents, '\n');
+
+	if (is_ip6) {
+		add_hostname6 (new_contents, hostname);
+		add_request (reqs, "dhcp6.name-servers");
+		add_request (reqs, "dhcp6.domain-search");
+		add_request (reqs, "dhcp6.client-id");
+	} else {
+		add_ip4_config (new_contents, client_id, hostname, fqdn);
+		add_request (reqs, "rfc3442-classless-static-routes");
+		add_request (reqs, "ms-classless-static-routes");
+		add_request (reqs, "static-routes");
+		add_request (reqs, "wpad");
+		add_request (reqs, "ntp-servers");
+	}
+
+	/* And add it to the dhclient configuration */
+	for (i = 0; i < reqs->len; i++)
+		g_string_append_printf (new_contents, "also request %s;\n", (char *) reqs->pdata[i]);
+	g_ptr_array_free (reqs, TRUE);
+
+	for (i = 0; i < fqdn_opts->len; i++) {
+		char *t = g_ptr_array_index (fqdn_opts, i);
+
+		if (i == 0)
+			g_string_append_printf (new_contents, "\n# FQDN options from %s\n", orig_path);
+		g_string_append_printf (new_contents, FQDN_TAG_PREFIX "%s\n", t);
+		g_free (t);
+	}
+	g_ptr_array_free (fqdn_opts, TRUE);
+
+	g_string_append_c (new_contents, '\n');
+
+	if (anycast_addr) {
+		g_string_append_printf (new_contents, "interface \"%s\" {\n"
+		                        " initial-interval 1; \n"
+		                        " anycast-mac ethernet %s;\n"
+		                        "}\n",
+		                        interface, anycast_addr);
+	}
+
+	return g_string_free (new_contents, FALSE);
+}
+
+/* Roughly follow what dhclient's quotify_buf() and pretty_escape() functions do */
+char *
+nm_dhcp_dhclient_escape_duid (const GByteArray *duid)
+{
+	char *escaped;
+	const guint8 *s = duid->data;
+	char *d;
+
+	d = escaped = g_malloc0 ((duid->len * 4) + 1);
+	while (s < (duid->data + duid->len)) {
+		if (!g_ascii_isprint (*s)) {
+			*d++ = '\\';
+			*d++ = '0' + ((*s >> 6) & 0x7);
+			*d++ = '0' + ((*s >> 3) & 0x7);
+			*d++ = '0' + (*s++ & 0x7);
+		} else if (*s == '"' || *s == '\'' || *s == '$' ||
+		           *s == '`' || *s == '\\' || *s == '|' ||
+		           *s == '&') {
+			*d++ = '\\';
+			*d++ = *s++;
+		} else
+			*d++ = *s++;
+	}
+	return escaped;
+}
+
+static inline gboolean
+isoctal (const guint8 *p)
+{
+	return (   p[0] >= '0' && p[0] <= '3'
+	        && p[1] >= '0' && p[1] <= '7'
+	        && p[2] >= '0' && p[2] <= '7');
+}
+
+GByteArray *
+nm_dhcp_dhclient_unescape_duid (const char *duid)
+{
+	GByteArray *unescaped;
+	const guint8 *p = (const guint8 *) duid;
+	guint i, len;
+	guint8 octal;
+
+	len = strlen (duid);
+	unescaped = g_byte_array_sized_new (len);
+	for (i = 0; i < len; i++) {
+		if (p[i] == '\\') {
+			i++;
+			if (isdigit (p[i])) {
+				/* Octal escape sequence */
+				if (i + 2 >= len || !isoctal (p + i))
+					goto error;
+				octal = ((p[i] - '0') << 6) + ((p[i + 1] - '0') << 3) + (p[i + 2] - '0');
+				g_byte_array_append (unescaped, &octal, 1);
+				i += 2;
+			} else {
+				/* One of ", ', $, `, \, |, or & */
+				g_warn_if_fail (p[i] == '"' || p[i] == '\'' || p[i] == '$' ||
+				                p[i] == '`' || p[i] == '\\' || p[i] == '|' ||
+				                p[i] == '&');
+				g_byte_array_append (unescaped, &p[i], 1);
+			}
+		} else
+			g_byte_array_append (unescaped, &p[i], 1);
+	}
+
+	return unescaped;
+
+error:
+	g_byte_array_free (unescaped, TRUE);
+	return NULL;
+}
+
+#define DUID_PREFIX "default-duid \""
+
+GByteArray *
+nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
+{
+	GByteArray *duid = NULL;
+	char *contents;
+	char **line, **split, *p, *e;
+
+	if (!g_file_test (leasefile, G_FILE_TEST_EXISTS))
+		return NULL;
+
+	if (!g_file_get_contents (leasefile, &contents, NULL, error))
+		return NULL;
+
+	split = g_strsplit_set (contents, "\n\r", -1);
+	for (line = split; line && *line && (duid == NULL); line++) {
+		p = g_strstrip (*line);
+		if (g_str_has_prefix (p, DUID_PREFIX)) {
+			p += strlen (DUID_PREFIX);
+
+			/* look for trailing "; */
+			e = p + strlen (p) - 2;
+			if (strcmp (e, "\";") != 0)
+				continue;
+			*e = '\0';
+
+			duid = nm_dhcp_dhclient_unescape_duid (p);
+		}
+	}
+	g_free (contents);
+	g_strfreev (split);
+
+	return duid;
+}
+
+gboolean
+nm_dhcp_dhclient_save_duid (const char *leasefile,
+                            const char *escaped_duid,
+                            GError **error)
+{
+	char **lines = NULL, **iter, *l;
+	GString *s;
+	gboolean success;
+	gsize len = 0;
+
+	g_return_val_if_fail (leasefile != NULL, FALSE);
+	g_return_val_if_fail (escaped_duid != NULL, FALSE);
+
+	if (g_file_test (leasefile, G_FILE_TEST_EXISTS)) {
+		char *contents = NULL;
+
+		if (!g_file_get_contents (leasefile, &contents, &len, error)) {
+			g_prefix_error (error, "failed to read lease file %s: ", leasefile);
+			return FALSE;
+		}
+
+		/* If the file already contains an uncommented DUID, leave it */
+		g_assert (contents);
+		lines = g_strsplit_set (contents, "\n\r", -1);
+		g_free (contents);
+		for (iter = lines; iter && *iter; iter++) {
+			l = *iter;
+			while (g_ascii_isspace (*l))
+				l++;
+			if (g_str_has_prefix (l, DUID_PREFIX)) {
+				g_strfreev (lines);
+				return TRUE;
+			}
+		}
+	}
+
+	s = g_string_sized_new (len + 50);
+	g_string_append_printf (s, DUID_PREFIX "%s\";\n", escaped_duid);
+
+	/* Preserve existing leasefile contents */
+	if (lines) {
+		for (iter = lines; iter && *iter; iter++)
+			g_string_append (s, *iter[0] ? *iter : "\n");
+		g_strfreev (lines);
+	}
+
+	success = g_file_set_contents (leasefile, s->str, -1, error);
+	if (!success)
+		g_prefix_error (error, "failed to set DUID in lease file %s: ", leasefile);
+
+	g_string_free (s, TRUE);
+	return success;
+}
+
+static void
+add_lease_option (GHashTable *hash, char *line)
+{
+	char *spc;
+	size_t len;
+
+	/* Find the space after "option" */
+	spc = strchr (line, ' ');
+	if (!spc)
+		return;
+
+	/* Find the option tag's data, which is after the second space */
+	if (g_str_has_prefix (line, "option ")) {
+		while (g_ascii_isspace (*spc))
+			spc++;
+		spc = strchr (spc + 1, ' ');
+		if (!spc)
+			return;
+	}
+
+	/* Split the line at the space */
+	*spc = '\0';
+	spc++;
+
+	/* Kill the ';' at the end of the line, if any */
+	len = strlen (spc);
+	if (*(spc + len - 1) == ';')
+		*(spc + len - 1) = '\0';
+
+	/* Strip leading quote */
+	while (g_ascii_isspace (*spc))
+		spc++;
+	if (*spc == '"')
+		spc++;
+
+	/* Strip trailing quote */
+	len = strlen (spc);
+	if (len > 0 && spc[len - 1] == '"')
+		spc[len - 1] = '\0';
+
+	if (spc[0])
+		g_hash_table_insert (hash, g_strdup (line), g_strdup (spc));
+}
+
+#define LEASE_INVALID    G_MININT64
+static GTimeSpan
+lease_validity_span (const char *str_expire, GDateTime *now)
+{
+	GDateTime *expire = NULL;
+	struct tm expire_tm;
+	GTimeSpan span;
+
+	g_return_val_if_fail (now != NULL, LEASE_INVALID);
+	g_return_val_if_fail (str_expire != NULL, LEASE_INVALID);
+
+	/* Skip initial number (day of week?) */
+	if (!isdigit (*str_expire++))
+		return LEASE_INVALID;
+	if (!isspace (*str_expire++))
+		return LEASE_INVALID;
+	/* Read lease expiration (in UTC) */
+	if (!strptime (str_expire, "%t%Y/%m/%d %H:%M:%S", &expire_tm))
+		return LEASE_INVALID;
+
+	expire = g_date_time_new_utc (expire_tm.tm_year + 1900,
+	                              expire_tm.tm_mon + 1,
+	                              expire_tm.tm_mday,
+	                              expire_tm.tm_hour,
+	                              expire_tm.tm_min,
+	                              expire_tm.tm_sec);
+	if (!expire)
+		return LEASE_INVALID;
+
+	span = g_date_time_difference (expire, now);
+	g_date_time_unref (expire);
+
+	/* GDateTime only supports a range of less then 10000 years, so span can
+	 * not overflow or be equal to LEASE_INVALID */
+	return span;
+}
+
+/**
+ * nm_dhcp_dhclient_read_lease_ip_configs:
+ * @iface: the interface name to match leases with
+ * @ifindex: interface index of @iface
+ * @contents: the contents of a dhclient leasefile
+ * @ipv6: whether to read IPv4 or IPv6 leases
+ * @now: the current UTC date/time; pass %NULL to automatically use current
+ *  UTC time.  Testcases may need a different value for 'now'
+ *
+ * Reads dhclient leases from @contents and parses them into either
+ * #NMIP4Config or #NMIP6Config objects depending on the value of @ipv6.
+ *
+ * Returns: a #GSList of #NMIP4Config objects (if @ipv6 is %FALSE) or a list of
+ * #NMIP6Config objects (if @ipv6 is %TRUE) containing the lease data.
+ */
+GSList *
+nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
+                                        int ifindex,
+                                        const char *contents,
+                                        gboolean ipv6,
+                                        GDateTime *now)
+{
+	GSList *parsed = NULL, *iter, *leases = NULL;
+	char **line, **split = NULL;
+	GHashTable *hash = NULL;
+	gint32 now_monotonic_ts;
+
+	g_return_val_if_fail (contents != NULL, NULL);
+
+	split = g_strsplit_set (contents, "\n\r", -1);
+	if (!split)
+		return NULL;
+
+	for (line = split; line && *line; line++) {
+		*line = g_strstrip (*line);
+
+		if (*line[0] == '#') {
+			/* Comment */
+		} else if (!strcmp (*line, "}")) {
+			/* Lease ends */
+			parsed = g_slist_append (parsed, hash);
+			hash = NULL;
+		} else if (!strcmp (*line, "lease {")) {
+			/* Beginning of a new lease */
+			if (hash) {
+				/* Ignore malformed lease that doesn't end before new one starts */
+				g_hash_table_destroy (hash);
+			}
+
+			hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+		} else if (hash && strlen (*line))
+			add_lease_option (hash, *line);
+	}
+	g_strfreev (split);
+
+	/* Check if the last lease in the file was properly ended */
+	if (hash) {
+		/* Ignore malformed lease that doesn't end before new one starts */
+		g_hash_table_destroy (hash);
+		hash = NULL;
+	}
+
+	if (now)
+		g_date_time_ref (now);
+	else
+		now = g_date_time_new_now_utc ();
+	now_monotonic_ts = nm_utils_get_monotonic_timestamp_s ();
+
+	for (iter = parsed; iter; iter = g_slist_next (iter)) {
+		NMIP4Config *ip4;
+		NMPlatformIP4Address address;
+		const char *value;
+		GTimeSpan expiry;
+		guint32 tmp, gw = 0;
+
+		hash = iter->data;
+
+		/* Make sure this lease is for the interface we want */
+		value = g_hash_table_lookup (hash, "interface");
+		if (!value || strcmp (value, iface))
+			continue;
+
+		value = g_hash_table_lookup (hash, "expire");
+		if (!value)
+			continue;
+		expiry = lease_validity_span (value, now);
+		if (expiry == LEASE_INVALID)
+			continue;
+
+		/* scale expiry to seconds (and CLAMP into the range of guint32) */
+		expiry = CLAMP (expiry / G_TIME_SPAN_SECOND, 0, NM_PLATFORM_LIFETIME_PERMANENT-1);
+		if (expiry <= 0) {
+			/* the address is already expired. Don't even add it. */
+			continue;
+		}
+
+		memset (&address, 0, sizeof (address));
+
+		/* IP4 address */
+		value = g_hash_table_lookup (hash, "fixed-address");
+		if (!value)
+			continue;
+		if (!inet_pton (AF_INET, value, &address.address))
+			continue;
+		address.peer_address = address.address;
+
+		/* Gateway */
+		value = g_hash_table_lookup (hash, "option routers");
+		if (!value)
+			continue;
+		if (!inet_pton (AF_INET, value, &gw))
+			continue;
+
+		/* Netmask */
+		value = g_hash_table_lookup (hash, "option subnet-mask");
+		if (value && inet_pton (AF_INET, value, &tmp))
+			address.plen = nm_utils_ip4_netmask_to_prefix (tmp);
+
+		/* Get default netmask for the IP according to appropriate class. */
+		if (!address.plen)
+			address.plen = nm_utils_ip4_get_default_prefix (address.address);
+
+		address.timestamp = now_monotonic_ts;
+		address.lifetime = address.preferred = expiry;
+		address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
+
+		ip4 = nm_ip4_config_new (ifindex);
+		nm_ip4_config_add_address (ip4, &address);
+		nm_ip4_config_set_gateway (ip4, gw);
+
+		value = g_hash_table_lookup (hash, "option domain-name-servers");
+		if (value) {
+			char **dns, **dns_iter;
+
+			dns = g_strsplit_set (value, ",", -1);
+			for (dns_iter = dns; dns_iter && *dns_iter; dns_iter++) {
+				if (inet_pton (AF_INET, *dns_iter, &tmp))
+					nm_ip4_config_add_nameserver (ip4, tmp);
+			}
+			if (dns)
+				g_strfreev (dns);
+		}
+
+		value = g_hash_table_lookup (hash, "option domain-name");
+		if (value && value[0])
+			nm_ip4_config_add_domain (ip4, value);
+
+		/* FIXME: static routes */
+
+		leases = g_slist_append (leases, ip4);
+	}
+
+	g_date_time_unref (now);
+	g_slist_free_full (parsed, (GDestroyNotify) g_hash_table_destroy);
+	return leases;
+}
+
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.h b/src/dhcp/nm-dhcp-dhclient-utils.h
new file mode 100644
index 00000000..83d5a23d
--- /dev/null
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__
+#define __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__
+
+#include <nm-setting-ip4-config.h>
+#include <nm-setting-ip6-config.h>
+
+char *nm_dhcp_dhclient_create_config (const char *interface,
+                                      gboolean is_ip6,
+                                      GBytes *client_id,
+                                      const char *anycast_addr,
+                                      const char *hostname,
+                                      const char *fqdn,
+                                      const char *orig_path,
+                                      const char *orig_contents,
+                                      GBytes **out_new_client_id);
+
+char *nm_dhcp_dhclient_escape_duid (const GByteArray *duid);
+
+GByteArray *nm_dhcp_dhclient_unescape_duid (const char *duid);
+
+GByteArray *nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error);
+
+gboolean nm_dhcp_dhclient_save_duid (const char *leasefile,
+                                     const char *escaped_duid,
+                                     GError **error);
+
+GSList *nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
+                                                int ifindex,
+                                                const char *contents,
+                                                gboolean ipv6,
+                                                GDateTime *now);
+
+GBytes *nm_dhcp_dhclient_get_client_id_from_config_file (const char *path);
+
+#endif /* __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__ */
+
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
new file mode 100644
index 00000000..64d93744
--- /dev/null
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -0,0 +1,706 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-dhcp-dhclient.c - dhclient specific hooks for NetworkManager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2012 Red Hat, Inc.
+ */
+
+#include <config.h>
+#define __CONFIG_H__
+
+#define _XOPEN_SOURCE
+#include <time.h>
+#undef _XOPEN_SOURCE
+
+#include "nm-default.h"
+
+#if WITH_DHCLIENT
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+
+#include "nm-utils.h"
+#include "nm-dhcp-dhclient-utils.h"
+#include "nm-dhcp-manager.h"
+#include "NetworkManagerUtils.h"
+#include "nm-dhcp-listener.h"
+#include "nm-dhcp-client-logging.h"
+
+/*****************************************************************************/
+
+#define NM_TYPE_DHCP_DHCLIENT            (nm_dhcp_dhclient_get_type ())
+#define NM_DHCP_DHCLIENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclient))
+#define NM_DHCP_DHCLIENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass))
+#define NM_IS_DHCP_DHCLIENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_DHCLIENT))
+#define NM_IS_DHCP_DHCLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_DHCLIENT))
+#define NM_DHCP_DHCLIENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass))
+
+typedef struct _NMDhcpDhclient NMDhcpDhclient;
+typedef struct _NMDhcpDhclientClass NMDhcpDhclientClass;
+
+static GType nm_dhcp_dhclient_get_type (void);
+
+/*****************************************************************************/
+
+typedef struct {
+	char *conf_file;
+	const char *def_leasefile;
+	char *lease_file;
+	char *pid_file;
+	NMDhcpListener *dhcp_listener;
+} NMDhcpDhclientPrivate;
+
+struct _NMDhcpDhclient {
+	NMDhcpClient parent;
+	NMDhcpDhclientPrivate _priv;
+};
+
+struct _NMDhcpDhclientClass {
+	NMDhcpClientClass parent;
+};
+
+G_DEFINE_TYPE (NMDhcpDhclient, nm_dhcp_dhclient, NM_TYPE_DHCP_CLIENT)
+
+#define NM_DHCP_DHCLIENT_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcpDhclient, NM_IS_DHCP_DHCLIENT)
+
+/*****************************************************************************/
+
+static const char *
+nm_dhcp_dhclient_get_path (void)
+{
+	return nm_utils_find_helper ("dhclient", DHCLIENT_PATH, NULL);
+}
+
+/**
+ * get_dhclient_leasefile():
+ * @iface: the interface name of the device on which DHCP will be done
+ * @uuid: the connection UUID to which the returned lease should belong
+ * @ipv6: %TRUE for IPv6, %FALSE for IPv4
+ * @out_preferred_path: on return, the "most preferred" leasefile path
+ *
+ * Returns the path of an existing leasefile (if any) for this interface and
+ * connection UUID.  Also returns the "most preferred" leasefile path, which
+ * may be different than any found leasefile.
+ *
+ * Returns: an existing leasefile, or %NULL if no matching leasefile could be found
+ */
+static char *
+get_dhclient_leasefile (const char *iface,
+                        const char *uuid,
+                        gboolean ipv6,
+                        char **out_preferred_path)
+{
+	char *path;
+
+	/* /var/lib/NetworkManager is the preferred leasefile path */
+	path = g_strdup_printf (NMSTATEDIR "/dhclient%s-%s-%s.lease",
+	                        ipv6 ? "6" : "",
+	                        uuid,
+	                        iface);
+	if (out_preferred_path)
+		*out_preferred_path = g_strdup (path);
+
+	if (g_file_test (path, G_FILE_TEST_EXISTS))
+		return path;
+
+	/* If the leasefile we're looking for doesn't exist yet in the new location
+	 * (eg, /var/lib/NetworkManager) then look in old locations to maintain
+	 * backwards compatibility with external tools (like dracut) that put
+	 * leasefiles there.
+	 */
+
+	/* Old Debian, SUSE, and Mandriva location */
+	g_free (path);
+	path = g_strdup_printf (LOCALSTATEDIR "/lib/dhcp/dhclient%s-%s-%s.lease",
+	                        ipv6 ? "6" : "", uuid, iface);
+	if (g_file_test (path, G_FILE_TEST_EXISTS))
+		return path;
+
+	/* Old Red Hat and Fedora location */
+	g_free (path);
+	path = g_strdup_printf (LOCALSTATEDIR "/lib/dhclient/dhclient%s-%s-%s.lease",
+	                        ipv6 ? "6" : "", uuid, iface);
+	if (g_file_test (path, G_FILE_TEST_EXISTS))
+		return path;
+
+	/* Fail */
+	g_free (path);
+	return NULL;
+}
+
+static GSList *
+nm_dhcp_dhclient_get_lease_ip_configs (const char *iface,
+                                       int ifindex,
+                                       const char *uuid,
+                                       gboolean ipv6,
+                                       guint32 default_route_metric)
+{
+	char *contents = NULL;
+	char *leasefile;
+	GSList *leases = NULL;
+
+	leasefile = get_dhclient_leasefile (iface, uuid, FALSE, NULL);
+	if (!leasefile)
+		return NULL;
+
+	if (   g_file_test (leasefile, G_FILE_TEST_EXISTS)
+	    && g_file_get_contents (leasefile, &contents, NULL, NULL)
+	    && contents
+	    && contents[0])
+		leases = nm_dhcp_dhclient_read_lease_ip_configs (iface, ifindex, contents, ipv6, NULL);
+
+	g_free (leasefile);
+	g_free (contents);
+
+	return leases;
+}
+
+static gboolean
+merge_dhclient_config (NMDhcpDhclient *self,
+                       const char *iface,
+                       const char *conf_file,
+                       gboolean is_ip6,
+                       GBytes *client_id,
+                       const char *anycast_addr,
+                       const char *hostname,
+                       const char *fqdn,
+                       const char *orig_path,
+                       GBytes **out_new_client_id,
+                       GError **error)
+{
+	char *orig = NULL, *new;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail (iface != NULL, FALSE);
+	g_return_val_if_fail (conf_file != NULL, FALSE);
+
+	if (orig_path && g_file_test (orig_path, G_FILE_TEST_EXISTS)) {
+		GError *read_error = NULL;
+
+		if (!g_file_get_contents (orig_path, &orig, NULL, &read_error)) {
+			_LOGW ("error reading dhclient configuration %s: %s",
+			       orig_path, read_error->message);
+			g_error_free (read_error);
+		}
+	}
+
+	if (is_ip6 && hostname && !strchr (hostname, '.'))
+		_LOGW ("hostname is not a FQDN, it will be ignored");
+
+	new = nm_dhcp_dhclient_create_config (iface, is_ip6, client_id, anycast_addr, hostname, fqdn, orig_path, orig, out_new_client_id);
+	g_assert (new);
+	success = g_file_set_contents (conf_file, new, -1, error);
+	g_free (new);
+	g_free (orig);
+
+	return success;
+}
+
+static char *
+find_existing_config (NMDhcpDhclient *self, const char *iface, const char *uuid, gboolean ipv6)
+{
+	char *path;
+
+	/* NetworkManager-overridden configuration can be used to ship DHCP config
+	 * with NetworkManager itself. It can be uuid-specific, device-specific
+	 * or generic.
+	 */
+	if (uuid) {
+		path = g_strdup_printf (NMCONFDIR "/dhclient%s-%s.conf", ipv6 ? "6" : "", uuid);
+		_LOGD ("looking for existing config %s", path);
+		if (g_file_test (path, G_FILE_TEST_EXISTS))
+			return path;
+		g_free (path);
+	}
+
+	path = g_strdup_printf (NMCONFDIR "/dhclient%s-%s.conf", ipv6 ? "6" : "", iface);
+	_LOGD ("looking for existing config %s", path);
+	if (g_file_test (path, G_FILE_TEST_EXISTS))
+		return path;
+	g_free (path);
+
+	path = g_strdup_printf (NMCONFDIR "/dhclient%s.conf", ipv6 ? "6" : "");
+	_LOGD ("looking for existing config %s", path);
+	if (g_file_test (path, G_FILE_TEST_EXISTS))
+		return path;
+	g_free (path);
+
+	/* Distribution's dhclient configuration is used so that we can use
+	 * configuration shipped with dhclient (if any).
+	 *
+	 * This replaces conditional compilation based on distribution name. Fedora
+	 * and Debian store the configs in /etc/dhcp while upstream defaults to /etc
+	 * which is then used by many other distributions. Some distributions
+	 * (including Fedora) don't even provide a default configuration file.
+	 */
+	path = g_strdup_printf (SYSCONFDIR "/dhcp/dhclient%s-%s.conf", ipv6 ? "6" : "", iface);
+	_LOGD ("looking for existing config %s", path);
+	if (g_file_test (path, G_FILE_TEST_EXISTS))
+		return path;
+	g_free (path);
+
+	path = g_strdup_printf (SYSCONFDIR "/dhclient%s-%s.conf", ipv6 ? "6" : "", iface);
+	_LOGD ("looking for existing config %s", path);
+	if (g_file_test (path, G_FILE_TEST_EXISTS))
+		return path;
+	g_free (path);
+
+	path = g_strdup_printf (SYSCONFDIR "/dhcp/dhclient%s.conf", ipv6 ? "6" : "");
+	_LOGD ("looking for existing config %s", path);
+	if (g_file_test (path, G_FILE_TEST_EXISTS))
+		return path;
+	g_free (path);
+
+	path = g_strdup_printf (SYSCONFDIR "/dhclient%s.conf", ipv6 ? "6" : "");
+	_LOGD ("looking for existing config %s", path);
+	if (g_file_test (path, G_FILE_TEST_EXISTS))
+		return path;
+	g_free (path);
+
+	return NULL;
+}
+
+
+/* NM provides interface-specific options; thus the same dhclient config
+ * file cannot be used since DHCP transactions can happen in parallel.
+ * Since some distros don't have default per-interface dhclient config files,
+ * read their single config file and merge that into a custom per-interface
+ * config file along with the NM options.
+ */
+static char *
+create_dhclient_config (NMDhcpDhclient *self,
+                        const char *iface,
+                        gboolean is_ip6,
+                        const char *uuid,
+                        GBytes *client_id,
+                        const char *dhcp_anycast_addr,
+                        const char *hostname,
+                        const char *fqdn,
+                        GBytes **out_new_client_id)
+{
+	char *orig = NULL, *new = NULL;
+	GError *error = NULL;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail (iface != NULL, NULL);
+
+	new = g_strdup_printf (NMSTATEDIR "/dhclient%s-%s.conf", is_ip6 ? "6" : "", iface);
+	_LOGD ("creating composite dhclient config %s", new);
+
+	orig = find_existing_config (self, iface, uuid, is_ip6);
+	if (orig)
+		_LOGD ("merging existing dhclient config %s", orig);
+	else
+		_LOGD ("no existing dhclient configuration to merge");
+
+	error = NULL;
+	success = merge_dhclient_config (self, iface, new, is_ip6, client_id, dhcp_anycast_addr,
+			                         hostname, fqdn, orig, out_new_client_id, &error);
+	if (!success) {
+		_LOGW ("error creating dhclient configuration: %s", error->message);
+		g_error_free (error);
+	}
+
+	g_free (orig);
+	return new;
+}
+
+
+static gboolean
+dhclient_start (NMDhcpClient *client,
+                const char *mode_opt,
+                const GByteArray *duid,
+                gboolean release,
+                pid_t *out_pid,
+                int prefixes)
+{
+	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
+	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
+	GPtrArray *argv = NULL;
+	pid_t pid;
+	GError *error = NULL;
+	const char *iface, *uuid, *system_bus_address, *dhclient_path = NULL;
+	char *binary_name, *cmd_str, *pid_file = NULL, *system_bus_address_env = NULL;
+	gboolean ipv6, success;
+	char *escaped, *preferred_leasefile_path = NULL;
+
+	g_return_val_if_fail (priv->pid_file == NULL, FALSE);
+
+	iface = nm_dhcp_client_get_iface (client);
+	uuid = nm_dhcp_client_get_uuid (client);
+	ipv6 = nm_dhcp_client_get_ipv6 (client);
+
+	dhclient_path = nm_dhcp_dhclient_get_path ();
+	if (!dhclient_path) {
+		_LOGW ("dhclient could not be found");
+		return FALSE;
+	}
+
+	pid_file = g_strdup_printf (RUNSTATEDIR "/dhclient%s-%s.pid",
+		                        ipv6 ? "6" : "",
+		                        iface);
+
+	/* Kill any existing dhclient from the pidfile */
+	binary_name = g_path_get_basename (dhclient_path);
+	nm_dhcp_client_stop_existing (pid_file, binary_name);
+	g_free (binary_name);
+
+	if (release) {
+		/* release doesn't use the pidfile after killing an old client */
+		g_free (pid_file);
+		pid_file = NULL;
+	}
+
+	g_free (priv->lease_file);
+	priv->lease_file = get_dhclient_leasefile (iface, uuid, ipv6, &preferred_leasefile_path);
+	if (!priv->lease_file) {
+		/* No existing leasefile, dhclient will create one at the preferred path */
+		priv->lease_file = g_strdup (preferred_leasefile_path);
+	} else if (g_strcmp0 (priv->lease_file, preferred_leasefile_path) != 0) {
+		GFile *src = g_file_new_for_path (priv->lease_file);
+		GFile *dst = g_file_new_for_path (preferred_leasefile_path);
+
+		/* Try to copy the existing leasefile to the preferred location */
+		if (g_file_copy (src, dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error)) {
+			/* Success; use the preferred leasefile path */
+			g_free (priv->lease_file);
+			priv->lease_file = g_strdup (g_file_get_path (dst));
+		} else {
+			/* Failure; just use the existing leasefile */
+			_LOGW ("failed to copy leasefile %s to %s: %s",
+			       g_file_get_path (src), g_file_get_path (dst),
+			       error->message);
+			g_clear_error (&error);
+		}
+		g_object_unref (src);
+		g_object_unref (dst);
+	}
+	g_free (preferred_leasefile_path);
+
+	/* Save the DUID to the leasefile dhclient will actually use */
+	if (ipv6) {
+		escaped = nm_dhcp_dhclient_escape_duid (duid);
+		success = nm_dhcp_dhclient_save_duid (priv->lease_file, escaped, &error);
+		g_free (escaped);
+		if (!success) {
+			_LOGW ("failed to save DUID to %s: %s", priv->lease_file, error->message);
+			g_free (pid_file);
+			return FALSE;
+		}
+	}
+
+	argv = g_ptr_array_new ();
+	g_ptr_array_add (argv, (gpointer) dhclient_path);
+
+	g_ptr_array_add (argv, (gpointer) "-d");
+
+	/* Be quiet. dhclient logs to syslog anyway. And we duplicate the syslog
+	 * to stderr in case of NM running with --debug.
+	 */
+	g_ptr_array_add (argv, (gpointer) "-q");
+
+	if (release)
+		g_ptr_array_add (argv, (gpointer) "-r");
+
+	if (ipv6) {
+		g_ptr_array_add (argv, (gpointer) "-6");
+		if (mode_opt)
+			g_ptr_array_add (argv, (gpointer) mode_opt);
+		while (prefixes--)
+			g_ptr_array_add (argv, (gpointer) "-P");
+	}
+	g_ptr_array_add (argv, (gpointer) "-sf");	/* Set script file */
+	g_ptr_array_add (argv, (gpointer) nm_dhcp_helper_path);
+
+	if (pid_file) {
+		g_ptr_array_add (argv, (gpointer) "-pf");	/* Set pid file */
+		g_ptr_array_add (argv, (gpointer) pid_file);
+	}
+
+	g_ptr_array_add (argv, (gpointer) "-lf");	/* Set lease file */
+	g_ptr_array_add (argv, (gpointer) priv->lease_file);
+
+	if (priv->conf_file) {
+		g_ptr_array_add (argv, (gpointer) "-cf");	/* Set interface config file */
+		g_ptr_array_add (argv, (gpointer) priv->conf_file);
+	}
+
+	/* Usually the system bus address is well-known; but if it's supposed
+	 * to be something else, we need to push it to dhclient, since dhclient
+	 * sanitizes the environment it gives the action scripts.
+	 */
+	system_bus_address = getenv ("DBUS_SYSTEM_BUS_ADDRESS");
+	if (system_bus_address) {
+		system_bus_address_env = g_strdup_printf ("DBUS_SYSTEM_BUS_ADDRESS=%s", system_bus_address);
+		g_ptr_array_add (argv, (gpointer) "-e");
+		g_ptr_array_add (argv, (gpointer) system_bus_address_env);
+	}
+
+
+	g_ptr_array_add (argv, (gpointer) iface);
+	g_ptr_array_add (argv, NULL);
+
+	cmd_str = g_strjoinv (" ", (gchar **) argv->pdata);
+	_LOGD ("running: %s", cmd_str);
+	g_free (cmd_str);
+
+	if (g_spawn_async (NULL, (char **) argv->pdata, NULL,
+	                   G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
+	                   nm_utils_setpgid, NULL, &pid, &error)) {
+		g_assert (pid > 0);
+		_LOGI ("dhclient started with pid %d", pid);
+		if (release == FALSE)
+			nm_dhcp_client_watch_child (client, pid);
+		priv->pid_file = pid_file;
+	} else {
+		_LOGW ("dhclient failed to start: '%s'", error->message);
+		g_error_free (error);
+		g_free (pid_file);
+	}
+
+	if (out_pid)
+		*out_pid = pid;
+
+	g_ptr_array_free (argv, TRUE);
+	g_free (system_bus_address_env);
+	return pid > 0 ? TRUE : FALSE;
+}
+
+static gboolean
+ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last_ip4_address)
+{
+	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
+	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
+	GBytes *client_id;
+	gs_unref_bytes GBytes *new_client_id = NULL;
+	const char *iface, *uuid, *hostname, *fqdn;
+	gboolean success = FALSE;
+
+	iface = nm_dhcp_client_get_iface (client);
+	uuid = nm_dhcp_client_get_uuid (client);
+	client_id = nm_dhcp_client_get_client_id (client);
+	hostname = nm_dhcp_client_get_hostname (client);
+	fqdn = nm_dhcp_client_get_fqdn (client);
+
+	priv->conf_file = create_dhclient_config (self, iface, FALSE, uuid, client_id, dhcp_anycast_addr,
+	                                          hostname, fqdn, &new_client_id);
+	if (priv->conf_file) {
+		if (new_client_id)
+			nm_dhcp_client_set_client_id (client, new_client_id);
+		success = dhclient_start (client, NULL, NULL, FALSE, NULL, 0);
+	} else
+		_LOGW ("error creating dhclient configuration file");
+
+	return success;
+}
+
+static gboolean
+ip6_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const struct in6_addr *ll_addr,
+           gboolean info_only,
+           NMSettingIP6ConfigPrivacy privacy,
+           const GByteArray *duid,
+           guint needed_prefixes)
+{
+	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
+	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
+	const char *iface, *uuid, *hostname;
+
+	iface = nm_dhcp_client_get_iface (client);
+	uuid = nm_dhcp_client_get_uuid (client);
+	hostname = nm_dhcp_client_get_hostname (client);
+
+	priv->conf_file = create_dhclient_config (self, iface, TRUE, uuid, NULL, dhcp_anycast_addr, hostname, NULL, NULL);
+	if (!priv->conf_file) {
+		_LOGW ("error creating dhclient configuration file");
+		return FALSE;
+	}
+
+	return dhclient_start (client, info_only ? "-S" : "-N", duid, FALSE, NULL, needed_prefixes);
+}
+
+static void
+stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+{
+	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
+	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
+
+	/* Chain up to parent */
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->stop (client, release, duid);
+
+	if (priv->conf_file)
+		if (remove (priv->conf_file) == -1)
+			_LOGD ("could not remove dhcp config file \"%s\": %d (%s)", priv->conf_file, errno, g_strerror (errno));
+	if (priv->pid_file) {
+		if (remove (priv->pid_file) == -1)
+			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errno, g_strerror (errno));
+		g_free (priv->pid_file);
+		priv->pid_file = NULL;
+	}
+
+	if (release) {
+		pid_t rpid = -1;
+
+		if (dhclient_start (client, NULL, duid, TRUE, &rpid, 0)) {
+			/* Wait a few seconds for the release to happen */
+			nm_dhcp_client_stop_pid (rpid, nm_dhcp_client_get_iface (client));
+		}
+	}
+}
+
+static void
+state_changed (NMDhcpClient *client,
+               NMDhcpState state,
+               GObject *ip_config,
+               GHashTable *options)
+{
+	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE ((NMDhcpDhclient *) client);
+	gs_unref_bytes GBytes *client_id = NULL;
+
+	if (nm_dhcp_client_get_client_id (client))
+		return;
+	if (state != NM_DHCP_STATE_BOUND)
+		return;
+
+	client_id = nm_dhcp_dhclient_get_client_id_from_config_file (priv->conf_file);
+	nm_dhcp_client_set_client_id (client, client_id);
+}
+
+static GByteArray *
+get_duid (NMDhcpClient *client)
+{
+	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
+	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
+	GByteArray *duid = NULL;
+	char *leasefile;
+	GError *error = NULL;
+
+	/* Look in interface-specific leasefile first for backwards compat */
+	leasefile = get_dhclient_leasefile (nm_dhcp_client_get_iface (client),
+	                                    nm_dhcp_client_get_uuid (client),
+	                                    TRUE,
+	                                    NULL);
+	if (leasefile) {
+		_LOGD ("looking for DUID in '%s'", leasefile);
+		duid = nm_dhcp_dhclient_read_duid (leasefile, &error);
+
+		if (error) {
+			_LOGW ("failed to read leasefile '%s': %s",
+			       leasefile, error->message);
+			g_clear_error (&error);
+		}
+		g_free (leasefile);
+	}
+
+	if (!duid && priv->def_leasefile) {
+		/* Otherwise read the default machine-wide DUID */
+		_LOGD ("looking for default DUID in '%s'", priv->def_leasefile);
+		duid = nm_dhcp_dhclient_read_duid (priv->def_leasefile, &error);
+		if (error) {
+			_LOGW ("failed to read leasefile '%s': %s",
+			        priv->def_leasefile,
+			        error->message);
+			g_clear_error (&error);
+		}
+	}
+
+	/* return our DUID, otherwise let the parent class make a default DUID */
+	return duid ? duid : NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->get_duid (client);
+}
+
+/*****************************************************************************/
+
+static const char *def_leasefiles[] = {
+	SYSCONFDIR "/dhclient6.leases",
+	LOCALSTATEDIR "/lib/dhcp/dhclient6.leases",
+	LOCALSTATEDIR "/lib/dhclient/dhclient6.leases",
+	NULL
+};
+
+static void
+nm_dhcp_dhclient_init (NMDhcpDhclient *self)
+{
+	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
+	const char **iter = &def_leasefiles[0];
+
+	while (iter && *iter) {
+		if (g_file_test (*iter, G_FILE_TEST_EXISTS)) {
+			priv->def_leasefile = *iter;
+			break;
+		}
+		iter++;
+	}
+
+	/* Fallback option */
+	if (!priv->def_leasefile)
+		priv->def_leasefile = SYSCONFDIR "/dhclient6.leases";
+
+	priv->dhcp_listener = g_object_ref (nm_dhcp_listener_get ());
+	g_signal_connect (priv->dhcp_listener,
+	                  NM_DHCP_LISTENER_EVENT,
+	                  G_CALLBACK (nm_dhcp_client_handle_event),
+	                  self);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE ((NMDhcpDhclient *) object);
+
+	if (priv->dhcp_listener) {
+		g_signal_handlers_disconnect_by_func (priv->dhcp_listener,
+		                                      G_CALLBACK (nm_dhcp_client_handle_event),
+		                                      NM_DHCP_DHCLIENT (object));
+		g_clear_object (&priv->dhcp_listener);
+	}
+
+	nm_clear_g_free (&priv->pid_file);
+	nm_clear_g_free (&priv->conf_file);
+	nm_clear_g_free (&priv->lease_file);
+
+	G_OBJECT_CLASS (nm_dhcp_dhclient_parent_class)->dispose (object);
+}
+
+static void
+nm_dhcp_dhclient_class_init (NMDhcpDhclientClass *dhclient_class)
+{
+	NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS (dhclient_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (dhclient_class);
+
+	object_class->dispose = dispose;
+
+	client_class->ip4_start = ip4_start;
+	client_class->ip6_start = ip6_start;
+	client_class->stop = stop;
+	client_class->get_duid = get_duid;
+	client_class->state_changed = state_changed;
+}
+
+const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient = {
+	.name = "dhclient",
+	.get_type = nm_dhcp_dhclient_get_type,
+	.get_path = nm_dhcp_dhclient_get_path,
+	.get_lease_ip_configs = nm_dhcp_dhclient_get_lease_ip_configs,
+};
+
+#endif /* WITH_DHCLIENT */
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
new file mode 100644
index 00000000..c8643881
--- /dev/null
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -0,0 +1,265 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-dhcp-dhcpcd.c - dhcpcd specific hooks for NetworkManager
+ *
+ * Copyright (C) 2008 Roy Marples
+ * Copyright (C) 2010 Dan Williams <dcbw@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "nm-default.h"
+
+#if WITH_DHCPCD
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "nm-dhcp-manager.h"
+#include "nm-utils.h"
+#include "NetworkManagerUtils.h"
+#include "nm-dhcp-listener.h"
+#include "nm-dhcp-client-logging.h"
+
+/*****************************************************************************/
+
+#define NM_TYPE_DHCP_DHCPCD            (nm_dhcp_dhcpcd_get_type ())
+#define NM_DHCP_DHCPCD(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcd))
+#define NM_DHCP_DHCPCD_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcdClass))
+#define NM_IS_DHCP_DHCPCD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_DHCPCD))
+#define NM_IS_DHCP_DHCPCD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_DHCPCD))
+#define NM_DHCP_DHCPCD_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcdClass))
+
+typedef struct _NMDhcpDhcpcd NMDhcpDhcpcd;
+typedef struct _NMDhcpDhcpcdClass NMDhcpDhcpcdClass;
+
+static GType nm_dhcp_dhcpcd_get_type (void);
+
+/*****************************************************************************/
+
+typedef struct {
+	char *pid_file;
+	NMDhcpListener *dhcp_listener;
+} NMDhcpDhcpcdPrivate;
+
+struct _NMDhcpDhcpcd {
+	NMDhcpClient parent;
+	NMDhcpDhcpcdPrivate _priv;
+};
+
+struct _NMDhcpDhcpcdClass {
+	NMDhcpClientClass parent;
+};
+
+G_DEFINE_TYPE (NMDhcpDhcpcd, nm_dhcp_dhcpcd, NM_TYPE_DHCP_CLIENT)
+
+#define NM_DHCP_DHCPCD_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcpDhcpcd, NM_IS_DHCP_DHCPCD)
+
+/*****************************************************************************/
+
+static const char *
+nm_dhcp_dhcpcd_get_path (void)
+{
+	return nm_utils_find_helper ("dhcpcd", DHCPCD_PATH, NULL);
+}
+
+static gboolean
+ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last_ip4_address)
+{
+	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
+	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (self);
+	GPtrArray *argv = NULL;
+	pid_t pid = -1;
+	GError *error = NULL;
+	char *pid_contents = NULL, *binary_name, *cmd_str, *dot;
+	const char *iface, *dhcpcd_path, *hostname, *fqdn;
+	gs_free char *prefix = NULL;
+
+	g_return_val_if_fail (priv->pid_file == NULL, FALSE);
+
+	iface = nm_dhcp_client_get_iface (client);
+
+	/* dhcpcd does not allow custom pidfiles; the pidfile is always
+	 * RUNDIR "dhcpcd-<ifname>.pid".
+	 */
+	priv->pid_file = g_strdup_printf (RUNDIR "/dhcpcd-%s.pid", iface);
+
+	dhcpcd_path = nm_dhcp_dhcpcd_get_path ();
+	if (!dhcpcd_path) {
+		_LOGW ("dhcpcd could not be found");
+		return FALSE;
+	}
+
+	/* Kill any existing dhcpcd from the pidfile */
+	binary_name = g_path_get_basename (dhcpcd_path);
+	nm_dhcp_client_stop_existing (priv->pid_file, binary_name);
+	g_free (binary_name);
+
+	argv = g_ptr_array_new ();
+	g_ptr_array_add (argv, (gpointer) dhcpcd_path);
+
+	g_ptr_array_add (argv, (gpointer) "-B");	/* Don't background on lease (disable fork()) */
+
+	g_ptr_array_add (argv, (gpointer) "-K");	/* Disable built-in carrier detection */
+
+	g_ptr_array_add (argv, (gpointer) "-L");	/* Disable built-in IPv4LL */
+
+	/* --noarp. Don't request or claim the address by ARP; this also disables IPv4LL. */
+	g_ptr_array_add (argv, (gpointer) "-A");
+
+	g_ptr_array_add (argv, (gpointer) "-G");	/* Let NM handle routing */
+
+	g_ptr_array_add (argv, (gpointer) "-c");	/* Set script file */
+	g_ptr_array_add (argv, (gpointer) nm_dhcp_helper_path);
+
+#ifdef DHCPCD_SUPPORTS_IPV6
+	/* IPv4-only for now.  NetworkManager knows better than dhcpcd when to
+	 * run IPv6, and dhcpcd's automatic Router Solicitations cause problems
+	 * with devices that don't expect them.
+	 */
+	g_ptr_array_add (argv, (gpointer) "-4");
+#endif
+
+	hostname = nm_dhcp_client_get_hostname (client);
+	fqdn = nm_dhcp_client_get_fqdn (client);
+
+	if (fqdn) {
+		g_ptr_array_add (argv, (gpointer) "-h");
+		g_ptr_array_add (argv, (gpointer) fqdn);
+		g_ptr_array_add (argv, (gpointer) "-F");
+		g_ptr_array_add (argv, (gpointer) "both");
+	} else if (hostname) {
+		prefix = strdup (hostname);
+		dot = strchr (prefix, '.');
+		/* get rid of the domain */
+		if (dot)
+			*dot = '\0';
+
+		g_ptr_array_add (argv, (gpointer) "-h");	/* Send hostname to DHCP server */
+		g_ptr_array_add (argv, (gpointer) prefix);
+	}
+
+	g_ptr_array_add (argv, (gpointer) iface);
+	g_ptr_array_add (argv, NULL);
+
+	cmd_str = g_strjoinv (" ", (gchar **) argv->pdata);
+	_LOGD ("running: %s", cmd_str);
+	g_free (cmd_str);
+
+	if (g_spawn_async (NULL, (char **) argv->pdata, NULL,
+	                   G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
+	                   nm_utils_setpgid, NULL, &pid, &error)) {
+		g_assert (pid > 0);
+		_LOGI ("dhcpcd started with pid %d", pid);
+		nm_dhcp_client_watch_child (client, pid);
+	} else {
+		_LOGW ("dhcpcd failed to start, error: '%s'", error->message);
+		g_error_free (error);
+	}
+
+	g_free (pid_contents);
+	g_ptr_array_free (argv, TRUE);
+	return pid > 0 ? TRUE : FALSE;
+}
+
+static gboolean
+ip6_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const struct in6_addr *ll_addr,
+           gboolean info_only,
+           NMSettingIP6ConfigPrivacy privacy,
+           const GByteArray *duid,
+           guint needed_prefixes)
+{
+	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
+
+	_LOGW ("the dhcpcd backend does not support IPv6");
+	return FALSE;
+}
+
+static void
+stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+{
+	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
+	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (self);
+
+	/* Chain up to parent */
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcd_parent_class)->stop (client, release, duid);
+
+	if (priv->pid_file) {
+		if (remove (priv->pid_file) == -1)
+			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errno, g_strerror (errno));
+	}
+
+	/* FIXME: implement release... */
+}
+
+/*****************************************************************************/
+
+static void
+nm_dhcp_dhcpcd_init (NMDhcpDhcpcd *self)
+{
+	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (self);
+
+	priv->dhcp_listener = g_object_ref (nm_dhcp_listener_get ());
+	g_signal_connect (priv->dhcp_listener,
+	                  NM_DHCP_LISTENER_EVENT,
+	                  G_CALLBACK (nm_dhcp_client_handle_event),
+	                  self);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE ((NMDhcpDhcpcd *) object);
+
+	if (priv->dhcp_listener) {
+		g_signal_handlers_disconnect_by_func (priv->dhcp_listener,
+		                                      G_CALLBACK (nm_dhcp_client_handle_event),
+		                                      NM_DHCP_DHCPCD (object));
+		g_clear_object (&priv->dhcp_listener);
+	}
+
+	nm_clear_g_free (&priv->pid_file);
+
+	G_OBJECT_CLASS (nm_dhcp_dhcpcd_parent_class)->dispose (object);
+}
+
+static void
+nm_dhcp_dhcpcd_class_init (NMDhcpDhcpcdClass *dhcpcd_class)
+{
+	NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS (dhcpcd_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (dhcpcd_class);
+
+	object_class->dispose = dispose;
+
+	client_class->ip4_start = ip4_start;
+	client_class->ip6_start = ip6_start;
+	client_class->stop = stop;
+}
+
+const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcd = {
+	.name = "dhcpcd",
+	.get_type = nm_dhcp_dhcpcd_get_type,
+	.get_path = nm_dhcp_dhcpcd_get_path,
+	.get_lease_ip_configs = NULL,
+};
+
+#endif /* WITH_DHCPCD */
diff --git a/src/dhcp/nm-dhcp-helper-api.h b/src/dhcp/nm-dhcp-helper-api.h
new file mode 100644
index 00000000..58344215
--- /dev/null
+++ b/src/dhcp/nm-dhcp-helper-api.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2016 Red Hat, Inc.
+ */
+
+#ifndef __NM_DHCP_HELPER_API_H__
+#define __NM_DHCP_HELPER_API_H__
+
+/*****************************************************************************/
+
+#define NM_DHCP_CLIENT_DBUS_IFACE "org.freedesktop.nm_dhcp_client"
+
+#define NM_DHCP_HELPER_SERVER_BUS_NAME          "org.freedesktop.nm_dhcp_server"
+#define NM_DHCP_HELPER_SERVER_OBJECT_PATH       "/org/freedesktop/nm_dhcp_server"
+#define NM_DHCP_HELPER_SERVER_INTERFACE_NAME    "org.freedesktop.nm_dhcp_server"
+#define NM_DHCP_HELPER_SERVER_METHOD_NOTIFY     "Notify"
+
+/*****************************************************************************/
+
+#endif /* __NM_DHCP_HELPER_API_H__ */
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c
new file mode 100644
index 00000000..f50c5cec
--- /dev/null
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -0,0 +1,227 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2007 - 2013 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include "nm-utils/nm-vpn-plugin-macros.h"
+
+#include "nm-dhcp-helper-api.h"
+
+/*****************************************************************************/
+
+#ifdef NM_MORE_LOGGING
+#define _NMLOG_ENABLED(level) TRUE
+#else
+#define _NMLOG_ENABLED(level) ((level) <= LOG_ERR)
+#endif
+
+#define _NMLOG(always_enabled, level, ...) \
+	G_STMT_START { \
+		if ((always_enabled) || _NMLOG_ENABLED (level)) { \
+			GTimeVal _tv; \
+			\
+			g_get_current_time (&_tv); \
+			g_print ("nm-dhcp-helper[%ld] %-7s [%ld.%04ld] " _NM_UTILS_MACRO_FIRST (__VA_ARGS__) "\n", \
+			         (long) getpid (), \
+			         nm_utils_syslog_to_str (level), \
+			         _tv.tv_sec, _tv.tv_usec / 100 \
+			         _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+		} \
+	} G_STMT_END
+
+#define _LOGD(...) _NMLOG(TRUE,  LOG_INFO,    __VA_ARGS__)
+#define _LOGI(...) _NMLOG(TRUE,  LOG_NOTICE,  __VA_ARGS__)
+#define _LOGW(...) _NMLOG(TRUE,  LOG_WARNING, __VA_ARGS__)
+#define _LOGE(...) _NMLOG(TRUE,  LOG_ERR,     __VA_ARGS__)
+
+#define _LOGd(...) _NMLOG(FALSE, LOG_INFO,    __VA_ARGS__)
+#define _LOGi(...) _NMLOG(FALSE, LOG_NOTICE,  __VA_ARGS__)
+#define _LOGw(...) _NMLOG(FALSE, LOG_WARNING, __VA_ARGS__)
+
+/*****************************************************************************/
+
+static const char * ignore[] = {"PATH", "SHLVL", "_", "PWD", "dhc_dbus", NULL};
+
+static GVariant *
+build_signal_parameters (void)
+{
+	char **item;
+	GVariantBuilder builder;
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+
+	/* List environment and format for dbus dict */
+	for (item = environ; *item; item++) {
+		char *name, *val, **p;
+
+		/* Split on the = */
+		name = g_strdup (*item);
+		val = strchr (name, '=');
+		if (!val || val == name)
+			goto next;
+		*val++ = '\0';
+
+		/* Ignore non-DCHP-related environment variables */
+		for (p = (char **) ignore; *p; p++) {
+			if (strncmp (name, *p, strlen (*p)) == 0)
+				goto next;
+		}
+
+		/* Value passed as a byte array rather than a string, because there are
+		 * no character encoding guarantees with DHCP, and D-Bus requires
+		 * strings to be UTF-8.
+		 *
+		 * Note that we can't use g_variant_new_bytestring() here, because that
+		 * includes the trailing '\0'. (??!?)
+		 */
+		g_variant_builder_add (&builder, "{sv}",
+		                       name,
+		                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                                  val, strlen (val), 1));
+
+	next:
+		g_free (name);
+	}
+
+	return g_variant_ref_sink (g_variant_new ("(a{sv})", &builder));
+}
+
+static void
+kill_pid (void)
+{
+	const char *pid_str;
+	pid_t pid = 0;
+
+	pid_str = getenv ("pid");
+	if (pid_str)
+		pid = strtol (pid_str, NULL, 10);
+	if (pid) {
+		_LOGI ("a fatal error occured, kill dhclient instance with pid %d\n", pid);
+		kill (pid, SIGTERM);
+	}
+}
+
+int
+main (int argc, char *argv[])
+{
+	gs_unref_object GDBusConnection *connection = NULL;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *parameters = NULL;
+	gs_unref_variant GVariant *result = NULL;
+	gboolean success = FALSE;
+	guint try_count = 0;
+	gint64 time_end;
+
+	nm_g_type_init ();
+
+	/* FIXME: g_dbus_connection_new_for_address_sync() tries to connect to the socket in
+	 * non-blocking mode, which can easily fail with EAGAIN, causing the creation of the
+	 * socket to fail with "Could not connect: Resource temporarily unavailable".
+	 *
+	 * We should instead create the GIOStream ourself and block on connecting to
+	 * the socket. */
+	connection = g_dbus_connection_new_for_address_sync ("unix:path=" NMRUNDIR "/private-dhcp",
+	                                                     G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+	                                                     NULL, NULL, &error);
+	if (!connection) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGE ("could not connect to NetworkManager D-Bus socket: %s",
+		       error->message);
+		goto out;
+	}
+
+	parameters = build_signal_parameters ();
+
+	time_end = g_get_monotonic_time () + (200 * 1000L); /* retry for at most 200 milliseconds */
+
+do_notify:
+	try_count++;
+	result = g_dbus_connection_call_sync (connection,
+	                                      NULL,
+	                                      NM_DHCP_HELPER_SERVER_OBJECT_PATH,
+	                                      NM_DHCP_HELPER_SERVER_INTERFACE_NAME,
+	                                      NM_DHCP_HELPER_SERVER_METHOD_NOTIFY,
+	                                      parameters,
+	                                      NULL,
+	                                      G_DBUS_CALL_FLAGS_NONE,
+	                                      1000,
+	                                      NULL,
+	                                      &error);
+
+	if (!result) {
+		gs_free char *s_err = NULL;
+
+		s_err = g_dbus_error_get_remote_error (error);
+		if (NM_IN_STRSET (s_err, "org.freedesktop.DBus.Error.UnknownMethod")) {
+			gint64 remaining_time = time_end - g_get_monotonic_time ();
+
+			/* I am not sure that a race can actually happen, as we register the object
+			 * on the server side during GDBusServer:new-connection signal.
+			 *
+			 * However, there was also a race for subscribing to an event, so let's just
+			 * do some retry. */
+			if (remaining_time > 0) {
+				_LOGi ("failure to call notify: %s (retry %u)", error->message, try_count);
+				g_usleep (NM_MIN (NM_CLAMP ((gint64) (100L * (1L << try_count)), 5000, 25000), remaining_time));
+				g_clear_error (&error);
+				goto do_notify;
+			}
+		}
+		_LOGW ("failure to call notify: %s (try signal via Event)", error->message);
+		g_clear_error (&error);
+
+		/* for backward compatibilty, try to emit the signal. There is no stable
+		 * API between the dhcp-helper and NetworkManager. However, while upgrading
+		 * the NetworkManager package, a newer helper might want to notify an
+		 * older server, which still uses the "Event". */
+		if (!g_dbus_connection_emit_signal (connection,
+		                                    NULL,
+		                                    "/",
+		                                    NM_DHCP_CLIENT_DBUS_IFACE,
+		                                    "Event",
+		                                    parameters,
+		                                    &error)) {
+			g_dbus_error_strip_remote_error (error);
+			_LOGE ("could not send DHCP Event signal: %s", error->message);
+			goto out;
+		}
+		/* We were able to send the asynchronous Event. Consider that a success. */
+		success = TRUE;
+	} else
+		success = TRUE;
+
+	if (!g_dbus_connection_flush_sync (connection, NULL, &error)) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGE ("could not flush D-Bus connection: %s", error->message);
+		success = FALSE;
+		goto out;
+	}
+
+out:
+	if (!success)
+		kill_pid ();
+	return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
new file mode 100644
index 00000000..56bd9d17
--- /dev/null
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -0,0 +1,345 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2014 - 2016 Red Hat, Inc.
+ *
+ */
+
+#include "nm-default.h"
+
+#include "nm-dhcp-listener.h"
+
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "nm-dhcp-helper-api.h"
+#include "nm-dhcp-client.h"
+#include "nm-core-internal.h"
+#include "nm-bus-manager.h"
+#include "NetworkManagerUtils.h"
+
+#define PRIV_SOCK_PATH            NMRUNDIR "/private-dhcp"
+#define PRIV_SOCK_TAG             "dhcp"
+
+/*****************************************************************************/
+
+const NMDhcpClientFactory *const _nm_dhcp_manager_factories[3] = {
+	/* the order here matters, as we will try the plugins in this order to find
+	 * the first available plugin. */
+
+#if WITH_DHCLIENT
+	&_nm_dhcp_client_factory_dhclient,
+#endif
+#if WITH_DHCPCD
+	&_nm_dhcp_client_factory_dhcpcd,
+#endif
+	&_nm_dhcp_client_factory_internal,
+};
+
+/*****************************************************************************/
+
+typedef struct {
+	NMBusManager *      dbus_mgr;
+	gulong              new_conn_id;
+	gulong              dis_conn_id;
+	GHashTable *        connections;
+} NMDhcpListenerPrivate;
+
+struct _NMDhcpListener {
+	GObject parent;
+	NMDhcpListenerPrivate _priv;
+};
+
+struct _NMDhcpListenerClass {
+	GObjectClass parent;
+};
+
+enum {
+	EVENT,
+	LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (NMDhcpListener, nm_dhcp_listener, G_TYPE_OBJECT)
+
+#define NM_DHCP_LISTENER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDhcpListener, NM_IS_DHCP_LISTENER)
+
+NM_DEFINE_SINGLETON_GETTER (NMDhcpListener, nm_dhcp_listener_get, NM_TYPE_DHCP_LISTENER);
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME    "dhcp-listener"
+#define _NMLOG_DOMAIN         LOGD_DHCP
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        const NMDhcpListener *_self = (self); \
+        char _prefix[64]; \
+        \
+        nm_log ((level), (_NMLOG_DOMAIN), \
+                "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                (_self != singleton_instance \
+                    ? nm_sprintf_buf (_prefix, "%s[%p]", _NMLOG_PREFIX_NAME, _self) \
+                    : _NMLOG_PREFIX_NAME )\
+                _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static char *
+get_option (GVariant *options, const char *key)
+{
+	GVariant *value;
+	const guchar *bytes, *s;
+	gsize len;
+	char *converted, *d;
+
+	if (!g_variant_lookup (options, key, "@ay", &value))
+		return NULL;
+
+	bytes = g_variant_get_fixed_array (value, &len, 1);
+
+	/* Since the DHCP options come through environment variables, they should
+	 * already be UTF-8 safe, but just make sure.
+	 */
+	converted = g_malloc (len + 1);
+	for (s = bytes, d = converted; s < bytes + len; s++, d++) {
+		/* Convert NULLs to spaces and non-ASCII characters to ? */
+		if (*s == '\0')
+			*d = ' ';
+		else if (*s > 127)
+			*d = '?';
+		else
+			*d = *s;
+	}
+	*d = '\0';
+	g_variant_unref (value);
+
+	return converted;
+}
+
+static void
+_method_call (GDBusConnection *connection,
+              const char *sender,
+              const char *object_path,
+              const char *interface_name,
+              const char *method_name,
+              GVariant *parameters,
+              GDBusMethodInvocation *invocation,
+              gpointer user_data)
+{
+	NMDhcpListener *self = NM_DHCP_LISTENER (user_data);
+	char *iface = NULL;
+	char *pid_str = NULL;
+	char *reason = NULL;
+	gint pid;
+	gboolean handled = FALSE;
+	GVariant *options;
+
+	if (!nm_streq0 (interface_name, NM_DHCP_HELPER_SERVER_INTERFACE_NAME))
+		g_return_if_reached ();
+	if (!nm_streq0 (method_name, NM_DHCP_HELPER_SERVER_METHOD_NOTIFY))
+		g_return_if_reached ();
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})")))
+		g_return_if_reached ();
+
+	g_variant_get (parameters, "(@a{sv})", &options);
+
+	iface = get_option (options, "interface");
+	if (iface == NULL) {
+		_LOGW ("dhcp-event: didn't have associated interface.");
+		goto out;
+	}
+
+	pid_str = get_option (options, "pid");
+	pid = _nm_utils_ascii_str_to_int64 (pid_str, 10, 0, G_MAXINT32, -1);
+	if (pid == -1) {
+		_LOGW ("dhcp-event: couldn't convert PID '%s' to an integer", pid_str ? pid_str : "(null)");
+		goto out;
+	}
+
+	reason = get_option (options, "reason");
+	if (reason == NULL) {
+		_LOGW ("dhcp-event: (pid %d) DHCP event didn't have a reason", pid);
+		goto out;
+	}
+
+	g_signal_emit (self, signals[EVENT], 0, iface, pid, options, reason, &handled);
+	if (!handled) {
+		if (g_ascii_strcasecmp (reason, "RELEASE") == 0) {
+			/* Ignore event when the dhcp client gets killed and we receive its last message */
+			_LOGD ("dhcp-event: (pid %d) unhandled RELEASE DHCP event for interface %s", pid, iface);
+		} else
+			_LOGW ("dhcp-event: (pid %d) unhandled DHCP event for interface %s", pid, iface);
+	}
+
+out:
+	g_free (iface);
+	g_free (pid_str);
+	g_free (reason);
+	g_variant_unref (options);
+	g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static guint
+_dbus_connection_register_object (NMDhcpListener *self,
+                                  GDBusConnection *connection,
+                                  GError **error)
+{
+	static GDBusArgInfo arg_info_notify_in = {
+		.ref_count = -1,
+		.name = "data",
+		.signature = "a{sv}",
+		.annotations = NULL,
+	};
+	static GDBusArgInfo *arg_infos_notify[] = {
+		&arg_info_notify_in,
+		NULL,
+	};
+	static GDBusMethodInfo method_info_notify = {
+		.ref_count = -1,
+		.name = NM_DHCP_HELPER_SERVER_METHOD_NOTIFY,
+		.in_args = arg_infos_notify,
+		.out_args = NULL,
+		.annotations = NULL,
+	};
+	static GDBusMethodInfo *method_infos[] = {
+		&method_info_notify,
+		NULL,
+	};
+	static GDBusInterfaceInfo interface_info = {
+		.ref_count = -1,
+		.name = NM_DHCP_HELPER_SERVER_INTERFACE_NAME,
+		.methods = method_infos,
+		.signals = NULL,
+		.properties = NULL,
+		.annotations = NULL,
+	};
+
+	static GDBusInterfaceVTable interface_vtable = {
+		.method_call = _method_call,
+		.get_property = NULL,
+		.set_property = NULL,
+	};
+
+	return g_dbus_connection_register_object (connection,
+	                                          NM_DHCP_HELPER_SERVER_OBJECT_PATH,
+	                                          &interface_info,
+	                                          &interface_vtable,
+	                                          self,
+	                                          NULL,
+	                                          error);
+}
+
+static void
+new_connection_cb (NMBusManager *mgr,
+                   GDBusConnection *connection,
+                   GDBusObjectManager *manager,
+                   NMDhcpListener *self)
+{
+	NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE (self);
+	guint registration_id;
+	GError *error = NULL;
+
+	/* it is important to register the object during the new-connection signal,
+	 * as this avoids races with the connecting object. */
+	registration_id = _dbus_connection_register_object (self, connection, &error);
+	if (!registration_id) {
+		_LOGE ("failure to register %s for connection %p: %s",
+		       NM_DHCP_HELPER_SERVER_OBJECT_PATH, connection, error->message);
+		g_error_free (error);
+		return;
+	}
+
+	g_hash_table_insert (priv->connections, connection, GUINT_TO_POINTER (registration_id));
+}
+
+static void
+dis_connection_cb (NMBusManager *mgr,
+                   GDBusConnection *connection,
+                   NMDhcpListener *self)
+{
+	NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE (self);
+	guint id;
+
+	id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->connections, connection));
+	if (id) {
+		g_dbus_connection_unregister_object (connection, id);
+		g_hash_table_remove (priv->connections, connection);
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_dhcp_listener_init (NMDhcpListener *self)
+{
+	NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE (self);
+
+	/* Maps GDBusConnection :: signal-id */
+	priv->connections = g_hash_table_new (NULL, NULL);
+
+	priv->dbus_mgr = nm_bus_manager_get ();
+
+	/* Register the socket our DHCP clients will return lease info on */
+	nm_bus_manager_private_server_register (priv->dbus_mgr, PRIV_SOCK_PATH, PRIV_SOCK_TAG);
+	priv->new_conn_id = g_signal_connect (priv->dbus_mgr,
+	                                      NM_BUS_MANAGER_PRIVATE_CONNECTION_NEW "::" PRIV_SOCK_TAG,
+	                                      G_CALLBACK (new_connection_cb),
+	                                      self);
+	priv->dis_conn_id = g_signal_connect (priv->dbus_mgr,
+	                                      NM_BUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED "::" PRIV_SOCK_TAG,
+	                                      G_CALLBACK (dis_connection_cb),
+	                                      self);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE ((NMDhcpListener *) object);
+
+	nm_clear_g_signal_handler (priv->dbus_mgr, &priv->new_conn_id);
+	nm_clear_g_signal_handler (priv->dbus_mgr, &priv->dis_conn_id);
+	priv->dbus_mgr = NULL;
+
+	g_clear_pointer (&priv->connections, g_hash_table_destroy);
+
+	G_OBJECT_CLASS (nm_dhcp_listener_parent_class)->dispose (object);
+}
+
+static void
+nm_dhcp_listener_class_init (NMDhcpListenerClass *listener_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (listener_class);
+
+	object_class->dispose = dispose;
+
+	signals[EVENT] =
+	    g_signal_new (NM_DHCP_LISTENER_EVENT,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST, 0,
+	                  g_signal_accumulator_true_handled,
+	                  NULL, NULL,
+	                  G_TYPE_BOOLEAN,     /* listeners return TRUE if handled */
+	                  4,
+	                  G_TYPE_STRING,      /* iface */
+	                  G_TYPE_INT,         /* pid */
+	                  G_TYPE_VARIANT,     /* options */
+	                  G_TYPE_STRING);     /* reason */
+}
diff --git a/src/dhcp/nm-dhcp-listener.h b/src/dhcp/nm-dhcp-listener.h
new file mode 100644
index 00000000..3018b97a
--- /dev/null
+++ b/src/dhcp/nm-dhcp-listener.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2014 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DHCP_LISTENER_H__
+#define __NETWORKMANAGER_DHCP_LISTENER_H__
+
+#define NM_TYPE_DHCP_LISTENER           (nm_dhcp_listener_get_type ())
+#define NM_DHCP_LISTENER(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_LISTENER, NMDhcpListener))
+#define NM_IS_DHCP_LISTENER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_LISTENER))
+#define NM_DHCP_LISTENER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_LISTENER, NMDhcpListenerClass))
+
+#define NM_DHCP_LISTENER_EVENT "event"
+
+typedef struct _NMDhcpListener NMDhcpListener;
+typedef struct _NMDhcpListenerClass NMDhcpListenerClass;
+
+GType nm_dhcp_listener_get_type (void);
+
+NMDhcpListener *nm_dhcp_listener_get (void);
+
+#endif /* __NETWORKMANAGER_DHCP_LISTENER_H__ */
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
new file mode 100644
index 00000000..9c1fbb38
--- /dev/null
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -0,0 +1,442 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-dhcp-manager.c - Handle the DHCP daemon for NetworkManager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2013 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ *
+ */
+
+#include "nm-default.h"
+
+#include "nm-dhcp-manager.h"
+
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+#include "nm-config.h"
+#include "NetworkManagerUtils.h"
+
+#define DHCP_TIMEOUT 45 /* default DHCP timeout, in seconds */
+
+/*****************************************************************************/
+
+typedef struct {
+	const NMDhcpClientFactory *client_factory;
+	GHashTable *        clients;
+	char *              default_hostname;
+} NMDhcpManagerPrivate;
+
+struct _NMDhcpManager {
+	GObject parent;
+	NMDhcpManagerPrivate _priv;
+};
+
+struct _NMDhcpManagerClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMDhcpManager, nm_dhcp_manager, G_TYPE_OBJECT)
+
+#define NM_DHCP_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcpManager, NM_IS_DHCP_MANAGER)
+
+/*****************************************************************************/
+
+/* default to installed helper, but can be modified for testing */
+const char *nm_dhcp_helper_path = LIBEXECDIR "/nm-dhcp-helper";
+
+/*****************************************************************************/
+
+static const NMDhcpClientFactory *
+_client_factory_find_by_name (const char *name)
+{
+	int i;
+
+	g_return_val_if_fail (name, NULL);
+
+	for (i = 0; i < G_N_ELEMENTS (_nm_dhcp_manager_factories); i++) {
+		const NMDhcpClientFactory *f = _nm_dhcp_manager_factories[i];
+
+		if (f && nm_streq (f->name, name))
+			return f;
+	}
+	return NULL;
+}
+
+static const NMDhcpClientFactory *
+_client_factory_available (const NMDhcpClientFactory *client_factory)
+{
+	if (   client_factory
+	    && (!client_factory->get_path || client_factory->get_path ()))
+		return client_factory;
+	return NULL;
+}
+
+/*****************************************************************************/
+
+static NMDhcpClient *
+get_client_for_ifindex (NMDhcpManager *manager, int ifindex, gboolean ip6)
+{
+	NMDhcpManagerPrivate *priv;
+	GHashTableIter iter;
+	gpointer value;
+
+	g_return_val_if_fail (NM_IS_DHCP_MANAGER (manager), NULL);
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	priv = NM_DHCP_MANAGER_GET_PRIVATE (manager);
+
+	g_hash_table_iter_init (&iter, priv->clients);
+	while (g_hash_table_iter_next (&iter, NULL, &value)) {
+		NMDhcpClient *candidate = NM_DHCP_CLIENT (value);
+
+		if (   nm_dhcp_client_get_ifindex (candidate) == ifindex
+		    && nm_dhcp_client_get_ipv6 (candidate) == ip6)
+			return candidate;
+	}
+
+	return NULL;
+}
+
+static void client_state_changed (NMDhcpClient *client,
+                                  NMDhcpState state,
+                                  GObject *ip_config,
+                                  GVariant *options,
+                                  const char *event_id,
+                                  NMDhcpManager *self);
+
+static void
+remove_client (NMDhcpManager *self, NMDhcpClient *client)
+{
+	g_signal_handlers_disconnect_by_func (client, client_state_changed, self);
+
+	/* Stopping the client is left up to the controlling device
+	 * explicitly since we may want to quit NetworkManager but not terminate
+	 * the DHCP client.
+	 */
+
+	g_hash_table_remove (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client);
+}
+
+static void
+client_state_changed (NMDhcpClient *client,
+                      NMDhcpState state,
+                      GObject *ip_config,
+                      GVariant *options,
+                      const char *event_id,
+                      NMDhcpManager *self)
+{
+	if (state >= NM_DHCP_STATE_TIMEOUT)
+		remove_client (self, client);
+}
+
+static NMDhcpClient *
+client_start (NMDhcpManager *self,
+              const char *iface,
+              int ifindex,
+              const GByteArray *hwaddr,
+              const char *uuid,
+              guint32 priority,
+              gboolean ipv6,
+              const struct in6_addr *ipv6_ll_addr,
+              const char *dhcp_client_id,
+              guint32 timeout,
+              const char *dhcp_anycast_addr,
+              const char *hostname,
+              const char *fqdn,
+              gboolean info_only,
+              NMSettingIP6ConfigPrivacy privacy,
+              const char *last_ip4_address,
+              guint needed_prefixes)
+{
+	NMDhcpManagerPrivate *priv;
+	NMDhcpClient *client;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail (self, NULL);
+	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
+	g_return_val_if_fail (ifindex > 0, NULL);
+	g_return_val_if_fail (uuid != NULL, NULL);
+
+	priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
+
+	/* Ensure we have a usable DHCP client */
+	if (!priv->client_factory)
+		return NULL;
+
+	/* Kill any old client instance */
+	client = get_client_for_ifindex (self, ifindex, ipv6);
+	if (client) {
+		g_object_ref (client);
+		remove_client (self, client);
+		nm_dhcp_client_stop (client, FALSE);
+		g_object_unref (client);
+	}
+
+	/* And make a new one */
+	client = g_object_new (priv->client_factory->get_type (),
+	                       NM_DHCP_CLIENT_INTERFACE, iface,
+	                       NM_DHCP_CLIENT_IFINDEX, ifindex,
+	                       NM_DHCP_CLIENT_HWADDR, hwaddr,
+	                       NM_DHCP_CLIENT_IPV6, ipv6,
+	                       NM_DHCP_CLIENT_UUID, uuid,
+	                       NM_DHCP_CLIENT_PRIORITY, priority,
+	                       NM_DHCP_CLIENT_TIMEOUT, timeout ? timeout : DHCP_TIMEOUT,
+	                       NULL);
+	g_hash_table_insert (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client, g_object_ref (client));
+	g_signal_connect (client, NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, G_CALLBACK (client_state_changed), self);
+
+	if (ipv6)
+		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, info_only, privacy, needed_prefixes);
+	else
+		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, fqdn, last_ip4_address);
+
+	if (!success) {
+		remove_client (self, client);
+		client = NULL;
+	}
+
+	return client;
+}
+
+static const char *
+get_send_hostname (NMDhcpManager *self, const char *setting_hostname)
+{
+	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
+
+	/* Always prefer the explicit dhcp-send-hostname if given */
+	return setting_hostname ? setting_hostname : priv->default_hostname;
+}
+
+/* Caller owns a reference to the NMDhcpClient on return */
+NMDhcpClient *
+nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
+                           const char *iface,
+                           int ifindex,
+                           const GByteArray *hwaddr,
+                           const char *uuid,
+                           guint32 priority,
+                           gboolean send_hostname,
+                           const char *dhcp_hostname,
+                           const char *dhcp_fqdn,
+                           const char *dhcp_client_id,
+                           guint32 timeout,
+                           const char *dhcp_anycast_addr,
+                           const char *last_ip_address)
+{
+	const char *hostname = NULL;
+	const char *fqdn = NULL;
+
+	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
+
+	if (send_hostname) {
+		hostname = get_send_hostname (self, dhcp_hostname);
+		fqdn = dhcp_fqdn;
+	}
+	return client_start (self, iface, ifindex, hwaddr, uuid, priority, FALSE, NULL,
+	                     dhcp_client_id, timeout, dhcp_anycast_addr, hostname,
+	                     fqdn, FALSE, 0, last_ip_address, 0);
+}
+
+/* Caller owns a reference to the NMDhcpClient on return */
+NMDhcpClient *
+nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
+                           const char *iface,
+                           int ifindex,
+                           const GByteArray *hwaddr,
+                           const struct in6_addr *ll_addr,
+                           const char *uuid,
+                           guint32 priority,
+                           gboolean send_hostname,
+                           const char *dhcp_hostname,
+                           guint32 timeout,
+                           const char *dhcp_anycast_addr,
+                           gboolean info_only,
+                           NMSettingIP6ConfigPrivacy privacy,
+                           guint needed_prefixes)
+{
+	const char *hostname = NULL;
+
+	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
+
+	if (send_hostname)
+		hostname = get_send_hostname (self, dhcp_hostname);
+	return client_start (self, iface, ifindex, hwaddr, uuid, priority, TRUE,
+	                     ll_addr, NULL, timeout, dhcp_anycast_addr, hostname, NULL, info_only,
+	                     privacy, NULL, needed_prefixes);
+}
+
+void
+nm_dhcp_manager_set_default_hostname (NMDhcpManager *manager, const char *hostname)
+{
+	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (manager);
+
+	g_clear_pointer (&priv->default_hostname, g_free);
+
+	/* Never send 'localhost'-type names to the DHCP server */
+	if (!nm_utils_is_specific_hostname (hostname))
+		return;
+
+	priv->default_hostname = g_strdup (hostname);
+}
+
+GSList *
+nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
+                                      const char *iface,
+                                      int ifindex,
+                                      const char *uuid,
+                                      gboolean ipv6,
+                                      guint32 default_route_metric)
+{
+	NMDhcpManagerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
+	g_return_val_if_fail (iface != NULL, NULL);
+	g_return_val_if_fail (ifindex >= -1, NULL);
+	g_return_val_if_fail (uuid != NULL, NULL);
+
+	priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
+	if (   priv->client_factory
+	    && priv->client_factory->get_lease_ip_configs)
+		return priv->client_factory->get_lease_ip_configs (iface, ifindex, uuid, ipv6, default_route_metric);
+	return NULL;
+}
+
+const char *
+nm_dhcp_manager_get_config (NMDhcpManager *self)
+{
+	const NMDhcpClientFactory *factory;
+
+	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
+
+	factory = NM_DHCP_MANAGER_GET_PRIVATE (self)->client_factory;
+	return factory ? factory->name : NULL;
+}
+
+/*****************************************************************************/
+
+NM_DEFINE_SINGLETON_GETTER (NMDhcpManager, nm_dhcp_manager_get, NM_TYPE_DHCP_MANAGER);
+
+static void
+nm_dhcp_manager_init (NMDhcpManager *self)
+{
+	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
+	NMConfig *config = nm_config_get ();
+	gs_free char *client_free = NULL;
+	const char *client;
+	int i;
+	const NMDhcpClientFactory *client_factory = NULL;
+
+	for (i = 0; i < G_N_ELEMENTS (_nm_dhcp_manager_factories); i++) {
+		const NMDhcpClientFactory *f = _nm_dhcp_manager_factories[i];
+
+		if (!f)
+			continue;
+
+		nm_log_dbg (LOGD_DHCP, "dhcp-init: enabled DHCP client '%s' (%s)%s",
+		            f->name, g_type_name (f->get_type ()),
+		            _client_factory_available (f) ? "" : " (not available)");
+	}
+
+	/* Client-specific setup */
+	client_free = nm_config_data_get_value (nm_config_get_data_orig (config),
+	                                        NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                                        NM_CONFIG_KEYFILE_KEY_MAIN_DHCP,
+	                                        NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
+	client = client_free;
+	if (nm_config_get_configure_and_quit (config)) {
+		client_factory = &_nm_dhcp_client_factory_internal;
+		if (client && !nm_streq (client, client_factory->name))
+			nm_log_info (LOGD_DHCP, "dhcp-init: Using internal DHCP client since configure-and-quit is set.");
+	} else {
+		if (client) {
+			client_factory = _client_factory_available (_client_factory_find_by_name (client));
+			if (!client_factory)
+				nm_log_warn (LOGD_DHCP, "dhcp-init: DHCP client '%s' not available", client);
+		}
+		if (!client_factory) {
+			client_factory = _client_factory_find_by_name (""NM_CONFIG_DEFAULT_MAIN_DHCP);
+			if (!client_factory)
+				nm_log_err (LOGD_DHCP, "dhcp-init: default DHCP client '%s' is not installed", NM_CONFIG_DEFAULT_MAIN_DHCP);
+			else {
+				client_factory = _client_factory_available (client_factory);
+				if (!client_factory)
+					nm_log_info (LOGD_DHCP, "dhcp-init: default DHCP client '%s' is not available", NM_CONFIG_DEFAULT_MAIN_DHCP);
+			}
+		}
+		if (!client_factory) {
+			for (i = 0; i < G_N_ELEMENTS (_nm_dhcp_manager_factories); i++) {
+				client_factory = _client_factory_available (_nm_dhcp_manager_factories[i]);
+				if (client_factory)
+					break;
+			}
+		}
+	}
+
+	nm_assert (client_factory);
+
+	nm_log_info (LOGD_DHCP, "dhcp-init: Using DHCP client '%s'", client_factory->name);
+
+	priv->client_factory = client_factory;
+	priv->clients = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+	                                       NULL,
+	                                       (GDestroyNotify) g_object_unref);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE ((NMDhcpManager *) object);
+	GList *values, *iter;
+
+	if (priv->clients) {
+		values = g_hash_table_get_values (priv->clients);
+		for (iter = values; iter; iter = g_list_next (iter))
+			remove_client (NM_DHCP_MANAGER (object), NM_DHCP_CLIENT (iter->data));
+		g_list_free (values);
+	}
+
+	G_OBJECT_CLASS (nm_dhcp_manager_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE ((NMDhcpManager *) object);
+
+	g_free (priv->default_hostname);
+
+	if (priv->clients)
+		g_hash_table_destroy (priv->clients);
+
+	G_OBJECT_CLASS (nm_dhcp_manager_parent_class)->finalize (object);
+}
+
+static void
+nm_dhcp_manager_class_init (NMDhcpManagerClass *manager_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (manager_class);
+
+	object_class->finalize = finalize;
+	object_class->dispose = dispose;
+}
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
new file mode 100644
index 00000000..66fdd145
--- /dev/null
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-dhcp-manager.c - Handle the DHCP daemon for NetworkManager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2010 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DHCP_MANAGER_H__
+#define __NETWORKMANAGER_DHCP_MANAGER_H__
+
+#include "nm-dhcp-client.h"
+#include "nm-ip4-config.h"
+#include "nm-dhcp4-config.h"
+
+#define NM_TYPE_DHCP_MANAGER            (nm_dhcp_manager_get_type ())
+#define NM_DHCP_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_MANAGER, NMDhcpManager))
+#define NM_DHCP_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_MANAGER, NMDhcpManagerClass))
+#define NM_IS_DHCP_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_MANAGER))
+#define NM_IS_DHCP_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_MANAGER))
+#define NM_DHCP_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_MANAGER, NMDhcpManagerClass))
+
+typedef struct _NMDhcpManager NMDhcpManager;
+typedef struct _NMDhcpManagerClass NMDhcpManagerClass;
+
+GType nm_dhcp_manager_get_type (void);
+
+NMDhcpManager *nm_dhcp_manager_get (void);
+
+const char *nm_dhcp_manager_get_config (NMDhcpManager *self);
+
+void           nm_dhcp_manager_set_default_hostname (NMDhcpManager *manager,
+                                                     const char *hostname);
+
+NMDhcpClient * nm_dhcp_manager_start_ip4     (NMDhcpManager *manager,
+                                              const char *iface,
+                                              int ifindex,
+                                              const GByteArray *hwaddr,
+                                              const char *uuid,
+                                              guint32 priority,
+                                              gboolean send_hostname,
+                                              const char *dhcp_hostname,
+                                              const char *dhcp_fqdn,
+                                              const char *dhcp_client_id,
+                                              guint32 timeout,
+                                              const char *dhcp_anycast_addr,
+                                              const char *last_ip_address);
+
+NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
+                                              const char *iface,
+                                              int ifindex,
+                                              const GByteArray *hwaddr,
+                                              const struct in6_addr *ll_addr,
+                                              const char *uuid,
+                                              guint32 priority,
+                                              gboolean send_hostname,
+                                              const char *dhcp_hostname,
+                                              guint32 timeout,
+                                              const char *dhcp_anycast_addr,
+                                              gboolean info_only,
+                                              NMSettingIP6ConfigPrivacy privacy,
+                                              guint needed_prefixes);
+
+GSList *       nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
+                                                     const char *iface,
+                                                     int ifindex,
+                                                     const char *uuid,
+                                                     gboolean ipv6,
+                                                     guint32 default_route_metric);
+
+/* For testing only */
+extern const char* nm_dhcp_helper_path;
+
+extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[3];
+
+#endif /* __NETWORKMANAGER_DHCP_MANAGER_H__ */
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
new file mode 100644
index 00000000..7067275b
--- /dev/null
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -0,0 +1,1070 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <net/if_arp.h>
+
+#include "nm-utils.h"
+#include "nm-dhcp-utils.h"
+#include "NetworkManagerUtils.h"
+#include "platform/nm-platform.h"
+#include "nm-dhcp-client-logging.h"
+#include "systemd/nm-sd.h"
+
+/*****************************************************************************/
+
+#define NM_TYPE_DHCP_SYSTEMD            (nm_dhcp_systemd_get_type ())
+#define NM_DHCP_SYSTEMD(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemd))
+#define NM_DHCP_SYSTEMD_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemdClass))
+#define NM_IS_DHCP_SYSTEMD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_SYSTEMD))
+#define NM_IS_DHCP_SYSTEMD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_SYSTEMD))
+#define NM_DHCP_SYSTEMD_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemdClass))
+
+typedef struct _NMDhcpSystemd NMDhcpSystemd;
+typedef struct _NMDhcpSystemdClass NMDhcpSystemdClass;
+
+static GType nm_dhcp_systemd_get_type (void);
+
+/*****************************************************************************/
+
+typedef struct {
+	sd_dhcp_client *client4;
+	sd_dhcp6_client *client6;
+	char *lease_file;
+
+	guint request_count;
+
+	gboolean privacy;
+	gboolean info_only;
+} NMDhcpSystemdPrivate;
+
+struct _NMDhcpSystemd {
+	NMDhcpClient parent;
+	NMDhcpSystemdPrivate _priv;
+};
+
+struct _NMDhcpSystemdClass {
+	NMDhcpClientClass parent;
+};
+
+G_DEFINE_TYPE (NMDhcpSystemd, nm_dhcp_systemd, NM_TYPE_DHCP_CLIENT)
+
+#define NM_DHCP_SYSTEMD_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcpSystemd, NM_IS_DHCP_SYSTEMD)
+
+/*****************************************************************************/
+
+#define DHCP_OPTION_NIS_DOMAIN         40
+#define DHCP_OPTION_NIS_SERVERS        41
+#define DHCP_OPTION_DOMAIN_SEARCH     119
+#define DHCP_OPTION_MS_ROUTES         249
+#define DHCP_OPTION_WPAD              252
+
+/* Internal values */
+#define DHCP_OPTION_IP_ADDRESS       1024
+#define DHCP_OPTION_EXPIRY           1025
+#define DHCP6_OPTION_IP_ADDRESS      1026
+#define DHCP6_OPTION_PREFIXLEN       1027
+#define DHCP6_OPTION_PREFERRED_LIFE  1028
+#define DHCP6_OPTION_MAX_LIFE        1029
+#define DHCP6_OPTION_STARTS          1030
+#define DHCP6_OPTION_LIFE_STARTS     1031
+#define DHCP6_OPTION_RENEW           1032
+#define DHCP6_OPTION_REBIND          1033
+#define DHCP6_OPTION_IAID            1034
+
+typedef struct {
+	guint num;
+	const char *name;
+	gboolean include;
+} ReqOption;
+
+#define REQPREFIX "requested_"
+
+static const ReqOption dhcp4_requests[] = {
+	{ SD_DHCP_OPTION_SUBNET_MASK,            REQPREFIX "subnet_mask",                     TRUE },
+	{ SD_DHCP_OPTION_TIME_OFFSET,            REQPREFIX "time_offset",                     TRUE },
+	{ SD_DHCP_OPTION_ROUTER,                 REQPREFIX "routers",                         TRUE },
+	{ SD_DHCP_OPTION_DOMAIN_NAME_SERVER,     REQPREFIX "domain_name_servers",             TRUE },
+	{ SD_DHCP_OPTION_HOST_NAME,              REQPREFIX "host_name",                       TRUE },
+	{ SD_DHCP_OPTION_DOMAIN_NAME,            REQPREFIX "domain_name",                     TRUE },
+	{ SD_DHCP_OPTION_INTERFACE_MTU,          REQPREFIX "interface_mtu",                   TRUE },
+	{ SD_DHCP_OPTION_BROADCAST,              REQPREFIX "broadcast_address",               TRUE },
+	{ SD_DHCP_OPTION_STATIC_ROUTE,           REQPREFIX "static_routes",                   TRUE },
+	{ DHCP_OPTION_NIS_DOMAIN,                REQPREFIX "nis_domain",                      TRUE },
+	{ DHCP_OPTION_NIS_SERVERS,               REQPREFIX "nis_servers",                     TRUE },
+	{ SD_DHCP_OPTION_NTP_SERVER,             REQPREFIX "ntp_servers",                     TRUE },
+	{ SD_DHCP_OPTION_SERVER_IDENTIFIER,      REQPREFIX "dhcp_server_identifier",          TRUE },
+	{ DHCP_OPTION_DOMAIN_SEARCH,             REQPREFIX "domain_search",                   TRUE },
+	{ SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, REQPREFIX "rfc3442_classless_static_routes", TRUE },
+	{ DHCP_OPTION_MS_ROUTES,                 REQPREFIX "ms_classless_static_routes",      TRUE },
+	{ DHCP_OPTION_WPAD,                      REQPREFIX "wpad",                            TRUE },
+
+	/* Internal values */
+	{ SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,  REQPREFIX "expiry",                          FALSE },
+	{ SD_DHCP_OPTION_CLIENT_IDENTIFIER,      REQPREFIX "dhcp_client_identifier",          FALSE },
+	{ DHCP_OPTION_IP_ADDRESS,                REQPREFIX "ip_address",                      FALSE },
+	{ 0, NULL, FALSE }
+};
+
+static const ReqOption dhcp6_requests[] = {
+	{ SD_DHCP6_OPTION_CLIENTID,              REQPREFIX "dhcp6_client_id",     TRUE },
+
+	/* Don't request server ID by default; some servers don't reply to
+	 * Information Requests that request the Server ID.
+	 */
+	{ SD_DHCP6_OPTION_SERVERID,              REQPREFIX "dhcp6_server_id",     FALSE },
+
+	{ SD_DHCP6_OPTION_DNS_SERVERS,           REQPREFIX "dhcp6_name_servers",  TRUE },
+	{ SD_DHCP6_OPTION_DOMAIN_LIST,           REQPREFIX "dhcp6_domain_search", TRUE },
+	{ SD_DHCP6_OPTION_SNTP_SERVERS,          REQPREFIX "dhcp6_sntp_servers",  TRUE },
+
+	/* Internal values */
+	{ DHCP6_OPTION_IP_ADDRESS,               REQPREFIX "ip6_address",         FALSE },
+	{ DHCP6_OPTION_PREFIXLEN,                REQPREFIX "ip6_prefixlen",       FALSE },
+	{ DHCP6_OPTION_PREFERRED_LIFE,           REQPREFIX "preferred_life",      FALSE },
+	{ DHCP6_OPTION_MAX_LIFE,                 REQPREFIX "max_life",            FALSE },
+	{ DHCP6_OPTION_STARTS,                   REQPREFIX "starts",              FALSE },
+	{ DHCP6_OPTION_LIFE_STARTS,              REQPREFIX "life_starts",         FALSE },
+	{ DHCP6_OPTION_RENEW,                    REQPREFIX "renew",               FALSE },
+	{ DHCP6_OPTION_REBIND,                   REQPREFIX "rebind",              FALSE },
+	{ DHCP6_OPTION_IAID,                     REQPREFIX "iaid",                FALSE },
+	{ 0, NULL, FALSE }
+};
+
+static void
+take_option (GHashTable *options,
+             const ReqOption *requests,
+             guint option,
+             char *value)
+{
+	guint i;
+
+	g_return_if_fail (value != NULL);
+
+	for (i = 0; requests[i].name; i++) {
+		if (requests[i].num == option) {
+			g_hash_table_insert (options,
+			                     (gpointer) (requests[i].name + NM_STRLEN (REQPREFIX)),
+			                     value);
+			break;
+		}
+	}
+	/* Option should always be found */
+	g_assert (requests[i].name);
+}
+
+static void
+add_option (GHashTable *options, const ReqOption *requests, guint option, const char *value)
+{
+	if (options)
+		take_option (options, requests, option, g_strdup (value));
+}
+
+static void
+add_option_u32 (GHashTable *options, const ReqOption *requests, guint option, guint32 value)
+{
+	if (options)
+		take_option (options, requests, option, g_strdup_printf ("%u", value));
+}
+
+static void
+add_option_u64 (GHashTable *options, const ReqOption *requests, guint option, guint64 value)
+{
+	if (options)
+		take_option (options, requests, option, g_strdup_printf ("%" G_GUINT64_FORMAT, value));
+}
+
+static void
+add_requests_to_options (GHashTable *options, const ReqOption *requests)
+{
+	guint i;
+
+	for (i = 0; options && requests[i].name; i++) {
+		if (requests[i].include)
+			g_hash_table_insert (options, (gpointer) requests[i].name, g_strdup ("1"));
+	}
+}
+
+#define LOG_LEASE(domain, ...) \
+G_STMT_START { \
+	if (log_lease) { \
+		_LOG2I ((domain), (iface), __VA_ARGS__); \
+	} \
+} G_STMT_END
+
+static NMIP4Config *
+lease_to_ip4_config (const char *iface,
+                     int ifindex,
+                     sd_dhcp_lease *lease,
+                     GHashTable *options,
+                     guint32 default_priority,
+                     gboolean log_lease,
+                     GError **error)
+{
+	NMIP4Config *ip4_config = NULL;
+	struct in_addr tmp_addr;
+	const struct in_addr *addr_list;
+	char buf[INET_ADDRSTRLEN];
+	const char *str;
+	guint32 lifetime = 0, i;
+	NMPlatformIP4Address address;
+	GString *l;
+	gs_free sd_dhcp_route **routes = NULL;
+	guint16 mtu;
+	int r, num;
+	guint64 end_time;
+	const void *data;
+	gsize data_len;
+	gboolean metered = FALSE;
+	gboolean static_default_gateway = FALSE;
+
+	g_return_val_if_fail (lease != NULL, NULL);
+
+	ip4_config = nm_ip4_config_new (ifindex);
+
+	/* Address */
+	sd_dhcp_lease_get_address (lease, &tmp_addr);
+	memset (&address, 0, sizeof (address));
+	address.address = tmp_addr.s_addr;
+	address.peer_address = tmp_addr.s_addr;
+	str = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
+	LOG_LEASE (LOGD_DHCP4, "  address %s", str);
+	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, str);
+
+	/* Prefix/netmask */
+	sd_dhcp_lease_get_netmask (lease, &tmp_addr);
+	address.plen = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr);
+	LOG_LEASE (LOGD_DHCP4, "  plen %d", address.plen);
+	add_option (options,
+	            dhcp4_requests,
+	            SD_DHCP_OPTION_SUBNET_MASK,
+	            nm_utils_inet4_ntop (tmp_addr.s_addr, NULL));
+
+	/* Lease time */
+	sd_dhcp_lease_get_lifetime (lease, &lifetime);
+	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+	address.lifetime = address.preferred = lifetime;
+	end_time = (guint64) time (NULL) + lifetime;
+	LOG_LEASE (LOGD_DHCP4, "  expires in %" G_GUINT32_FORMAT " seconds", lifetime);
+	add_option_u64 (options,
+	                dhcp4_requests,
+	                SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
+	                end_time);
+
+	address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
+	nm_ip4_config_add_address (ip4_config, &address);
+
+	/* DNS Servers */
+	num = sd_dhcp_lease_get_dns (lease, &addr_list);
+	if (num > 0) {
+		l = g_string_sized_new (30);
+		for (i = 0; i < num; i++) {
+			if (addr_list[i].s_addr) {
+				nm_ip4_config_add_nameserver (ip4_config, addr_list[i].s_addr);
+				str = nm_utils_inet4_ntop (addr_list[i].s_addr, NULL);
+				LOG_LEASE (LOGD_DHCP4, "  nameserver '%s'", str);
+				g_string_append_printf (l, "%s%s", l->len ? " " : "", str);
+			}
+		}
+		if (l->len)
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, l->str);
+		g_string_free (l, TRUE);
+	}
+
+	/* Domain Name */
+	r = sd_dhcp_lease_get_domainname (lease, &str);
+	if (r == 0) {
+		/* Multiple domains sometimes stuffed into option 15 "Domain Name".
+		 * As systemd escapes such characters, split them at \\032. */
+		char **domains = g_strsplit (str, "\\032", 0);
+		char **s;
+
+		for (s = domains; *s; s++) {
+			LOG_LEASE (LOGD_DHCP4, "  domain name '%s'", *s);
+			nm_ip4_config_add_domain (ip4_config, *s);
+		}
+		g_strfreev (domains);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME, str);
+	}
+
+	/* Hostname */
+	r = sd_dhcp_lease_get_hostname (lease, &str);
+	if (r == 0) {
+		LOG_LEASE (LOGD_DHCP4, "  hostname '%s'", str);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_HOST_NAME, str);
+	}
+
+	/* Routes */
+	num = sd_dhcp_lease_get_routes (lease, &routes);
+	if (num > 0) {
+		l = g_string_sized_new (30);
+		for (i = 0; i < num; i++) {
+			NMPlatformIP4Route route = { 0 };
+			const char *gw_str;
+			guint8 plen;
+			struct in_addr a;
+
+			if (sd_dhcp_route_get_destination (routes[i], &a) < 0)
+				continue;
+			route.network = a.s_addr;
+
+			if (   sd_dhcp_route_get_destination_prefix_length (routes[i], &plen) < 0
+			    || plen > 32)
+				continue;
+			route.plen = plen;
+
+			if (sd_dhcp_route_get_gateway (routes[i], &a) < 0)
+				continue;
+			route.gateway = a.s_addr;
+
+			if (route.plen) {
+				route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
+				route.metric = default_priority;
+				nm_ip4_config_add_route (ip4_config, &route);
+
+				str = nm_utils_inet4_ntop (route.network, buf);
+				gw_str = nm_utils_inet4_ntop (route.gateway, NULL);
+				LOG_LEASE (LOGD_DHCP4, "  static route %s/%d gw %s", str, route.plen, gw_str);
+
+				g_string_append_printf (l, "%s%s/%d %s", l->len ? " " : "", str, route.plen, gw_str);
+			} else {
+				if (!static_default_gateway) {
+					static_default_gateway = TRUE;
+					nm_ip4_config_set_gateway (ip4_config, route.gateway);
+
+					str = nm_utils_inet4_ntop (route.gateway, NULL);
+					LOG_LEASE (LOGD_DHCP4, "  gateway %s", str);
+					add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, str);
+				}
+			}
+		}
+		if (l->len)
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, l->str);
+		g_string_free (l, TRUE);
+	}
+
+	/* If the DHCP server returns both a Classless Static Routes option and a
+	 * Router option, the DHCP client MUST ignore the Router option [RFC 3442].
+	 * Be more lenient and ignore the Router option only if Classless Static
+	 * Routes contain a default gateway (as other DHCP backends do).
+	 */
+	/* Gateway */
+	if (!static_default_gateway) {
+		r = sd_dhcp_lease_get_router (lease, &tmp_addr);
+		if (r == 0) {
+			nm_ip4_config_set_gateway (ip4_config, tmp_addr.s_addr);
+			str = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
+			LOG_LEASE (LOGD_DHCP4, "  gateway %s", str);
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, str);
+		}
+	}
+
+	/* MTU */
+	r = sd_dhcp_lease_get_mtu (lease, &mtu);
+	if (r == 0 && mtu) {
+		nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
+		add_option_u32 (options, dhcp4_requests, SD_DHCP_OPTION_INTERFACE_MTU, mtu);
+		LOG_LEASE (LOGD_DHCP4, "  mtu %u", mtu);
+	}
+
+	/* NTP servers */
+	num = sd_dhcp_lease_get_ntp (lease, &addr_list);
+	if (num > 0) {
+		l = g_string_sized_new (30);
+		for (i = 0; i < num; i++) {
+			str = nm_utils_inet4_ntop (addr_list[i].s_addr, buf);
+			LOG_LEASE (LOGD_DHCP4, "  ntp server '%s'", str);
+			g_string_append_printf (l, "%s%s", l->len ? " " : "", str);
+		}
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_NTP_SERVER, l->str);
+		g_string_free (l, TRUE);
+	}
+
+	r = sd_dhcp_lease_get_vendor_specific (lease, &data, &data_len);
+	if (r >= 0)
+		metered = !!memmem (data, data_len, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
+	nm_ip4_config_set_metered (ip4_config, metered);
+
+	return ip4_config;
+}
+
+/*****************************************************************************/
+
+static char *
+get_leasefile_path (const char *iface, const char *uuid, gboolean ipv6)
+{
+	return g_strdup_printf (NMSTATEDIR "/internal%s-%s-%s.lease",
+	                        ipv6 ? "6" : "",
+	                        uuid,
+	                        iface);
+}
+
+static GSList *
+nm_dhcp_systemd_get_lease_ip_configs (const char *iface,
+                                      int ifindex,
+                                      const char *uuid,
+                                      gboolean ipv6,
+                                      guint32 default_route_metric)
+{
+	GSList *leases = NULL;
+	gs_free char *path = NULL;
+	sd_dhcp_lease *lease = NULL;
+	NMIP4Config *ip4_config;
+	int r;
+
+	if (ipv6)
+		return NULL;
+
+	path = get_leasefile_path (iface, uuid, FALSE);
+	r = dhcp_lease_load (&lease, path);
+	if (r == 0 && lease) {
+		ip4_config = lease_to_ip4_config (iface, ifindex, lease, NULL, default_route_metric, FALSE, NULL);
+		if (ip4_config)
+			leases = g_slist_append (leases, ip4_config);
+		sd_dhcp_lease_unref (lease);
+	}
+
+	return leases;
+}
+
+/*****************************************************************************/
+
+static void
+_save_client_id (NMDhcpSystemd *self,
+                 uint8_t type,
+                 const uint8_t *client_id,
+                 size_t len)
+{
+	gs_unref_bytes GBytes *b = NULL;
+	gs_free char *buf = NULL;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (client_id != NULL);
+	g_return_if_fail (len > 0);
+
+	if (!nm_dhcp_client_get_client_id (NM_DHCP_CLIENT (self))) {
+		buf = g_malloc (len + 1);
+		buf[0] = type;
+		memcpy (buf + 1, client_id, len);
+		b = g_bytes_new (buf, len + 1);
+		nm_dhcp_client_set_client_id (NM_DHCP_CLIENT (self), b);
+	}
+}
+
+static void
+bound4_handle (NMDhcpSystemd *self)
+{
+	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
+	const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
+	sd_dhcp_lease *lease;
+	NMIP4Config *ip4_config;
+	GHashTable *options;
+	GError *error = NULL;
+	int r;
+
+	r = sd_dhcp_client_get_lease (priv->client4, &lease);
+	if (r < 0 || !lease) {
+		_LOGW ("no lease!");
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+		return;
+	}
+
+	_LOGD ("lease available");
+
+	options = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+	ip4_config = lease_to_ip4_config (iface,
+	                                  nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
+	                                  lease,
+	                                  options,
+	                                  nm_dhcp_client_get_priority (NM_DHCP_CLIENT (self)),
+	                                  TRUE,
+	                                  &error);
+	if (ip4_config) {
+		const uint8_t *client_id = NULL;
+		size_t client_id_len = 0;
+		uint8_t type = 0;
+
+		add_requests_to_options (options, dhcp4_requests);
+		dhcp_lease_save (lease, priv->lease_file);
+
+		sd_dhcp_client_get_client_id(priv->client4, &type, &client_id, &client_id_len);
+		if (client_id)
+			_save_client_id (self, type, client_id, client_id_len);
+
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
+		                          NM_DHCP_STATE_BOUND,
+		                          G_OBJECT (ip4_config),
+		                          options);
+	} else {
+		_LOGW ("%s", error->message);
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+		g_clear_error (&error);
+	}
+
+	g_hash_table_destroy (options);
+	g_clear_object (&ip4_config);
+}
+
+static void
+dhcp_event_cb (sd_dhcp_client *client, int event, gpointer user_data)
+{
+	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (user_data);
+	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
+
+	g_assert (priv->client4 == client);
+
+	_LOGD ("client event %d", event);
+
+	switch (event) {
+	case SD_DHCP_CLIENT_EVENT_EXPIRED:
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (user_data), NM_DHCP_STATE_EXPIRE, NULL, NULL);
+		break;
+	case SD_DHCP_CLIENT_EVENT_STOP:
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (user_data), NM_DHCP_STATE_FAIL, NULL, NULL);
+		break;
+	case SD_DHCP_CLIENT_EVENT_RENEW:
+	case SD_DHCP_CLIENT_EVENT_IP_CHANGE:
+	case SD_DHCP_CLIENT_EVENT_IP_ACQUIRE:
+		bound4_handle (self);
+		break;
+	default:
+		_LOGW ("unhandled DHCP event %d", event);
+		break;
+	}
+}
+
+static guint16
+get_arp_type (const GByteArray *hwaddr)
+{
+	if (hwaddr->len == ETH_ALEN)
+		return ARPHRD_ETHER;
+	else if (hwaddr->len == INFINIBAND_ALEN)
+		return ARPHRD_INFINIBAND;
+	else
+		return ARPHRD_NONE;
+}
+
+static gboolean
+ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last_ip4_address)
+{
+	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
+	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
+	const char *iface = nm_dhcp_client_get_iface (client);
+	const GByteArray *hwaddr;
+	sd_dhcp_lease *lease = NULL;
+	GBytes *override_client_id;
+	const uint8_t *client_id = NULL;
+	size_t client_id_len = 0;
+	struct in_addr last_addr = { 0 };
+	const char *hostname, *fqdn;
+	int r, i;
+	gboolean success = FALSE;
+	guint16 arp_type;
+
+	g_assert (priv->client4 == NULL);
+	g_assert (priv->client6 == NULL);
+
+	g_free (priv->lease_file);
+	priv->lease_file = get_leasefile_path (iface, nm_dhcp_client_get_uuid (client), FALSE);
+
+	r = sd_dhcp_client_new (&priv->client4);
+	if (r < 0) {
+		_LOGW ("failed to create client (%d)", r);
+		return FALSE;
+	}
+
+	_LOGT ("dhcp-client4: set %p", priv->client4);
+
+	r = sd_dhcp_client_attach_event (priv->client4, NULL, 0);
+	if (r < 0) {
+		_LOGW ("failed to attach event (%d)", r);
+		goto error;
+	}
+
+	hwaddr = nm_dhcp_client_get_hw_addr (client);
+	if (hwaddr) {
+		arp_type= get_arp_type (hwaddr);
+		if (arp_type == ARPHRD_NONE) {
+			_LOGW ("failed to determine ARP type");
+			goto error;
+		}
+
+		r = sd_dhcp_client_set_mac (priv->client4,
+		                            hwaddr->data,
+		                            hwaddr->len,
+		                            arp_type);
+		if (r < 0) {
+			_LOGW ("failed to set MAC address (%d)", r);
+			goto error;
+		}
+	}
+
+	r = sd_dhcp_client_set_ifindex (priv->client4, nm_dhcp_client_get_ifindex (client));
+	if (r < 0) {
+		_LOGW ("failed to set ififindex (%d)", r);
+		goto error;
+	}
+
+	r = sd_dhcp_client_set_callback (priv->client4, dhcp_event_cb, client);
+	if (r < 0) {
+		_LOGW ("failed to set callback (%d)", r);
+		goto error;
+	}
+
+	r = sd_dhcp_client_set_request_broadcast (priv->client4, true);
+	if (r < 0) {
+		_LOGW ("failed to enable broadcast mode (%d)", r);
+		goto error;
+	}
+
+	dhcp_lease_load (&lease, priv->lease_file);
+
+	if (last_ip4_address)
+		inet_pton (AF_INET, last_ip4_address, &last_addr);
+	else if (lease)
+		sd_dhcp_lease_get_address (lease, &last_addr);
+
+	if (last_addr.s_addr) {
+		r = sd_dhcp_client_set_request_address (priv->client4, &last_addr);
+		if (r < 0) {
+			_LOGW ("failed to set last IPv4 address (%d)", r);
+			goto error;
+		}
+	}
+
+	override_client_id = nm_dhcp_client_get_client_id (client);
+	if (override_client_id) {
+		client_id = g_bytes_get_data (override_client_id, &client_id_len);
+		g_assert (client_id && client_id_len);
+		sd_dhcp_client_set_client_id (priv->client4,
+		                              client_id[0],
+		                              client_id + 1,
+		                              client_id_len - 1);
+	} else if (lease) {
+		r = sd_dhcp_lease_get_client_id (lease, (const void **) &client_id, &client_id_len);
+		if (r == 0 && client_id_len) {
+			sd_dhcp_client_set_client_id (priv->client4,
+			                              client_id[0],
+			                              client_id + 1,
+			                              client_id_len - 1);
+			_save_client_id (NM_DHCP_SYSTEMD (client),
+			                 client_id[0],
+			                 client_id + 1,
+			                 client_id_len - 1);
+		}
+	}
+
+
+	/* Add requested options */
+	for (i = 0; dhcp4_requests[i].name; i++) {
+		if (dhcp4_requests[i].include)
+			sd_dhcp_client_set_request_option (priv->client4, dhcp4_requests[i].num);
+	}
+
+	hostname = nm_dhcp_client_get_hostname (client);
+	if (hostname) {
+		char *prefix, *dot;
+
+		prefix = strdup (hostname);
+		dot = strchr (prefix, '.');
+		/* get rid of the domain */
+		if (dot)
+			*dot = '\0';
+
+		r = sd_dhcp_client_set_hostname (priv->client4, prefix);
+		free (prefix);
+
+		if (r < 0) {
+			_LOGW ("failed to set DHCP hostname (%d)", r);
+			goto error;
+		}
+	}
+
+	fqdn = nm_dhcp_client_get_fqdn (client);
+	if (fqdn) {
+		r = sd_dhcp_client_set_hostname (priv->client4, fqdn);
+		if (r < 0) {
+			_LOGW ("failed to set DHCP FQDN (%d)", r);
+			goto error;
+		}
+	}
+
+	r = sd_dhcp_client_start (priv->client4);
+	if (r < 0) {
+		_LOGW ("failed to start client (%d)", r);
+		goto error;
+	}
+
+	nm_dhcp_client_start_timeout (client);
+
+	success = TRUE;
+
+error:
+	sd_dhcp_lease_unref (lease);
+	if (!success)
+		priv->client4 = sd_dhcp_client_unref (priv->client4);
+	return success;
+}
+
+static NMIP6Config *
+lease_to_ip6_config (const char *iface,
+                     int ifindex,
+                     sd_dhcp6_lease *lease,
+                     GHashTable *options,
+                     gboolean log_lease,
+                     gboolean info_only,
+                     GError **error)
+{
+	struct in6_addr tmp_addr, *dns;
+	uint32_t lft_pref, lft_valid;
+	NMIP6Config *ip6_config;
+	const char *addr_str;
+	char **domains;
+	GString *str;
+	int num, i;
+	gint32 ts;
+
+	g_return_val_if_fail (lease, NULL);
+	ip6_config = nm_ip6_config_new (ifindex);
+	ts = nm_utils_get_monotonic_timestamp_s ();
+	str = g_string_sized_new (30);
+
+	/* Addresses */
+	sd_dhcp6_lease_reset_address_iter (lease);
+	while (sd_dhcp6_lease_get_address (lease, &tmp_addr, &lft_pref, &lft_valid) >= 0) {
+		NMPlatformIP6Address address = {
+			.plen = 128,
+			.address = tmp_addr,
+			.timestamp = ts,
+			.lifetime = lft_valid,
+			.preferred = lft_pref,
+			.addr_source = NM_IP_CONFIG_SOURCE_DHCP,
+		};
+
+		nm_ip6_config_add_address (ip6_config, &address);
+
+		addr_str = nm_utils_inet6_ntop (&tmp_addr, NULL);
+		g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);
+
+		LOG_LEASE (LOGD_DHCP6,
+		           "  address %s",
+		           nm_platform_ip6_address_to_string (&address, NULL, 0));
+	};
+
+	if (str->len) {
+		add_option (options, dhcp6_requests, DHCP6_OPTION_IP_ADDRESS, str->str);
+		g_string_set_size (str , 0);
+	}
+
+	if (!info_only && nm_ip6_config_get_num_addresses (ip6_config) == 0) {
+		g_string_free (str, TRUE);
+		g_object_unref (ip6_config);
+		g_set_error_literal (error,
+		                     NM_MANAGER_ERROR,
+		                     NM_MANAGER_ERROR_FAILED,
+		                     "no address received in managed mode");
+		return NULL;
+	}
+
+	/* DNS servers */
+	num = sd_dhcp6_lease_get_dns (lease, &dns);
+	if (num > 0) {
+		for (i = 0; i < num; i++) {
+			nm_ip6_config_add_nameserver (ip6_config, &dns[i]);
+			addr_str = nm_utils_inet6_ntop (&dns[i], NULL);
+			g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);
+			LOG_LEASE (LOGD_DHCP6, "  nameserver %s", addr_str);
+		}
+		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DNS_SERVERS, str->str);
+		g_string_set_size (str, 0);
+	}
+
+	/* Search domains */
+	num = sd_dhcp6_lease_get_domains (lease, &domains);
+	if (num > 0) {
+		for (i = 0; i < num; i++) {
+			nm_ip6_config_add_search (ip6_config, domains[i]);
+			g_string_append_printf (str, "%s%s", str->len ? " " : "", domains[i]);
+			LOG_LEASE (LOGD_DHCP6, "  domain name '%s'", domains[i]);
+		}
+		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DOMAIN_LIST, str->str);
+		g_string_set_size (str, 0);
+	}
+
+	g_string_free (str, TRUE);
+
+	return ip6_config;
+}
+
+static void
+bound6_handle (NMDhcpSystemd *self)
+{
+	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
+	const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
+	gs_unref_object NMIP6Config *ip6_config = NULL;
+	gs_unref_hashtable GHashTable *options = NULL;
+	gs_free_error GError *error = NULL;
+	sd_dhcp6_lease *lease;
+	int r;
+
+	r = sd_dhcp6_client_get_lease (priv->client6, &lease);
+	if (r < 0 || !lease) {
+		_LOGW (" no lease!");
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+		return;
+	}
+
+	_LOGD ("lease available");
+
+	options = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+	ip6_config = lease_to_ip6_config (iface,
+	                                  nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
+	                                  lease,
+	                                  options,
+	                                  TRUE,
+	                                  priv->info_only,
+	                                  &error);
+
+	if (ip6_config) {
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
+		                          NM_DHCP_STATE_BOUND,
+		                          G_OBJECT (ip6_config),
+		                          options);
+	} else {
+		_LOGW ("%s", error->message);
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+	}
+}
+
+static void
+dhcp6_event_cb (sd_dhcp6_client *client, int event, gpointer user_data)
+{
+	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (user_data);
+	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
+
+	g_assert (priv->client6 == client);
+
+	_LOGD ("client event %d", event);
+
+	switch (event) {
+	case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX:
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (user_data), NM_DHCP_STATE_TIMEOUT, NULL, NULL);
+		break;
+	case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE:
+	case SD_DHCP6_CLIENT_EVENT_STOP:
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (user_data), NM_DHCP_STATE_FAIL, NULL, NULL);
+		break;
+	case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE:
+	case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST:
+		bound6_handle (self);
+		break;
+	default:
+		_LOGW ("unhandled event %d", event);
+		break;
+	}
+}
+
+static gboolean
+ip6_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const struct in6_addr *ll_addr,
+           gboolean info_only,
+           NMSettingIP6ConfigPrivacy privacy,
+           const GByteArray *duid,
+           guint needed_prefixes)
+{
+	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
+	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
+	const char *iface = nm_dhcp_client_get_iface (client);
+	const GByteArray *hwaddr;
+	int r, i;
+
+	g_assert (priv->client4 == NULL);
+	g_assert (priv->client6 == NULL);
+	g_return_val_if_fail (duid != NULL, FALSE);
+
+	g_free (priv->lease_file);
+	priv->lease_file = get_leasefile_path (iface, nm_dhcp_client_get_uuid (client), TRUE);
+	priv->info_only = info_only;
+
+	r = sd_dhcp6_client_new (&priv->client6);
+	if (r < 0) {
+		_LOGW ("failed to create client (%d)", r);
+		return FALSE;
+	}
+
+	if (needed_prefixes > 0) {
+		_LOGW ("dhcp-client6: prefix delegation not yet supported, won't supply %d prefixes\n",
+		       needed_prefixes);
+	}
+
+	_LOGT ("dhcp-client6: set %p", priv->client6);
+
+	if (info_only)
+	    sd_dhcp6_client_set_information_request (priv->client6, 1);
+
+	/* NM stores the entire DUID which includes the uint16 "type", while systemd
+	 * wants the type passed separately from the following data.
+	 */
+	r = sd_dhcp6_client_set_duid (priv->client6,
+	                              ntohs (((const guint16 *) duid->data)[0]),
+	                              duid->data + 2,
+	                              duid->len - 2);
+	if (r < 0) {
+		_LOGW ("failed to set DUID (%d)", r);
+		return FALSE;
+	}
+
+	r = sd_dhcp6_client_attach_event (priv->client6, NULL, 0);
+	if (r < 0) {
+		_LOGW ("failed to attach event (%d)", r);
+		goto error;
+	}
+
+	hwaddr = nm_dhcp_client_get_hw_addr (client);
+	if (hwaddr) {
+		r = sd_dhcp6_client_set_mac (priv->client6,
+		                             hwaddr->data,
+		                             hwaddr->len,
+		                             get_arp_type (hwaddr));
+		if (r < 0) {
+			_LOGW ("failed to set MAC address (%d)", r);
+			goto error;
+		}
+	}
+
+	r = sd_dhcp6_client_set_ifindex (priv->client6, nm_dhcp_client_get_ifindex (client));
+	if (r < 0) {
+		_LOGW ("failed to set ifindex (%d)", r);
+		goto error;
+	}
+
+	r = sd_dhcp6_client_set_callback (priv->client6, dhcp6_event_cb, client);
+	if (r < 0) {
+		_LOGW ("failed to set callback (%d)", r);
+		goto error;
+	}
+
+	/* Add requested options */
+	for (i = 0; dhcp6_requests[i].name; i++) {
+		if (dhcp6_requests[i].include)
+			sd_dhcp6_client_set_request_option (priv->client6, dhcp6_requests[i].num);
+	}
+
+	r = sd_dhcp6_client_set_local_address (priv->client6, ll_addr);
+	if (r < 0) {
+		_LOGW ("failed to set local address (%d)", r);
+		goto error;
+	}
+
+	r = sd_dhcp6_client_start (priv->client6);
+	if (r < 0) {
+		_LOGW ("failed to start client (%d)", r);
+		goto error;
+	}
+
+	nm_dhcp_client_start_timeout (client);
+
+	return TRUE;
+
+error:
+	sd_dhcp6_client_unref (priv->client6);
+	priv->client6 = NULL;
+	return FALSE;
+}
+
+static void
+stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+{
+	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
+	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
+	int r = 0;
+
+	_LOGT ("dhcp-client%d: stop %p",
+	       priv->client4 ? '4' : '6',
+	       priv->client4 ? (gpointer) priv->client4 : (gpointer) priv->client6);
+
+	if (priv->client4) {
+		sd_dhcp_client_set_callback (priv->client4, NULL, NULL);
+		r = sd_dhcp_client_stop (priv->client4);
+	} else if (priv->client6) {
+		sd_dhcp6_client_set_callback (priv->client6, NULL, NULL);
+		r = sd_dhcp6_client_stop (priv->client6);
+	}
+
+	if (r)
+		_LOGW ("failed to stop client (%d)", r);
+}
+
+/*****************************************************************************/
+
+static void
+nm_dhcp_systemd_init (NMDhcpSystemd *self)
+{
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE ((NMDhcpSystemd *) object);
+
+	g_clear_pointer (&priv->lease_file, g_free);
+
+	if (priv->client4) {
+		sd_dhcp_client_stop (priv->client4);
+		sd_dhcp_client_unref (priv->client4);
+		priv->client4 = NULL;
+	}
+
+	if (priv->client6) {
+		sd_dhcp6_client_stop (priv->client6);
+		sd_dhcp6_client_unref (priv->client6);
+		priv->client6 = NULL;
+	}
+
+	G_OBJECT_CLASS (nm_dhcp_systemd_parent_class)->dispose (object);
+}
+
+static void
+nm_dhcp_systemd_class_init (NMDhcpSystemdClass *sdhcp_class)
+{
+	NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS (sdhcp_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (sdhcp_class);
+
+	object_class->dispose = dispose;
+
+	client_class->ip4_start = ip4_start;
+	client_class->ip6_start = ip6_start;
+	client_class->stop = stop;
+}
+
+const NMDhcpClientFactory _nm_dhcp_client_factory_internal = {
+	.name = "internal",
+	.get_type = nm_dhcp_systemd_get_type,
+	.get_path = NULL,
+	.get_lease_ip_configs = nm_dhcp_systemd_get_lease_ip_configs,
+};
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
new file mode 100644
index 00000000..e020ca33
--- /dev/null
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -0,0 +1,779 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2010 Red Hat, Inc.
+ *
+ */
+
+#include "nm-default.h"
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+
+#include "nm-dhcp-utils.h"
+#include "nm-utils.h"
+#include "NetworkManagerUtils.h"
+#include "platform/nm-platform.h"
+#include "nm-dhcp-client-logging.h"
+#include "nm-core-internal.h"
+
+/*****************************************************************************/
+
+static gboolean
+ip4_process_dhcpcd_rfc3442_routes (const char *iface,
+                                   const char *str,
+                                   guint32 priority,
+                                   NMIP4Config *ip4_config,
+                                   guint32 *gwaddr)
+{
+	char **routes, **r;
+	gboolean have_routes = FALSE;
+
+	routes = g_strsplit (str, " ", 0);
+	if (g_strv_length (routes) == 0)
+		goto out;
+
+	if ((g_strv_length (routes) % 2) != 0) {
+		_LOG2W (LOGD_DHCP4, iface, "  classless static routes provided, but invalid");
+		goto out;
+	}
+
+	for (r = routes; *r; r += 2) {
+		char *slash;
+		NMPlatformIP4Route route;
+		int rt_cidr = 32;
+		guint32 rt_addr, rt_route;
+
+		slash = strchr(*r, '/');
+		if (slash) {
+			*slash = '\0';
+			errno = 0;
+			rt_cidr = strtol (slash + 1, NULL, 10);
+			if (errno || rt_cidr > 32) {
+				_LOG2W (LOGD_DHCP4, iface, "DHCP provided invalid classless static route cidr: '%s'", slash + 1);
+				continue;
+			}
+		}
+		if (inet_pton (AF_INET, *r, &rt_addr) <= 0) {
+			_LOG2W (LOGD_DHCP4, iface, "DHCP provided invalid classless static route address: '%s'", *r);
+			continue;
+		}
+		if (inet_pton (AF_INET, *(r + 1), &rt_route) <= 0) {
+			_LOG2W (LOGD_DHCP4, iface, "DHCP provided invalid classless static route gateway: '%s'", *(r + 1));
+			continue;
+		}
+
+		have_routes = TRUE;
+		if (rt_cidr == 0 && rt_addr == 0) {
+			/* FIXME: how to handle multiple routers? */
+			*gwaddr = rt_route;
+		} else {
+			_LOG2I (LOGD_DHCP4, iface, "  classless static route %s/%d gw %s", *r, rt_cidr, *(r + 1));
+			memset (&route, 0, sizeof (route));
+			route.network = rt_addr;
+			route.plen = rt_cidr;
+			route.gateway = rt_route;
+			route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
+			route.metric = priority;
+			nm_ip4_config_add_route (ip4_config, &route);
+		}
+	}
+
+out:
+	g_strfreev (routes);
+	return have_routes;
+}
+
+static const char **
+process_dhclient_rfc3442_route (const char **octets,
+                                NMPlatformIP4Route *route,
+                                gboolean *success)
+{
+	const char **o = octets;
+	int addr_len = 0, i = 0;
+	long int tmp;
+	char *next_hop;
+	guint32 tmp_addr;
+
+	*success = FALSE;
+
+	if (!*o)
+		return o; /* no prefix */
+
+	tmp = strtol (*o, NULL, 10);
+	if (tmp < 0 || tmp > 32)  /* 32 == max IP4 prefix length */
+		return o;
+
+	memset (route, 0, sizeof (*route));
+	route->plen = tmp;
+	o++;
+
+	if (tmp > 0)
+		addr_len = ((tmp - 1) / 8) + 1;
+
+	/* ensure there's at least the address + next hop left */
+	if (g_strv_length ((char **) o) < addr_len + 4)
+		goto error;
+
+	if (tmp) {
+		const char *addr[4] = { "0", "0", "0", "0" };
+		char *str_addr;
+
+		for (i = 0; i < addr_len; i++)
+			addr[i] = *o++;
+
+		str_addr = g_strjoin (".", addr[0], addr[1], addr[2], addr[3], NULL);
+		if (inet_pton (AF_INET, str_addr, &tmp_addr) <= 0) {
+			g_free (str_addr);
+			goto error;
+		}
+		g_free (str_addr);
+		tmp_addr &= nm_utils_ip4_prefix_to_netmask ((guint32) tmp);
+		route->network = tmp_addr;
+	}
+
+	/* Handle next hop */
+	next_hop = g_strjoin (".", o[0], o[1], o[2], o[3], NULL);
+	if (inet_pton (AF_INET, next_hop, &tmp_addr) <= 0) {
+		g_free (next_hop);
+		goto error;
+	}
+	route->gateway = tmp_addr;
+	g_free (next_hop);
+
+	*success = TRUE;
+	return o + 4; /* advance to past the next hop */
+
+error:
+	return o;
+}
+
+static gboolean
+ip4_process_dhclient_rfc3442_routes (const char *iface,
+                                     const char *str,
+                                     guint32 priority,
+                                     NMIP4Config *ip4_config,
+                                     guint32 *gwaddr)
+{
+	char **octets, **o;
+	gboolean have_routes = FALSE;
+	NMPlatformIP4Route route;
+	gboolean success;
+
+	o = octets = g_strsplit_set (str, " .", 0);
+	if (g_strv_length (octets) < 5) {
+		_LOG2W (LOGD_DHCP4, iface, "ignoring invalid classless static routes '%s'", str);
+		goto out;
+	}
+
+	while (*o) {
+		memset (&route, 0, sizeof (route));
+		o = (char **) process_dhclient_rfc3442_route ((const char **) o, &route, &success);
+		if (!success) {
+			_LOG2W (LOGD_DHCP4, iface, "ignoring invalid classless static routes");
+			break;
+		}
+
+		have_routes = TRUE;
+		if (!route.plen) {
+			/* gateway passed as classless static route */
+			*gwaddr = route.gateway;
+		} else {
+			char addr[INET_ADDRSTRLEN];
+
+			/* normal route */
+			route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
+			route.metric = priority;
+			nm_ip4_config_add_route (ip4_config, &route);
+
+			_LOG2I (LOGD_DHCP4, iface, "  classless static route %s/%d gw %s",
+			        nm_utils_inet4_ntop (route.network, addr), route.plen,
+			        nm_utils_inet4_ntop (route.gateway, NULL));
+		}
+	}
+
+out:
+	g_strfreev (octets);
+	return have_routes;
+}
+
+static gboolean
+ip4_process_classless_routes (const char *iface,
+                              GHashTable *options,
+                              guint32 priority,
+                              NMIP4Config *ip4_config,
+                              guint32 *gwaddr)
+{
+	const char *str, *p;
+
+	g_return_val_if_fail (options != NULL, FALSE);
+	g_return_val_if_fail (ip4_config != NULL, FALSE);
+
+	*gwaddr = 0;
+
+	/* dhcpd/dhclient in Fedora has support for rfc3442 implemented using a
+	 * slightly different format:
+	 *
+	 * option classless-static-routes = array of (destination-descriptor ip-address);
+	 *
+	 * which results in:
+	 *
+	 * 0 192.168.0.113 25.129.210.177.132 192.168.0.113 7.2 10.34.255.6
+	 *
+	 * dhcpcd supports classless static routes natively and uses this same
+	 * option identifier with the following format:
+	 *
+	 * 192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41
+	 */
+	str = g_hash_table_lookup (options, "classless_static_routes");
+
+	/* dhclient doesn't have actual support for rfc3442 classless static routes
+	 * upstream.  Thus, people resort to defining the option in dhclient.conf
+	 * and using arbitrary formats like so:
+	 *
+	 * option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
+	 *
+	 * See https://lists.isc.org/pipermail/dhcp-users/2008-December/007629.html
+	 */
+	if (!str)
+		str = g_hash_table_lookup (options, "rfc3442_classless_static_routes");
+
+	/* Microsoft version; same as rfc3442 but with a different option # (249) */
+	if (!str)
+		str = g_hash_table_lookup (options, "ms_classless_static_routes");
+
+	if (!str || !strlen (str))
+		return FALSE;
+
+	p = str;
+	while (*p) {
+		if (!g_ascii_isdigit (*p) && (*p != ' ') && (*p != '.') && (*p != '/')) {
+			_LOG2W (LOGD_DHCP4, iface, "ignoring invalid classless static routes '%s'", str);
+			return FALSE;
+		}
+		p++;
+	};
+
+	if (strchr (str, '/')) {
+		/* dhcpcd format */
+		return ip4_process_dhcpcd_rfc3442_routes (iface, str, priority, ip4_config, gwaddr);
+	}
+
+	return ip4_process_dhclient_rfc3442_routes (iface, str, priority, ip4_config, gwaddr);
+}
+
+static void
+process_classful_routes (const char *iface,
+                         GHashTable *options,
+                         guint32 priority,
+                         NMIP4Config *ip4_config)
+{
+	const char *str;
+	char **searches, **s;
+
+	str = g_hash_table_lookup (options, "static_routes");
+	if (!str)
+		return;
+
+	searches = g_strsplit (str, " ", 0);
+	if ((g_strv_length (searches) % 2)) {
+		_LOG2I (LOGD_DHCP, iface, "  static routes provided, but invalid");
+		goto out;
+	}
+
+	for (s = searches; *s; s += 2) {
+		NMPlatformIP4Route route;
+		guint32 rt_addr, rt_route;
+
+		if (inet_pton (AF_INET, *s, &rt_addr) <= 0) {
+			_LOG2W (LOGD_DHCP, iface, "DHCP provided invalid static route address: '%s'", *s);
+			continue;
+		}
+		if (inet_pton (AF_INET, *(s + 1), &rt_route) <= 0) {
+			_LOG2W (LOGD_DHCP, iface, "DHCP provided invalid static route gateway: '%s'", *(s + 1));
+			continue;
+		}
+
+		// FIXME: ensure the IP address and route are sane
+
+		memset (&route, 0, sizeof (route));
+		route.network = rt_addr;
+		/* RFC 2132, updated by RFC 3442:
+		   The Static Routes option (option 33) does not provide a subnet mask
+		   for each route - it is assumed that the subnet mask is implicit in
+		   whatever network number is specified in each route entry */
+		route.plen = nm_utils_ip4_get_default_prefix (rt_addr);
+		if (rt_addr & ~nm_utils_ip4_prefix_to_netmask (route.plen)) {
+			/* RFC 943: target not "this network"; using host routing */
+			route.plen = 32;
+		}
+		route.gateway = rt_route;
+		route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
+		route.metric = priority;
+
+		nm_ip4_config_add_route (ip4_config, &route);
+		_LOG2I (LOGD_DHCP, iface, "  static route %s",
+		             nm_platform_ip4_route_to_string (&route, NULL, 0));
+	}
+
+out:
+	g_strfreev (searches);
+}
+
+static void
+process_domain_search (const char *iface,
+                       const char *str,
+                       GFunc add_func,
+                       gpointer user_data)
+{
+	char **searches, **s;
+	char *unescaped, *p;
+	int i;
+
+	g_return_if_fail (str != NULL);
+	g_return_if_fail (add_func != NULL);
+
+	p = unescaped = g_strdup (str);
+	do {
+		p = strstr (p, "\\032");
+		if (!p)
+			break;
+
+		/* Clear the escaped space with real spaces */
+		for (i = 0; i < 4; i++)
+			*p++ = ' ';
+	} while (*p++);
+
+	if (strchr (unescaped, '\\')) {
+		_LOG2W (LOGD_DHCP, iface, "  invalid domain search: '%s'", unescaped);
+		goto out;
+	}
+
+	searches = g_strsplit (unescaped, " ", 0);
+	for (s = searches; *s; s++) {
+		if (strlen (*s)) {
+			_LOG2I (LOGD_DHCP, iface, "  domain search '%s'", *s);
+			add_func (*s, user_data);
+		}
+	}
+	g_strfreev (searches);
+
+out:
+	g_free (unescaped);
+}
+
+static void
+ip4_add_domain_search (gpointer data, gpointer user_data)
+{
+	nm_ip4_config_add_search (NM_IP4_CONFIG (user_data), (const char *) data);
+}
+
+NMIP4Config *
+nm_dhcp_utils_ip4_config_from_options (int ifindex,
+                                       const char *iface,
+                                       GHashTable *options,
+                                       guint32 priority)
+{
+	NMIP4Config *ip4_config = NULL;
+	guint32 tmp_addr;
+	in_addr_t addr;
+	NMPlatformIP4Address address;
+	char *str = NULL;
+	guint32 gwaddr = 0;
+	guint8 plen = 0;
+
+	g_return_val_if_fail (options != NULL, NULL);
+
+	ip4_config = nm_ip4_config_new (ifindex);
+	memset (&address, 0, sizeof (address));
+	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+
+	str = g_hash_table_lookup (options, "ip_address");
+	if (str && (inet_pton (AF_INET, str, &addr) > 0))
+		_LOG2I (LOGD_DHCP4, iface, "  address %s", str);
+	else
+		goto error;
+
+	str = g_hash_table_lookup (options, "subnet_mask");
+	if (str && (inet_pton (AF_INET, str, &tmp_addr) > 0)) {
+		plen = nm_utils_ip4_netmask_to_prefix (tmp_addr);
+		_LOG2I (LOGD_DHCP4, iface, "  plen %d (%s)", plen, str);
+	} else {
+		/* Get default netmask for the IP according to appropriate class. */
+		plen = nm_utils_ip4_get_default_prefix (addr);
+		_LOG2I (LOGD_DHCP4, iface, "  plen %d (default)", plen);
+	}
+	nm_platform_ip4_address_set_addr (&address, addr, plen);
+
+	/* Routes: if the server returns classless static routes, we MUST ignore
+	 * the 'static_routes' option.
+	 */
+	if (!ip4_process_classless_routes (iface, options, priority, ip4_config, &gwaddr))
+		process_classful_routes (iface, options, priority, ip4_config);
+
+	if (gwaddr) {
+		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", nm_utils_inet4_ntop (gwaddr, NULL));
+		nm_ip4_config_set_gateway (ip4_config, gwaddr);
+	} else {
+		/* If the gateway wasn't provided as a classless static route with a
+		 * subnet length of 0, try to find it using the old-style 'routers' option.
+		 */
+		str = g_hash_table_lookup (options, "routers");
+		if (str) {
+			char **routers = g_strsplit (str, " ", 0);
+			char **s;
+
+			for (s = routers; *s; s++) {
+				/* FIXME: how to handle multiple routers? */
+				if (inet_pton (AF_INET, *s, &gwaddr) > 0) {
+					nm_ip4_config_set_gateway (ip4_config, gwaddr);
+					_LOG2I (LOGD_DHCP4, iface, "  gateway %s", *s);
+					break;
+				} else
+					_LOG2W (LOGD_DHCP4, iface, "ignoring invalid gateway '%s'", *s);
+			}
+			g_strfreev (routers);
+		}
+	}
+
+	/*
+	 * RFC 2132, section 9.7
+	 *   DHCP clients use the contents of the 'server identifier' field
+	 *   as the destination address for any DHCP messages unicast to
+	 *   the DHCP server.
+	 *
+	 * Some ISP's provide leases from central servers that are on
+	 * different subnets that the address offered.  If the host
+	 * does not configure the interface as the default route, the
+	 * dhcp server may not be reachable via unicast, and a host
+	 * specific route is needed.
+	 **/
+	str = g_hash_table_lookup (options, "dhcp_server_identifier");
+	if (str) {
+		if (inet_pton (AF_INET, str, &tmp_addr) > 0) {
+
+			_LOG2I (LOGD_DHCP4, iface, "  server identifier %s", str);
+			if (   nm_utils_ip4_address_clear_host_address(tmp_addr, address.plen) != nm_utils_ip4_address_clear_host_address(address.address, address.plen)
+			    && !nm_ip4_config_get_direct_route_for_host (ip4_config, tmp_addr)) {
+				/* DHCP server not on assigned subnet and the no direct route was returned. Add route */
+				NMPlatformIP4Route route = { 0 };
+
+				route.network = tmp_addr;
+				route.plen = 32;
+				/* this will be a device route if gwaddr is 0 */
+				route.gateway = gwaddr;
+				route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
+				route.metric = priority;
+				nm_ip4_config_add_route (ip4_config, &route);
+				_LOG2D (LOGD_IP, iface, "adding route for server identifier: %s",
+				        nm_platform_ip4_route_to_string (&route, NULL, 0));
+			}
+		}
+		else
+			_LOG2W (LOGD_DHCP4, iface, "ignoring invalid server identifier '%s'", str);
+	}
+
+	str = g_hash_table_lookup (options, "dhcp_lease_time");
+	if (str) {
+		address.lifetime = address.preferred = strtoul (str, NULL, 10);
+		_LOG2I (LOGD_DHCP4, iface, "  lease time %u", address.lifetime);
+	}
+
+	address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
+	nm_ip4_config_add_address (ip4_config, &address);
+
+	str = g_hash_table_lookup (options, "host_name");
+	if (str)
+		_LOG2I (LOGD_DHCP4, iface, "  hostname '%s'", str);
+
+	str = g_hash_table_lookup (options, "domain_name_servers");
+	if (str) {
+		char **dns = g_strsplit (str, " ", 0);
+		char **s;
+
+		for (s = dns; *s; s++) {
+			if (inet_pton (AF_INET, *s, &tmp_addr) > 0) {
+				if (tmp_addr) {
+					nm_ip4_config_add_nameserver (ip4_config, tmp_addr);
+					_LOG2I (LOGD_DHCP4, iface, "  nameserver '%s'", *s);
+				}
+			} else
+				_LOG2W (LOGD_DHCP4, iface, "ignoring invalid nameserver '%s'", *s);
+		}
+		g_strfreev (dns);
+	}
+
+	str = g_hash_table_lookup (options, "domain_name");
+	if (str) {
+		char **domains = g_strsplit (str, " ", 0);
+		char **s;
+
+		for (s = domains; *s; s++) {
+			_LOG2I (LOGD_DHCP4, iface, "  domain name '%s'", *s);
+			nm_ip4_config_add_domain (ip4_config, *s);
+		}
+		g_strfreev (domains);
+	}
+
+	str = g_hash_table_lookup (options, "domain_search");
+	if (str)
+		process_domain_search (iface, str, ip4_add_domain_search, ip4_config);
+
+	str = g_hash_table_lookup (options, "netbios_name_servers");
+	if (str) {
+		char **nbns = g_strsplit (str, " ", 0);
+		char **s;
+
+		for (s = nbns; *s; s++) {
+			if (inet_pton (AF_INET, *s, &tmp_addr) > 0) {
+				if (tmp_addr) {
+					nm_ip4_config_add_wins (ip4_config, tmp_addr);
+					_LOG2I (LOGD_DHCP4, iface, "  wins '%s'", *s);
+				}
+			} else
+				_LOG2W (LOGD_DHCP4, iface, "ignoring invalid WINS server '%s'", *s);
+		}
+		g_strfreev (nbns);
+	}
+
+	str = g_hash_table_lookup (options, "interface_mtu");
+	if (str) {
+		int int_mtu;
+
+		errno = 0;
+		int_mtu = strtol (str, NULL, 10);
+		if ((errno == EINVAL) || (errno == ERANGE))
+			goto error;
+
+		if (int_mtu > 576)
+			nm_ip4_config_set_mtu (ip4_config, int_mtu, NM_IP_CONFIG_SOURCE_DHCP);
+	}
+
+	str = g_hash_table_lookup (options, "nis_domain");
+	if (str) {
+		_LOG2I (LOGD_DHCP4, iface, "  NIS domain '%s'", str);
+		nm_ip4_config_set_nis_domain (ip4_config, str);
+	}
+
+	str = g_hash_table_lookup (options, "nis_servers");
+	if (str) {
+		char **nis = g_strsplit (str, " ", 0);
+		char **s;
+
+		for (s = nis; *s; s++) {
+			if (inet_pton (AF_INET, *s, &tmp_addr) > 0) {
+				if (tmp_addr) {
+					nm_ip4_config_add_nis_server (ip4_config, tmp_addr);
+					_LOG2I (LOGD_DHCP4, iface, "  nis '%s'", *s);
+				}
+			} else
+				_LOG2W (LOGD_DHCP4, iface, "ignoring invalid NIS server '%s'", *s);
+		}
+		g_strfreev (nis);
+	}
+
+	str = g_hash_table_lookup (options, "vendor_encapsulated_options");
+	nm_ip4_config_set_metered (ip4_config, str && strstr (str, "ANDROID_METERED"));
+
+	return ip4_config;
+
+error:
+	g_object_unref (ip4_config);
+	return NULL;
+}
+
+/*****************************************************************************/
+
+static void
+ip6_add_domain_search (gpointer data, gpointer user_data)
+{
+	nm_ip6_config_add_search (NM_IP6_CONFIG (user_data), (const char *) data);
+}
+
+NMPlatformIP6Address
+nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options)
+{
+	gs_strfreev gchar **split_addr = NULL;
+	NMPlatformIP6Address address = { 0, };
+	struct in6_addr tmp_addr;
+	char *str = NULL;
+	int prefix;
+
+	g_return_val_if_fail (options != NULL, address);
+
+	str = g_hash_table_lookup (options, "ip6_prefix");
+	if (!str)
+		return address;
+
+	split_addr = g_strsplit (str, "/", 2);
+	if (split_addr[0] == NULL && split_addr[1] == NULL) {
+		nm_log_warn (LOGD_DHCP6, "DHCP returned prefix without length '%s'", str);
+		return address;
+	}
+
+	if (!inet_pton (AF_INET6, split_addr[0], &tmp_addr)) {
+		nm_log_warn (LOGD_DHCP6, "DHCP returned invalid prefix '%s'", str);
+		return address;
+	}
+
+	prefix = _nm_utils_ascii_str_to_int64 (split_addr[1], 10, 0, 128, -1);
+	if (prefix < 0) {
+		nm_log_warn (LOGD_DHCP6, "DHCP returned prefix with invalid length '%s'", str);
+		return address;
+	}
+
+	address.address = tmp_addr;
+	address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
+	address.plen = prefix;
+	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+
+	str = g_hash_table_lookup (options, "max_life");
+	if (str)
+		address.lifetime = strtoul (str, NULL, 10);
+
+	str = g_hash_table_lookup (options, "preferred_life");
+	if (str)
+		address.preferred = strtoul (str, NULL, 10);
+
+	return address;
+}
+
+NMIP6Config *
+nm_dhcp_utils_ip6_config_from_options (int ifindex,
+                                       const char *iface,
+                                       GHashTable *options,
+                                       guint32 priority,
+                                       gboolean info_only)
+{
+	NMIP6Config *ip6_config = NULL;
+	struct in6_addr tmp_addr;
+	NMPlatformIP6Address address;
+	char *str = NULL;
+
+	g_return_val_if_fail (options != NULL, NULL);
+
+	memset (&address, 0, sizeof (address));
+	address.plen = 128;
+	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+
+	ip6_config = nm_ip6_config_new (ifindex);
+
+	str = g_hash_table_lookup (options, "max_life");
+	if (str) {
+		address.lifetime = strtoul (str, NULL, 10);
+		_LOG2I (LOGD_DHCP6, iface, "  valid_lft %u", address.lifetime);
+	}
+
+	str = g_hash_table_lookup (options, "preferred_life");
+	if (str) {
+		address.preferred = strtoul (str, NULL, 10);
+		_LOG2I (LOGD_DHCP6, iface, "  preferred_lft %u", address.preferred);
+	}
+
+	str = g_hash_table_lookup (options, "ip6_address");
+	if (str) {
+		if (!inet_pton (AF_INET6, str, &tmp_addr)) {
+			_LOG2W (LOGD_DHCP6, iface, "(%s): DHCP returned invalid address '%s'",
+			        iface, str);
+			goto error;
+		}
+
+		address.address = tmp_addr;
+		address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
+		nm_ip6_config_add_address (ip6_config, &address);
+		_LOG2I (LOGD_DHCP6, iface, "  address %s", str);
+	} else if (info_only == FALSE) {
+		/* No address in Managed mode is a hard error */
+		goto error;
+	}
+
+	str = g_hash_table_lookup (options, "host_name");
+	if (str)
+		_LOG2I (LOGD_DHCP6, iface, "  hostname '%s'", str);
+
+	str = g_hash_table_lookup (options, "dhcp6_name_servers");
+	if (str) {
+		char **dns = g_strsplit (str, " ", 0);
+		char **s;
+
+		for (s = dns; *s; s++) {
+			if (inet_pton (AF_INET6, *s, &tmp_addr) > 0) {
+				if (!IN6_IS_ADDR_UNSPECIFIED (&tmp_addr)) {
+					nm_ip6_config_add_nameserver (ip6_config, &tmp_addr);
+					_LOG2I (LOGD_DHCP6, iface, "  nameserver '%s'", *s);
+				}
+			} else
+				_LOG2W (LOGD_DHCP6, iface, "ignoring invalid nameserver '%s'", *s);
+		}
+		g_strfreev (dns);
+	}
+
+	str = g_hash_table_lookup (options, "dhcp6_domain_search");
+	if (str)
+		process_domain_search (iface, str, ip6_add_domain_search, ip6_config);
+
+	return ip6_config;
+
+error:
+	g_object_unref (ip6_config);
+	return NULL;
+}
+
+char *
+nm_dhcp_utils_duid_to_string (const GByteArray *duid)
+{
+	g_return_val_if_fail (duid != NULL, NULL);
+
+	return _nm_utils_bin2str (duid->data, duid->len, FALSE);
+}
+
+/**
+ * nm_dhcp_utils_client_id_string_to_bytes:
+ * @client_id: the client ID string
+ *
+ * Accepts either a hex string ("aa:bb:cc") representing a binary client ID
+ * (the first byte is assumed to be the 'type' field per RFC 2132 section 9.14),
+ * or a string representing a non-hardware-address client ID, in which case
+ * the 'type' field is set to 0.
+ *
+ * Returns: the binary client ID suitable for sending over the wire
+ * to the DHCP server.
+ */
+GBytes *
+nm_dhcp_utils_client_id_string_to_bytes (const char *client_id)
+{
+	GBytes *bytes = NULL;
+	guint len;
+	char *c;
+
+	g_return_val_if_fail (client_id && client_id[0], NULL);
+
+	/* Try as hex encoded */
+	if (strchr (client_id, ':'))
+		bytes = nm_utils_hexstr2bin (client_id);
+	if (!bytes) {
+		/* Fall back to string */
+		len = strlen (client_id);
+		c = g_malloc (len + 1);
+		c[0] = 0;  /* type: non-hardware address per RFC 2132 section 9.14 */
+		memcpy (c + 1, client_id, len);
+		bytes = g_bytes_new_take (c, len + 1);
+	}
+
+	return bytes;
+}
+
diff --git a/src/dhcp/nm-dhcp-utils.h b/src/dhcp/nm-dhcp-utils.h
new file mode 100644
index 00000000..b45c5e89
--- /dev/null
+++ b/src/dhcp/nm-dhcp-utils.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DHCP_UTILS_H__
+#define __NETWORKMANAGER_DHCP_UTILS_H__
+
+#include <stdlib.h>
+
+#include <nm-ip4-config.h>
+#include <nm-ip6-config.h>
+
+NMIP4Config *nm_dhcp_utils_ip4_config_from_options (int ifindex,
+                                                    const char *iface,
+                                                    GHashTable *options,
+                                                    guint priority);
+
+NMIP6Config *nm_dhcp_utils_ip6_config_from_options (int ifindex,
+                                                    const char *iface,
+                                                    GHashTable *options,
+                                                    guint priority,
+                                                    gboolean info_only);
+
+NMPlatformIP6Address nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options);
+
+char *       nm_dhcp_utils_duid_to_string          (const GByteArray *duid);
+
+GBytes *     nm_dhcp_utils_client_id_string_to_bytes (const char *client_id);
+
+#endif /* __NETWORKMANAGER_DHCP_UTILS_H__ */
+
diff --git a/src/dhcp/tests/leases/basic.leases b/src/dhcp/tests/leases/basic.leases
new file mode 100644
index 00000000..703d9247
--- /dev/null
+++ b/src/dhcp/tests/leases/basic.leases
@@ -0,0 +1,31 @@
+lease {
+  interface "wlan0";
+  fixed-address 192.168.1.180;
+  option subnet-mask 255.255.255.0;
+  option routers 192.168.1.1;
+  option dhcp-lease-time 600;
+  option dhcp-message-type 5;
+  option domain-name-servers 192.168.1.1;
+  option dhcp-server-identifier 192.168.1.1;
+  option broadcast-address 192.168.1.255;
+  renew 5 2013/11/01 19:56:15;
+  rebind 5 2013/11/01 20:00:44;
+  expire 5 2013/11/01 20:01:59;
+}
+lease {
+  interface "wlan0";
+  fixed-address 10.77.52.141;
+  option subnet-mask 255.0.0.0;
+  option dhcp-lease-time 1200;
+  option routers 10.77.52.254;
+  option dhcp-message-type 5;
+  option dhcp-server-identifier 10.77.52.254;
+  option domain-name-servers 8.8.8.8,8.8.4.4;
+  option dhcp-renewal-time 600;
+  option dhcp-rebinding-time 1050;
+  option domain-name "morriesguest.local";
+  renew 5 2013/11/01 20:01:08;
+  rebind 5 2013/11/01 20:05:00;
+  expire 5 2013/11/01 20:06:15;
+}
+
diff --git a/src/dhcp/tests/leases/malformed1.leases b/src/dhcp/tests/leases/malformed1.leases
new file mode 100644
index 00000000..401d982a
--- /dev/null
+++ b/src/dhcp/tests/leases/malformed1.leases
@@ -0,0 +1,15 @@
+# missing fixed-address option
+lease {
+  interface "wlan0";
+  option subnet-mask 255.255.255.0;
+  option routers 192.168.1.1;
+  option dhcp-lease-time 600;
+  option dhcp-message-type 5;
+  option domain-name-servers 192.168.1.1;
+  option dhcp-server-identifier 192.168.1.1;
+  option broadcast-address 192.168.1.255;
+  renew 5 2013/11/01 19:56:15;
+  rebind 5 2013/11/01 20:00:44;
+  expire 5 2013/11/01 20:01:59;
+}
+
diff --git a/src/dhcp/tests/leases/malformed2.leases b/src/dhcp/tests/leases/malformed2.leases
new file mode 100644
index 00000000..adf5f6de
--- /dev/null
+++ b/src/dhcp/tests/leases/malformed2.leases
@@ -0,0 +1,15 @@
+# missing routers option
+lease {
+  interface "wlan0";
+  fixed-address 192.168.1.180;
+  option subnet-mask 255.255.255.0;
+  option dhcp-lease-time 600;
+  option dhcp-message-type 5;
+  option domain-name-servers 192.168.1.1;
+  option dhcp-server-identifier 192.168.1.1;
+  option broadcast-address 192.168.1.255;
+  renew 5 2013/11/01 19:56:15;
+  rebind 5 2013/11/01 20:00:44;
+  expire 5 2013/11/01 20:01:59;
+}
+
diff --git a/src/dhcp/tests/leases/malformed3.leases b/src/dhcp/tests/leases/malformed3.leases
new file mode 100644
index 00000000..a2afc8b6
--- /dev/null
+++ b/src/dhcp/tests/leases/malformed3.leases
@@ -0,0 +1,15 @@
+# missing expire time
+lease {
+  interface "wlan0";
+  fixed-address 192.168.1.180;
+  option subnet-mask 255.255.255.0;
+  option routers 192.168.1.1;
+  option dhcp-lease-time 600;
+  option dhcp-message-type 5;
+  option domain-name-servers 192.168.1.1;
+  option dhcp-server-identifier 192.168.1.1;
+  option broadcast-address 192.168.1.255;
+  renew 5 2013/11/01 19:56:15;
+  rebind 5 2013/11/01 20:00:44;
+}
+
diff --git a/src/dhcp/tests/test-dhclient-commented-duid.leases b/src/dhcp/tests/test-dhclient-commented-duid.leases
new file mode 100644
index 00000000..3e46ae7d
--- /dev/null
+++ b/src/dhcp/tests/test-dhclient-commented-duid.leases
@@ -0,0 +1,2 @@
+#default-duid "\000\001\000\001\030y\246\023`g \354Lp";
+
diff --git a/src/dhcp/tests/test-dhclient-duid.leases b/src/dhcp/tests/test-dhclient-duid.leases
new file mode 100644
index 00000000..229331d4
--- /dev/null
+++ b/src/dhcp/tests/test-dhclient-duid.leases
@@ -0,0 +1,2 @@
+default-duid "\000\001\000\001\030y\246\023`g \354Lp";
+
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
new file mode 100644
index 00000000..82f0c080
--- /dev/null
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -0,0 +1,915 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ */
+
+#include "nm-default.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+
+#include "NetworkManagerUtils.h"
+#include "dhcp/nm-dhcp-dhclient-utils.h"
+#include "dhcp/nm-dhcp-utils.h"
+#include "nm-utils.h"
+#include "nm-ip4-config.h"
+#include "platform/nm-platform.h"
+
+#include "nm-test-utils-core.h"
+
+#define DEBUG 1
+
+static void
+test_config (const char *orig,
+             const char *expected,
+             gboolean ipv6,
+             const char *hostname,
+             const char *fqdn,
+             const char *dhcp_client_id,
+             GBytes *expected_new_client_id,
+             const char *iface,
+             const char *anycast_addr)
+{
+	gs_free char *new = NULL;
+	gs_unref_bytes GBytes *client_id = NULL;
+	gs_unref_bytes GBytes *new_client_id = NULL;
+
+	if (dhcp_client_id) {
+		client_id = nm_dhcp_utils_client_id_string_to_bytes (dhcp_client_id);
+		g_assert (client_id);
+	}
+
+	new = nm_dhcp_dhclient_create_config (iface,
+	                                      ipv6,
+	                                      client_id,
+	                                      anycast_addr,
+	                                      hostname,
+	                                      fqdn,
+	                                      "/path/to/dhclient.conf",
+	                                      orig,
+	                                      &new_client_id);
+	g_assert (new != NULL);
+
+#if DEBUG
+	if (   strlen (new) != strlen (expected)
+	    || strcmp (new, expected)) {
+		g_message ("\n- NEW ---------------------------------\n"
+		           "%s"
+		           "+ EXPECTED ++++++++++++++++++++++++++++++\n"
+		           "%s"
+		           "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
+		           new, expected);
+	}
+#endif
+	g_assert_cmpstr (new, ==, expected);
+
+	if (expected_new_client_id) {
+		g_assert (new_client_id);
+		g_assert (g_bytes_equal (new_client_id, expected_new_client_id));
+	 } else
+		g_assert (new_client_id == NULL);
+}
+
+/*****************************************************************************/
+
+static const char *orig_missing_expected = \
+	"# Created by NetworkManager\n"
+	"\n\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_orig_missing (void)
+{
+	test_config (NULL, orig_missing_expected, FALSE, NULL, NULL, NULL, NULL, "eth0", NULL);
+}
+
+/*****************************************************************************/
+
+static const char *override_client_id_orig = \
+	"send dhcp-client-identifier 00:30:04:20:7A:08;\n";
+
+static const char *override_client_id_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n"
+	"send dhcp-client-identifier 11:22:33:44:55:66; # added by NetworkManager\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_override_client_id (void)
+{
+	test_config (override_client_id_orig, override_client_id_expected,
+	             FALSE, NULL, NULL,
+	             "11:22:33:44:55:66",
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *quote_client_id_expected = \
+	"# Created by NetworkManager\n"
+	"\n"
+	"send dhcp-client-identifier \"1234\"; # added by NetworkManager\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_quote_client_id (void)
+{
+	test_config (NULL, quote_client_id_expected,
+	             FALSE, NULL, NULL,
+	             "1234",
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *ascii_client_id_expected = \
+	"# Created by NetworkManager\n"
+	"\n"
+	"send dhcp-client-identifier \"qb:cd:ef:12:34:56\"; # added by NetworkManager\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_ascii_client_id (void)
+{
+	test_config (NULL, ascii_client_id_expected,
+	             FALSE, NULL, NULL,
+	             "qb:cd:ef:12:34:56",
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *hex_single_client_id_expected = \
+	"# Created by NetworkManager\n"
+	"\n"
+	"send dhcp-client-identifier ab:cd:0e:12:34:56; # added by NetworkManager\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_hex_single_client_id (void)
+{
+	test_config (NULL, hex_single_client_id_expected,
+	             FALSE, NULL, NULL,
+	             "ab:cd:e:12:34:56",
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *existing_hex_client_id_orig = \
+	"send dhcp-client-identifier 00:30:04:20:7A:08;\n";
+
+static const char *existing_hex_client_id_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n"
+	"send dhcp-client-identifier 00:30:04:20:7A:08;\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_existing_hex_client_id (void)
+{
+	gs_unref_bytes GBytes *new_client_id = NULL;
+	const guint8 bytes[] = { 0x00, 0x30, 0x04,0x20, 0x7A, 0x08 };
+
+	new_client_id = g_bytes_new (bytes, sizeof (bytes));
+	test_config (existing_hex_client_id_orig, existing_hex_client_id_expected,
+	             FALSE, NULL, NULL,
+	             NULL,
+	             new_client_id,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+#define EACID "qb:cd:ef:12:34:56"
+
+static const char *existing_ascii_client_id_orig = \
+	"send dhcp-client-identifier \"" EACID "\";\n";
+
+static const char *existing_ascii_client_id_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n"
+	"send dhcp-client-identifier \"" EACID "\";\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_existing_ascii_client_id (void)
+{
+	gs_unref_bytes GBytes *new_client_id = NULL;
+	char buf[NM_STRLEN (EACID) + 1] = { 0 };
+
+	memcpy (buf + 1, EACID, NM_STRLEN (EACID));
+	new_client_id = g_bytes_new (buf, sizeof (buf));
+	test_config (existing_ascii_client_id_orig, existing_ascii_client_id_expected,
+	             FALSE, NULL, NULL,
+	             NULL,
+	             new_client_id,
+	             "eth0",
+	             NULL);
+}
+/*****************************************************************************/
+
+static const char *fqdn_expected = \
+	"# Created by NetworkManager\n"
+	"\n"
+	"send fqdn.fqdn \"foo.bar.com\"; # added by NetworkManager\n"
+	"send fqdn.encoded on;\n"
+	"send fqdn.server-update on;\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n\n";
+
+static void
+test_fqdn (void)
+{
+	test_config (NULL, fqdn_expected,
+	             FALSE, NULL,
+	             "foo.bar.com", NULL,
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+static const char *fqdn_options_override_orig = \
+	"\n"
+	"send fqdn.fqdn \"foobar.com\"\n"    /* NM must ignore this ... */
+	"send fqdn.encoded off;\n"           /* ... and honor these */
+	"send fqdn.server-update off;\n";
+
+static const char *fqdn_options_override_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n"
+	"send fqdn.fqdn \"example2.com\"; # added by NetworkManager\n"
+	"send fqdn.encoded on;\n"
+	"send fqdn.server-update on;\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n"
+	"# FQDN options from /path/to/dhclient.conf\n"
+	"send fqdn.encoded off;\n"
+	"send fqdn.server-update off;\n\n";
+
+static void
+test_fqdn_options_override (void)
+{
+	test_config (fqdn_options_override_orig,
+	             fqdn_options_override_expected,
+	             FALSE, NULL,
+	             "example2.com", NULL,
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *override_hostname_orig = \
+	"send host-name \"foobar\";\n";
+
+static const char *override_hostname_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n"
+	"send host-name \"blahblah\"; # added by NetworkManager\n"
+	"\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_override_hostname (void)
+{
+	test_config (override_hostname_orig, override_hostname_expected,
+	             FALSE, "blahblah", NULL,
+	             NULL,
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *override_hostname6_orig = \
+	"send fqdn.fqdn \"foobar\";\n";
+
+static const char *override_hostname6_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n"
+	"send fqdn.fqdn \"blahblah.local\"; # added by NetworkManager\n"
+	"send fqdn.encoded on;\n"
+	"send fqdn.server-update on;\n"
+	"\n"
+	"also request dhcp6.name-servers;\n"
+	"also request dhcp6.domain-search;\n"
+	"also request dhcp6.client-id;\n"
+	"\n";
+
+static void
+test_override_hostname6 (void)
+{
+	test_config (override_hostname6_orig, override_hostname6_expected,
+	             TRUE, "blahblah.local", NULL,
+	             NULL,
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *nonfqdn_hostname6_expected = \
+	"# Created by NetworkManager\n"
+	"\n"
+	"also request dhcp6.name-servers;\n"
+	"also request dhcp6.domain-search;\n"
+	"also request dhcp6.client-id;\n"
+	"\n";
+
+static void
+test_nonfqdn_hostname6 (void)
+{
+	/* Non-FQDN hostname can't be used with dhclient */
+	test_config (NULL, nonfqdn_hostname6_expected,
+	             TRUE, "blahblah",
+	             NULL, NULL,
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *existing_alsoreq_orig = \
+	"also request something;\n"
+	"also request another-thing;\n"
+	;
+
+static const char *existing_alsoreq_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request something;\n"
+	"also request another-thing;\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_existing_alsoreq (void)
+{
+	test_config (existing_alsoreq_orig, existing_alsoreq_expected,
+	             FALSE, NULL,
+	             NULL,
+	             NULL,
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *existing_req_orig = \
+	"request something;\n"
+	"also request some-other-thing;\n"
+	"request another-thing;\n"
+	"also request yet-another-thing;\n"
+	;
+
+static const char *existing_req_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request another-thing;\n"
+	"also request yet-another-thing;\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_existing_req (void)
+{
+	test_config (existing_req_orig, existing_req_expected,
+	             FALSE, NULL,
+	             NULL,
+	             NULL,
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static const char *existing_multiline_alsoreq_orig = \
+	"also request something another-thing yet-another-thing\n"
+	"    foobar baz blah;\n"
+	;
+
+static const char *existing_multiline_alsoreq_expected = \
+	"# Created by NetworkManager\n"
+	"# Merged from /path/to/dhclient.conf\n"
+	"\n\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request something;\n"
+	"also request another-thing;\n"
+	"also request yet-another-thing;\n"
+	"also request foobar;\n"
+	"also request baz;\n"
+	"also request blah;\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"\n";
+
+static void
+test_existing_multiline_alsoreq (void)
+{
+	test_config (existing_multiline_alsoreq_orig, existing_multiline_alsoreq_expected,
+	             FALSE, NULL, NULL,
+	             NULL,
+	             NULL,
+	             "eth0",
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static void
+test_one_duid (const char *escaped, const guint8 *unescaped, guint len)
+{
+	GByteArray *t;
+	char *w;
+
+	t = nm_dhcp_dhclient_unescape_duid (escaped);
+	g_assert (t);
+	g_assert_cmpint (t->len, ==, len);
+	g_assert_cmpint (memcmp (t->data, unescaped, len), ==, 0);
+	g_byte_array_free (t, TRUE);
+
+	t = g_byte_array_sized_new (len);
+	g_byte_array_append (t, unescaped, len);
+	w = nm_dhcp_dhclient_escape_duid (t);
+	g_assert (w);
+	g_assert_cmpint (strlen (escaped), ==, strlen (w));
+	g_assert_cmpstr (escaped, ==, w);
+
+	g_byte_array_free (t, TRUE);
+	g_free (w);
+}
+
+static void
+test_duids (void)
+{
+	const guint8 test1_u[] = { 0x00, 0x01, 0x00, 0x01, 0x13, 0x6f, 0x13, 0x6e,
+	                           0x00, 0x22, 0xfa, 0x8c, 0xd6, 0xc2 };
+	const char *test1_s = "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302";
+
+	const guint8 test2_u[] = { 0x00, 0x01, 0x00, 0x01, 0x17, 0x57, 0xee, 0x39,
+	                           0x00, 0x23, 0x15, 0x08, 0x7E, 0xac };
+	const char *test2_s = "\\000\\001\\000\\001\\027W\\3569\\000#\\025\\010~\\254";
+
+	const guint8 test3_u[] = { 0x00, 0x01, 0x00, 0x01, 0x17, 0x58, 0xe8, 0x58,
+	                           0x00, 0x23, 0x15, 0x08, 0x7e, 0xac };
+	const char *test3_s = "\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254";
+
+	const guint8 test4_u[] = { 0x00, 0x01, 0x00, 0x01, 0x15, 0xd5, 0x31, 0x97,
+	                           0x00, 0x16, 0xeb, 0x04, 0x45, 0x18 };
+	const char *test4_s = "\\000\\001\\000\\001\\025\\3251\\227\\000\\026\\353\\004E\\030";
+
+	const char *bad_s = "\\000\\001\\000\\001\\425\\3251\\227\\000\\026\\353\\004E\\030";
+
+	test_one_duid (test1_s, test1_u, sizeof (test1_u));
+	test_one_duid (test2_s, test2_u, sizeof (test2_u));
+	test_one_duid (test3_s, test3_u, sizeof (test3_u));
+	test_one_duid (test4_s, test4_u, sizeof (test4_u));
+
+	/* Invalid octal digit */
+	g_assert (nm_dhcp_dhclient_unescape_duid (bad_s) == NULL);
+}
+
+static void
+test_read_duid_from_leasefile (void)
+{
+	const guint8 expected[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0x79, 0xa6,
+	                            0x13, 0x60, 0x67, 0x20, 0xec, 0x4c, 0x70 };
+	GByteArray *duid;
+	GError *error = NULL;
+
+	duid = nm_dhcp_dhclient_read_duid (TESTDIR "/test-dhclient-duid.leases", &error);
+	g_assert_no_error (error);
+	g_assert (duid);
+	g_assert_cmpint (duid->len, ==, sizeof (expected));
+	g_assert_cmpint (memcmp (duid->data, expected, duid->len), ==, 0);
+
+	g_byte_array_free (duid, TRUE);
+}
+
+static void
+test_read_commented_duid_from_leasefile (void)
+{
+	GByteArray *duid;
+	GError *error = NULL;
+
+	duid = nm_dhcp_dhclient_read_duid (TESTDIR "/test-dhclient-commented-duid.leases", &error);
+	g_assert_no_error (error);
+	g_assert (duid == NULL);
+}
+
+static void
+test_write_duid (void)
+{
+	const char *duid = "\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254";
+	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
+	GError *error = NULL;
+	char *contents = NULL;
+	gboolean success;
+	const char *path = "test-dhclient-write-duid.leases";
+
+	success = nm_dhcp_dhclient_save_duid (path, duid, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	success = g_file_get_contents (path, &contents, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	unlink (path);
+	g_assert_cmpstr (expected_contents, ==, contents);
+
+	g_free (contents);
+}
+
+static void
+test_write_existing_duid (void)
+{
+	const char *duid = "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302";
+	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
+	GError *error = NULL;
+	char *contents = NULL;
+	gboolean success;
+	const char *path = "test-dhclient-write-existing-duid.leases";
+
+	success = g_file_set_contents (path, expected_contents, -1, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* Save other DUID; should be a no-op */
+	success = nm_dhcp_dhclient_save_duid (path, duid, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* reread original contents */
+	success = g_file_get_contents (path, &contents, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	unlink (path);
+	g_assert_cmpstr (expected_contents, ==, contents);
+
+	g_free (contents);
+}
+
+static void
+test_write_existing_commented_duid (void)
+{
+	#define DUID "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302"
+	#define ORIG_CONTENTS "#default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n"
+	const char *expected_contents = \
+		"default-duid \"" DUID "\";\n"
+		ORIG_CONTENTS;
+	GError *error = NULL;
+	char *contents = NULL;
+	gboolean success;
+	const char *path = "test-dhclient-write-existing-commented-duid.leases";
+
+	success = g_file_set_contents (path, ORIG_CONTENTS, -1, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* Save other DUID; should be a no-op */
+	success = nm_dhcp_dhclient_save_duid (path, DUID, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* reread original contents */
+	success = g_file_get_contents (path, &contents, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	unlink (path);
+	g_assert_cmpstr (expected_contents, ==, contents);
+
+	g_free (contents);
+}
+
+/*****************************************************************************/
+
+static void
+test_read_lease_ip4_config_basic (void)
+{
+	GError *error = NULL;
+	char *contents = NULL;
+	gboolean success;
+	const char *path = TESTDIR "/leases/basic.leases";
+	GSList *leases;
+	GDateTime *now;
+	NMIP4Config *config;
+	const NMPlatformIP4Address *addr;
+	guint32 expected_addr;
+
+	success = g_file_get_contents (path, &contents, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* Date from before the least expiration */
+	now = g_date_time_new_utc (2013, 11, 1, 19, 55, 32);
+	leases = nm_dhcp_dhclient_read_lease_ip_configs ("wlan0", -1, contents, FALSE, now);
+	g_assert_cmpint (g_slist_length (leases), ==, 2);
+
+	/* IP4Config #1 */
+	config = g_slist_nth_data (leases, 0);
+	g_assert (NM_IS_IP4_CONFIG (config));
+
+	/* Address */
+	g_assert_cmpint (nm_ip4_config_get_num_addresses (config), ==, 1);
+	g_assert (inet_aton ("192.168.1.180", (struct in_addr *) &expected_addr));
+	addr = nm_ip4_config_get_address (config, 0);
+	g_assert_cmpint (addr->address, ==, expected_addr);
+	g_assert_cmpint (addr->peer_address, ==, expected_addr);
+	g_assert_cmpint (addr->plen, ==, 24);
+
+	/* Gateway */
+	g_assert (inet_aton ("192.168.1.1", (struct in_addr *) &expected_addr));
+	g_assert_cmpint (nm_ip4_config_get_gateway (config), ==, expected_addr);
+
+	/* DNS */
+	g_assert_cmpint (nm_ip4_config_get_num_nameservers (config), ==, 1);
+	g_assert (inet_aton ("192.168.1.1", (struct in_addr *) &expected_addr));
+	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 0), ==, expected_addr);
+
+	g_assert_cmpint (nm_ip4_config_get_num_domains (config), ==, 0);
+
+	/* IP4Config #2 */
+	config = g_slist_nth_data (leases, 1);
+	g_assert (NM_IS_IP4_CONFIG (config));
+
+	/* Address */
+	g_assert_cmpint (nm_ip4_config_get_num_addresses (config), ==, 1);
+	g_assert (inet_aton ("10.77.52.141", (struct in_addr *) &expected_addr));
+	addr = nm_ip4_config_get_address (config, 0);
+	g_assert_cmpint (addr->address, ==, expected_addr);
+	g_assert_cmpint (addr->peer_address, ==, expected_addr);
+	g_assert_cmpint (addr->plen, ==, 8);
+
+	/* Gateway */
+	g_assert (inet_aton ("10.77.52.254", (struct in_addr *) &expected_addr));
+	g_assert_cmpint (nm_ip4_config_get_gateway (config), ==, expected_addr);
+
+	/* DNS */
+	g_assert_cmpint (nm_ip4_config_get_num_nameservers (config), ==, 2);
+	g_assert (inet_aton ("8.8.8.8", (struct in_addr *) &expected_addr));
+	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 0), ==, expected_addr);
+	g_assert (inet_aton ("8.8.4.4", (struct in_addr *) &expected_addr));
+	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 1), ==, expected_addr);
+
+	/* Domains */
+	g_assert_cmpint (nm_ip4_config_get_num_domains (config), ==, 1);
+	g_assert_cmpstr (nm_ip4_config_get_domain (config, 0), ==, "morriesguest.local");
+
+	g_slist_free_full (leases, g_object_unref);
+	g_date_time_unref (now);
+	g_free (contents);
+}
+
+static void
+test_read_lease_ip4_config_expired (void)
+{
+	GError *error = NULL;
+	char *contents = NULL;
+	gboolean success;
+	const char *path = TESTDIR "/leases/basic.leases";
+	GSList *leases;
+	GDateTime *now;
+
+	success = g_file_get_contents (path, &contents, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* Date from *after* the lease expiration */
+	now = g_date_time_new_utc (2013, 12, 1, 19, 55, 32);
+	leases = nm_dhcp_dhclient_read_lease_ip_configs ("wlan0", -1, contents, FALSE, now);
+	g_assert (leases == NULL);
+
+	g_date_time_unref (now);
+	g_free (contents);
+}
+
+static void
+test_read_lease_ip4_config_expect_failure (gconstpointer user_data)
+{
+	GError *error = NULL;
+	char *contents = NULL;
+	gboolean success;
+	GSList *leases;
+	GDateTime *now;
+
+	success = g_file_get_contents ((const char *) user_data, &contents, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* Date from before the least expiration */
+	now = g_date_time_new_utc (2013, 11, 1, 1, 1, 1);
+	leases = nm_dhcp_dhclient_read_lease_ip_configs ("wlan0", -1, contents, FALSE, now);
+	g_assert (leases == NULL);
+
+	g_date_time_unref (now);
+	g_free (contents);
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE ();
+
+int
+main (int argc, char **argv)
+{
+	nmtst_init_with_logging (&argc, &argv, NULL, "DEFAULT");
+
+	g_test_add_func ("/dhcp/dhclient/orig_missing", test_orig_missing);
+	g_test_add_func ("/dhcp/dhclient/override_client_id", test_override_client_id);
+	g_test_add_func ("/dhcp/dhclient/quote_client_id", test_quote_client_id);
+	g_test_add_func ("/dhcp/dhclient/ascii_client_id", test_ascii_client_id);
+	g_test_add_func ("/dhcp/dhclient/hex_single_client_id", test_hex_single_client_id);
+	g_test_add_func ("/dhcp/dhclient/existing-hex-client-id", test_existing_hex_client_id);
+	g_test_add_func ("/dhcp/dhclient/existing-ascii-client-id", test_existing_ascii_client_id);
+	g_test_add_func ("/dhcp/dhclient/fqdn", test_fqdn);
+	g_test_add_func ("/dhcp/dhclient/fqdn_options_override", test_fqdn_options_override);
+	g_test_add_func ("/dhcp/dhclient/override_hostname", test_override_hostname);
+	g_test_add_func ("/dhcp/dhclient/override_hostname6", test_override_hostname6);
+	g_test_add_func ("/dhcp/dhclient/nonfqdn_hostname6", test_nonfqdn_hostname6);
+	g_test_add_func ("/dhcp/dhclient/existing_req", test_existing_req);
+	g_test_add_func ("/dhcp/dhclient/existing_alsoreq", test_existing_alsoreq);
+	g_test_add_func ("/dhcp/dhclient/existing_multiline_alsoreq", test_existing_multiline_alsoreq);
+	g_test_add_func ("/dhcp/dhclient/duids", test_duids);
+
+	g_test_add_func ("/dhcp/dhclient/read_duid_from_leasefile", test_read_duid_from_leasefile);
+	g_test_add_func ("/dhcp/dhclient/read_commented_duid_from_leasefile", test_read_commented_duid_from_leasefile);
+
+	g_test_add_func ("/dhcp/dhclient/write_duid", test_write_duid);
+	g_test_add_func ("/dhcp/dhclient/write_existing_duid", test_write_existing_duid);
+	g_test_add_func ("/dhcp/dhclient/write_existing_commented_duid", test_write_existing_commented_duid);
+
+	g_test_add_func ("/dhcp/dhclient/leases/ip4-config/basic", test_read_lease_ip4_config_basic);
+	g_test_add_func ("/dhcp/dhclient/leases/ip4-config/expired", test_read_lease_ip4_config_expired);
+	g_test_add_data_func ("/dhcp/dhclient/leases/ip4-config/missing-address",
+	                      TESTDIR "/leases/malformed1.leases",
+	                      test_read_lease_ip4_config_expect_failure);
+	g_test_add_data_func ("/dhcp/dhclient/leases/ip4-config/missing-gateway",
+	                      TESTDIR "/leases/malformed2.leases",
+	                      test_read_lease_ip4_config_expect_failure);
+	g_test_add_data_func ("/dhcp/dhclient/leases/ip4-config/missing-expire",
+	                      TESTDIR "/leases/malformed3.leases",
+	                      test_read_lease_ip4_config_expect_failure);
+
+	return g_test_run ();
+}
+
diff --git a/src/dhcp/tests/test-dhcp-utils.c b/src/dhcp/tests/test-dhcp-utils.c
new file mode 100644
index 00000000..ffd63493
--- /dev/null
+++ b/src/dhcp/tests/test-dhcp-utils.c
@@ -0,0 +1,748 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2008 - 2014 Red Hat, Inc.
+ *
+ */
+
+#include "nm-default.h"
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "nm-utils.h"
+
+#include "dhcp/nm-dhcp-utils.h"
+#include "platform/nm-platform.h"
+
+#include "nm-test-utils-core.h"
+
+typedef struct {
+	const char *name;
+	const char *value;
+} Option;
+
+static GHashTable *
+fill_table (const Option *test_options, GHashTable *table)
+{
+	const Option *opt;
+
+	if (!table)
+		table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+	for (opt = test_options; opt->name; opt++)
+		g_hash_table_insert (table, (gpointer) opt->name, (gpointer) opt->value);
+	return table;
+}
+
+static const Option generic_options[] = {
+	{ "subnet_mask",            "255.255.255.0" },
+	{ "ip_address",             "192.168.1.106" },
+	{ "network_number",         "192.168.1.0" },
+	{ "expiry",                 "1232324877" },
+	{ "dhcp_lease_time",        "3600" },
+	{ "dhcp_server_identifier", "192.168.1.1" },
+	{ "routers",                "192.168.1.1" },
+	{ "domain_name_servers",    "216.254.95.2 216.231.41.2" },
+	{ "dhcp_message_type",      "5" },
+	{ "broadcast_address",      "192.168.1.255" },
+	{ "domain_search",          "foobar.com blah.foobar.com" },
+	{ "host_name",              "nmreallywhipsthe" },
+	{ "domain_name",            "lamasass.com" },
+	{ "interface_mtu",          "987" },
+	{ "static_routes",          "10.1.1.5 10.1.1.1 100.99.88.56 10.1.1.1" },
+	{ NULL, NULL }
+};
+
+static void
+test_generic_options (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const NMPlatformIP4Address *address;
+	const NMPlatformIP4Route *route;
+	guint32 tmp;
+	const char *expected_addr = "192.168.1.106";
+	const char *expected_gw = "192.168.1.1";
+	const char *expected_dns1 = "216.254.95.2";
+	const char *expected_dns2 = "216.231.41.2";
+	const char *expected_search1 = "foobar.com";
+	const char *expected_search2 = "blah.foobar.com";
+	const char *expected_route1_dest = "10.1.1.5";
+	const char *expected_route1_gw = "10.1.1.1";
+	const char *expected_route2_dest = "100.99.88.56";
+	const char *expected_route2_gw = "10.1.1.1";
+
+	options = fill_table (generic_options, NULL);
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+
+	/* IP4 address */
+	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
+	address = nm_ip4_config_get_address (ip4_config, 0);
+	g_assert (inet_pton (AF_INET, expected_addr, &tmp) > 0);
+	g_assert (address->address == tmp);
+	g_assert (address->peer_address == tmp);
+	g_assert_cmpint (address->plen, ==, 24);
+
+	/* Gateway */
+	g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0);
+	g_assert (nm_ip4_config_get_gateway (ip4_config) == tmp);
+
+	g_assert_cmpint (nm_ip4_config_get_num_wins (ip4_config), ==, 0);
+
+	g_assert_cmpint (nm_ip4_config_get_mtu (ip4_config), ==, 987);
+
+	/* Domain searches */
+	g_assert_cmpint (nm_ip4_config_get_num_searches (ip4_config), ==, 2);
+	g_assert_cmpstr (nm_ip4_config_get_search (ip4_config, 0), ==, expected_search1);
+	g_assert_cmpstr (nm_ip4_config_get_search (ip4_config, 1), ==, expected_search2);
+
+	/* DNS servers */
+	g_assert_cmpint (nm_ip4_config_get_num_nameservers (ip4_config), ==, 2);
+	g_assert (inet_pton (AF_INET, expected_dns1, &tmp) > 0);
+	g_assert (nm_ip4_config_get_nameserver (ip4_config, 0) == tmp);
+	g_assert (inet_pton (AF_INET, expected_dns2, &tmp) > 0);
+	g_assert (nm_ip4_config_get_nameserver (ip4_config, 1) == tmp);
+
+	/* Routes */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+
+	/* Route #1 */
+	route = nm_ip4_config_get_route (ip4_config, 0);
+	g_assert (inet_pton (AF_INET, expected_route1_dest, &tmp) > 0);
+	g_assert (route->network == tmp);
+	g_assert (inet_pton (AF_INET, expected_route1_gw, &tmp) > 0);
+	g_assert (route->gateway == tmp);
+	g_assert_cmpint (route->plen, ==, 32);
+	g_assert_cmpint (route->metric, ==, 0);
+
+	/* Route #2 */
+	route = nm_ip4_config_get_route (ip4_config, 1);
+	g_assert (inet_pton (AF_INET, expected_route2_dest, &tmp) > 0);
+	g_assert (route->network == tmp);
+	g_assert (inet_pton (AF_INET, expected_route2_gw, &tmp) > 0);
+	g_assert (route->gateway == tmp);
+	g_assert_cmpint (route->plen, ==, 32);
+	g_assert_cmpint (route->metric, ==, 0);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_wins_options (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const NMPlatformIP4Address *address;
+	guint32 tmp;
+	const char *expected_wins1 = "63.12.199.5";
+	const char *expected_wins2 = "150.4.88.120";
+	static const Option data[] = {
+		{ "netbios_name_servers", "63.12.199.5 150.4.88.120" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+
+	/* IP4 address */
+	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
+	address = nm_ip4_config_get_address (ip4_config, 0);
+	g_assert (address);
+	g_assert_cmpint (nm_ip4_config_get_num_wins (ip4_config), ==, 2);
+	g_assert (inet_pton (AF_INET, expected_wins1, &tmp) > 0);
+	g_assert (nm_ip4_config_get_wins (ip4_config, 0) == tmp);
+	g_assert (inet_pton (AF_INET, expected_wins2, &tmp) > 0);
+	g_assert (nm_ip4_config_get_wins (ip4_config, 1) == tmp);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_vendor_option_metered (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	static const Option data[] = {
+		{ "vendor_encapsulated_options", "ANDROID_METERED" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+	g_assert (nm_ip4_config_get_metered (ip4_config) == FALSE);
+	g_hash_table_destroy (options);
+	g_clear_object (&ip4_config);
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+	g_assert (nm_ip4_config_get_metered (ip4_config) == TRUE);
+	g_hash_table_destroy (options);
+}
+
+static void
+ip4_test_route (NMIP4Config *ip4_config,
+                guint route_num,
+                const char *expected_dest,
+                const char *expected_gw,
+                guint expected_prefix)
+{
+	const NMPlatformIP4Route *route;
+	guint32 tmp;
+
+	g_assert (expected_prefix <= 32);
+
+	route = nm_ip4_config_get_route (ip4_config, route_num);
+	g_assert (inet_pton (AF_INET, expected_dest, &tmp) > 0);
+	g_assert (route->network == tmp);
+	g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0);
+	g_assert (route->gateway == tmp);
+	g_assert_cmpint (route->plen, ==, expected_prefix);
+	g_assert_cmpint (route->metric, ==, 0);
+}
+
+static void
+ip4_test_gateway (NMIP4Config *ip4_config, const char *expected_gw)
+{
+	guint32 tmp;
+
+	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
+	g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0);
+	g_assert (nm_ip4_config_get_gateway (ip4_config) == tmp);
+}
+
+static void
+test_classless_static_routes_1 (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_route1_dest = "192.168.10.0";
+	const char *expected_route1_gw = "192.168.1.1";
+	const char *expected_route2_dest = "10.0.0.0";
+	const char *expected_route2_gw = "10.17.66.41";
+	static const Option data[] = {
+		/* dhclient custom format */
+		{ "rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 8 10 10 17 66 41" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+
+	/* IP4 routes */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 8);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_classless_static_routes_2 (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_route1_dest = "192.168.10.0";
+	const char *expected_route1_gw = "192.168.1.1";
+	const char *expected_route2_dest = "10.0.0.0";
+	const char *expected_route2_gw = "10.17.66.41";
+	static const Option data[] = {
+		/* dhcpcd format */
+		{ "classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.0.0/8 10.17.66.41" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+
+	/* IP4 routes */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 8);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_fedora_dhclient_classless_static_routes (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_route1_dest = "129.210.177.128";
+	const char *expected_route1_gw = "192.168.0.113";
+	const char *expected_route2_dest = "2.0.0.0";
+	const char *expected_route2_gw = "10.34.255.6";
+	const char *expected_gateway = "192.168.0.113";
+	static const Option data[] = {
+		/* Fedora dhclient format */
+		{ "classless_static_routes", "0 192.168.0.113 25.129.210.177.132 192.168.0.113 7.2 10.34.255.6" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+
+	/* IP4 routes */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 25);
+	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 7);
+
+	/* Gateway */
+	ip4_test_gateway (ip4_config, expected_gateway);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_dhclient_invalid_classless_routes_1 (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_route1_dest = "192.168.10.0";
+	const char *expected_route1_gw = "192.168.1.1";
+	static const Option data[] = {
+		/* dhclient format */
+		{ "rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 45 10 17 66 41" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*ignoring invalid classless static routes*");
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+	g_test_assert_expected_messages ();
+
+	/* IP4 routes */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_dhcpcd_invalid_classless_routes_1 (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_route1_dest = "10.1.1.5";
+	const char *expected_route1_gw = "10.1.1.1";
+	const char *expected_route2_dest = "100.99.88.56";
+	const char *expected_route2_gw = "10.1.1.1";
+	static const Option data[] = {
+		/* dhcpcd format */
+		{ "classless_static_routes", "192.168.10.0/24 192.168.1.1 10.0.adfadf/44 10.17.66.41" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*ignoring invalid classless static routes*");
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+	g_test_assert_expected_messages ();
+
+	/* Test falling back to old-style static routes if the classless static
+	 * routes are invalid.
+	 */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32);
+	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_dhclient_invalid_classless_routes_2 (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_route1_dest = "10.1.1.5";
+	const char *expected_route1_gw = "10.1.1.1";
+	const char *expected_route2_dest = "100.99.88.56";
+	const char *expected_route2_gw = "10.1.1.1";
+	static const Option data[] = {
+		{ "rfc3442_classless_static_routes", "45 10 17 66 41 24 192 168 10 192 168 1 1" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*ignoring invalid classless static routes*");
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+	g_test_assert_expected_messages ();
+
+	/* Test falling back to old-style static routes if the classless static
+	 * routes are invalid.
+	 */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32);
+	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_dhcpcd_invalid_classless_routes_2 (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_route1_dest = "10.1.1.5";
+	const char *expected_route1_gw = "10.1.1.1";
+	const char *expected_route2_dest = "100.99.88.56";
+	const char *expected_route2_gw = "10.1.1.1";
+	static const Option data[] = {
+		{ "classless_static_routes", "10.0.adfadf/44 10.17.66.41 192.168.10.0/24 192.168.1.1" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*ignoring invalid classless static routes*");
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+	g_test_assert_expected_messages ();
+
+	/* Test falling back to old-style static routes if the classless static
+	 * routes are invalid.
+	 */
+
+	/* Routes */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32);
+	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_dhclient_invalid_classless_routes_3 (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_route1_dest = "192.168.10.0";
+	const char *expected_route1_gw = "192.168.1.1";
+	static const Option data[] = {
+		{ "rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 32 128 10 17 66 41" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*ignoring invalid classless static routes*");
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+	g_test_assert_expected_messages ();
+
+	/* IP4 routes */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_dhcpcd_invalid_classless_routes_3 (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_route1_dest = "192.168.10.0";
+	const char *expected_route1_gw = "192.168.1.1";
+	static Option data[] = {
+		{ "classless_static_routes", "192.168.10.0/24 192.168.1.1 128/32 10.17.66.41" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*DHCP provided invalid classless static route*");
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+	g_test_assert_expected_messages ();
+
+	/* IP4 routes */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_dhclient_gw_in_classless_routes (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_route1_dest = "192.168.10.0";
+	const char *expected_route1_gw = "192.168.1.1";
+	const char *expected_gateway = "192.2.3.4";
+	static Option data[] = {
+		{ "rfc3442_classless_static_routes", "24 192 168 10 192 168 1 1 0 192 2 3 4" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+
+	/* IP4 routes */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+
+	/* Gateway */
+	ip4_test_gateway (ip4_config, expected_gateway);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_dhcpcd_gw_in_classless_routes (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_route1_dest = "192.168.10.0";
+	const char *expected_route1_gw = "192.168.1.1";
+	const char *expected_gateway = "192.2.3.4";
+	static Option data[] = {
+		{ "classless_static_routes", "192.168.10.0/24 192.168.1.1 0.0.0.0/0 192.2.3.4" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+
+	/* IP4 routes */
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+
+	/* Gateway */
+	ip4_test_gateway (ip4_config, expected_gateway);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_escaped_domain_searches (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const char *expected_search0 = "host1";
+	const char *expected_search1 = "host2";
+	const char *expected_search2 = "host3";
+	static const Option data[] = {
+		{ "domain_search", "host1\\032host2\\032host3" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+
+	/* domain searches */
+	g_assert_cmpint (nm_ip4_config_get_num_searches (ip4_config), ==, 3);
+	g_assert_cmpstr (nm_ip4_config_get_search (ip4_config, 0), ==, expected_search0);
+	g_assert_cmpstr (nm_ip4_config_get_search (ip4_config, 1), ==, expected_search1);
+	g_assert_cmpstr (nm_ip4_config_get_search (ip4_config, 2), ==, expected_search2);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_invalid_escaped_domain_searches (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	static const Option data[] = {
+		{ "domain_search", "host1\\aahost2\\032host3" },
+		{ NULL, NULL }
+	};
+
+	options = fill_table (generic_options, NULL);
+	options = fill_table (data, options);
+
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*invalid domain search*");
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+	g_test_assert_expected_messages ();
+
+	/* domain searches */
+	g_assert_cmpint (nm_ip4_config_get_num_searches (ip4_config), ==, 0);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_ip4_missing_prefix (const char *ip, guint32 expected_prefix)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const NMPlatformIP4Address *address;
+
+	options = fill_table (generic_options, NULL);
+	g_hash_table_insert (options, "ip_address", (gpointer) ip);
+	g_hash_table_remove (options, "subnet_mask");
+
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+
+	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
+	address = nm_ip4_config_get_address (ip4_config, 0);
+	g_assert (address);
+	g_assert_cmpint (address->plen, ==, expected_prefix);
+
+	g_hash_table_destroy (options);
+}
+
+static void
+test_ip4_missing_prefix_24 (void)
+{
+	test_ip4_missing_prefix ("192.168.1.10", 24);
+}
+
+static void
+test_ip4_missing_prefix_16 (void)
+{
+	test_ip4_missing_prefix ("172.16.54.50", 16);
+}
+
+static void
+test_ip4_missing_prefix_8 (void)
+{
+	test_ip4_missing_prefix ("10.1.2.3", 8);
+}
+
+static void
+test_ip4_prefix_classless (void)
+{
+	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	const NMPlatformIP4Address *address;
+
+	/* Ensure that the missing-subnet-mask handler doesn't mangle classless
+	 * subnet masks at all.  The handler should trigger only if the server
+	 * doesn't send the subnet mask.
+	 */
+
+	options = fill_table (generic_options, NULL);
+	g_hash_table_insert (options, "ip_address", "172.16.54.22");
+	g_hash_table_insert (options, "subnet_mask", "255.255.252.0");
+
+	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
+	g_assert (ip4_config);
+
+	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
+	address = nm_ip4_config_get_address (ip4_config, 0);
+	g_assert (address);
+	g_assert_cmpint (address->plen, ==, 22);
+
+	g_hash_table_destroy (options);
+}
+
+#define COMPARE_ID(src, is_str, expected, expected_len) \
+G_STMT_START { \
+	gs_unref_bytes GBytes *b = NULL; \
+	const char *p; \
+	gsize l; \
+ \
+	b = nm_dhcp_utils_client_id_string_to_bytes (src); \
+	g_assert (b); \
+	p = g_bytes_get_data (b, &l); \
+	if (is_str) { \
+		g_assert_cmpint (l, ==, expected_len + 1); \
+		g_assert_cmpint (((const char *) p)[0], ==, 0); \
+		g_assert (memcmp (p + 1, expected, expected_len) == 0); \
+	} else { \
+		g_assert_cmpint (l, ==, expected_len); \
+		g_assert (memcmp (p, expected, expected_len) == 0); \
+	} \
+} G_STMT_END
+
+static void
+test_client_id_from_string (void)
+{
+	const char *nothex = "asdfasdfasdfasdfasdfasdfasdf";
+	const char *allhex = "00:11:22:33:4:55:66:77:88";
+	const guint8 allhex_bin[] = { 0x00, 0x11, 0x22, 0x33, 0x04, 0x55, 0x66, 0x77, 0x88 };
+	const char *somehex = "00:11:22:33:44:55:asdfasdfasdf:99:10";
+	const char *nocolons = "0011223344559910";
+	const char *endcolon = "00:11:22:33:44:55:";
+
+	COMPARE_ID (nothex, TRUE, nothex, strlen (nothex));
+	COMPARE_ID (allhex, FALSE, allhex_bin, sizeof (allhex_bin));
+	COMPARE_ID (somehex, TRUE, somehex, strlen (somehex));
+	COMPARE_ID (nocolons, TRUE, nocolons, strlen (nocolons));
+	COMPARE_ID (endcolon, TRUE, endcolon, strlen (endcolon));
+}
+
+NMTST_DEFINE ();
+
+int main (int argc, char **argv)
+{
+	nmtst_init_assert_logging (&argc, &argv, "WARN", "DEFAULT");
+
+	g_test_add_func ("/dhcp/generic-options", test_generic_options);
+	g_test_add_func ("/dhcp/wins-options", test_wins_options);
+	g_test_add_func ("/dhcp/classless-static-routes-1", test_classless_static_routes_1);
+	g_test_add_func ("/dhcp/classless-static-routes-2", test_classless_static_routes_2);
+	g_test_add_func ("/dhcp/fedora-dhclient-classless-static-routes", test_fedora_dhclient_classless_static_routes);
+	g_test_add_func ("/dhcp/dhclient-invalid-classless-routes-1", test_dhclient_invalid_classless_routes_1);
+	g_test_add_func ("/dhcp/dhcpcd-invalid-classless-routes-1", test_dhcpcd_invalid_classless_routes_1);
+	g_test_add_func ("/dhcp/dhclient-invalid-classless-routes-2", test_dhclient_invalid_classless_routes_2);
+	g_test_add_func ("/dhcp/dhcpcd-invalid-classless-routes-2", test_dhcpcd_invalid_classless_routes_2);
+	g_test_add_func ("/dhcp/dhclient-invalid-classless-routes-3", test_dhclient_invalid_classless_routes_3);
+	g_test_add_func ("/dhcp/dhcpcd-invalid-classless-routes-3", test_dhcpcd_invalid_classless_routes_3);
+	g_test_add_func ("/dhcp/dhclient-gw-in-classless-routes", test_dhclient_gw_in_classless_routes);
+	g_test_add_func ("/dhcp/dhcpcd-gw-in-classless-routes", test_dhcpcd_gw_in_classless_routes);
+	g_test_add_func ("/dhcp/escaped-domain-searches", test_escaped_domain_searches);
+	g_test_add_func ("/dhcp/invalid-escaped-domain-searches", test_invalid_escaped_domain_searches);
+	g_test_add_func ("/dhcp/ip4-missing-prefix-24", test_ip4_missing_prefix_24);
+	g_test_add_func ("/dhcp/ip4-missing-prefix-16", test_ip4_missing_prefix_16);
+	g_test_add_func ("/dhcp/ip4-missing-prefix-8", test_ip4_missing_prefix_8);
+	g_test_add_func ("/dhcp/ip4-prefix-classless", test_ip4_prefix_classless);
+	g_test_add_func ("/dhcp/client-id-from-string", test_client_id_from_string);
+	g_test_add_func ("/dhcp/vendor-option-metered", test_vendor_option_metered);
+
+	return g_test_run ();
+}
+