summary refs log tree commit diff
path: root/src/dhcp
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-05-11 22:08:45 +0200
committerMichael Biebl <biebl@debian.org>2018-05-11 22:08:45 +0200
commitee9c73a923909e23a649407be77e25235d769e25 (patch)
treee21c923621fa278e737da693df9eb60ea31a6067 /src/dhcp
parentf60117b41d5433be1b4a96d82cd11d0c3dce9b63 (diff)
New upstream version 1.10.8 upstream/1.10.8
Diffstat (limited to 'src/dhcp')
-rw-r--r--src/dhcp/meson.build22
-rw-r--r--src/dhcp/nm-dhcp-client.c119
-rw-r--r--src/dhcp/nm-dhcp-client.h49
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c330
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h15
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c59
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c14
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c17
-rw-r--r--src/dhcp/nm-dhcp-helper.c2
-rw-r--r--src/dhcp/nm-dhcp-listener.c84
-rw-r--r--src/dhcp/nm-dhcp-manager.c118
-rw-r--r--src/dhcp/nm-dhcp-manager.h15
-rw-r--r--src/dhcp/nm-dhcp-systemd.c115
-rw-r--r--src/dhcp/nm-dhcp-utils.c8
-rw-r--r--src/dhcp/nm-dhcp-utils.h2
-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/meson.build19
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c318
-rw-r--r--src/dhcp/tests/test-dhcp-utils.c21
22 files changed, 917 insertions, 486 deletions
diff --git a/src/dhcp/meson.build b/src/dhcp/meson.build
deleted file mode 100644
index 289a16ca..00000000
--- a/src/dhcp/meson.build
+++ /dev/null
@@ -1,22 +0,0 @@
-name = 'nm-dhcp-helper'
-
-cflags = [
-  '-DG_LOG_DOMAIN="@0@"'.format(name),
-  '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB',
-  '-DNMRUNDIR="@0@"'.format(nm_pkgrundir),
-]
-
-executable(
-  name,
-  name + '.c',
-  dependencies: nm_core_dep,
-  c_args: cflags,
-  link_args: ldflags_linker_script_binary,
-  link_depends: linker_script_binary,
-  install: true,
-  install_dir: nm_libexecdir
-)
-
-if enable_tests
-  subdir('tests')
-endif
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 96c02653..ea3938d6 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -52,24 +52,23 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+	PROP_MULTI_IDX,
 	PROP_ADDR_FAMILY,
-	PROP_FLAGS,
-	PROP_HWADDR,
 	PROP_IFACE,
 	PROP_IFINDEX,
-	PROP_MULTI_IDX,
-	PROP_ROUTE_METRIC,
+	PROP_HWADDR,
+	PROP_UUID,
 	PROP_ROUTE_TABLE,
+	PROP_ROUTE_METRIC,
 	PROP_TIMEOUT,
-	PROP_UUID,
 );
 
 typedef struct _NMDhcpClientPrivate {
 	NMDedupMultiIndex *multi_idx;
 	char *       iface;
-	GBytes *     hwaddr;
+	GByteArray * hwaddr;
 	char *       uuid;
-	GBytes *     duid;
+	GByteArray * duid;
 	GBytes *     client_id;
 	char *       hostname;
 	pid_t        pid;
@@ -139,7 +138,7 @@ nm_dhcp_client_get_uuid (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->uuid;
 }
 
-GBytes *
+const GByteArray *
 nm_dhcp_client_get_duid (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
@@ -147,7 +146,7 @@ nm_dhcp_client_get_duid (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->duid;
 }
 
-GBytes *
+const GByteArray *
 nm_dhcp_client_get_hw_addr (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
@@ -239,20 +238,26 @@ nm_dhcp_client_set_client_id_bin (NMDhcpClient *self,
 	_set_client_id (self, b, TRUE);
 }
 
-const char *
-nm_dhcp_client_get_hostname (NMDhcpClient *self)
+void
+nm_dhcp_client_set_client_id_str (NMDhcpClient *self,
+                                  const char *dhcp_client_id)
 {
-	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
+	g_return_if_fail (!dhcp_client_id || dhcp_client_id[0]);
 
-	return NM_DHCP_CLIENT_GET_PRIVATE (self)->hostname;
+	_set_client_id (self,
+	                dhcp_client_id
+	                  ? nm_dhcp_utils_client_id_string_to_bytes (dhcp_client_id)
+	                  : NULL,
+	                TRUE);
 }
 
-gboolean
-nm_dhcp_client_get_info_only (NMDhcpClient *self)
+const char *
+nm_dhcp_client_get_hostname (NMDhcpClient *self)
 {
-	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
 
-	return NM_DHCP_CLIENT_GET_PRIVATE (self)->info_only;
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->hostname;
 }
 
 gboolean
@@ -340,7 +345,7 @@ nm_dhcp_client_stop_pid (pid_t pid, const char *iface)
 }
 
 static void
-stop (NMDhcpClient *self, gboolean release, GBytes *duid)
+stop (NMDhcpClient *self, gboolean release, const GByteArray *duid)
 {
 	NMDhcpClientPrivate *priv;
 
@@ -354,6 +359,7 @@ stop (NMDhcpClient *self, gboolean release, GBytes *duid)
 		nm_dhcp_client_stop_pid (priv->pid, priv->iface);
 	}
 	priv->pid = -1;
+	priv->info_only = FALSE;
 }
 
 void
@@ -486,9 +492,10 @@ nm_dhcp_client_watch_child (NMDhcpClient *self, pid_t pid)
 
 gboolean
 nm_dhcp_client_start_ip4 (NMDhcpClient *self,
-                          GBytes *client_id,
+                          const char *dhcp_client_id,
                           const char *dhcp_anycast_addr,
                           const char *hostname,
+                          gboolean use_fqdn,
                           const char *last_ip4_address)
 {
 	NMDhcpClientPrivate *priv;
@@ -505,19 +512,19 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 	else
 		_LOGI ("activation: beginning transaction (timeout in %u seconds)", (guint) priv->timeout);
 
-	nm_dhcp_client_set_client_id (self, client_id);
+	nm_dhcp_client_set_client_id_str (self, dhcp_client_id);
 
 	g_clear_pointer (&priv->hostname, g_free);
 	priv->hostname = g_strdup (hostname);
+	priv->use_fqdn = use_fqdn;
 
 	return NM_DHCP_CLIENT_GET_CLASS (self)->ip4_start (self, dhcp_anycast_addr, last_ip4_address);
 }
 
-static GBytes *
+static GByteArray *
 generate_duid_from_machine_id (void)
 {
-	const int DUID_SIZE = 18;
-	guint8 *duid_buffer;
+	GByteArray *duid;
 	GChecksum *sum;
 	guint8 buffer[32]; /* SHA256 digest size */
 	gsize sumlen = sizeof (buffer);
@@ -525,7 +532,6 @@ generate_duid_from_machine_id (void)
 	uuid_t uuid;
 	gs_free char *machine_id_s = NULL;
 	gs_free char *str = NULL;
-	GBytes *duid;
 
 	machine_id_s = nm_utils_machine_id_read ();
 	if (nm_utils_machine_id_parse (machine_id_s, uuid)) {
@@ -548,31 +554,36 @@ generate_duid_from_machine_id (void)
 	 * u16: type (DUID-UUID = 4)
 	 * u8[16]: UUID bytes
 	 */
-	duid_buffer = g_malloc (DUID_SIZE);
-
-	G_STATIC_ASSERT_EXPR (sizeof (duid_type) == 2);
-	memcpy (&duid_buffer[0], &duid_type, 2);
+	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.
 	 */
-	memcpy (&duid_buffer[2], buffer, 16);
+	g_byte_array_append (duid, buffer, 16);
 
-	duid = g_bytes_new_take (duid_buffer, DUID_SIZE);
 	nm_log_dbg (LOGD_DHCP, "dhcp: generated DUID %s",
 	            (str = nm_dhcp_utils_duid_to_string (duid)));
 	return duid;
 }
 
-static GBytes *
+static GByteArray *
 get_duid (NMDhcpClient *self)
 {
-	static GBytes *duid = NULL;
+	static GByteArray *duid = NULL;
+	GByteArray *copy = NULL;
 
-	if (G_UNLIKELY (!duid))
+	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 g_bytes_ref (duid);
+	return copy;
 }
 
 gboolean
@@ -580,6 +591,7 @@ 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)
 {
@@ -604,6 +616,8 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 	g_clear_pointer (&priv->hostname, g_free);
 	priv->hostname = g_strdup (hostname);
 
+	priv->info_only = info_only;
+
 	if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
 		_LOGI ("activation: beginning transaction (no timeout)");
 	else
@@ -612,6 +626,7 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 	return NM_DHCP_CLIENT_GET_CLASS (self)->ip6_start (self,
 	                                                   dhcp_anycast_addr,
 	                                                   ll_addr,
+	                                                   info_only,
 	                                                   privacy,
 	                                                   priv->duid,
 	                                                   needed_prefixes);
@@ -796,8 +811,8 @@ nm_dhcp_client_handle_event (gpointer unused,
 
 	old_state = priv->state;
 	new_state = reason_to_state (self, priv->iface, reason);
-	_LOGD ("DHCP state '%s' -> '%s' (reason: '%s')",
-	       state_to_string (old_state), state_to_string (new_state), reason);
+	_LOGD ("DHCP reason '%s' -> state '%s'",
+	       reason, state_to_string (new_state));
 
 	if (new_state == NM_DHCP_STATE_BOUND) {
 		GVariantIter iter;
@@ -908,16 +923,8 @@ set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
 	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE ((NMDhcpClient *) object);
-	guint flags;
 
 	switch (prop_id) {
-	case PROP_FLAGS:
-		/* construct-only */
-		flags = g_value_get_uint (value);
-		nm_assert ((flags & ~((guint) (NM_DHCP_CLIENT_FLAGS_INFO_ONLY | NM_DHCP_CLIENT_FLAGS_USE_FQDN))) == 0);
-		priv->info_only = NM_FLAGS_HAS (flags, NM_DHCP_CLIENT_FLAGS_INFO_ONLY);
-		priv->use_fqdn = NM_FLAGS_HAS (flags, NM_DHCP_CLIENT_FLAGS_USE_FQDN);
-		break;
 	case PROP_MULTI_IDX:
 		/* construct-only */
 		priv->multi_idx = g_value_get_pointer (value);
@@ -976,8 +983,6 @@ nm_dhcp_client_init (NMDhcpClient *self)
 	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_DHCP_CLIENT, NMDhcpClientPrivate);
 	self->_priv = priv;
 
-	c_list_init (&self->dhcp_client_lst);
-
 	priv->pid = -1;
 }
 
@@ -992,8 +997,6 @@ dispose (GObject *object)
 	 * the DHCP client.
 	 */
 
-	nm_assert (c_list_is_empty (&self->dhcp_client_lst));
-
 	watch_cleanup (self);
 	timeout_cleanup (self);
 
@@ -1001,8 +1004,16 @@ dispose (GObject *object)
 	g_clear_pointer (&priv->hostname, g_free);
 	g_clear_pointer (&priv->uuid, g_free);
 	g_clear_pointer (&priv->client_id, g_bytes_unref);
-	g_clear_pointer (&priv->hwaddr, g_bytes_unref);
-	g_clear_pointer (&priv->duid, 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);
 
@@ -1043,7 +1054,7 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 
 	obj_properties[PROP_HWADDR] =
 	    g_param_spec_boxed (NM_DHCP_CLIENT_HWADDR, "", "",
-	                        G_TYPE_BYTES,
+	                        G_TYPE_BYTE_ARRAY,
 	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                        G_PARAM_STATIC_STRINGS);
 
@@ -1077,12 +1088,6 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                       G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_FLAGS] =
-	    g_param_spec_uint (NM_DHCP_CLIENT_FLAGS, "", "",
-	                       0, G_MAXUINT32, 0,
-	                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
-	                       G_PARAM_STATIC_STRINGS);
-
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	signals[SIGNAL_STATE_CHANGED] =
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 0d92d743..2c634168 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -34,16 +34,15 @@
 #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_ADDR_FAMILY  "addr-family"
-#define NM_DHCP_CLIENT_FLAGS        "flags"
-#define NM_DHCP_CLIENT_HWADDR       "hwaddr"
-#define NM_DHCP_CLIENT_IFINDEX      "ifindex"
-#define NM_DHCP_CLIENT_INTERFACE    "iface"
-#define NM_DHCP_CLIENT_MULTI_IDX    "multi-idx"
-#define NM_DHCP_CLIENT_ROUTE_METRIC "route-metric"
+#define NM_DHCP_CLIENT_INTERFACE "iface"
+#define NM_DHCP_CLIENT_ADDR_FAMILY "addr-family"
+#define NM_DHCP_CLIENT_IFINDEX   "ifindex"
+#define NM_DHCP_CLIENT_HWADDR    "hwaddr"
+#define NM_DHCP_CLIENT_UUID      "uuid"
 #define NM_DHCP_CLIENT_ROUTE_TABLE  "route-table"
-#define NM_DHCP_CLIENT_TIMEOUT      "timeout"
-#define NM_DHCP_CLIENT_UUID         "uuid"
+#define NM_DHCP_CLIENT_ROUTE_METRIC "route-metric"
+#define NM_DHCP_CLIENT_TIMEOUT   "timeout"
+#define NM_DHCP_CLIENT_MULTI_IDX "multi-idx"
 
 #define NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED "state-changed"
 #define NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED "prefix-delegated"
@@ -65,14 +64,8 @@ struct _NMDhcpClientPrivate;
 typedef struct {
 	GObject parent;
 	struct _NMDhcpClientPrivate *_priv;
-	CList dhcp_client_lst;
 } NMDhcpClient;
 
-typedef enum {
-	NM_DHCP_CLIENT_FLAGS_INFO_ONLY = (1LL <<  0),
-	NM_DHCP_CLIENT_FLAGS_USE_FQDN  = (1LL <<  1),
-} NMDhcpClientFlags;
-
 typedef struct {
 	GObjectClass parent;
 
@@ -85,13 +78,14 @@ typedef struct {
 	gboolean (*ip6_start)     (NMDhcpClient *self,
 	                           const char *anycast_addr,
 	                           const struct in6_addr *ll_addr,
+	                           gboolean info_only,
 	                           NMSettingIP6ConfigPrivacy privacy,
-	                           GBytes *duid,
+	                           const GByteArray *duid,
 	                           guint needed_prefixes);
 
 	void (*stop)              (NMDhcpClient *self,
 	                           gboolean release,
-	                           GBytes *duid);
+	                           const GByteArray *duid);
 
 	/**
 	 * get_duid:
@@ -102,7 +96,7 @@ typedef struct {
 	 * representation of the DUID.  If no DUID is found, %NULL should be
 	 * returned.
 	 */
-	GBytes *(*get_duid) (NMDhcpClient *self);
+	GByteArray * (*get_duid) (NMDhcpClient *self);
 
 	/* Signals */
 	void (*state_changed) (NMDhcpClient *self,
@@ -125,9 +119,9 @@ int         nm_dhcp_client_get_ifindex (NMDhcpClient *self);
 
 const char *nm_dhcp_client_get_uuid (NMDhcpClient *self);
 
-GBytes *nm_dhcp_client_get_duid (NMDhcpClient *self);
+const GByteArray *nm_dhcp_client_get_duid (NMDhcpClient *self);
 
-GBytes *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
+const GByteArray *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
 
 guint32 nm_dhcp_client_get_route_table (NMDhcpClient *self);
 
@@ -139,20 +133,20 @@ GBytes *nm_dhcp_client_get_client_id (NMDhcpClient *self);
 
 const char *nm_dhcp_client_get_hostname (NMDhcpClient *self);
 
-gboolean nm_dhcp_client_get_info_only (NMDhcpClient *self);
-
 gboolean nm_dhcp_client_get_use_fqdn (NMDhcpClient *self);
 
 gboolean nm_dhcp_client_start_ip4 (NMDhcpClient *self,
-                                   GBytes *client_id,
+                                   const char *dhcp_client_id,
                                    const char *dhcp_anycast_addr,
                                    const char *hostname,
+                                   gboolean use_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);
 
@@ -185,6 +179,8 @@ void nm_dhcp_client_set_client_id_bin (NMDhcpClient *self,
                                        guint8 type,
                                        const guint8 *client_id,
                                        gsize len);
+void nm_dhcp_client_set_client_id_str (NMDhcpClient *self,
+                                       const char *dhcp_client_id);
 
 /*****************************************************************************
  * Client data
@@ -194,6 +190,13 @@ typedef struct {
 	GType (*get_type)(void);
 	const char *name;
 	const char *(*get_path) (void);
+	GSList *(*get_lease_ip_configs) (struct _NMDedupMultiIndex *multi_idx,
+	                                 int addr_family,
+	                                 const char *iface,
+	                                 int ifindex,
+	                                 const char *uuid,
+	                                 guint32 route_table,
+	                                 guint32 route_metric);
 } NMDhcpClientFactory;
 
 extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon;
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index 52923310..4df90d76 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -125,7 +125,7 @@ add_ip4_config (GString *str, GBytes *client_id, const char *hostname, gboolean
 		 * as long as all the characters are printable.
 		 */
 		for (i = 1; (p[0] == 0) && i < l; i++) {
-			if (!g_ascii_isprint (p[i]) || p[i] == '\\' || p[i] == '"')
+			if (!g_ascii_isprint (p[i]))
 				break;
 		}
 
@@ -138,9 +138,8 @@ add_ip4_config (GString *str, GBytes *client_id, const char *hostname, gboolean
 				g_string_append_printf (str, "%02x", (guint8) p[i]);
 			}
 		} else {
-			/* Printable; just add to the line with type 0 */
+			/* Printable; just add to the line minus the 'type' */
 			g_string_append_c (str, '"');
-			g_string_append (str, "\\x00");
 			g_string_append_len (str, p + 1, l - 1);
 			g_string_append_c (str, '"');
 		}
@@ -178,60 +177,31 @@ read_client_id (const char *str)
 {
 	gs_free char *s = NULL;
 	char *p;
-	int i = 0, j = 0;
 
 	nm_assert (!strncmp (str, CLIENTID_TAG, NM_STRLEN (CLIENTID_TAG)));
-	str += NM_STRLEN (CLIENTID_TAG);
 
-	if (!g_ascii_isspace (*str))
-		return NULL;
+	str += NM_STRLEN (CLIENTID_TAG);
 	while (g_ascii_isspace (*str))
 		str++;
 
 	if (*str == '"') {
-		/* Parse string literal with escape sequences */
 		s = g_strdup (str + 1);
 		p = strrchr (s, '"');
 		if (p)
 			*p = '\0';
 		else
 			return NULL;
+	} else
+		s = g_strdup (str);
 
-		if (!s[0])
-			return NULL;
-
-		while (s[i]) {
-			if (   s[i] == '\\'
-			    && s[i + 1] == 'x'
-			    && g_ascii_isxdigit (s[i + 2])
-			    && g_ascii_isxdigit (s[i + 3])) {
-				s[j++] =  (g_ascii_xdigit_value (s[i + 2]) << 4)
-				         + g_ascii_xdigit_value (s[i + 3]);
-				i += 4;
-				continue;
-			}
-			if (   s[i] == '\\'
-			    && s[i + 1] >= '0' && s[i + 1] <= '7'
-			    && s[1 + 2] >= '0' && s[i + 2] <= '7'
-			    && s[1 + 3] >= '0' && s[i + 3] <= '7') {
-				s[j++] =    ((s[i + 1] - '0') << 6)
-				          + ((s[i + 2] - '0') << 3)
-				          + ( s[i + 3] - '0');
-				i += 4;
-				continue;
-			}
-			s[j++] = s[i++];
-		}
-		return g_bytes_new_take (g_steal_pointer (&s), j);
-	}
-
-	/* Otherwise, try to read a hexadecimal sequence */
-	s = g_strdup (str);
 	g_strchomp (s);
 	if (s[strlen (s) - 1] == ';')
 		s[strlen (s) - 1] = '\0';
 
-	return nm_utils_hexstr2bin (s);
+	if (!s[0])
+		return NULL;
+
+	return nm_dhcp_utils_client_id_string_to_bytes (s);
 }
 
 GBytes *
@@ -309,7 +279,6 @@ nm_dhcp_dhclient_create_config (const char *interface,
 
 	g_return_val_if_fail (!anycast_addr || nm_utils_hwaddr_valid (anycast_addr, ETH_ALEN), NULL);
 	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NULL);
-	nm_assert (!out_new_client_id || !*out_new_client_id);
 
 	new_contents = g_string_new (_("# Created by NetworkManager\n"));
 	fqdn_opts = g_ptr_array_sized_new (5);
@@ -363,8 +332,6 @@ nm_dhcp_dhclient_create_config (const char *interface,
 					continue;
 
 				/* Otherwise capture and return the existing client id */
-				if (out_new_client_id)
-					g_clear_pointer (out_new_client_id, g_bytes_unref);
 				NM_SET_OUT (out_new_client_id, read_client_id (p));
 			}
 
@@ -477,20 +444,14 @@ nm_dhcp_dhclient_create_config (const char *interface,
 
 /* Roughly follow what dhclient's quotify_buf() and pretty_escape() functions do */
 char *
-nm_dhcp_dhclient_escape_duid (GBytes *duid)
+nm_dhcp_dhclient_escape_duid (const GByteArray *duid)
 {
 	char *escaped;
-	const guint8 *s, *s0;
-	gsize len;
+	const guint8 *s = duid->data;
 	char *d;
 
-	g_return_val_if_fail (duid, NULL);
-
-	s0 = g_bytes_get_data (duid, &len);
-	s = s0;
-
-	d = escaped = g_malloc ((len * 4) + 1);
-	while (s < (s0 + len)) {
+	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);
@@ -504,7 +465,6 @@ nm_dhcp_dhclient_escape_duid (GBytes *duid)
 		} else
 			*d++ = *s++;
 	}
-	*d++ = '\0';
 	return escaped;
 }
 
@@ -516,7 +476,7 @@ isoctal (const guint8 *p)
 	        && p[2] >= '0' && p[2] <= '7');
 }
 
-GBytes *
+GByteArray *
 nm_dhcp_dhclient_unescape_duid (const char *duid)
 {
 	GByteArray *unescaped;
@@ -547,7 +507,7 @@ nm_dhcp_dhclient_unescape_duid (const char *duid)
 			g_byte_array_append (unescaped, &p[i], 1);
 	}
 
-	return g_byte_array_free_to_bytes (unescaped);
+	return unescaped;
 
 error:
 	g_byte_array_free (unescaped, TRUE);
@@ -556,10 +516,10 @@ error:
 
 #define DUID_PREFIX "default-duid \""
 
-GBytes *
+GByteArray *
 nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
 {
-	GBytes *duid = NULL;
+	GByteArray *duid = NULL;
 	char *contents;
 	char **line, **split, *p, *e;
 
@@ -643,3 +603,259 @@ nm_dhcp_dhclient_save_duid (const char *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:
+ * @multi_idx: the multi index instance for the ip config object
+ * @addr_family: whether to read IPv4 or IPv6 leases
+ * @iface: the interface name to match leases with
+ * @ifindex: interface index of @iface
+ * @route_table: the route table for the default route.
+ * @route_metric: the route metric for the default route.
+ * @contents: the contents of a dhclient leasefile
+ * @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 @addr_family.
+ *
+ * Returns: a #GSList of #NMIP4Config objects (if @addr_family is %AF_INET) or a list of
+ * #NMIP6Config objects (if @addr_family is %AF_INET6) containing the lease data.
+ */
+GSList *
+nm_dhcp_dhclient_read_lease_ip_configs (NMDedupMultiIndex *multi_idx,
+                                        int addr_family,
+                                        const char *iface,
+                                        int ifindex,
+                                        guint32 route_table,
+                                        guint32 route_metric,
+                                        const char *contents,
+                                        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);
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+
+	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 (nm_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 (multi_idx, ifindex);
+		nm_ip4_config_add_address (ip4, &address);
+
+		{
+			const NMPlatformIP4Route r = {
+				.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
+				.gateway = gw,
+				.table_coerced = nm_platform_route_table_coerce (route_table),
+				.metric = route_metric,
+			};
+
+			nm_ip4_config_add_route (ip4, &r, NULL);
+		}
+
+		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
index fab9196a..94de1963 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -33,16 +33,25 @@ char *nm_dhcp_dhclient_create_config (const char *interface,
                                       const char *orig_contents,
                                       GBytes **out_new_client_id);
 
-char *nm_dhcp_dhclient_escape_duid (GBytes *duid);
+char *nm_dhcp_dhclient_escape_duid (const GByteArray *duid);
 
-GBytes *nm_dhcp_dhclient_unescape_duid (const char *duid);
+GByteArray *nm_dhcp_dhclient_unescape_duid (const char *duid);
 
-GBytes *nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error);
+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 (struct _NMDedupMultiIndex *multi_idx,
+                                                int addr_family,
+                                                const char *iface,
+                                                int ifindex,
+                                                guint32 route_table,
+                                                guint32 route_metric,
+                                                const char *contents,
+                                                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
index 738e9f91..74d920a8 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -158,6 +158,32 @@ get_dhclient_leasefile (int addr_family,
 	return NULL;
 }
 
+static GSList *
+nm_dhcp_dhclient_get_lease_ip_configs (NMDedupMultiIndex *multi_idx,
+                                       int addr_family,
+                                       const char *iface,
+                                       int ifindex,
+                                       const char *uuid,
+                                       guint32 route_table,
+                                       guint32 route_metric)
+{
+	gs_free char *contents = NULL;
+	gs_free char *leasefile = NULL;
+
+	leasefile = get_dhclient_leasefile (addr_family, iface, uuid, 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]) {
+		return nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, addr_family, iface, ifindex,
+		                                               route_table, route_metric, contents, NULL);
+	}
+	return NULL;
+}
+
 static gboolean
 merge_dhclient_config (NMDhcpDhclient *self,
                        int addr_family,
@@ -312,7 +338,7 @@ create_dhclient_config (NMDhcpDhclient *self,
 static gboolean
 dhclient_start (NMDhcpClient *client,
                 const char *mode_opt,
-                GBytes *duid,
+                const GByteArray *duid,
                 gboolean release,
                 pid_t *out_pid,
                 int prefixes)
@@ -413,19 +439,19 @@ dhclient_start (NMDhcpClient *client,
 		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) "-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) "-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) "-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) "-cf");	/* Set interface config file */
 		g_ptr_array_add (argv, (gpointer) priv->conf_file);
 	}
 
@@ -492,10 +518,8 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	priv->conf_file = create_dhclient_config (self, AF_INET, iface, uuid, client_id, dhcp_anycast_addr,
 	                                          hostname, timeout, use_fqdn, &new_client_id);
 	if (priv->conf_file) {
-		if (new_client_id) {
-			nm_assert (!client_id);
+		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");
@@ -507,8 +531,9 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
+           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
+           const GByteArray *duid,
            guint needed_prefixes)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
@@ -528,19 +553,16 @@ ip6_start (NMDhcpClient *client,
 		return FALSE;
 	}
 
-	return dhclient_start (client,
-	                       nm_dhcp_client_get_info_only (NM_DHCP_CLIENT (self))
-	                         ? "-S"
-	                         : "-N",
-	                       duid, FALSE, NULL, needed_prefixes);
+	return dhclient_start (client, info_only ? "-S" : "-N", duid, FALSE, NULL, needed_prefixes);
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+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)
@@ -581,12 +603,12 @@ state_changed (NMDhcpClient *client,
 	nm_dhcp_client_set_client_id (client, client_id);
 }
 
-static GBytes *
+static GByteArray *
 get_duid (NMDhcpClient *client)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
-	GBytes *duid = NULL;
+	GByteArray *duid = NULL;
 	char *leasefile;
 	GError *error = NULL;
 
@@ -620,7 +642,7 @@ get_duid (NMDhcpClient *client)
 	}
 
 	/* return our DUID, otherwise let the parent class make a default DUID */
-	return duid ?: NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->get_duid (client);
+	return duid ? duid : NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->get_duid (client);
 }
 
 /*****************************************************************************/
@@ -695,6 +717,7 @@ 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-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
index 82b3db4f..d7ddd194 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -80,7 +80,7 @@ nm_dhcp_dhcpcanon_get_path (void)
 static gboolean
 dhcpcanon_start (NMDhcpClient *client,
                 const char *mode_opt,
-                GBytes *duid,
+                const GByteArray *duid,
                 gboolean release,
                 pid_t *out_pid,
                 int prefixes)
@@ -118,16 +118,16 @@ dhcpcanon_start (NMDhcpClient *client,
 	argv = g_ptr_array_new ();
 	g_ptr_array_add (argv, (gpointer) dhcpcanon_path);
 
-	g_ptr_array_add (argv, (gpointer) "-sf"); /* Set script file */
+	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) "-pf");	/* Set pid file */
 		g_ptr_array_add (argv, (gpointer) pid_file);
 	}
 
 	if (priv->conf_file) {
-		g_ptr_array_add (argv, (gpointer) "-cf"); /* Set interface config file */
+		g_ptr_array_add (argv, (gpointer) "-cf");	/* Set interface config file */
 		g_ptr_array_add (argv, (gpointer) priv->conf_file);
 	}
 
@@ -179,8 +179,9 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
+           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
+           const GByteArray *duid,
            guint needed_prefixes)
 {
 	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
@@ -189,7 +190,7 @@ ip6_start (NMDhcpClient *client,
 	return FALSE;
 }
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
 {
 	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
 	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
@@ -265,6 +266,7 @@ const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon = {
 	.name = "dhcpcanon",
 	.get_type = nm_dhcp_dhcpcanon_get_type,
 	.get_path = nm_dhcp_dhcpcanon_get_path,
+	.get_lease_ip_configs = NULL,
 };
 
 #endif /* WITH_DHCPCANON */
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index c4bcb084..66a31acf 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -114,18 +114,18 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	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) "-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) "-K");	/* Disable built-in carrier detection */
 
-	g_ptr_array_add (argv, (gpointer) "-L");    /* Disable built-in IPv4LL */
+	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) "-G");	/* Let NM handle routing */
 
-	g_ptr_array_add (argv, (gpointer) "-c");    /* Set script file */
+	g_ptr_array_add (argv, (gpointer) "-c");	/* Set script file */
 	g_ptr_array_add (argv, (gpointer) nm_dhcp_helper_path);
 
 #ifdef DHCPCD_SUPPORTS_IPV6
@@ -177,8 +177,9 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
+           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
+           const GByteArray *duid,
            guint needed_prefixes)
 {
 	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
@@ -188,11 +189,12 @@ ip6_start (NMDhcpClient *client,
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+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) {
@@ -251,6 +253,7 @@ 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.c b/src/dhcp/nm-dhcp-helper.c
index 8ea55061..f50c5cec 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -134,6 +134,8 @@ main (int argc, char *argv[])
 	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".
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index d7d38e54..1cce5a1c 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -33,7 +33,7 @@
 #include "nm-dhcp-client.h"
 #include "nm-dhcp-manager.h"
 #include "nm-core-internal.h"
-#include "nm-dbus-manager.h"
+#include "nm-bus-manager.h"
 #include "NetworkManagerUtils.h"
 
 #define PRIV_SOCK_PATH            NMRUNDIR "/private-dhcp"
@@ -60,7 +60,7 @@ const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
 /*****************************************************************************/
 
 typedef struct {
-	NMDBusManager *      dbus_mgr;
+	NMBusManager *      dbus_mgr;
 	gulong              new_conn_id;
 	gulong              dis_conn_id;
 	GHashTable *        connections;
@@ -192,52 +192,70 @@ _method_call (GDBusConnection *connection,
 {
 	NMDhcpListener *self = NM_DHCP_LISTENER (user_data);
 
-	if (   !nm_streq (interface_name, NM_DHCP_HELPER_SERVER_INTERFACE_NAME)
-	    || !nm_streq (method_name, NM_DHCP_HELPER_SERVER_METHOD_NOTIFY)) {
-		g_dbus_method_invocation_return_error (invocation,
-		                                       G_DBUS_ERROR,
-		                                       G_DBUS_ERROR_UNKNOWN_METHOD,
-		                                       "Unknown method %s",
-		                                       method_name);
-		return;
-	}
+	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 ();
 
 	_method_call_handle (self, parameters);
+
 	g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
-static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO (
-	NM_DHCP_HELPER_SERVER_INTERFACE_NAME,
-	.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
-		NM_DEFINE_GDBUS_METHOD_INFO (
-			NM_DHCP_HELPER_SERVER_METHOD_NOTIFY,
-			.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
-				NM_DEFINE_GDBUS_ARG_INFO ("data", "a{sv}"),
-			),
-		),
-	),
-);
-
 static guint
 _dbus_connection_register_object (NMDhcpListener *self,
                                   GDBusConnection *connection,
                                   GError **error)
 {
-	static const GDBusInterfaceVTable interface_vtable = {
+	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,
-	                                          NM_UNCONST_PTR (GDBusInterfaceVTable, &interface_vtable),
+	                                          &interface_info,
+	                                          &interface_vtable,
 	                                          self,
 	                                          NULL,
 	                                          error);
 }
 
 static void
-new_connection_cb (NMDBusManager *mgr,
+new_connection_cb (NMBusManager *mgr,
                    GDBusConnection *connection,
                    GDBusObjectManager *manager,
                    NMDhcpListener *self)
@@ -260,7 +278,7 @@ new_connection_cb (NMDBusManager *mgr,
 }
 
 static void
-dis_connection_cb (NMDBusManager *mgr,
+dis_connection_cb (NMBusManager *mgr,
                    GDBusConnection *connection,
                    NMDhcpListener *self)
 {
@@ -282,18 +300,18 @@ nm_dhcp_listener_init (NMDhcpListener *self)
 	NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE (self);
 
 	/* Maps GDBusConnection :: signal-id */
-	priv->connections = g_hash_table_new (nm_direct_hash, NULL);
+	priv->connections = g_hash_table_new (NULL, NULL);
 
-	priv->dbus_mgr = nm_dbus_manager_get ();
+	priv->dbus_mgr = nm_bus_manager_get ();
 
 	/* Register the socket our DHCP clients will return lease info on */
-	nm_dbus_manager_private_server_register (priv->dbus_mgr, PRIV_SOCK_PATH, PRIV_SOCK_TAG);
+	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_DBUS_MANAGER_PRIVATE_CONNECTION_NEW "::" PRIV_SOCK_TAG,
+	                                      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_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED "::" PRIV_SOCK_TAG,
+	                                      NM_BUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED "::" PRIV_SOCK_TAG,
 	                                      G_CALLBACK (dis_connection_cb),
 	                                      self);
 }
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index bf22872d..f5c7c84b 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -43,8 +43,8 @@
 
 typedef struct {
 	const NMDhcpClientFactory *client_factory;
-	char *default_hostname;
-	CList dhcp_client_lst_head;
+	GHashTable *        clients;
+	char *              default_hostname;
 } NMDhcpManagerPrivate;
 
 struct _NMDhcpManager {
@@ -98,17 +98,21 @@ static NMDhcpClient *
 get_client_for_ifindex (NMDhcpManager *manager, int addr_family, int ifindex)
 {
 	NMDhcpManagerPrivate *priv;
-	NMDhcpClient *client;
+	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);
 
-	c_list_for_each_entry (client, &priv->dhcp_client_lst_head, dhcp_client_lst) {
-		if (   nm_dhcp_client_get_ifindex (client) == ifindex
-		    && nm_dhcp_client_get_addr_family (client) == addr_family)
-			return client;
+	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_addr_family (candidate) == addr_family)
+			return candidate;
 	}
 
 	return NULL;
@@ -125,19 +129,13 @@ static void
 remove_client (NMDhcpManager *self, NMDhcpClient *client)
 {
 	g_signal_handlers_disconnect_by_func (client, client_state_changed, self);
-	c_list_unlink (&client->dhcp_client_lst);
 
 	/* Stopping the client is left up to the controlling device
 	 * explicitly since we may want to quit NetworkManager but not terminate
 	 * the DHCP client.
 	 */
-}
 
-static void
-remove_client_unref (NMDhcpManager *self, NMDhcpClient *client)
-{
-	remove_client (self, client);
-	g_object_unref (client);
+	g_hash_table_remove (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client);
 }
 
 static void
@@ -149,7 +147,7 @@ client_state_changed (NMDhcpClient *client,
                       NMDhcpManager *self)
 {
 	if (state >= NM_DHCP_STATE_TIMEOUT)
-		remove_client_unref (self, client);
+		remove_client (self, client);
 }
 
 static NMDhcpClient *
@@ -158,12 +156,12 @@ client_start (NMDhcpManager *self,
               NMDedupMultiIndex *multi_idx,
               const char *iface,
               int ifindex,
-              GBytes *hwaddr,
+              const GByteArray *hwaddr,
               const char *uuid,
               guint32 route_table,
               guint32 route_metric,
               const struct in6_addr *ipv6_ll_addr,
-              GBytes *dhcp_client_id,
+              const char *dhcp_client_id,
               guint32 timeout,
               const char *dhcp_anycast_addr,
               const char *hostname,
@@ -181,21 +179,23 @@ client_start (NMDhcpManager *self,
 	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);
-	g_return_val_if_fail (!dhcp_client_id || g_bytes_get_size (dhcp_client_id) >= 2, 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, addr_family, ifindex);
 	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_MULTI_IDX, multi_idx,
 	                       NM_DHCP_CLIENT_ADDR_FAMILY, addr_family,
@@ -206,26 +206,21 @@ client_start (NMDhcpManager *self,
 	                       NM_DHCP_CLIENT_ROUTE_TABLE, (guint) route_table,
 	                       NM_DHCP_CLIENT_ROUTE_METRIC, (guint) route_metric,
 	                       NM_DHCP_CLIENT_TIMEOUT, (guint) timeout,
-	                       NM_DHCP_CLIENT_FLAGS, (guint) (0
-	                           | (hostname_use_fqdn ? NM_DHCP_CLIENT_FLAGS_USE_FQDN  : 0)
-	                           | (info_only         ? NM_DHCP_CLIENT_FLAGS_INFO_ONLY : 0)
-	                       ),
 	                       NULL);
-	nm_assert (client && c_list_is_empty (&client->dhcp_client_lst));
-	c_list_link_tail (&priv->dhcp_client_lst_head, &client->dhcp_client_lst);
+	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 (addr_family == AF_INET)
-		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, last_ip4_address);
+		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, hostname_use_fqdn, last_ip4_address);
 	else
-		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, privacy, needed_prefixes);
+		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, info_only, privacy, needed_prefixes);
 
 	if (!success) {
-		remove_client_unref (self, client);
-		return NULL;
+		remove_client (self, client);
+		client = NULL;
 	}
 
-	return g_object_ref (client);
+	return client;
 }
 
 /* Caller owns a reference to the NMDhcpClient on return */
@@ -234,14 +229,14 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
                            NMDedupMultiIndex *multi_idx,
                            const char *iface,
                            int ifindex,
-                           GBytes *hwaddr,
+                           const GByteArray *hwaddr,
                            const char *uuid,
                            guint32 route_table,
                            guint32 route_metric,
                            gboolean send_hostname,
                            const char *dhcp_hostname,
                            const char *dhcp_fqdn,
-                           GBytes *dhcp_client_id,
+                           const char *dhcp_client_id,
                            guint32 timeout,
                            const char *dhcp_anycast_addr,
                            const char *last_ip_address)
@@ -290,7 +285,7 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
                            NMDedupMultiIndex *multi_idx,
                            const char *iface,
                            int ifindex,
-                           GBytes *hwaddr,
+                           const GByteArray *hwaddr,
                            const struct in6_addr *ll_addr,
                            const char *uuid,
                            guint32 route_table,
@@ -333,6 +328,31 @@ nm_dhcp_manager_set_default_hostname (NMDhcpManager *manager, const char *hostna
 	priv->default_hostname = g_strdup (hostname);
 }
 
+GSList *
+nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
+                                      NMDedupMultiIndex *multi_idx,
+                                      int addr_family,
+                                      const char *iface,
+                                      int ifindex,
+                                      const char *uuid,
+                                      guint32 route_table,
+                                      guint32 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);
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), 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 (multi_idx, addr_family, iface, ifindex, uuid, route_table, route_metric);
+	return NULL;
+}
+
 const char *
 nm_dhcp_manager_get_config (NMDhcpManager *self)
 {
@@ -358,8 +378,6 @@ nm_dhcp_manager_init (NMDhcpManager *self)
 	int i;
 	const NMDhcpClientFactory *client_factory = NULL;
 
-	c_list_init (&priv->dhcp_client_lst_head);
-
 	for (i = 0; i < G_N_ELEMENTS (_nm_dhcp_manager_factories); i++) {
 		const NMDhcpClientFactory *f = _nm_dhcp_manager_factories[i];
 
@@ -411,21 +429,38 @@ nm_dhcp_manager_init (NMDhcpManager *self)
 	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)
 {
-	NMDhcpManager *self = NM_DHCP_MANAGER (object);
-	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
-	NMDhcpClient *client, *client_safe;
-
-	c_list_for_each_entry_safe (client, client_safe, &priv->dhcp_client_lst_head, dhcp_client_lst)
-		remove_client_unref (self, client);
+	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);
 
-	nm_clear_g_free (&priv->default_hostname);
+	G_OBJECT_CLASS (nm_dhcp_manager_parent_class)->finalize (object);
 }
 
 static void
@@ -433,5 +468,6 @@ 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
index f8a7e31d..078117ff 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -49,14 +49,14 @@ NMDhcpClient * nm_dhcp_manager_start_ip4     (NMDhcpManager *manager,
                                               struct _NMDedupMultiIndex *multi_idx,
                                               const char *iface,
                                               int ifindex,
-                                              GBytes *hwaddr,
+                                              const GByteArray *hwaddr,
                                               const char *uuid,
                                               guint32 route_table,
                                               guint32 route_metric,
                                               gboolean send_hostname,
                                               const char *dhcp_hostname,
                                               const char *dhcp_fqdn,
-                                              GBytes *dhcp_client_id,
+                                              const char *dhcp_client_id,
                                               guint32 timeout,
                                               const char *dhcp_anycast_addr,
                                               const char *last_ip_address);
@@ -65,7 +65,7 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               struct _NMDedupMultiIndex *multi_idx,
                                               const char *iface,
                                               int ifindex,
-                                              GBytes *hwaddr,
+                                              const GByteArray *hwaddr,
                                               const struct in6_addr *ll_addr,
                                               const char *uuid,
                                               guint32 route_table,
@@ -78,6 +78,15 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               NMSettingIP6ConfigPrivacy privacy,
                                               guint needed_prefixes);
 
+GSList *       nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
+                                                     struct _NMDedupMultiIndex *multi_idx,
+                                                     int addr_family,
+                                                     const char *iface,
+                                                     int ifindex,
+                                                     const char *uuid,
+                                                     guint32 route_table,
+                                                     guint32 route_metric);
+
 /* For testing only */
 extern const char* nm_dhcp_helper_path;
 
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 4f37f069..f79b7cb1 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -29,7 +29,6 @@
 #include <net/if_arp.h>
 
 #include "nm-utils/nm-dedup-multi.h"
-#include "nm-utils/unaligned.h"
 
 #include "nm-utils.h"
 #include "nm-dhcp-utils.h"
@@ -61,7 +60,8 @@ typedef struct {
 
 	guint request_count;
 
-	bool privacy:1;
+	gboolean privacy;
+	gboolean info_only;
 } NMDhcpSystemdPrivate;
 
 struct _NMDhcpSystemd {
@@ -451,6 +451,36 @@ get_leasefile_path (int addr_family, const char *iface, const char *uuid)
 	                        iface);
 }
 
+static GSList *
+nm_dhcp_systemd_get_lease_ip_configs (NMDedupMultiIndex *multi_idx,
+                                      int addr_family,
+                                      const char *iface,
+                                      int ifindex,
+                                      const char *uuid,
+                                      guint32 route_table,
+                                      guint32 route_metric)
+{
+	GSList *leases = NULL;
+	gs_free char *path = NULL;
+	sd_dhcp_lease *lease = NULL;
+	NMIP4Config *ip4_config;
+	int r;
+
+	if (addr_family != AF_INET)
+		return NULL;
+
+	path = get_leasefile_path (addr_family, iface, uuid);
+	r = dhcp_lease_load (&lease, path);
+	if (r == 0 && lease) {
+		ip4_config = lease_to_ip4_config (multi_idx, iface, ifindex, lease, NULL, route_table, route_metric, FALSE, NULL);
+		if (ip4_config)
+			leases = g_slist_append (leases, ip4_config);
+		sd_dhcp_lease_unref (lease);
+	}
+
+	return leases;
+}
+
 /*****************************************************************************/
 
 static void
@@ -554,16 +584,14 @@ dhcp_event_cb (sd_dhcp_client *client, int event, gpointer user_data)
 }
 
 static guint16
-get_arp_type (GBytes *hwaddr)
+get_arp_type (const GByteArray *hwaddr)
 {
-	switch (g_bytes_get_size (hwaddr)) {
-	case ETH_ALEN:
+	if (hwaddr->len == ETH_ALEN)
 		return ARPHRD_ETHER;
-	case INFINIBAND_ALEN:
+	else if (hwaddr->len == INFINIBAND_ALEN)
 		return ARPHRD_INFINIBAND;
-	default:
+	else
 		return ARPHRD_NONE;
-	}
 }
 
 static gboolean
@@ -572,7 +600,7 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 	const char *iface = nm_dhcp_client_get_iface (client);
-	GBytes *hwaddr;
+	const GByteArray *hwaddr;
 	sd_dhcp_lease *lease = NULL;
 	GBytes *override_client_id;
 	const uint8_t *client_id = NULL;
@@ -581,6 +609,7 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	const char *hostname;
 	int r, i;
 	gboolean success = FALSE;
+	guint16 arp_type;
 
 	g_assert (priv->client4 == NULL);
 	g_assert (priv->client6 == NULL);
@@ -604,14 +633,16 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 
 	hwaddr = nm_dhcp_client_get_hw_addr (client);
 	if (hwaddr) {
-		const uint8_t *data;
-		gsize len;
+		arp_type= get_arp_type (hwaddr);
+		if (arp_type == ARPHRD_NONE) {
+			_LOGW ("failed to determine ARP type");
+			goto error;
+		}
 
-		data = g_bytes_get_data (hwaddr, &len);
 		r = sd_dhcp_client_set_mac (priv->client4,
-		                            data,
-		                            len,
-		                            get_arp_type (hwaddr));
+		                            hwaddr->data,
+		                            hwaddr->len,
+		                            arp_type);
 		if (r < 0) {
 			_LOGW ("failed to set MAC address (%d)", r);
 			goto error;
@@ -630,6 +661,12 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 		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)
@@ -817,7 +854,7 @@ bound6_handle (NMDhcpSystemd *self)
 	                                  lease,
 	                                  options,
 	                                  TRUE,
-	                                  nm_dhcp_client_get_info_only (NM_DHCP_CLIENT (self)),
+	                                  priv->info_only,
 	                                  &error);
 
 	if (ip6_config) {
@@ -863,29 +900,24 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
+           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
+           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);
-	GBytes *hwaddr;
-	const char *hostname;
+	const GByteArray *hwaddr;
 	int r, i;
-	const guint8 *duid_arr;
-	gsize duid_len;
 
 	g_assert (priv->client4 == NULL);
 	g_assert (priv->client6 == NULL);
 	g_return_val_if_fail (duid != NULL, FALSE);
 
-	duid_arr = g_bytes_get_data (duid, &duid_len);
-	if (!duid_arr || duid_len < 2)
-		g_return_val_if_reached (FALSE);
-
 	g_free (priv->lease_file);
 	priv->lease_file = get_leasefile_path (AF_INET6, iface, nm_dhcp_client_get_uuid (client));
+	priv->info_only = info_only;
 
 	r = sd_dhcp6_client_new (&priv->client6);
 	if (r < 0) {
@@ -900,13 +932,16 @@ ip6_start (NMDhcpClient *client,
 
 	_LOGT ("dhcp-client6: set %p", priv->client6);
 
-	if (nm_dhcp_client_get_info_only (client))
-		sd_dhcp6_client_set_information_request (priv->client6, 1);
+	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,
-	                              unaligned_read_be16 (&duid_arr[0]),
-	                              &duid_arr[2],
-	                              duid_len - 2);
+	                              ntohs (((const guint16 *) duid->data)[0]),
+	                              duid->data + 2,
+	                              duid->len - 2);
 	if (r < 0) {
 		_LOGW ("failed to set DUID (%d)", r);
 		return FALSE;
@@ -920,13 +955,9 @@ ip6_start (NMDhcpClient *client,
 
 	hwaddr = nm_dhcp_client_get_hw_addr (client);
 	if (hwaddr) {
-		const uint8_t *data;
-		gsize len;
-
-		data = g_bytes_get_data (hwaddr, &len);
 		r = sd_dhcp6_client_set_mac (priv->client6,
-		                             data,
-		                             len,
+		                             hwaddr->data,
+		                             hwaddr->len,
 		                             get_arp_type (hwaddr));
 		if (r < 0) {
 			_LOGW ("failed to set MAC address (%d)", r);
@@ -958,13 +989,6 @@ ip6_start (NMDhcpClient *client,
 		goto error;
 	}
 
-	hostname = nm_dhcp_client_get_hostname (client);
-	r = sd_dhcp6_client_set_fqdn (priv->client6, hostname);
-	if (r < 0) {
-		_LOGW ("failed to set DHCP hostname to '%s' (%d)", hostname, r);
-		goto error;
-	}
-
 	r = sd_dhcp6_client_start (priv->client6);
 	if (r < 0) {
 		_LOGW ("failed to start client (%d)", r);
@@ -982,14 +1006,12 @@ error:
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+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;
 
-	NM_DHCP_CLIENT_CLASS (nm_dhcp_systemd_parent_class)->stop (client, release, duid);
-
 	_LOGT ("dhcp-client%d: stop %p",
 	       priv->client4 ? '4' : '6',
 	       priv->client4 ? (gpointer) priv->client4 : (gpointer) priv->client6);
@@ -1052,4 +1074,5 @@ 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
index 9185a135..50ca2abe 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -721,15 +721,11 @@ error:
 }
 
 char *
-nm_dhcp_utils_duid_to_string (GBytes *duid)
+nm_dhcp_utils_duid_to_string (const GByteArray *duid)
 {
-	gconstpointer data;
-	gsize len;
-
 	g_return_val_if_fail (duid != NULL, NULL);
 
-	data = g_bytes_get_data (duid, &len);
-	return _nm_utils_bin2str (data, len, FALSE);
+	return _nm_utils_bin2str (duid->data, duid->len, FALSE);
 }
 
 /**
diff --git a/src/dhcp/nm-dhcp-utils.h b/src/dhcp/nm-dhcp-utils.h
index 5c127bd1..32140f48 100644
--- a/src/dhcp/nm-dhcp-utils.h
+++ b/src/dhcp/nm-dhcp-utils.h
@@ -39,7 +39,7 @@ NMIP6Config *nm_dhcp_utils_ip6_config_from_options (struct _NMDedupMultiIndex *m
 
 NMPlatformIP6Address nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options);
 
-char *nm_dhcp_utils_duid_to_string (GBytes *duid);
+char *       nm_dhcp_utils_duid_to_string          (const GByteArray *duid);
 
 GBytes *     nm_dhcp_utils_client_id_string_to_bytes (const char *client_id);
 
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/meson.build b/src/dhcp/tests/meson.build
deleted file mode 100644
index 32badae8..00000000
--- a/src/dhcp/tests/meson.build
+++ /dev/null
@@ -1,19 +0,0 @@
-test_units = [
-  'test-dhcp-dhclient',
-  'test-dhcp-utils'
-]
-
-foreach test_unit: test_units
-  exe = executable(
-    test_unit,
-    test_unit + '.c',
-    dependencies: test_nm_dep,
-    c_args: '-DTESTDIR="@0@"'.format(meson.current_source_dir())
-  )
-
-  test(
-    'dhcp/' + test_unit,
-    test_script,
-    args: test_args + [exe.full_path()]
-  )
-endforeach
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index 25af51a1..f2e1f321 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -36,6 +36,12 @@
 
 #include "nm-test-utils-core.h"
 
+#define DEBUG 1
+
+static const int IFINDEX = 5;
+static const guint32 ROUTE_TABLE = RT_TABLE_MAIN;
+static const guint32 ROUTE_METRIC = 100;
+
 static void
 test_config (const char *orig,
              const char *expected,
@@ -148,7 +154,7 @@ test_override_client_id (void)
 static const char *quote_client_id_expected = \
 	"# Created by NetworkManager\n"
 	"\n"
-	"send dhcp-client-identifier \"\\x00abcd\"; # added by NetworkManager\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"
@@ -166,65 +172,7 @@ test_quote_client_id (void)
 {
 	test_config (NULL, quote_client_id_expected,
 	             AF_INET, NULL, 0, FALSE,
-	             "abcd",
-	             NULL,
-	             "eth0",
-	             NULL);
-}
-
-/*****************************************************************************/
-
-static const char *quote_client_id_expected_2 = \
-	"# Created by NetworkManager\n"
-	"\n"
-	"send dhcp-client-identifier 00:61:5c:62:63; # 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_2 (void)
-{
-	test_config (NULL, quote_client_id_expected_2,
-	             AF_INET, NULL, 0, FALSE,
-	             "a\\bc",
-	             NULL,
-	             "eth0",
-	             NULL);
-}
-
-/*****************************************************************************/
-
-static const char *hex_zero_client_id_expected = \
-	"# Created by NetworkManager\n"
-	"\n"
-	"send dhcp-client-identifier 00:11:22:33; # 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_zero_client_id (void)
-{
-	test_config (NULL, hex_zero_client_id_expected,
-	             AF_INET, NULL, 0, FALSE,
-	             "00:11:22:33",
+	             "1234",
 	             NULL,
 	             "eth0",
 	             NULL);
@@ -235,7 +183,7 @@ test_hex_zero_client_id (void)
 static const char *ascii_client_id_expected = \
 	"# Created by NetworkManager\n"
 	"\n"
-	"send dhcp-client-identifier \"\\x00qb:cd:ef:12:34:56\"; # added by NetworkManager\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"
@@ -291,13 +239,13 @@ test_hex_single_client_id (void)
 /*****************************************************************************/
 
 static const char *existing_hex_client_id_orig = \
-	"send dhcp-client-identifier 10:30:04:20:7A:08;\n";
+	"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 10:30:04:20:7A:08;\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"
@@ -314,7 +262,7 @@ static void
 test_existing_hex_client_id (void)
 {
 	gs_unref_bytes GBytes *new_client_id = NULL;
-	const guint8 bytes[] = { 0x10, 0x30, 0x04, 0x20, 0x7A, 0x08 };
+	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,
@@ -327,52 +275,16 @@ test_existing_hex_client_id (void)
 
 /*****************************************************************************/
 
-static const char *existing_escaped_client_id_orig = \
-	"send dhcp-client-identifier \"\\044test\\xfe\";\n";
-
-static const char *existing_escaped_client_id_expected = \
-	"# Created by NetworkManager\n"
-	"# Merged from /path/to/dhclient.conf\n"
-	"\n"
-	"send dhcp-client-identifier \"\\044test\\xfe\";\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_escaped_client_id (void)
-{
-	gs_unref_bytes GBytes *new_client_id = NULL;
-
-	new_client_id = g_bytes_new ("$test\xfe", 6);
-	test_config (existing_escaped_client_id_orig, existing_escaped_client_id_expected,
-	             AF_INET, NULL, 0, FALSE,
-	             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 \"\\x00" EACID "\";\n";
+	"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 \"\\x00" EACID "\";\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"
@@ -673,26 +585,23 @@ test_existing_multiline_alsoreq (void)
 static void
 test_one_duid (const char *escaped, const guint8 *unescaped, guint len)
 {
-	GBytes *t;
+	GByteArray *t;
 	char *w;
-	gsize t_len;
-	gconstpointer t_arr;
 
 	t = nm_dhcp_dhclient_unescape_duid (escaped);
 	g_assert (t);
-	t_arr = g_bytes_get_data (t, &t_len);
-	g_assert (t_arr);
-	g_assert_cmpint (t_len, ==, len);
-	g_assert_cmpint (memcmp (t_arr, unescaped, len), ==, 0);
-	g_bytes_unref (t);
+	g_assert_cmpint (t->len, ==, len);
+	g_assert_cmpint (memcmp (t->data, unescaped, len), ==, 0);
+	g_byte_array_free (t, TRUE);
 
-	t = g_bytes_new_static (unescaped, len);
+	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_bytes_unref (t);
+	g_byte_array_free (t, TRUE);
 	g_free (w);
 }
 
@@ -731,23 +640,22 @@ test_read_duid_from_leasefile (void)
 {
 	const guint8 expected[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0x79, 0xa6,
 	                            0x13, 0x60, 0x67, 0x20, 0xec, 0x4c, 0x70 };
-	gs_unref_bytes GBytes *duid = NULL;
+	GByteArray *duid;
 	GError *error = NULL;
-	gconstpointer duid_arr;
-	gsize duid_len;
 
 	duid = nm_dhcp_dhclient_read_duid (TESTDIR "/test-dhclient-duid.leases", &error);
 	g_assert_no_error (error);
 	g_assert (duid);
-	duid_arr = g_bytes_get_data (duid, &duid_len);
-	g_assert_cmpint (duid_len, ==, sizeof (expected));
-	g_assert_cmpint (memcmp (duid_arr, expected, duid_len), ==, 0);
+	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)
 {
-	GBytes *duid;
+	GByteArray *duid;
 	GError *error = NULL;
 
 	duid = nm_dhcp_dhclient_read_duid (TESTDIR "/test-dhclient-commented-duid.leases", &error);
@@ -846,12 +754,12 @@ test_write_existing_commented_duid (void)
 
 static const char *interface1_orig = \
 	"interface \"eth0\" {\n"
-	"\talso request my-option;\n"
-	"\tinitial-delay 5;\n"
+	"	also request my-option;\n"
+	"	initial-delay 5;\n"
 	"}\n"
 	"interface \"eth1\" {\n"
-	"\talso request another-option;\n"
-	"\tinitial-delay 0;\n"
+	"	also request another-option;\n"
+	"	initial-delay 0;\n"
 	"}\n"
 	"\n"
 	"also request yet-another-option;\n";
@@ -890,12 +798,12 @@ test_interface1 (void)
 
 static const char *interface2_orig = \
 	"interface eth0 {\n"
-	"\talso request my-option;\n"
-	"\tinitial-delay 5;\n"
+	"	also request my-option;\n"
+	"	initial-delay 5;\n"
 	" }\n"
 	"interface eth1 {\n"
-	"\tinitial-delay 0;\n"
-	"\trequest another-option;\n"
+	"	initial-delay 0;\n"
+	"	request another-option;\n"
 	" } \n"
 	"\n"
 	"also request yet-another-option;\n";
@@ -936,12 +844,12 @@ test_config_req_intf (void)
 {
 	static const char *const orig = \
 		"request subnet-mask, broadcast-address, routers,\n"
-		"\trfc3442-classless-static-routes,\n"
-		"\tinterface-mtu, host-name, domain-name, domain-search,\n"
-		"\tdomain-name-servers, nis-domain, nis-servers,\n"
-		"\tnds-context, nds-servers, nds-tree-name,\n"
-		"\tnetbios-name-servers, netbios-dd-server,\n"
-		"\tnetbios-node-type, netbios-scope, ntp-servers;\n"
+		"	rfc3442-classless-static-routes,\n"
+		"	interface-mtu, host-name, domain-name, domain-search,\n"
+		"	domain-name-servers, nis-domain, nis-servers,\n"
+		"	nds-context, nds-servers, nds-tree-name,\n"
+		"	netbios-name-servers, netbios-dd-server,\n"
+		"	netbios-node-type, netbios-scope, ntp-servers;\n"
 		"";
 	static const char *const expected = \
 		"# Created by NetworkManager\n"
@@ -987,6 +895,133 @@ test_config_req_intf (void)
 
 /*****************************************************************************/
 
+static void
+test_read_lease_ip4_config_basic (void)
+{
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
+	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 (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, 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);
+	expected_addr = nmtst_inet4_from_string ("192.168.1.180");
+	addr = _nmtst_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 */
+	expected_addr = nmtst_inet4_from_string ("192.168.1.1");
+	g_assert_cmpint (nmtst_ip4_config_get_gateway (config), ==, expected_addr);
+
+	/* DNS */
+	g_assert_cmpint (nm_ip4_config_get_num_nameservers (config), ==, 1);
+	expected_addr = nmtst_inet4_from_string ("192.168.1.1");
+	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);
+	expected_addr = nmtst_inet4_from_string ("10.77.52.141");
+	addr = _nmtst_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 */
+	expected_addr = nmtst_inet4_from_string ("10.77.52.254");
+	g_assert_cmpint (nmtst_ip4_config_get_gateway (config), ==, expected_addr);
+
+	/* DNS */
+	g_assert_cmpint (nm_ip4_config_get_num_nameservers (config), ==, 2);
+	expected_addr = nmtst_inet4_from_string ("8.8.8.8");
+	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 0), ==, expected_addr);
+	expected_addr = nmtst_inet4_from_string ("8.8.4.4");
+	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)
+{
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
+	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 (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, 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)
+{
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
+	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 (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, now);
+	g_assert (leases == NULL);
+
+	g_date_time_unref (now);
+	g_free (contents);
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int
@@ -996,13 +1031,10 @@ main (int argc, char **argv)
 
 	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/1", test_quote_client_id);
-	g_test_add_func ("/dhcp/dhclient/quote_client_id/2", test_quote_client_id_2);
-	g_test_add_func ("/dhcp/dhclient/hex_zero_client_id", test_hex_zero_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-client-id", test_existing_escaped_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);
@@ -1024,6 +1056,18 @@ main (int argc, char **argv)
 	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
index 617a3c6c..72f31191 100644
--- a/src/dhcp/tests/test-dhcp-utils.c
+++ b/src/dhcp/tests/test-dhcp-utils.c
@@ -349,7 +349,8 @@ test_dhclient_invalid_classless_routes_1 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -379,7 +380,8 @@ test_dhcpcd_invalid_classless_routes_1 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -411,7 +413,8 @@ test_dhclient_invalid_classless_routes_2 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -443,7 +446,8 @@ test_dhcpcd_invalid_classless_routes_2 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -475,7 +479,8 @@ test_dhclient_invalid_classless_routes_3 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -502,7 +507,8 @@ test_dhcpcd_invalid_classless_routes_3 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	NMTST_EXPECT_NM_WARN ("*DHCP provided invalid classless static route*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*DHCP provided invalid classless static route*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -609,7 +615,8 @@ test_invalid_escaped_domain_searches (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	NMTST_EXPECT_NM_WARN ("*invalid domain search*");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
+	                       "*invalid domain search*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();