summary refs log tree commit diff
path: root/src/dhcp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dhcp')
-rw-r--r--src/dhcp/meson.build22
-rw-r--r--src/dhcp/nm-dhcp-client.c202
-rw-r--r--src/dhcp/nm-dhcp-client.h52
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c331
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h15
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c191
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c16
-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.c86
-rw-r--r--src/dhcp/nm-dhcp-manager.c120
-rw-r--r--src/dhcp/nm-dhcp-manager.h15
-rw-r--r--src/dhcp/nm-dhcp-systemd.c120
-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.c322
-rw-r--r--src/dhcp/tests/test-dhcp-utils.c21
22 files changed, 594 insertions, 1043 deletions
diff --git a/src/dhcp/meson.build b/src/dhcp/meson.build
new file mode 100644
index 00000000..289a16ca
--- /dev/null
+++ b/src/dhcp/meson.build
@@ -0,0 +1,22 @@
+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 ea3938d6..ba517606 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -52,23 +52,24 @@ 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_HWADDR,
-	PROP_UUID,
-	PROP_ROUTE_TABLE,
+	PROP_MULTI_IDX,
 	PROP_ROUTE_METRIC,
+	PROP_ROUTE_TABLE,
 	PROP_TIMEOUT,
+	PROP_UUID,
 );
 
 typedef struct _NMDhcpClientPrivate {
 	NMDedupMultiIndex *multi_idx;
 	char *       iface;
-	GByteArray * hwaddr;
+	GBytes *     hwaddr;
 	char *       uuid;
-	GByteArray * duid;
+	GBytes *     duid;
 	GBytes *     client_id;
 	char *       hostname;
 	pid_t        pid;
@@ -138,7 +139,7 @@ nm_dhcp_client_get_uuid (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->uuid;
 }
 
-const GByteArray *
+GBytes *
 nm_dhcp_client_get_duid (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
@@ -146,7 +147,7 @@ nm_dhcp_client_get_duid (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->duid;
 }
 
-const GByteArray *
+GBytes *
 nm_dhcp_client_get_hw_addr (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
@@ -238,20 +239,6 @@ nm_dhcp_client_set_client_id_bin (NMDhcpClient *self,
 	_set_client_id (self, b, TRUE);
 }
 
-void
-nm_dhcp_client_set_client_id_str (NMDhcpClient *self,
-                                  const char *dhcp_client_id)
-{
-	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
-	g_return_if_fail (!dhcp_client_id || dhcp_client_id[0]);
-
-	_set_client_id (self,
-	                dhcp_client_id
-	                  ? nm_dhcp_utils_client_id_string_to_bytes (dhcp_client_id)
-	                  : NULL,
-	                TRUE);
-}
-
 const char *
 nm_dhcp_client_get_hostname (NMDhcpClient *self)
 {
@@ -261,6 +248,14 @@ nm_dhcp_client_get_hostname (NMDhcpClient *self)
 }
 
 gboolean
+nm_dhcp_client_get_info_only (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->info_only;
+}
+
+gboolean
 nm_dhcp_client_get_use_fqdn (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
@@ -339,13 +334,13 @@ nm_dhcp_client_stop_pid (pid_t pid, const char *iface)
 
 	g_return_if_fail (pid > 1);
 
-	nm_utils_kill_child_sync (pid, SIGTERM, LOGD_DHCP, name ? name : "dhcp-client", NULL,
+	nm_utils_kill_child_sync (pid, SIGTERM, LOGD_DHCP, name ?: "dhcp-client", NULL,
 	                          1000 / 2, 1000 / 20);
 	g_free (name);
 }
 
 static void
-stop (NMDhcpClient *self, gboolean release, const GByteArray *duid)
+stop (NMDhcpClient *self, gboolean release, GBytes *duid)
 {
 	NMDhcpClientPrivate *priv;
 
@@ -359,32 +354,30 @@ stop (NMDhcpClient *self, gboolean release, const GByteArray *duid)
 		nm_dhcp_client_stop_pid (priv->pid, priv->iface);
 	}
 	priv->pid = -1;
-	priv->info_only = FALSE;
 }
 
 void
 nm_dhcp_client_set_state (NMDhcpClient *self,
                           NMDhcpState new_state,
-                          GObject *ip_config,
+                          NMIPConfig *ip_config,
                           GHashTable *options)
 {
 	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
 	gs_free char *event_id = NULL;
 
+	if (new_state == NM_DHCP_STATE_BOUND) {
+		g_return_if_fail (NM_IS_IP_CONFIG (ip_config, priv->addr_family));
+		g_return_if_fail (options);
+	} else {
+		g_return_if_fail (!ip_config);
+		g_return_if_fail (!options);
+	}
+
 	if (new_state >= NM_DHCP_STATE_BOUND)
 		timeout_cleanup (self);
 	if (new_state >= NM_DHCP_STATE_TIMEOUT)
 		watch_cleanup (self);
 
-	if (new_state == NM_DHCP_STATE_BOUND) {
-		g_assert (   (priv->addr_family == AF_INET  && NM_IS_IP4_CONFIG (ip_config))
-		          || (priv->addr_family == AF_INET6 && NM_IS_IP6_CONFIG (ip_config)));
-		g_assert (options);
-	} else {
-		g_assert (ip_config == NULL);
-		g_assert (options == NULL);
-	}
-
 	/* The client may send same-state transitions for RENEW/REBIND events and
 	 * the lease may have changed, so handle same-state transitions for the
 	 * BOUND state.  Ignore same-state transitions for other events since
@@ -492,10 +485,9 @@ nm_dhcp_client_watch_child (NMDhcpClient *self, pid_t pid)
 
 gboolean
 nm_dhcp_client_start_ip4 (NMDhcpClient *self,
-                          const char *dhcp_client_id,
+                          GBytes *client_id,
                           const char *dhcp_anycast_addr,
                           const char *hostname,
-                          gboolean use_fqdn,
                           const char *last_ip4_address)
 {
 	NMDhcpClientPrivate *priv;
@@ -512,19 +504,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_str (self, dhcp_client_id);
+	nm_dhcp_client_set_client_id (self, 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 GByteArray *
+static GBytes *
 generate_duid_from_machine_id (void)
 {
-	GByteArray *duid;
+	const int DUID_SIZE = 18;
+	guint8 *duid_buffer;
 	GChecksum *sum;
 	guint8 buffer[32]; /* SHA256 digest size */
 	gsize sumlen = sizeof (buffer);
@@ -532,6 +524,7 @@ 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)) {
@@ -554,36 +547,31 @@ generate_duid_from_machine_id (void)
 	 * u16: type (DUID-UUID = 4)
 	 * u8[16]: UUID bytes
 	 */
-	duid = g_byte_array_sized_new (18);
-	g_byte_array_append (duid, (guint8 *) &duid_type, sizeof (duid_type));
+	duid_buffer = g_malloc (DUID_SIZE);
+
+	G_STATIC_ASSERT_EXPR (sizeof (duid_type) == 2);
+	memcpy (&duid_buffer[0], &duid_type, 2);
 
 	/* Since SHA256 is 256 bits, but UUID is 128 bits, we just take the first
 	 * 128 bits of the SHA256 as the DUID-UUID.
 	 */
-	g_byte_array_append (duid, buffer, 16);
+	memcpy (&duid_buffer[2], 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 GByteArray *
+static GBytes *
 get_duid (NMDhcpClient *self)
 {
-	static GByteArray *duid = NULL;
-	GByteArray *copy = NULL;
+	static GBytes *duid = NULL;
 
-	if (G_UNLIKELY (duid == NULL)) {
+	if (G_UNLIKELY (!duid))
 		duid = generate_duid_from_machine_id ();
-		g_assert (duid);
-	}
 
-	if (G_LIKELY (duid)) {
-		copy = g_byte_array_sized_new (duid->len);
-		g_byte_array_append (copy, duid->data, duid->len);
-	}
-
-	return copy;
+	return g_bytes_ref (duid);
 }
 
 gboolean
@@ -591,7 +579,6 @@ 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)
 {
@@ -616,8 +603,6 @@ 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
@@ -626,7 +611,6 @@ 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);
@@ -675,8 +659,10 @@ nm_dhcp_client_stop_existing (const char *pid_file, const char *binary_name)
 
 out:
 	if (remove (pid_file) == -1) {
-		nm_log_dbg (LOGD_DHCP, "dhcp: could not remove pid file \"%s\": %d (%s)",
-		            pid_file, errno, g_strerror (errno));
+		int errsv = errno;
+
+		nm_log_dbg (LOGD_DHCP, "dhcp: could not remove pid file \"%s\": %s (%d)",
+		            pid_file, g_strerror (errsv), errsv);
 	}
 }
 
@@ -751,14 +737,6 @@ maybe_add_option (NMDhcpClient *self,
                   GVariant *value)
 {
 	char *str_value = NULL;
-	const char **p;
-	static const char *ignored_keys[] = {
-		"interface",
-		"pid",
-		"reason",
-		"dhcp_message_type",
-		NULL
-	};
 
 	g_return_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE_BYTESTRING));
 
@@ -766,10 +744,11 @@ maybe_add_option (NMDhcpClient *self,
 		return;
 
 	/* Filter out stuff that's not actually new DHCP options */
-	for (p = ignored_keys; *p; p++) {
-		if (!strcmp (*p, key))
-			return;
-	}
+	if (NM_IN_STRSET (key, "interface",
+	                       "pid",
+	                       "reason",
+	                       "dhcp_message_type"))
+		return;
 
 	if (g_str_has_prefix (key, NEW_TAG))
 		key += NM_STRLEN (NEW_TAG);
@@ -792,8 +771,8 @@ nm_dhcp_client_handle_event (gpointer unused,
 	NMDhcpClientPrivate *priv;
 	guint32 old_state;
 	guint32 new_state;
-	GHashTable *str_options = NULL;
-	GObject *ip_config = NULL;
+	gs_unref_hashtable GHashTable *str_options = NULL;
+	gs_unref_object NMIPConfig *ip_config = NULL;
 	NMPlatformIP6Address prefix = { 0, };
 
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
@@ -811,8 +790,8 @@ nm_dhcp_client_handle_event (gpointer unused,
 
 	old_state = priv->state;
 	new_state = reason_to_state (self, priv->iface, reason);
-	_LOGD ("DHCP reason '%s' -> state '%s'",
-	       reason, state_to_string (new_state));
+	_LOGD ("DHCP state '%s' -> '%s' (reason: '%s')",
+	       state_to_string (old_state), state_to_string (new_state), reason);
 
 	if (new_state == NM_DHCP_STATE_BOUND) {
 		GVariantIter iter;
@@ -837,24 +816,24 @@ nm_dhcp_client_handle_event (gpointer unused,
 		}
 
 		/* Create the IP config */
-		g_warn_if_fail (g_hash_table_size (str_options));
-		if (g_hash_table_size (str_options)) {
+		if (g_hash_table_size (str_options) > 0) {
 			if (priv->addr_family == AF_INET) {
-				ip_config = (GObject *) nm_dhcp_utils_ip4_config_from_options (nm_dhcp_client_get_multi_idx (self),
-				                                                               priv->ifindex,
-				                                                               priv->iface,
-				                                                               str_options,
-				                                                               priv->route_table,
-				                                                               priv->route_metric);
+				ip_config = NM_IP_CONFIG_CAST (nm_dhcp_utils_ip4_config_from_options (nm_dhcp_client_get_multi_idx (self),
+				                                                                      priv->ifindex,
+				                                                                      priv->iface,
+				                                                                      str_options,
+				                                                                      priv->route_table,
+				                                                                      priv->route_metric));
 			} else {
 				prefix = nm_dhcp_utils_ip6_prefix_from_options (str_options);
-				ip_config = (GObject *) nm_dhcp_utils_ip6_config_from_options (nm_dhcp_client_get_multi_idx (self),
-				                                                               priv->ifindex,
-				                                                               priv->iface,
-				                                                               str_options,
-				                                                               priv->info_only);
+				ip_config = NM_IP_CONFIG_CAST (nm_dhcp_utils_ip6_config_from_options (nm_dhcp_client_get_multi_idx (self),
+				                                                                      priv->ifindex,
+				                                                                      priv->iface,
+				                                                                      str_options,
+				                                                                      priv->info_only));
 			}
-		}
+		} else
+			g_warn_if_reached ();
 	}
 
 	if (!IN6_IS_ADDR_UNSPECIFIED (&prefix.address)) {
@@ -866,7 +845,8 @@ nm_dhcp_client_handle_event (gpointer unused,
 		               &prefix);
 	} else {
 		/* Fail if no valid IP config was received */
-		if (new_state == NM_DHCP_STATE_BOUND && ip_config == NULL) {
+		if (   new_state == NM_DHCP_STATE_BOUND
+		    && !ip_config) {
 			_LOGW ("client bound but IP config not received");
 			new_state = NM_DHCP_STATE_FAIL;
 			g_clear_pointer (&str_options, g_hash_table_unref);
@@ -875,10 +855,6 @@ nm_dhcp_client_handle_event (gpointer unused,
 		nm_dhcp_client_set_state (self, new_state, ip_config, str_options);
 	}
 
-	if (str_options)
-		g_hash_table_destroy (str_options);
-	g_clear_object (&ip_config);
-
 	return TRUE;
 }
 
@@ -923,8 +899,16 @@ 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);
@@ -983,6 +967,8 @@ 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;
 }
 
@@ -997,6 +983,8 @@ dispose (GObject *object)
 	 * the DHCP client.
 	 */
 
+	nm_assert (c_list_is_empty (&self->dhcp_client_lst));
+
 	watch_cleanup (self);
 	timeout_cleanup (self);
 
@@ -1004,16 +992,8 @@ 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);
-
-	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_clear_pointer (&priv->hwaddr, g_bytes_unref);
+	g_clear_pointer (&priv->duid, g_bytes_unref);
 
 	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
 
@@ -1054,7 +1034,7 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 
 	obj_properties[PROP_HWADDR] =
 	    g_param_spec_boxed (NM_DHCP_CLIENT_HWADDR, "", "",
-	                        G_TYPE_BYTE_ARRAY,
+	                        G_TYPE_BYTES,
 	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                        G_PARAM_STATIC_STRINGS);
 
@@ -1088,6 +1068,12 @@ 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 2c634168..111b063b 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -34,20 +34,20 @@
 #define NM_IS_DHCP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_CLIENT))
 #define NM_DHCP_CLIENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClientClass))
 
-#define NM_DHCP_CLIENT_INTERFACE "iface"
-#define NM_DHCP_CLIENT_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_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_TIMEOUT   "timeout"
-#define NM_DHCP_CLIENT_MULTI_IDX "multi-idx"
+#define NM_DHCP_CLIENT_ROUTE_TABLE  "route-table"
+#define NM_DHCP_CLIENT_TIMEOUT      "timeout"
+#define NM_DHCP_CLIENT_UUID         "uuid"
 
 #define NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED "state-changed"
 #define NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED "prefix-delegated"
 
-
 typedef enum {
 	NM_DHCP_STATE_UNKNOWN = 0,
 	NM_DHCP_STATE_BOUND,        /* new lease or lease changed */
@@ -64,8 +64,14 @@ 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;
 
@@ -78,14 +84,13 @@ typedef struct {
 	gboolean (*ip6_start)     (NMDhcpClient *self,
 	                           const char *anycast_addr,
 	                           const struct in6_addr *ll_addr,
-	                           gboolean info_only,
 	                           NMSettingIP6ConfigPrivacy privacy,
-	                           const GByteArray *duid,
+	                           GBytes *duid,
 	                           guint needed_prefixes);
 
 	void (*stop)              (NMDhcpClient *self,
 	                           gboolean release,
-	                           const GByteArray *duid);
+	                           GBytes *duid);
 
 	/**
 	 * get_duid:
@@ -96,7 +101,7 @@ typedef struct {
 	 * representation of the DUID.  If no DUID is found, %NULL should be
 	 * returned.
 	 */
-	GByteArray * (*get_duid) (NMDhcpClient *self);
+	GBytes *(*get_duid) (NMDhcpClient *self);
 
 	/* Signals */
 	void (*state_changed) (NMDhcpClient *self,
@@ -119,9 +124,9 @@ int         nm_dhcp_client_get_ifindex (NMDhcpClient *self);
 
 const char *nm_dhcp_client_get_uuid (NMDhcpClient *self);
 
-const GByteArray *nm_dhcp_client_get_duid (NMDhcpClient *self);
+GBytes *nm_dhcp_client_get_duid (NMDhcpClient *self);
 
-const GByteArray *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
+GBytes *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
 
 guint32 nm_dhcp_client_get_route_table (NMDhcpClient *self);
 
@@ -133,20 +138,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,
-                                   const char *dhcp_client_id,
+                                   GBytes *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);
 
@@ -163,7 +168,7 @@ void nm_dhcp_client_watch_child (NMDhcpClient *self, pid_t pid);
 
 void nm_dhcp_client_set_state (NMDhcpClient *self,
                                NMDhcpState new_state,
-                               GObject *ip_config,   /* NMIP4Config or NMIP6Config */
+                               NMIPConfig *ip_config,
                                GHashTable *options); /* str:str hash */
 
 gboolean nm_dhcp_client_handle_event (gpointer unused,
@@ -179,8 +184,6 @@ 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
@@ -190,13 +193,6 @@ 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 4df90d76..16a76d23 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -95,7 +95,6 @@ grab_request_options (GPtrArray *store, const char* line)
 	return end;
 }
 
-
 static void
 add_hostname4 (GString *str, const char *hostname, gboolean use_fqdn)
 {
@@ -125,7 +124,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]))
+			if (!g_ascii_isprint (p[i]) || p[i] == '\\' || p[i] == '"')
 				break;
 		}
 
@@ -138,8 +137,9 @@ 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 minus the 'type' */
+			/* Printable; just add to the line with type 0 */
 			g_string_append_c (str, '"');
+			g_string_append (str, "\\x00");
 			g_string_append_len (str, p + 1, l - 1);
 			g_string_append_c (str, '"');
 		}
@@ -177,31 +177,60 @@ 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;
 	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';
 
-	if (!s[0])
-		return NULL;
-
-	return nm_dhcp_utils_client_id_string_to_bytes (s);
+	return nm_utils_hexstr2bin (s);
 }
 
 GBytes *
@@ -279,6 +308,7 @@ 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);
@@ -332,6 +362,8 @@ 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));
 			}
 
@@ -444,14 +476,20 @@ 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 (const GByteArray *duid)
+nm_dhcp_dhclient_escape_duid (GBytes *duid)
 {
 	char *escaped;
-	const guint8 *s = duid->data;
+	const guint8 *s, *s0;
+	gsize len;
 	char *d;
 
-	d = escaped = g_malloc0 ((duid->len * 4) + 1);
-	while (s < (duid->data + duid->len)) {
+	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)) {
 		if (!g_ascii_isprint (*s)) {
 			*d++ = '\\';
 			*d++ = '0' + ((*s >> 6) & 0x7);
@@ -465,6 +503,7 @@ nm_dhcp_dhclient_escape_duid (const GByteArray *duid)
 		} else
 			*d++ = *s++;
 	}
+	*d++ = '\0';
 	return escaped;
 }
 
@@ -476,7 +515,7 @@ isoctal (const guint8 *p)
 	        && p[2] >= '0' && p[2] <= '7');
 }
 
-GByteArray *
+GBytes *
 nm_dhcp_dhclient_unescape_duid (const char *duid)
 {
 	GByteArray *unescaped;
@@ -507,7 +546,7 @@ nm_dhcp_dhclient_unescape_duid (const char *duid)
 			g_byte_array_append (unescaped, &p[i], 1);
 	}
 
-	return unescaped;
+	return g_byte_array_free_to_bytes (unescaped);
 
 error:
 	g_byte_array_free (unescaped, TRUE);
@@ -516,10 +555,10 @@ error:
 
 #define DUID_PREFIX "default-duid \""
 
-GByteArray *
+GBytes *
 nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
 {
-	GByteArray *duid = NULL;
+	GBytes *duid = NULL;
 	char *contents;
 	char **line, **split, *p, *e;
 
@@ -603,259 +642,3 @@ 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 94de1963..fab9196a 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -33,25 +33,16 @@ char *nm_dhcp_dhclient_create_config (const char *interface,
                                       const char *orig_contents,
                                       GBytes **out_new_client_id);
 
-char *nm_dhcp_dhclient_escape_duid (const GByteArray *duid);
+char *nm_dhcp_dhclient_escape_duid (GBytes *duid);
 
-GByteArray *nm_dhcp_dhclient_unescape_duid (const char *duid);
+GBytes *nm_dhcp_dhclient_unescape_duid (const char *duid);
 
-GByteArray *nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error);
+GBytes *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 74d920a8..93306ddd 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -127,11 +127,13 @@ get_dhclient_leasefile (int addr_family,
 	                        _addr_family_to_path_part (addr_family),
 	                        uuid,
 	                        iface);
-	if (out_preferred_path)
-		*out_preferred_path = g_strdup (path);
 
-	if (g_file_test (path, G_FILE_TEST_EXISTS))
+	if (g_file_test (path, G_FILE_TEST_EXISTS)) {
+		NM_SET_OUT (out_preferred_path, g_strdup (path));
 		return path;
+	}
+
+	NM_SET_OUT (out_preferred_path, g_steal_pointer (&path));
 
 	/* If the leasefile we're looking for doesn't exist yet in the new location
 	 * (eg, /var/lib/NetworkManager) then look in old locations to maintain
@@ -158,32 +160,6 @@ 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,
@@ -288,7 +264,6 @@ find_existing_config (NMDhcpDhclient *self, int addr_family, const char *iface,
 	return NULL;
 }
 
-
 /* NM provides interface-specific options; thus the same dhclient config
  * file cannot be used since DHCP transactions can happen in parallel.
  * Since some distros don't have default per-interface dhclient config files,
@@ -334,31 +309,33 @@ create_dhclient_config (NMDhcpDhclient *self,
 	return new;
 }
 
-
 static gboolean
 dhclient_start (NMDhcpClient *client,
                 const char *mode_opt,
-                const GByteArray *duid,
+                GBytes *duid,
                 gboolean release,
                 pid_t *out_pid,
                 int prefixes)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
-	GPtrArray *argv = NULL;
+	gs_unref_ptrarray GPtrArray *argv = NULL;
 	pid_t pid;
 	GError *error = NULL;
-	const char *iface, *uuid, *system_bus_address, *dhclient_path = NULL;
-	char *binary_name, *cmd_str, *pid_file = NULL, *system_bus_address_env = NULL;
-	int addr_family;
-	gboolean success;
-	char *escaped, *preferred_leasefile_path = NULL;
+	const char *iface;
+	const char *uuid;
+	const char *system_bus_address;
+	const char *dhclient_path;
+	char *binary_name;
+	gs_free char *cmd_str = NULL;
+	gs_free char *pid_file = NULL;
+	gs_free char *system_bus_address_env = NULL;
+	gs_free char *preferred_leasefile_path = NULL;
+	const int addr_family = nm_dhcp_client_get_addr_family (client);
 
-	g_return_val_if_fail (priv->pid_file == NULL, FALSE);
+	g_return_val_if_fail (!priv->pid_file, FALSE);
 
-	iface = nm_dhcp_client_get_iface (client);
-	uuid = nm_dhcp_client_get_uuid (client);
-	addr_family = nm_dhcp_client_get_addr_family (client);
+	NM_SET_OUT (out_pid, 0);
 
 	dhclient_path = nm_dhcp_dhclient_get_path ();
 	if (!dhclient_path) {
@@ -366,6 +343,9 @@ dhclient_start (NMDhcpClient *client,
 		return FALSE;
 	}
 
+	iface = nm_dhcp_client_get_iface (client);
+	uuid = nm_dhcp_client_get_uuid (client);
+
 	pid_file = g_strdup_printf (RUNSTATEDIR "/dhclient%s-%s.pid",
 	                            _addr_family_to_path_part (addr_family),
 	                            iface);
@@ -377,18 +357,18 @@ dhclient_start (NMDhcpClient *client,
 
 	if (release) {
 		/* release doesn't use the pidfile after killing an old client */
-		g_free (pid_file);
-		pid_file = NULL;
+		nm_clear_g_free (&pid_file);
 	}
 
 	g_free (priv->lease_file);
 	priv->lease_file = get_dhclient_leasefile (addr_family, iface, uuid, &preferred_leasefile_path);
+	nm_assert (preferred_leasefile_path);
 	if (!priv->lease_file) {
 		/* No existing leasefile, dhclient will create one at the preferred path */
-		priv->lease_file = g_strdup (preferred_leasefile_path);
-	} else if (g_strcmp0 (priv->lease_file, preferred_leasefile_path) != 0) {
-		GFile *src = g_file_new_for_path (priv->lease_file);
-		GFile *dst = g_file_new_for_path (preferred_leasefile_path);
+		priv->lease_file = g_steal_pointer (&preferred_leasefile_path);
+	} else if (!nm_streq0 (priv->lease_file, preferred_leasefile_path)) {
+		gs_unref_object GFile *src = g_file_new_for_path (priv->lease_file);
+		gs_unref_object GFile *dst = g_file_new_for_path (preferred_leasefile_path);
 
 		/* Try to copy the existing leasefile to the preferred location */
 		if (g_file_copy (src, dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error)) {
@@ -402,19 +382,16 @@ dhclient_start (NMDhcpClient *client,
 			       error->message);
 			g_clear_error (&error);
 		}
-		g_object_unref (src);
-		g_object_unref (dst);
 	}
-	g_free (preferred_leasefile_path);
 
 	/* Save the DUID to the leasefile dhclient will actually use */
 	if (addr_family == AF_INET6) {
+		gs_free char *escaped = NULL;
+
 		escaped = nm_dhcp_dhclient_escape_duid (duid);
-		success = nm_dhcp_dhclient_save_duid (priv->lease_file, escaped, &error);
-		g_free (escaped);
-		if (!success) {
+		if (!nm_dhcp_dhclient_save_duid (priv->lease_file, escaped, &error)) {
 			_LOGW ("failed to save DUID to %s: %s", priv->lease_file, error->message);
-			g_free (pid_file);
+			g_clear_error (&error);
 			return FALSE;
 		}
 	}
@@ -439,19 +416,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);
 	}
 
@@ -466,34 +443,29 @@ dhclient_start (NMDhcpClient *client,
 		g_ptr_array_add (argv, (gpointer) system_bus_address_env);
 	}
 
-
 	g_ptr_array_add (argv, (gpointer) iface);
 	g_ptr_array_add (argv, NULL);
 
-	cmd_str = g_strjoinv (" ", (gchar **) argv->pdata);
-	_LOGD ("running: %s", cmd_str);
-	g_free (cmd_str);
-
-	if (g_spawn_async (NULL, (char **) argv->pdata, NULL,
-	                   G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
-	                   nm_utils_setpgid, NULL, &pid, &error)) {
-		g_assert (pid > 0);
-		_LOGI ("dhclient started with pid %d", pid);
-		if (release == FALSE)
-			nm_dhcp_client_watch_child (client, pid);
-		priv->pid_file = pid_file;
-	} else {
+	_LOGD ("running: %s",
+	       (cmd_str = g_strjoinv (" ", (gchar **) argv->pdata)));
+
+	if (!g_spawn_async (NULL, (char **) argv->pdata, NULL,
+	                    G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
+	                    nm_utils_setpgid, NULL, &pid, &error)) {
 		_LOGW ("dhclient failed to start: '%s'", error->message);
 		g_error_free (error);
-		g_free (pid_file);
+		return FALSE;
 	}
 
-	if (out_pid)
-		*out_pid = pid;
+	_LOGI ("dhclient started with pid %lld", (long long int) pid);
+
+	if (!release)
+		nm_dhcp_client_watch_child (client, pid);
 
-	g_ptr_array_free (argv, TRUE);
-	g_free (system_bus_address_env);
-	return pid > 0 ? TRUE : FALSE;
+	priv->pid_file = g_steal_pointer (&pid_file);
+
+	NM_SET_OUT (out_pid, pid);
+	return TRUE;
 }
 
 static gboolean
@@ -518,8 +490,10 @@ 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)
+		if (new_client_id) {
+			nm_assert (!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");
@@ -531,9 +505,8 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
-           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           const GByteArray *duid,
+           GBytes *duid,
            guint needed_prefixes)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
@@ -553,26 +526,31 @@ ip6_start (NMDhcpClient *client,
 		return FALSE;
 	}
 
-	return dhclient_start (client, info_only ? "-S" : "-N", duid, FALSE, NULL, needed_prefixes);
+	return dhclient_start (client,
+	                       nm_dhcp_client_get_info_only (NM_DHCP_CLIENT (self))
+	                         ? "-S"
+	                         : "-N",
+	                       duid, FALSE, NULL, needed_prefixes);
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
 
-	/* Chain up to parent */
 	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->stop (client, release, duid);
 
 	if (priv->conf_file)
 		if (remove (priv->conf_file) == -1)
 			_LOGD ("could not remove dhcp config file \"%s\": %d (%s)", priv->conf_file, errno, g_strerror (errno));
 	if (priv->pid_file) {
-		if (remove (priv->pid_file) == -1)
-			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errno, g_strerror (errno));
-		g_free (priv->pid_file);
-		priv->pid_file = NULL;
+		if (remove (priv->pid_file) == -1) {
+			int errsv = errno;
+
+			_LOGD ("could not remove dhcp pid file \"%s\": %s (%d)", priv->pid_file, g_strerror (errsv), errsv);
+		}
+		nm_clear_g_free (&priv->pid_file);
 	}
 
 	if (release) {
@@ -603,12 +581,12 @@ state_changed (NMDhcpClient *client,
 	nm_dhcp_client_set_client_id (client, client_id);
 }
 
-static GByteArray *
+static GBytes *
 get_duid (NMDhcpClient *client)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
-	GByteArray *duid = NULL;
+	GBytes *duid = NULL;
 	char *leasefile;
 	GError *error = NULL;
 
@@ -629,7 +607,7 @@ get_duid (NMDhcpClient *client)
 		g_free (leasefile);
 	}
 
-	if (!duid && priv->def_leasefile) {
+	if (!duid) {
 		/* Otherwise read the default machine-wide DUID */
 		_LOGD ("looking for default DUID in '%s'", priv->def_leasefile);
 		duid = nm_dhcp_dhclient_read_duid (priv->def_leasefile, &error);
@@ -642,36 +620,30 @@ get_duid (NMDhcpClient *client)
 	}
 
 	/* return our DUID, otherwise let the parent class make a default DUID */
-	return duid ? duid : NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->get_duid (client);
+	return duid ?: NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->get_duid (client);
 }
 
 /*****************************************************************************/
 
-static const char *def_leasefiles[] = {
-	SYSCONFDIR "/dhclient6.leases",
-	LOCALSTATEDIR "/lib/dhcp/dhclient6.leases",
-	LOCALSTATEDIR "/lib/dhclient/dhclient6.leases",
-	NULL
-};
-
 static void
 nm_dhcp_dhclient_init (NMDhcpDhclient *self)
 {
+	static const char *const FILES[] = {
+		SYSCONFDIR "/dhclient6.leases", /* default */
+		LOCALSTATEDIR "/lib/dhcp/dhclient6.leases",
+		LOCALSTATEDIR "/lib/dhclient/dhclient6.leases",
+	};
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
-	const char **iter = &def_leasefiles[0];
+	int i;
 
-	while (iter && *iter) {
-		if (g_file_test (*iter, G_FILE_TEST_EXISTS)) {
-			priv->def_leasefile = *iter;
+	priv->def_leasefile = FILES[0];
+	for (i = 0; i < G_N_ELEMENTS (FILES); i++) {
+		if (g_file_test (FILES[i], G_FILE_TEST_EXISTS)) {
+			priv->def_leasefile = FILES[i];
 			break;
 		}
-		iter++;
 	}
 
-	/* Fallback option */
-	if (!priv->def_leasefile)
-		priv->def_leasefile = SYSCONFDIR "/dhclient6.leases";
-
 	priv->dhcp_listener = g_object_ref (nm_dhcp_listener_get ());
 	g_signal_connect (priv->dhcp_listener,
 	                  NM_DHCP_LISTENER_EVENT,
@@ -717,7 +689,6 @@ 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 d7ddd194..12aa57aa 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -76,11 +76,10 @@ nm_dhcp_dhcpcanon_get_path (void)
 	return nm_utils_find_helper ("dhcpcanon", DHCPCANON_PATH, NULL);
 }
 
-
 static gboolean
 dhcpcanon_start (NMDhcpClient *client,
                 const char *mode_opt,
-                const GByteArray *duid,
+                GBytes *duid,
                 gboolean release,
                 pid_t *out_pid,
                 int prefixes)
@@ -118,16 +117,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);
 	}
 
@@ -142,7 +141,6 @@ dhcpcanon_start (NMDhcpClient *client,
 		g_ptr_array_add (argv, (gpointer) system_bus_address_env);
 	}
 
-
 	g_ptr_array_add (argv, (gpointer) iface);
 	g_ptr_array_add (argv, NULL);
 
@@ -179,9 +177,8 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
-           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           const GByteArray *duid,
+           GBytes *duid,
            guint needed_prefixes)
 {
 	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
@@ -190,7 +187,7 @@ ip6_start (NMDhcpClient *client,
 	return FALSE;
 }
 static void
-stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 {
 	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
 	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
@@ -266,7 +263,6 @@ 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 66a31acf..c4bcb084 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,9 +177,8 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
-           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           const GByteArray *duid,
+           GBytes *duid,
            guint needed_prefixes)
 {
 	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
@@ -189,12 +188,11 @@ ip6_start (NMDhcpClient *client,
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+stop (NMDhcpClient *client, gboolean release, GBytes *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) {
@@ -253,7 +251,6 @@ 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 f50c5cec..8ea55061 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -134,8 +134,6 @@ 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 1cce5a1c..1770ead3 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-bus-manager.h"
+#include "nm-dbus-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 {
-	NMBusManager *      dbus_mgr;
+	NMDBusManager *      dbus_mgr;
 	gulong              new_conn_id;
 	gulong              dis_conn_id;
 	GHashTable *        connections;
@@ -160,7 +160,7 @@ _method_call_handle (NMDhcpListener *self,
 	pid_str = get_option (options, "pid");
 	pid = _nm_utils_ascii_str_to_int64 (pid_str, 10, 0, G_MAXINT32, -1);
 	if (pid == -1) {
-		_LOGW ("dhcp-event: couldn't convert PID '%s' to an integer", pid_str ? pid_str : "(null)");
+		_LOGW ("dhcp-event: couldn't convert PID '%s' to an integer", pid_str ?: "(null)");
 		return;
 	}
 
@@ -192,70 +192,52 @@ _method_call (GDBusConnection *connection,
 {
 	NMDhcpListener *self = NM_DHCP_LISTENER (user_data);
 
-	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 ();
+	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;
+	}
 
 	_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 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 = {
+	static const GDBusInterfaceVTable interface_vtable = {
 		.method_call = _method_call,
-		.get_property = NULL,
-		.set_property = NULL,
 	};
 
 	return g_dbus_connection_register_object (connection,
 	                                          NM_DHCP_HELPER_SERVER_OBJECT_PATH,
-	                                          &interface_info,
-	                                          &interface_vtable,
+	                                          interface_info,
+	                                          NM_UNCONST_PTR (GDBusInterfaceVTable, &interface_vtable),
 	                                          self,
 	                                          NULL,
 	                                          error);
 }
 
 static void
-new_connection_cb (NMBusManager *mgr,
+new_connection_cb (NMDBusManager *mgr,
                    GDBusConnection *connection,
                    GDBusObjectManager *manager,
                    NMDhcpListener *self)
@@ -278,7 +260,7 @@ new_connection_cb (NMBusManager *mgr,
 }
 
 static void
-dis_connection_cb (NMBusManager *mgr,
+dis_connection_cb (NMDBusManager *mgr,
                    GDBusConnection *connection,
                    NMDhcpListener *self)
 {
@@ -300,18 +282,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 (NULL, NULL);
+	priv->connections = g_hash_table_new (nm_direct_hash, NULL);
 
-	priv->dbus_mgr = nm_bus_manager_get ();
+	priv->dbus_mgr = nm_dbus_manager_get ();
 
 	/* Register the socket our DHCP clients will return lease info on */
-	nm_bus_manager_private_server_register (priv->dbus_mgr, PRIV_SOCK_PATH, PRIV_SOCK_TAG);
+	nm_dbus_manager_private_server_register (priv->dbus_mgr, PRIV_SOCK_PATH, PRIV_SOCK_TAG);
 	priv->new_conn_id = g_signal_connect (priv->dbus_mgr,
-	                                      NM_BUS_MANAGER_PRIVATE_CONNECTION_NEW "::" PRIV_SOCK_TAG,
+	                                      NM_DBUS_MANAGER_PRIVATE_CONNECTION_NEW "::" PRIV_SOCK_TAG,
 	                                      G_CALLBACK (new_connection_cb),
 	                                      self);
 	priv->dis_conn_id = g_signal_connect (priv->dbus_mgr,
-	                                      NM_BUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED "::" PRIV_SOCK_TAG,
+	                                      NM_DBUS_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 f5c7c84b..aa40e803 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;
-	GHashTable *        clients;
-	char *              default_hostname;
+	char *default_hostname;
+	CList dhcp_client_lst_head;
 } NMDhcpManagerPrivate;
 
 struct _NMDhcpManager {
@@ -98,21 +98,17 @@ static NMDhcpClient *
 get_client_for_ifindex (NMDhcpManager *manager, int addr_family, int ifindex)
 {
 	NMDhcpManagerPrivate *priv;
-	GHashTableIter iter;
-	gpointer value;
+	NMDhcpClient *client;
 
 	g_return_val_if_fail (NM_IS_DHCP_MANAGER (manager), NULL);
 	g_return_val_if_fail (ifindex > 0, NULL);
 
 	priv = NM_DHCP_MANAGER_GET_PRIVATE (manager);
 
-	g_hash_table_iter_init (&iter, priv->clients);
-	while (g_hash_table_iter_next (&iter, NULL, &value)) {
-		NMDhcpClient *candidate = NM_DHCP_CLIENT (value);
-
-		if (   nm_dhcp_client_get_ifindex (candidate) == ifindex
-		    && nm_dhcp_client_get_addr_family (candidate) == addr_family)
-			return candidate;
+	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;
 	}
 
 	return NULL;
@@ -129,13 +125,19 @@ 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.
 	 */
+}
 
-	g_hash_table_remove (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client);
+static void
+remove_client_unref (NMDhcpManager *self, NMDhcpClient *client)
+{
+	remove_client (self, client);
+	g_object_unref (client);
 }
 
 static void
@@ -147,7 +149,7 @@ client_state_changed (NMDhcpClient *client,
                       NMDhcpManager *self)
 {
 	if (state >= NM_DHCP_STATE_TIMEOUT)
-		remove_client (self, client);
+		remove_client_unref (self, client);
 }
 
 static NMDhcpClient *
@@ -156,12 +158,12 @@ client_start (NMDhcpManager *self,
               NMDedupMultiIndex *multi_idx,
               const char *iface,
               int ifindex,
-              const GByteArray *hwaddr,
+              GBytes *hwaddr,
               const char *uuid,
               guint32 route_table,
               guint32 route_metric,
               const struct in6_addr *ipv6_ll_addr,
-              const char *dhcp_client_id,
+              GBytes *dhcp_client_id,
               guint32 timeout,
               const char *dhcp_anycast_addr,
               const char *hostname,
@@ -179,23 +181,21 @@ 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,21 +206,26 @@ 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);
-	g_hash_table_insert (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client, g_object_ref (client));
+	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_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, hostname_use_fqdn, last_ip4_address);
+		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, last_ip4_address);
 	else
-		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, info_only, privacy, needed_prefixes);
+		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, privacy, needed_prefixes);
 
 	if (!success) {
-		remove_client (self, client);
-		client = NULL;
+		remove_client_unref (self, client);
+		return NULL;
 	}
 
-	return client;
+	return g_object_ref (client);
 }
 
 /* Caller owns a reference to the NMDhcpClient on return */
@@ -229,14 +234,14 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
                            NMDedupMultiIndex *multi_idx,
                            const char *iface,
                            int ifindex,
-                           const GByteArray *hwaddr,
+                           GBytes *hwaddr,
                            const char *uuid,
                            guint32 route_table,
                            guint32 route_metric,
                            gboolean send_hostname,
                            const char *dhcp_hostname,
                            const char *dhcp_fqdn,
-                           const char *dhcp_client_id,
+                           GBytes *dhcp_client_id,
                            guint32 timeout,
                            const char *dhcp_anycast_addr,
                            const char *last_ip_address)
@@ -285,7 +290,7 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
                            NMDedupMultiIndex *multi_idx,
                            const char *iface,
                            int ifindex,
-                           const GByteArray *hwaddr,
+                           GBytes *hwaddr,
                            const struct in6_addr *ll_addr,
                            const char *uuid,
                            guint32 route_table,
@@ -306,7 +311,7 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
 
 	if (send_hostname) {
 		/* Always prefer the explicit dhcp-hostname if given */
-		hostname = dhcp_hostname ? dhcp_hostname : priv->default_hostname;
+		hostname = dhcp_hostname ?: priv->default_hostname;
 	}
 	return client_start (self, AF_INET6, multi_idx, iface, ifindex, hwaddr, uuid,
 	                     route_table, route_metric, ll_addr,
@@ -328,31 +333,6 @@ 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)
 {
@@ -378,6 +358,8 @@ 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];
 
@@ -429,38 +411,21 @@ 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)
 {
-	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);
+	NMDhcpManager *self = NM_DHCP_MANAGER (object);
+	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
+	NMDhcpClient *client, *client_safe;
 
-	g_free (priv->default_hostname);
+	c_list_for_each_entry_safe (client, client_safe, &priv->dhcp_client_lst_head, dhcp_client_lst)
+		remove_client_unref (self, client);
 
-	if (priv->clients)
-		g_hash_table_destroy (priv->clients);
+	G_OBJECT_CLASS (nm_dhcp_manager_parent_class)->dispose (object);
 
-	G_OBJECT_CLASS (nm_dhcp_manager_parent_class)->finalize (object);
+	nm_clear_g_free (&priv->default_hostname);
 }
 
 static void
@@ -468,6 +433,5 @@ 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 078117ff..f8a7e31d 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,
-                                              const GByteArray *hwaddr,
+                                              GBytes *hwaddr,
                                               const char *uuid,
                                               guint32 route_table,
                                               guint32 route_metric,
                                               gboolean send_hostname,
                                               const char *dhcp_hostname,
                                               const char *dhcp_fqdn,
-                                              const char *dhcp_client_id,
+                                              GBytes *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,
-                                              const GByteArray *hwaddr,
+                                              GBytes *hwaddr,
                                               const struct in6_addr *ll_addr,
                                               const char *uuid,
                                               guint32 route_table,
@@ -78,15 +78,6 @@ 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 f79b7cb1..2d0202bb 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -29,6 +29,7 @@
 #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"
@@ -60,8 +61,7 @@ typedef struct {
 
 	guint request_count;
 
-	gboolean privacy;
-	gboolean info_only;
+	bool privacy:1;
 } NMDhcpSystemdPrivate;
 
 struct _NMDhcpSystemd {
@@ -451,36 +451,6 @@ 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
@@ -543,7 +513,7 @@ bound4_handle (NMDhcpSystemd *self)
 
 		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
 		                          NM_DHCP_STATE_BOUND,
-		                          G_OBJECT (ip4_config),
+		                          NM_IP_CONFIG_CAST (ip4_config),
 		                          options);
 	} else {
 		_LOGW ("%s", error->message);
@@ -584,14 +554,16 @@ dhcp_event_cb (sd_dhcp_client *client, int event, gpointer user_data)
 }
 
 static guint16
-get_arp_type (const GByteArray *hwaddr)
+get_arp_type (GBytes *hwaddr)
 {
-	if (hwaddr->len == ETH_ALEN)
+	switch (g_bytes_get_size (hwaddr)) {
+	case ETH_ALEN:
 		return ARPHRD_ETHER;
-	else if (hwaddr->len == INFINIBAND_ALEN)
+	case INFINIBAND_ALEN:
 		return ARPHRD_INFINIBAND;
-	else
+	default:
 		return ARPHRD_NONE;
+	}
 }
 
 static gboolean
@@ -600,7 +572,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);
-	const GByteArray *hwaddr;
+	GBytes *hwaddr;
 	sd_dhcp_lease *lease = NULL;
 	GBytes *override_client_id;
 	const uint8_t *client_id = NULL;
@@ -609,7 +581,6 @@ 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);
@@ -633,16 +604,14 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 
 	hwaddr = nm_dhcp_client_get_hw_addr (client);
 	if (hwaddr) {
-		arp_type= get_arp_type (hwaddr);
-		if (arp_type == ARPHRD_NONE) {
-			_LOGW ("failed to determine ARP type");
-			goto error;
-		}
+		const uint8_t *data;
+		gsize len;
 
+		data = g_bytes_get_data (hwaddr, &len);
 		r = sd_dhcp_client_set_mac (priv->client4,
-		                            hwaddr->data,
-		                            hwaddr->len,
-		                            arp_type);
+		                            data,
+		                            len,
+		                            get_arp_type (hwaddr));
 		if (r < 0) {
 			_LOGW ("failed to set MAC address (%d)", r);
 			goto error;
@@ -661,12 +630,6 @@ 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)
@@ -704,7 +667,6 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 		}
 	}
 
-
 	/* Add requested options */
 	for (i = 0; dhcp4_requests[i].name; i++) {
 		if (dhcp4_requests[i].include)
@@ -854,13 +816,13 @@ bound6_handle (NMDhcpSystemd *self)
 	                                  lease,
 	                                  options,
 	                                  TRUE,
-	                                  priv->info_only,
+	                                  nm_dhcp_client_get_info_only (NM_DHCP_CLIENT (self)),
 	                                  &error);
 
 	if (ip6_config) {
 		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
 		                          NM_DHCP_STATE_BOUND,
-		                          G_OBJECT (ip6_config),
+		                          NM_IP_CONFIG_CAST (ip6_config),
 		                          options);
 	} else {
 		_LOGW ("%s", error->message);
@@ -900,24 +862,29 @@ static gboolean
 ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
-           gboolean info_only,
            NMSettingIP6ConfigPrivacy privacy,
-           const GByteArray *duid,
+           GBytes *duid,
            guint needed_prefixes)
 {
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 	const char *iface = nm_dhcp_client_get_iface (client);
-	const GByteArray *hwaddr;
+	GBytes *hwaddr;
+	const char *hostname;
 	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) {
@@ -932,16 +899,13 @@ ip6_start (NMDhcpClient *client,
 
 	_LOGT ("dhcp-client6: set %p", priv->client6);
 
-	if (info_only)
-	    sd_dhcp6_client_set_information_request (priv->client6, 1);
+	if (nm_dhcp_client_get_info_only (client))
+		sd_dhcp6_client_set_information_request (priv->client6, 1);
 
-	/* NM stores the entire DUID which includes the uint16 "type", while systemd
-	 * wants the type passed separately from the following data.
-	 */
 	r = sd_dhcp6_client_set_duid (priv->client6,
-	                              ntohs (((const guint16 *) duid->data)[0]),
-	                              duid->data + 2,
-	                              duid->len - 2);
+	                              unaligned_read_be16 (&duid_arr[0]),
+	                              &duid_arr[2],
+	                              duid_len - 2);
 	if (r < 0) {
 		_LOGW ("failed to set DUID (%d)", r);
 		return FALSE;
@@ -955,9 +919,13 @@ 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,
-		                             hwaddr->data,
-		                             hwaddr->len,
+		                             data,
+		                             len,
 		                             get_arp_type (hwaddr));
 		if (r < 0) {
 			_LOGW ("failed to set MAC address (%d)", r);
@@ -989,6 +957,13 @@ 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);
@@ -1006,12 +981,14 @@ error:
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+stop (NMDhcpClient *client, gboolean release, GBytes *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);
@@ -1074,5 +1051,4 @@ 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 50ca2abe..9185a135 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -721,11 +721,15 @@ error:
 }
 
 char *
-nm_dhcp_utils_duid_to_string (const GByteArray *duid)
+nm_dhcp_utils_duid_to_string (GBytes *duid)
 {
+	gconstpointer data;
+	gsize len;
+
 	g_return_val_if_fail (duid != NULL, NULL);
 
-	return _nm_utils_bin2str (duid->data, duid->len, FALSE);
+	data = g_bytes_get_data (duid, &len);
+	return _nm_utils_bin2str (data, len, FALSE);
 }
 
 /**
diff --git a/src/dhcp/nm-dhcp-utils.h b/src/dhcp/nm-dhcp-utils.h
index 32140f48..5c127bd1 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          (const GByteArray *duid);
+char *nm_dhcp_utils_duid_to_string (GBytes *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
deleted file mode 100644
index 703d9247..00000000
--- a/src/dhcp/tests/leases/basic.leases
+++ /dev/null
@@ -1,31 +0,0 @@
-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
deleted file mode 100644
index 401d982a..00000000
--- a/src/dhcp/tests/leases/malformed1.leases
+++ /dev/null
@@ -1,15 +0,0 @@
-# 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
deleted file mode 100644
index adf5f6de..00000000
--- a/src/dhcp/tests/leases/malformed2.leases
+++ /dev/null
@@ -1,15 +0,0 @@
-# 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
deleted file mode 100644
index a2afc8b6..00000000
--- a/src/dhcp/tests/leases/malformed3.leases
+++ /dev/null
@@ -1,15 +0,0 @@
-# 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
new file mode 100644
index 00000000..9d8be427
--- /dev/null
+++ b/src/dhcp/tests/meson.build
@@ -0,0 +1,19 @@
+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: nm_build_cflags,
+  )
+
+  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 f2e1f321..a8284b23 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -36,11 +36,7 @@
 
 #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;
+#define TEST_DIR             NM_BUILD_SRCDIR"/src/dhcp/tests"
 
 static void
 test_config (const char *orig,
@@ -154,7 +150,7 @@ test_override_client_id (void)
 static const char *quote_client_id_expected = \
 	"# Created by NetworkManager\n"
 	"\n"
-	"send dhcp-client-identifier \"1234\"; # added by NetworkManager\n"
+	"send dhcp-client-identifier \"\\x00abcd\"; # 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"
@@ -172,7 +168,65 @@ test_quote_client_id (void)
 {
 	test_config (NULL, quote_client_id_expected,
 	             AF_INET, NULL, 0, FALSE,
-	             "1234",
+	             "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",
 	             NULL,
 	             "eth0",
 	             NULL);
@@ -183,7 +237,7 @@ test_quote_client_id (void)
 static const char *ascii_client_id_expected = \
 	"# Created by NetworkManager\n"
 	"\n"
-	"send dhcp-client-identifier \"qb:cd:ef:12:34:56\"; # added by NetworkManager\n"
+	"send dhcp-client-identifier \"\\x00qb: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"
@@ -239,13 +293,13 @@ test_hex_single_client_id (void)
 /*****************************************************************************/
 
 static const char *existing_hex_client_id_orig = \
-	"send dhcp-client-identifier 00:30:04:20:7A:08;\n";
+	"send dhcp-client-identifier 10:30:04:20:7A:08;\n";
 
 static const char *existing_hex_client_id_expected = \
 	"# Created by NetworkManager\n"
 	"# Merged from /path/to/dhclient.conf\n"
 	"\n"
-	"send dhcp-client-identifier 00:30:04:20:7A:08;\n"
+	"send dhcp-client-identifier 10: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"
@@ -262,7 +316,7 @@ static void
 test_existing_hex_client_id (void)
 {
 	gs_unref_bytes GBytes *new_client_id = NULL;
-	const guint8 bytes[] = { 0x00, 0x30, 0x04,0x20, 0x7A, 0x08 };
+	const guint8 bytes[] = { 0x10, 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,
@@ -275,16 +329,52 @@ 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 \"" EACID "\";\n";
+	"send dhcp-client-identifier \"\\x00" EACID "\";\n";
 
 static const char *existing_ascii_client_id_expected = \
 	"# Created by NetworkManager\n"
 	"# Merged from /path/to/dhclient.conf\n"
 	"\n"
-	"send dhcp-client-identifier \"" EACID "\";\n"
+	"send dhcp-client-identifier \"\\x00" 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"
@@ -585,23 +675,26 @@ test_existing_multiline_alsoreq (void)
 static void
 test_one_duid (const char *escaped, const guint8 *unescaped, guint len)
 {
-	GByteArray *t;
+	GBytes *t;
 	char *w;
+	gsize t_len;
+	gconstpointer t_arr;
 
 	t = nm_dhcp_dhclient_unescape_duid (escaped);
 	g_assert (t);
-	g_assert_cmpint (t->len, ==, len);
-	g_assert_cmpint (memcmp (t->data, unescaped, len), ==, 0);
-	g_byte_array_free (t, TRUE);
+	t_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);
 
-	t = g_byte_array_sized_new (len);
-	g_byte_array_append (t, unescaped, len);
+	t = g_bytes_new_static (unescaped, len);
 	w = nm_dhcp_dhclient_escape_duid (t);
 	g_assert (w);
 	g_assert_cmpint (strlen (escaped), ==, strlen (w));
 	g_assert_cmpstr (escaped, ==, w);
 
-	g_byte_array_free (t, TRUE);
+	g_bytes_unref (t);
 	g_free (w);
 }
 
@@ -640,25 +733,26 @@ test_read_duid_from_leasefile (void)
 {
 	const guint8 expected[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0x79, 0xa6,
 	                            0x13, 0x60, 0x67, 0x20, 0xec, 0x4c, 0x70 };
-	GByteArray *duid;
+	gs_unref_bytes GBytes *duid = NULL;
 	GError *error = NULL;
+	gconstpointer duid_arr;
+	gsize duid_len;
 
-	duid = nm_dhcp_dhclient_read_duid (TESTDIR "/test-dhclient-duid.leases", &error);
+	duid = nm_dhcp_dhclient_read_duid (TEST_DIR"/test-dhclient-duid.leases", &error);
 	g_assert_no_error (error);
 	g_assert (duid);
-	g_assert_cmpint (duid->len, ==, sizeof (expected));
-	g_assert_cmpint (memcmp (duid->data, expected, duid->len), ==, 0);
-
-	g_byte_array_free (duid, TRUE);
+	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);
 }
 
 static void
 test_read_commented_duid_from_leasefile (void)
 {
-	GByteArray *duid;
+	GBytes *duid;
 	GError *error = NULL;
 
-	duid = nm_dhcp_dhclient_read_duid (TESTDIR "/test-dhclient-commented-duid.leases", &error);
+	duid = nm_dhcp_dhclient_read_duid (TEST_DIR"/test-dhclient-commented-duid.leases", &error);
 	g_assert_no_error (error);
 	g_assert (duid == NULL);
 }
@@ -754,12 +848,12 @@ test_write_existing_commented_duid (void)
 
 static const char *interface1_orig = \
 	"interface \"eth0\" {\n"
-	"	also request my-option;\n"
-	"	initial-delay 5;\n"
+	"\talso request my-option;\n"
+	"\tinitial-delay 5;\n"
 	"}\n"
 	"interface \"eth1\" {\n"
-	"	also request another-option;\n"
-	"	initial-delay 0;\n"
+	"\talso request another-option;\n"
+	"\tinitial-delay 0;\n"
 	"}\n"
 	"\n"
 	"also request yet-another-option;\n";
@@ -798,12 +892,12 @@ test_interface1 (void)
 
 static const char *interface2_orig = \
 	"interface eth0 {\n"
-	"	also request my-option;\n"
-	"	initial-delay 5;\n"
+	"\talso request my-option;\n"
+	"\tinitial-delay 5;\n"
 	" }\n"
 	"interface eth1 {\n"
-	"	initial-delay 0;\n"
-	"	request another-option;\n"
+	"\tinitial-delay 0;\n"
+	"\trequest another-option;\n"
 	" } \n"
 	"\n"
 	"also request yet-another-option;\n";
@@ -844,12 +938,12 @@ test_config_req_intf (void)
 {
 	static const char *const orig = \
 		"request subnet-mask, broadcast-address, routers,\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"
+		"\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"
 		"";
 	static const char *const expected = \
 		"# Created by NetworkManager\n"
@@ -895,133 +989,6 @@ 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
@@ -1031,10 +998,13 @@ 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", test_quote_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/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);
@@ -1056,18 +1026,6 @@ 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 72f31191..617a3c6c 100644
--- a/src/dhcp/tests/test-dhcp-utils.c
+++ b/src/dhcp/tests/test-dhcp-utils.c
@@ -349,8 +349,7 @@ test_dhclient_invalid_classless_routes_1 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring invalid classless static routes*");
+	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -380,8 +379,7 @@ test_dhcpcd_invalid_classless_routes_1 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring invalid classless static routes*");
+	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -413,8 +411,7 @@ test_dhclient_invalid_classless_routes_2 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring invalid classless static routes*");
+	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -446,8 +443,7 @@ test_dhcpcd_invalid_classless_routes_2 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring invalid classless static routes*");
+	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -479,8 +475,7 @@ test_dhclient_invalid_classless_routes_3 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*ignoring invalid classless static routes*");
+	NMTST_EXPECT_NM_WARN ("*ignoring invalid classless static routes*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -507,8 +502,7 @@ test_dhcpcd_invalid_classless_routes_3 (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*DHCP provided invalid classless static route*");
+	NMTST_EXPECT_NM_WARN ("*DHCP provided invalid classless static route*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
@@ -615,8 +609,7 @@ test_invalid_escaped_domain_searches (void)
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
-	                       "*invalid domain search*");
+	NMTST_EXPECT_NM_WARN ("*invalid domain search*");
 	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();