about summary refs log tree commit diff
path: root/src/dhcp
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2019-01-25 11:24:41 +0100
committerSebastien Bacher <seb128@ubuntu.com>2019-01-25 11:24:41 +0100
commitbbae86d3d2997a853ca0365e8eb7a3ca7489ee09 (patch)
tree1f7fa49b47ab13aea3effbe839559d221f6323b4 /src/dhcp
parent404ebe62622150e77e311777dff8617eb974e834 (diff)
New upstream version 1.15.2
Diffstat (limited to 'src/dhcp')
-rw-r--r--src/dhcp/meson.build2
-rw-r--r--src/dhcp/nm-dhcp-client.c96
-rw-r--r--src/dhcp/nm-dhcp-client.h14
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c38
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h5
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c98
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c19
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c5
-rw-r--r--src/dhcp/nm-dhcp-helper.c2
-rw-r--r--src/dhcp/nm-dhcp-manager.c90
-rw-r--r--src/dhcp/nm-dhcp-manager.h2
-rw-r--r--src/dhcp/nm-dhcp-systemd.c856
-rw-r--r--src/dhcp/nm-dhcp-utils.c15
-rw-r--r--src/dhcp/tests/meson.build4
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c69
15 files changed, 711 insertions, 604 deletions
diff --git a/src/dhcp/meson.build b/src/dhcp/meson.build
index 76707bca..a5dd3151 100644
--- a/src/dhcp/meson.build
+++ b/src/dhcp/meson.build
@@ -13,7 +13,7 @@ executable(
   link_args: ldflags_linker_script_binary,
   link_depends: linker_script_binary,
   install: true,
-  install_dir: nm_libexecdir
+  install_dir: nm_libexecdir,
 )
 
 if enable_tests
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 16db8306..7ed7a686 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -28,7 +28,6 @@
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <uuid/uuid.h>
 #include <linux/rtnetlink.h>
 
 #include "nm-utils/nm-dedup-multi.h"
@@ -62,6 +61,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDhcpClient,
 	PROP_ROUTE_TABLE,
 	PROP_TIMEOUT,
 	PROP_UUID,
+	PROP_HOSTNAME,
 );
 
 typedef struct _NMDhcpClientPrivate {
@@ -69,7 +69,6 @@ typedef struct _NMDhcpClientPrivate {
 	char *       iface;
 	GBytes *     hwaddr;
 	char *       uuid;
-	GBytes *     duid;
 	GBytes *     client_id;
 	char *       hostname;
 	pid_t        pid;
@@ -140,14 +139,6 @@ nm_dhcp_client_get_uuid (NMDhcpClient *self)
 }
 
 GBytes *
-nm_dhcp_client_get_duid (NMDhcpClient *self)
-{
-	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
-
-	return NM_DHCP_CLIENT_GET_PRIVATE (self)->duid;
-}
-
-GBytes *
 nm_dhcp_client_get_hw_addr (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
@@ -230,6 +221,18 @@ _set_client_id (NMDhcpClient *self, GBytes *client_id, gboolean take)
 	priv->client_id = client_id;
 	if (!take && client_id)
 		g_bytes_ref (client_id);
+
+	{
+		gs_free char *s = NULL;
+
+		_LOGT ("%s: set %s",
+		       nm_dhcp_client_get_addr_family (self) == AF_INET6
+		         ? "duid"
+		         : "client-id",
+		       priv->client_id
+		         ? (s = nm_dhcp_utils_duid_to_string (priv->client_id))
+		         : "default");
+	}
 }
 
 void
@@ -288,12 +291,13 @@ nm_dhcp_client_get_use_fqdn (NMDhcpClient *self)
 /*****************************************************************************/
 
 static const char *state_table[NM_DHCP_STATE_MAX + 1] = {
-	[NM_DHCP_STATE_UNKNOWN]  = "unknown",
-	[NM_DHCP_STATE_BOUND]    = "bound",
-	[NM_DHCP_STATE_TIMEOUT]  = "timeout",
-	[NM_DHCP_STATE_EXPIRE]   = "expire",
-	[NM_DHCP_STATE_DONE]     = "done",
-	[NM_DHCP_STATE_FAIL]     = "fail",
+	[NM_DHCP_STATE_UNKNOWN]    = "unknown",
+	[NM_DHCP_STATE_BOUND]      = "bound",
+	[NM_DHCP_STATE_TIMEOUT]    = "timeout",
+	[NM_DHCP_STATE_EXPIRE]     = "expire",
+	[NM_DHCP_STATE_DONE]       = "done",
+	[NM_DHCP_STATE_FAIL]       = "fail",
+	[NM_DHCP_STATE_TERMINATED] = "terminated",
 };
 
 static const char *
@@ -362,7 +366,7 @@ nm_dhcp_client_stop_pid (pid_t pid, const char *iface)
 }
 
 static void
-stop (NMDhcpClient *self, gboolean release, GBytes *duid)
+stop (NMDhcpClient *self, gboolean release)
 {
 	NMDhcpClientPrivate *priv;
 
@@ -449,7 +453,6 @@ daemon_watch_cb (GPid pid, int status, gpointer user_data)
 {
 	NMDhcpClient *self = NM_DHCP_CLIENT (user_data);
 	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
-	NMDhcpState new_state;
 
 	g_return_if_fail (priv->watch_id);
 	priv->watch_id = 0;
@@ -465,14 +468,9 @@ daemon_watch_cb (GPid pid, int status, gpointer user_data)
 	else
 		_LOGW ("client died abnormally");
 
-	if (!WIFEXITED (status))
-		new_state = NM_DHCP_STATE_FAIL;
-	else
-		new_state = NM_DHCP_STATE_DONE;
-
 	priv->pid = -1;
 
-	nm_dhcp_client_set_state (self, new_state, NULL, NULL);
+	nm_dhcp_client_set_state (self, NM_DHCP_STATE_TERMINATED, NULL, NULL);
 }
 
 void
@@ -509,7 +507,6 @@ gboolean
 nm_dhcp_client_start_ip4 (NMDhcpClient *self,
                           GBytes *client_id,
                           const char *dhcp_anycast_addr,
-                          const char *hostname,
                           const char *last_ip4_address,
                           GError **error)
 {
@@ -529,9 +526,6 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 
 	nm_dhcp_client_set_client_id (self, client_id);
 
-	g_clear_pointer (&priv->hostname, g_free);
-	priv->hostname = g_strdup (hostname);
-
 	return NM_DHCP_CLIENT_GET_CLASS (self)->ip4_start (self,
 	                                                   dhcp_anycast_addr,
 	                                                   last_ip4_address,
@@ -550,34 +544,29 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
                           gboolean enforce_duid,
                           const char *dhcp_anycast_addr,
                           const struct in6_addr *ll_addr,
-                          const char *hostname,
                           NMSettingIP6ConfigPrivacy privacy,
                           guint needed_prefixes,
                           GError **error)
 {
 	NMDhcpClientPrivate *priv;
-	gs_free char *str = NULL;
+	gs_unref_bytes GBytes *own_client_id = NULL;
 
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+	g_return_val_if_fail (client_id, FALSE);
 
 	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
 	g_return_val_if_fail (priv->pid == -1, FALSE);
 	g_return_val_if_fail (priv->addr_family == AF_INET6, FALSE);
 	g_return_val_if_fail (priv->uuid != NULL, FALSE);
-
-	nm_assert (!priv->duid);
-	nm_assert (client_id);
+	g_return_val_if_fail (!priv->client_id, FALSE);
 
 	if (!enforce_duid)
-		priv->duid = NM_DHCP_CLIENT_GET_CLASS (self)->get_duid (self);
-
-	if (!priv->duid)
-		priv->duid = g_bytes_ref (client_id);
+		own_client_id = NM_DHCP_CLIENT_GET_CLASS (self)->get_duid (self);
 
-	_LOGD ("DUID is '%s'", (str = nm_dhcp_utils_duid_to_string (priv->duid)));
-
-	g_clear_pointer (&priv->hostname, g_free);
-	priv->hostname = g_strdup (hostname);
+	_set_client_id (self,
+	                own_client_id ?: client_id,
+	                FALSE);
 
 	if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
 		_LOGI ("activation: beginning transaction (no timeout)");
@@ -588,7 +577,6 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 	                                                   dhcp_anycast_addr,
 	                                                   ll_addr,
 	                                                   privacy,
-	                                                   priv->duid,
 	                                                   needed_prefixes,
 	                                                   error);
 }
@@ -655,7 +643,7 @@ nm_dhcp_client_stop (NMDhcpClient *self, gboolean release)
 
 	/* Kill the DHCP client */
 	old_pid = priv->pid;
-	NM_DHCP_CLIENT_GET_CLASS (self)->stop (self, release, priv->duid);
+	NM_DHCP_CLIENT_GET_CLASS (self)->stop (self, release);
 	if (old_pid > 0)
 		_LOGI ("canceled DHCP transaction, DHCP client pid %d", old_pid);
 	else
@@ -859,6 +847,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_UUID:
 		g_value_set_string (value, priv->uuid);
 		break;
+	case PROP_HOSTNAME:
+		g_value_set_string (value, priv->hostname);
+		break;
 	case PROP_ROUTE_METRIC:
 		g_value_set_uint (value, priv->route_metric);
 		break;
@@ -899,11 +890,13 @@ set_property (GObject *object, guint prop_id,
 	case PROP_IFACE:
 		/* construct-only */
 		priv->iface = g_value_dup_string (value);
+		g_return_if_fail (   priv->iface
+		                  && nm_utils_is_valid_iface_name (priv->iface, NULL));
 		break;
 	case PROP_IFINDEX:
 		/* construct-only */
 		priv->ifindex = g_value_get_int (value);
-		g_warn_if_fail (priv->ifindex > 0);
+		g_return_if_fail (priv->ifindex > 0);
 		break;
 	case PROP_HWADDR:
 		/* construct-only */
@@ -919,6 +912,10 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		priv->uuid = g_value_dup_string (value);
 		break;
+	case PROP_HOSTNAME:
+		/* construct-only */
+		priv->hostname = g_value_dup_string (value);
+		break;
 	case PROP_ROUTE_TABLE:
 		priv->route_table = g_value_get_uint (value);
 		break;
@@ -971,7 +968,6 @@ dispose (GObject *object)
 	g_clear_pointer (&priv->uuid, g_free);
 	g_clear_pointer (&priv->client_id, g_bytes_unref);
 	g_clear_pointer (&priv->hwaddr, g_bytes_unref);
-	g_clear_pointer (&priv->duid, g_bytes_unref);
 
 	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
 
@@ -1028,6 +1024,12 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                         G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_HOSTNAME] =
+	    g_param_spec_string (NM_DHCP_CLIENT_HOSTNAME, "", "",
+	                         NULL,
+	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                         G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_ROUTE_TABLE] =
 	    g_param_spec_uint (NM_DHCP_CLIENT_ROUTE_TABLE, "", "",
 	                       0, G_MAXUINT32, RT_TABLE_MAIN,
@@ -1058,7 +1060,7 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	    g_signal_new (NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_FIRST,
-	                  G_STRUCT_OFFSET (NMDhcpClientClass, state_changed),
+	                  0,
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_OBJECT, G_TYPE_HASH_TABLE, G_TYPE_STRING);
 
@@ -1066,7 +1068,7 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	    g_signal_new (NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_FIRST,
-	                  G_STRUCT_OFFSET (NMDhcpClientClass, state_changed),
+	                  0,
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_POINTER);
 }
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 86d60e38..1db7eac6 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -41,6 +41,7 @@
 #define NM_DHCP_CLIENT_IFINDEX      "ifindex"
 #define NM_DHCP_CLIENT_INTERFACE    "iface"
 #define NM_DHCP_CLIENT_MULTI_IDX    "multi-idx"
+#define NM_DHCP_CLIENT_HOSTNAME     "hostname"
 #define NM_DHCP_CLIENT_ROUTE_METRIC "route-metric"
 #define NM_DHCP_CLIENT_ROUTE_TABLE  "route-table"
 #define NM_DHCP_CLIENT_TIMEOUT      "timeout"
@@ -56,6 +57,7 @@ typedef enum {
 	NM_DHCP_STATE_DONE,         /* client quit or stopped */
 	NM_DHCP_STATE_EXPIRE,       /* lease expired or NAKed */
 	NM_DHCP_STATE_FAIL,         /* failed for some reason */
+	NM_DHCP_STATE_TERMINATED,   /* client is no longer running */
 	__NM_DHCP_STATE_MAX,
 	NM_DHCP_STATE_MAX = __NM_DHCP_STATE_MAX - 1,
 } NMDhcpState;
@@ -85,13 +87,11 @@ typedef struct {
 	                           const char *anycast_addr,
 	                           const struct in6_addr *ll_addr,
 	                           NMSettingIP6ConfigPrivacy privacy,
-	                           GBytes *duid,
 	                           guint needed_prefixes,
 	                           GError **error);
 
 	void (*stop)              (NMDhcpClient *self,
-	                           gboolean release,
-	                           GBytes *duid);
+	                           gboolean release);
 
 	/**
 	 * get_duid:
@@ -103,12 +103,6 @@ typedef struct {
 	 * returned.
 	 */
 	GBytes *(*get_duid) (NMDhcpClient *self);
-
-	/* Signals */
-	void (*state_changed) (NMDhcpClient *self,
-	                       NMDhcpState state,
-	                       GObject *ip_config,
-	                       GHashTable *options);
 } NMDhcpClientClass;
 
 GType nm_dhcp_client_get_type (void);
@@ -150,7 +144,6 @@ gboolean nm_dhcp_client_get_use_fqdn (NMDhcpClient *self);
 gboolean nm_dhcp_client_start_ip4 (NMDhcpClient *self,
                                    GBytes *client_id,
                                    const char *dhcp_anycast_addr,
-                                   const char *hostname,
                                    const char *last_ip4_address,
                                    GError **error);
 
@@ -159,7 +152,6 @@ gboolean nm_dhcp_client_start_ip6 (NMDhcpClient *self,
                                    gboolean enforce_duid,
                                    const char *dhcp_anycast_addr,
                                    const struct in6_addr *ll_addr,
-                                   const char *hostname,
                                    NMSettingIP6ConfigPrivacy privacy,
                                    guint needed_prefixes,
                                    GError **error);
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index a2c3bfb6..d6da3f5c 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -24,6 +24,7 @@
 #include <string.h>
 #include <ctype.h>
 #include <arpa/inet.h>
+#include <net/if.h>
 
 #include "nm-utils/nm-dedup-multi.h"
 
@@ -233,29 +234,6 @@ read_client_id (const char *str)
 	return nm_utils_hexstr2bin (s);
 }
 
-GBytes *
-nm_dhcp_dhclient_get_client_id_from_config_file (const char *path)
-{
-	gs_free char *contents = NULL;
-	gs_strfreev char **lines = NULL;
-	char **line;
-
-	g_return_val_if_fail (path != NULL, NULL);
-
-	if (!g_file_test (path, G_FILE_TEST_EXISTS))
-		return NULL;
-
-	if (!g_file_get_contents (path, &contents, NULL, NULL))
-		return NULL;
-
-	lines = g_strsplit_set (contents, "\n\r", 0);
-	for (line = lines; lines && *line; line++) {
-		if (!strncmp (*line, CLIENTID_TAG, NM_STRLEN (CLIENTID_TAG)))
-			return read_client_id (*line);
-	}
-	return NULL;
-}
-
 static gboolean
 read_interface (const char *line, char *interface, guint size)
 {
@@ -449,6 +427,8 @@ nm_dhcp_dhclient_create_config (const char *interface,
 		add_hostname6 (new_contents, hostname);
 		add_request (reqs, "dhcp6.name-servers");
 		add_request (reqs, "dhcp6.domain-search");
+
+		/* FIXME: internal client does not support requesting client-id option. Does this even work? */
 		add_request (reqs, "dhcp6.client-id");
 	}
 
@@ -570,6 +550,7 @@ error:
 
 #define DUID_PREFIX "default-duid \""
 
+/* Beware: @error may be unset even if the function returns %NULL. */
 GBytes *
 nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
 {
@@ -606,9 +587,10 @@ nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
 
 gboolean
 nm_dhcp_dhclient_save_duid (const char *leasefile,
-                            const char *escaped_duid,
+                            GBytes *duid,
                             GError **error)
 {
+	gs_free char *escaped_duid = NULL;
 	gs_strfreev char **lines = NULL;
 	char **iter, *l;
 	GString *s;
@@ -616,6 +598,14 @@ nm_dhcp_dhclient_save_duid (const char *leasefile,
 	gsize len = 0;
 
 	g_return_val_if_fail (leasefile != NULL, FALSE);
+
+	if (!duid) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN,
+		                            "missing duid");
+		g_return_val_if_reached (FALSE);
+	}
+
+	escaped_duid = nm_dhcp_dhclient_escape_duid (duid);
 	g_return_val_if_fail (escaped_duid != NULL, FALSE);
 
 	if (g_file_test (leasefile, G_FILE_TEST_EXISTS)) {
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.h b/src/dhcp/nm-dhcp-dhclient-utils.h
index fab9196a..57a711db 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -40,10 +40,7 @@ GBytes *nm_dhcp_dhclient_unescape_duid (const char *duid);
 GBytes *nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error);
 
 gboolean nm_dhcp_dhclient_save_duid (const char *leasefile,
-                                     const char *escaped_duid,
+                                     GBytes *duid,
                                      GError **error);
 
-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 33c26712..0146c8b4 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -121,8 +121,8 @@ get_dhclient_leasefile (int addr_family,
                         const char *uuid,
                         char **out_preferred_path)
 {
-	char *rundir_path;
-	char *path;
+	gs_free char *rundir_path = NULL;
+	gs_free char *path = NULL;
 
 	/* First, see if the lease file is in /run */
 	rundir_path = g_strdup_printf (NMRUNDIR "/dhclient%s-%s-%s.lease",
@@ -132,7 +132,7 @@ get_dhclient_leasefile (int addr_family,
 
 	if (g_file_test (rundir_path, G_FILE_TEST_EXISTS)) {
 		NM_SET_OUT (out_preferred_path, g_strdup (rundir_path));
-		return rundir_path;
+		return g_steal_pointer (&rundir_path);
 	}
 
 	/* /var/lib/NetworkManager is the preferred leasefile path */
@@ -142,18 +142,14 @@ get_dhclient_leasefile (int addr_family,
 	                        iface);
 
 	if (g_file_test (path, G_FILE_TEST_EXISTS)) {
-		g_free (rundir_path);
 		NM_SET_OUT (out_preferred_path, g_strdup (path));
-		return path;
+		return g_steal_pointer (&path);
 	}
 
-	if (nm_config_get_configure_and_quit (nm_config_get ()) == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD) {
-		g_free (path);
-		path = rundir_path;
-	} else {
-		g_free (rundir_path);
-	}
-	NM_SET_OUT (out_preferred_path, g_steal_pointer (&path));
+	if (nm_config_get_configure_and_quit (nm_config_get ()) == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD)
+		NM_SET_OUT (out_preferred_path, g_steal_pointer (&rundir_path));
+	else
+		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
@@ -166,17 +162,16 @@ get_dhclient_leasefile (int addr_family,
 	path = g_strdup_printf (LOCALSTATEDIR "/lib/dhcp/dhclient%s-%s-%s.lease",
 	                        _addr_family_to_path_part (addr_family), uuid, iface);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
-		return path;
+		return g_steal_pointer (&path);
 
 	/* Old Red Hat and Fedora location */
 	g_free (path);
 	path = g_strdup_printf (LOCALSTATEDIR "/lib/dhclient/dhclient%s-%s-%s.lease",
 	                        _addr_family_to_path_part (addr_family), uuid, iface);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
-		return path;
+		return g_steal_pointer (&path);
 
 	/* Fail */
-	g_free (path);
 	return NULL;
 }
 
@@ -327,8 +322,18 @@ create_dhclient_config (NMDhcpDhclient *self,
 	else
 		_LOGD ("no existing dhclient configuration to merge");
 
-	if (!merge_dhclient_config (self, addr_family, iface, new, client_id, dhcp_anycast_addr,
-	                            hostname, timeout, use_fqdn, orig, out_new_client_id, &error)) {
+	if (!merge_dhclient_config (self,
+	                            addr_family,
+	                            iface,
+	                            new,
+	                            client_id,
+	                            dhcp_anycast_addr,
+	                            hostname,
+	                            timeout,
+	                            use_fqdn,
+	                            orig,
+	                            out_new_client_id,
+	                            &error)) {
 		_LOGW ("error creating dhclient configuration: %s", error->message);
 		g_clear_error (&error);
 	}
@@ -339,7 +344,6 @@ create_dhclient_config (NMDhcpDhclient *self,
 static gboolean
 dhclient_start (NMDhcpClient *client,
                 const char *mode_opt,
-                GBytes *duid,
                 gboolean release,
                 pid_t *out_pid,
                 int prefixes,
@@ -418,10 +422,9 @@ dhclient_start (NMDhcpClient *client,
 
 	/* 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);
-		if (!nm_dhcp_dhclient_save_duid (priv->lease_file, escaped, &local)) {
+		if (!nm_dhcp_dhclient_save_duid (priv->lease_file,
+		                                 nm_dhcp_client_get_client_id (client),
+		                                 &local)) {
 			nm_utils_error_set (error,
 			                    NM_UTILS_ERROR_UNKNOWN,
 			                    "failed to save DUID to '%s': %s",
@@ -541,7 +544,6 @@ ip4_start (NMDhcpClient *client,
 	}
 	return dhclient_start (client,
 	                       NULL,
-	                       NULL,
 	                       FALSE,
 	                       NULL,
 	                       0,
@@ -553,7 +555,6 @@ ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
            guint needed_prefixes,
            GError **error)
 {
@@ -581,7 +582,6 @@ ip6_start (NMDhcpClient *client,
 	                       nm_dhcp_client_get_info_only (NM_DHCP_CLIENT (self))
 	                         ? "-S"
 	                         : "-N",
-	                       duid,
 	                       FALSE,
 	                       NULL,
 	                       needed_prefixes,
@@ -589,12 +589,12 @@ ip6_start (NMDhcpClient *client,
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
 
-	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->stop (client, release, duid);
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->stop (client, release);
 
 	if (priv->conf_file)
 		if (remove (priv->conf_file) == -1)
@@ -613,7 +613,6 @@ stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 
 		if (dhclient_start (client,
 		                    NULL,
-		                    duid,
 		                    TRUE,
 		                    &rpid,
 		                    0,
@@ -624,31 +623,13 @@ stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 	}
 }
 
-static void
-state_changed (NMDhcpClient *client,
-               NMDhcpState state,
-               GObject *ip_config,
-               GHashTable *options)
-{
-	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE ((NMDhcpDhclient *) client);
-	gs_unref_bytes GBytes *client_id = NULL;
-
-	if (nm_dhcp_client_get_client_id (client))
-		return;
-	if (state != NM_DHCP_STATE_BOUND)
-		return;
-
-	client_id = nm_dhcp_dhclient_get_client_id_from_config_file (priv->conf_file);
-	nm_dhcp_client_set_client_id (client, client_id);
-}
-
 static GBytes *
 get_duid (NMDhcpClient *client)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
 	GBytes *duid = NULL;
-	char *leasefile;
+	gs_free char *leasefile = NULL;
 	GError *error = NULL;
 
 	/* Look in interface-specific leasefile first for backwards compat */
@@ -659,25 +640,23 @@ get_duid (NMDhcpClient *client)
 	if (leasefile) {
 		_LOGD ("looking for DUID in '%s'", leasefile);
 		duid = nm_dhcp_dhclient_read_duid (leasefile, &error);
-
 		if (error) {
 			_LOGW ("failed to read leasefile '%s': %s",
 			       leasefile, error->message);
 			g_clear_error (&error);
 		}
-		g_free (leasefile);
+		if (duid)
+			return duid;
 	}
 
-	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);
-		if (error) {
-			_LOGW ("failed to read leasefile '%s': %s",
-			        priv->def_leasefile,
-			        error->message);
-			g_clear_error (&error);
-		}
+	/* Otherwise read the default machine-wide DUID */
+	_LOGD ("looking for default DUID in '%s'", priv->def_leasefile);
+	duid = nm_dhcp_dhclient_read_duid (priv->def_leasefile, &error);
+	if (error) {
+		_LOGW ("failed to read leasefile '%s': %s",
+		        priv->def_leasefile,
+		        error->message);
+		g_clear_error (&error);
 	}
 
 	return duid;
@@ -742,7 +721,6 @@ nm_dhcp_dhclient_class_init (NMDhcpDhclientClass *dhclient_class)
 	client_class->ip6_start = ip6_start;
 	client_class->stop = stop;
 	client_class->get_duid = get_duid;
-	client_class->state_changed = state_changed;
 }
 
 const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient = {
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
index de403020..0f033e22 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -193,20 +193,20 @@ ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
            guint needed_prefixes,
            GError **error)
 {
 	nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "dhcpcanon plugin does not support IPv6");
 	return FALSE;
 }
+
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
 	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
 
-	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcanon_parent_class)->stop (client, release, duid);
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcanon_parent_class)->stop (client, release);
 
 	if (priv->pid_file) {
 		if (remove (priv->pid_file) == -1)
@@ -216,18 +216,6 @@ stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 	}
 }
 
-static void
-state_changed (NMDhcpClient *client,
-               NMDhcpState state,
-               GObject *ip_config,
-               GHashTable *options)
-{
-	if (nm_dhcp_client_get_client_id (client))
-		return;
-	if (state != NM_DHCP_STATE_BOUND)
-		return;
-}
-
 /*****************************************************************************/
 
 static void
@@ -270,7 +258,6 @@ nm_dhcp_dhcpcanon_class_init (NMDhcpDhcpcanonClass *dhcpcanon_class)
 	client_class->ip4_start = ip4_start;
 	client_class->ip6_start = ip6_start;
 	client_class->stop = stop;
-	client_class->state_changed = state_changed;
 }
 
 const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon = {
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index 98ab5342..e2a1354f 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -187,7 +187,6 @@ ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
            guint needed_prefixes,
            GError **error)
 {
@@ -196,12 +195,12 @@ ip6_start (NMDhcpClient *client,
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
 	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (self);
 
-	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcd_parent_class)->stop (client, release, duid);
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcd_parent_class)->stop (client, release);
 
 	if (priv->pid_file) {
 		if (remove (priv->pid_file) == -1)
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c
index 7f1d2a7b..83cc4600 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -190,7 +190,7 @@ do_notify:
 		_LOGW ("failure to call notify: %s (try signal via Event)", error->message);
 		g_clear_error (&error);
 
-		/* for backward compatibilty, try to emit the signal. There is no stable
+		/* for backward compatibility, try to emit the signal. There is no stable
 		 * API between the dhcp-helper and NetworkManager. However, while upgrading
 		 * the NetworkManager package, a newer helper might want to notify an
 		 * older server, which still uses the "Event". */
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index 5ae16d72..c13c3043 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -181,6 +181,7 @@ client_start (NMDhcpManager *self,
 	gsize hwaddr_len;
 
 	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
+	g_return_val_if_fail (iface, 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);
@@ -221,6 +222,7 @@ client_start (NMDhcpManager *self,
 	                       NM_DHCP_CLIENT_IFINDEX, ifindex,
 	                       NM_DHCP_CLIENT_HWADDR, hwaddr,
 	                       NM_DHCP_CLIENT_UUID, uuid,
+	                       NM_DHCP_CLIENT_HOSTNAME, hostname,
 	                       NM_DHCP_CLIENT_ROUTE_TABLE, (guint) route_table,
 	                       NM_DHCP_CLIENT_ROUTE_METRIC, (guint) route_metric,
 	                       NM_DHCP_CLIENT_TIMEOUT, (guint) timeout,
@@ -233,11 +235,36 @@ client_start (NMDhcpManager *self,
 	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);
 
+	/* unfortunately, our implementations work differently per address-family regarding client-id/DUID.
+	 *
+	 * - for IPv4, the calling code may determine a client-id (from NM's connection profile).
+	 *   If present, it is taken. If not present, the DHCP plugin uses a plugin specific default.
+	 *     - for "internal" plugin, the default is just "mac".
+	 *     - for "dhclient", we try to get the configuration from dhclient's /etc/dhcp or fallback
+	 *       to whatever dhclient uses by default.
+	 *   We do it this way, because for dhclient the user may configure a default
+	 *   outside of NM, and we want to honor that. Worse, dhclient could be a wapper
+	 *   script where the wrapper script overwrites the client-id. We need to distinguish
+	 *   between: force a particular client-id and leave it unspecified to whatever dhclient
+	 *   wants.
+	 *
+	 * - for IPv6, the calling code always determines a client-id. It also specifies @enforce_duid,
+	 *   to determine whether the given client-id must be used.
+	 *     - for "internal" plugin @enforce_duid doesn't matter and the given client-id is
+	 *       always used.
+	 *     - for "dhclient", @enforce_duid FALSE means to first try to load the DUID from the
+	 *       lease file, and only otherwise fallback to the given client-id.
+	 *     - other plugins don't support DHCPv6.
+	 *   It's done this way, so that existing dhclient setups don't change behavior on upgrade.
+	 *
+	 * This difference is cumbersome and only exists because of "dhclient" which supports hacking the
+	 * default outside of NetworkManager API.
+	 */
+
 	if (addr_family == AF_INET) {
 		success = nm_dhcp_client_start_ip4 (client,
 		                                    dhcp_client_id,
 		                                    dhcp_anycast_addr,
-		                                    hostname,
 		                                    last_ip4_address,
 		                                    error);
 	} else {
@@ -246,7 +273,6 @@ client_start (NMDhcpManager *self,
 		                                    enforce_duid,
 		                                    dhcp_anycast_addr,
 		                                    ipv6_ll_addr,
-		                                    hostname,
 		                                    privacy,
 		                                    needed_prefixes,
 		                                    error);
@@ -311,10 +337,27 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
 		}
 	}
 
-	return client_start (self, AF_INET, multi_idx, iface, ifindex, hwaddr, uuid,
-	                     route_table, route_metric, NULL,
-	                     dhcp_client_id, 0, timeout, dhcp_anycast_addr, hostname,
-	                     use_fqdn, FALSE, 0, last_ip_address, 0, error);
+	return client_start (self,
+	                     AF_INET,
+	                     multi_idx,
+	                     iface,
+	                     ifindex,
+	                     hwaddr,
+	                     uuid,
+	                     route_table,
+	                     route_metric,
+	                     NULL,
+	                     dhcp_client_id,
+	                     FALSE,
+	                     timeout,
+	                     dhcp_anycast_addr,
+	                     hostname,
+	                     use_fqdn,
+	                     FALSE,
+	                     0,
+	                     last_ip_address,
+	                     0,
+	                     error);
 }
 
 /* Caller owns a reference to the NMDhcpClient on return */
@@ -349,10 +392,27 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
 		/* Always prefer the explicit dhcp-hostname if given */
 		hostname = dhcp_hostname ?: priv->default_hostname;
 	}
-	return client_start (self, AF_INET6, multi_idx, iface, ifindex, hwaddr, uuid,
-	                     route_table, route_metric, ll_addr, duid, enforce_duid,
-	                     timeout, dhcp_anycast_addr, hostname, TRUE, info_only,
-	                     privacy, NULL, needed_prefixes, error);
+	return client_start (self,
+	                     AF_INET6,
+	                     multi_idx,
+	                     iface,
+	                     ifindex,
+	                     hwaddr,
+	                     uuid,
+	                     route_table,
+	                     route_metric,
+	                     ll_addr,
+	                     duid,
+	                     enforce_duid,
+	                     timeout,
+	                     dhcp_anycast_addr,
+	                     hostname,
+	                     TRUE,
+	                     info_only,
+	                     privacy,
+	                     NULL,
+	                     needed_prefixes,
+	                     error);
 }
 
 void
@@ -384,6 +444,12 @@ nm_dhcp_manager_get_config (NMDhcpManager *self)
 
 NM_DEFINE_SINGLETON_GETTER (NMDhcpManager, nm_dhcp_manager_get, NM_TYPE_DHCP_MANAGER);
 
+void
+nmtst_dhcp_manager_unget (gpointer self)
+{
+	_nmtst_nm_dhcp_manager_get_reset (self);
+}
+
 static void
 nm_dhcp_manager_init (NMDhcpManager *self)
 {
@@ -446,6 +512,10 @@ nm_dhcp_manager_init (NMDhcpManager *self)
 
 	nm_log_info (LOGD_DHCP, "dhcp-init: Using DHCP client '%s'", client_factory->name);
 
+	/* NOTE: currently the DHCP plugin is chosen once at start. It's not
+	 * possible to reload that configuration. If that ever becomes possible,
+	 * beware that the "dhcp-plugin" device spec made decisions based on
+	 * the previous plugin and may need reevaluation. */
 	priv->client_factory = client_factory;
 }
 
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index 1d9e5c21..f8f39e53 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -87,4 +87,6 @@ extern const char* nm_dhcp_helper_path;
 
 extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4];
 
+void nmtst_dhcp_manager_unget (gpointer singleton_instance);
+
 #endif /* __NETWORKMANAGER_DHCP_MANAGER_H__ */
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 5b7b5fbe..bcbe916f 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -34,6 +34,7 @@
 #include "nm-utils.h"
 #include "nm-config.h"
 #include "nm-dhcp-utils.h"
+#include "nm-core-utils.h"
 #include "NetworkManagerUtils.h"
 #include "platform/nm-platform.h"
 #include "nm-dhcp-client-logging.h"
@@ -97,63 +98,77 @@ G_DEFINE_TYPE (NMDhcpSystemd, nm_dhcp_systemd, NM_TYPE_DHCP_CLIENT)
 #define DHCP6_OPTION_IAID            1034
 
 typedef struct {
-	guint num;
 	const char *name;
-	gboolean include;
+	uint16_t option_num;
+	bool include;
 } ReqOption;
 
 #define REQPREFIX "requested_"
 
+#define REQ(_num, _name, _include) \
+	{ \
+		.name = REQPREFIX""_name, \
+		.option_num = _num, \
+		.include = _include, \
+	}
+
 static const ReqOption dhcp4_requests[] = {
-	{ SD_DHCP_OPTION_SUBNET_MASK,                    REQPREFIX "subnet_mask",                     TRUE },
-	{ SD_DHCP_OPTION_TIME_OFFSET,                    REQPREFIX "time_offset",                     TRUE },
-	{ SD_DHCP_OPTION_ROUTER,                         REQPREFIX "routers",                         TRUE },
-	{ SD_DHCP_OPTION_DOMAIN_NAME_SERVER,             REQPREFIX "domain_name_servers",             TRUE },
-	{ SD_DHCP_OPTION_HOST_NAME,                      REQPREFIX "host_name",                       TRUE },
-	{ SD_DHCP_OPTION_DOMAIN_NAME,                    REQPREFIX "domain_name",                     TRUE },
-	{ SD_DHCP_OPTION_INTERFACE_MTU,                  REQPREFIX "interface_mtu",                   TRUE },
-	{ SD_DHCP_OPTION_BROADCAST,                      REQPREFIX "broadcast_address",               TRUE },
-	{ SD_DHCP_OPTION_STATIC_ROUTE,                   REQPREFIX "static_routes",                   TRUE },
-	{ DHCP_OPTION_NIS_DOMAIN,                        REQPREFIX "nis_domain",                      TRUE },
-	{ DHCP_OPTION_NIS_SERVERS,                       REQPREFIX "nis_servers",                     TRUE },
-	{ SD_DHCP_OPTION_NTP_SERVER,                     REQPREFIX "ntp_servers",                     TRUE },
-	{ SD_DHCP_OPTION_SERVER_IDENTIFIER,              REQPREFIX "dhcp_server_identifier",          TRUE },
-	{ SD_DHCP_OPTION_DOMAIN_SEARCH_LIST,             REQPREFIX "domain_search",                   TRUE },
-	{ SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,         REQPREFIX "rfc3442_classless_static_routes", TRUE },
-	{ SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, REQPREFIX "ms_classless_static_routes",      TRUE },
-	{ SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,    REQPREFIX "wpad",                            TRUE },
-	{ SD_DHCP_OPTION_ROOT_PATH,                      REQPREFIX "root_path",                       TRUE },
+	REQ (SD_DHCP_OPTION_SUBNET_MASK,                    "subnet_mask",                     TRUE ),
+	REQ (SD_DHCP_OPTION_TIME_OFFSET,                    "time_offset",                     TRUE ),
+	REQ (SD_DHCP_OPTION_DOMAIN_NAME_SERVER,             "domain_name_servers",             TRUE ),
+	REQ (SD_DHCP_OPTION_HOST_NAME,                      "host_name",                       TRUE ),
+	REQ (SD_DHCP_OPTION_DOMAIN_NAME,                    "domain_name",                     TRUE ),
+	REQ (SD_DHCP_OPTION_INTERFACE_MTU,                  "interface_mtu",                   TRUE ),
+	REQ (SD_DHCP_OPTION_BROADCAST,                      "broadcast_address",               TRUE ),
+
+	/* RFC 3442: The Classless Static Routes option code MUST appear in the parameter
+	 *   request list prior to both the Router option code and the Static
+	 *   Routes option code, if present. */
+	REQ (SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,         "rfc3442_classless_static_routes", TRUE ),
+	REQ (SD_DHCP_OPTION_ROUTER,                         "routers",                         TRUE ),
+	REQ (SD_DHCP_OPTION_STATIC_ROUTE,                   "static_routes",                   TRUE ),
+
+	REQ (DHCP_OPTION_NIS_DOMAIN,                        "nis_domain",                      TRUE ),
+	REQ (DHCP_OPTION_NIS_SERVERS,                       "nis_servers",                     TRUE ),
+	REQ (SD_DHCP_OPTION_NTP_SERVER,                     "ntp_servers",                     TRUE ),
+	REQ (SD_DHCP_OPTION_SERVER_IDENTIFIER,              "dhcp_server_identifier",          TRUE ),
+	REQ (SD_DHCP_OPTION_DOMAIN_SEARCH_LIST,             "domain_search",                   TRUE ),
+	REQ (SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, "ms_classless_static_routes",      TRUE ),
+	REQ (SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,    "wpad",                            TRUE ),
+	REQ (SD_DHCP_OPTION_ROOT_PATH,                      "root_path",                       TRUE ),
 
 	/* Internal values */
-	{ SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,          REQPREFIX "expiry",                          FALSE },
-	{ SD_DHCP_OPTION_CLIENT_IDENTIFIER,              REQPREFIX "dhcp_client_identifier",          FALSE },
-	{ DHCP_OPTION_IP_ADDRESS,                        REQPREFIX "ip_address",                      FALSE },
-	{ 0, NULL, FALSE }
+	REQ (SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,          "expiry",                          FALSE ),
+	REQ (SD_DHCP_OPTION_CLIENT_IDENTIFIER,              "dhcp_client_identifier",          FALSE ),
+	REQ (DHCP_OPTION_IP_ADDRESS,                        "ip_address",                      FALSE ),
+
+	{ 0 }
 };
 
 static const ReqOption dhcp6_requests[] = {
-	{ SD_DHCP6_OPTION_CLIENTID,                      REQPREFIX "dhcp6_client_id",     TRUE },
+	REQ (SD_DHCP6_OPTION_CLIENTID,                      "dhcp6_client_id",     FALSE ),
 
 	/* Don't request server ID by default; some servers don't reply to
 	 * Information Requests that request the Server ID.
 	 */
-	{ SD_DHCP6_OPTION_SERVERID,                      REQPREFIX "dhcp6_server_id",     FALSE },
+	REQ (SD_DHCP6_OPTION_SERVERID,                      "dhcp6_server_id",     FALSE ),
 
-	{ SD_DHCP6_OPTION_DNS_SERVERS,                   REQPREFIX "dhcp6_name_servers",  TRUE },
-	{ SD_DHCP6_OPTION_DOMAIN_LIST,                   REQPREFIX "dhcp6_domain_search", TRUE },
-	{ SD_DHCP6_OPTION_SNTP_SERVERS,                  REQPREFIX "dhcp6_sntp_servers",  TRUE },
+	REQ (SD_DHCP6_OPTION_DNS_SERVERS,                   "dhcp6_name_servers",  TRUE ),
+	REQ (SD_DHCP6_OPTION_DOMAIN_LIST,                   "dhcp6_domain_search", TRUE ),
+	REQ (SD_DHCP6_OPTION_SNTP_SERVERS,                  "dhcp6_sntp_servers",  TRUE ),
 
 	/* Internal values */
-	{ DHCP6_OPTION_IP_ADDRESS,                       REQPREFIX "ip6_address",         FALSE },
-	{ DHCP6_OPTION_PREFIXLEN,                        REQPREFIX "ip6_prefixlen",       FALSE },
-	{ DHCP6_OPTION_PREFERRED_LIFE,                   REQPREFIX "preferred_life",      FALSE },
-	{ DHCP6_OPTION_MAX_LIFE,                         REQPREFIX "max_life",            FALSE },
-	{ DHCP6_OPTION_STARTS,                           REQPREFIX "starts",              FALSE },
-	{ DHCP6_OPTION_LIFE_STARTS,                      REQPREFIX "life_starts",         FALSE },
-	{ DHCP6_OPTION_RENEW,                            REQPREFIX "renew",               FALSE },
-	{ DHCP6_OPTION_REBIND,                           REQPREFIX "rebind",              FALSE },
-	{ DHCP6_OPTION_IAID,                             REQPREFIX "iaid",                FALSE },
-	{ 0, NULL, FALSE }
+	REQ (DHCP6_OPTION_IP_ADDRESS,                       "ip6_address",         FALSE ),
+	REQ (DHCP6_OPTION_PREFIXLEN,                        "ip6_prefixlen",       FALSE ),
+	REQ (DHCP6_OPTION_PREFERRED_LIFE,                   "preferred_life",      FALSE ),
+	REQ (DHCP6_OPTION_MAX_LIFE,                         "max_life",            FALSE ),
+	REQ (DHCP6_OPTION_STARTS,                           "starts",              FALSE ),
+	REQ (DHCP6_OPTION_LIFE_STARTS,                      "life_starts",         FALSE ),
+	REQ (DHCP6_OPTION_RENEW,                            "renew",               FALSE ),
+	REQ (DHCP6_OPTION_REBIND,                           "rebind",              FALSE ),
+	REQ (DHCP6_OPTION_IAID,                             "iaid",                FALSE ),
+
+	{ 0 }
 };
 
 static void
@@ -164,18 +179,22 @@ take_option (GHashTable *options,
 {
 	guint i;
 
-	g_return_if_fail (value != NULL);
+	nm_assert (options);
+	nm_assert (requests);
+	nm_assert (value);
 
 	for (i = 0; requests[i].name; i++) {
-		if (requests[i].num == option) {
+		nm_assert (g_str_has_prefix (requests[i].name, REQPREFIX));
+		if (requests[i].option_num == option) {
 			g_hash_table_insert (options,
 			                     (gpointer) (requests[i].name + NM_STRLEN (REQPREFIX)),
 			                     value);
-			break;
+			return;
 		}
 	}
+
 	/* Option should always be found */
-	g_assert (requests[i].name);
+	nm_assert_not_reached ();
 }
 
 static void
@@ -186,13 +205,6 @@ add_option (GHashTable *options, const ReqOption *requests, guint option, const
 }
 
 static void
-add_option_u32 (GHashTable *options, const ReqOption *requests, guint option, guint32 value)
-{
-	if (options)
-		take_option (options, requests, option, g_strdup_printf ("%u", value));
-}
-
-static void
 add_option_u64 (GHashTable *options, const ReqOption *requests, guint option, guint64 value)
 {
 	if (options)
@@ -204,12 +216,21 @@ add_requests_to_options (GHashTable *options, const ReqOption *requests)
 {
 	guint i;
 
-	for (i = 0; options && requests[i].name; i++) {
+	if (!options)
+		return;
+
+	for (i = 0; requests[i].name; i++) {
 		if (requests[i].include)
 			g_hash_table_insert (options, (gpointer) requests[i].name, g_strdup ("1"));
 	}
 }
 
+static GHashTable *
+create_options_dict (void)
+{
+	return g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
+}
+
 #define LOG_LEASE(domain, ...) \
 G_STMT_START { \
 	if (log_lease) { \
@@ -222,76 +243,91 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
                      const char *iface,
                      int ifindex,
                      sd_dhcp_lease *lease,
-                     GHashTable *options,
                      guint32 route_table,
                      guint32 route_metric,
                      gboolean log_lease,
+                     GHashTable **out_options,
                      GError **error)
 {
-	NMIP4Config *ip4_config = NULL;
-	struct in_addr tmp_addr;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	gs_unref_hashtable GHashTable *options = NULL;
 	const struct in_addr *addr_list;
-	char buf[INET_ADDRSTRLEN];
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
 	const char *s;
-	guint32 lifetime = 0, i;
-	NMPlatformIP4Address address;
 	nm_auto_free_gstring GString *str = NULL;
 	gs_free sd_dhcp_route **routes = NULL;
 	const char *const*search_domains = NULL;
 	guint16 mtu;
-	int r, num;
-	guint64 end_time;
+	int i, num;
 	const void *data;
 	gsize data_len;
 	gboolean metered = FALSE;
-	gboolean static_default_gateway = FALSE;
-	gboolean gateway_has = FALSE;
-	in_addr_t gateway = 0;
+	gboolean has_router_from_classless = FALSE;
+	gboolean has_classless_route = FALSE;
+	gboolean has_static_route = FALSE;
+	const gint32 ts = nm_utils_get_monotonic_timestamp_s ();
+	gint64 ts_time = time (NULL);
+	struct in_addr a_address;
+	struct in_addr a_netmask;
+	struct in_addr a_router;
+	guint32 a_plen;
+	guint32 a_lifetime;
 
 	g_return_val_if_fail (lease != NULL, NULL);
 
 	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
 
-	/* Address */
-	sd_dhcp_lease_get_address (lease, &tmp_addr);
-	memset (&address, 0, sizeof (address));
-	address.address = tmp_addr.s_addr;
-	address.peer_address = tmp_addr.s_addr;
-	s = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
-	LOG_LEASE (LOGD_DHCP4, "address %s", s);
-	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, s);
-
-	/* Prefix/netmask */
-	sd_dhcp_lease_get_netmask (lease, &tmp_addr);
-	address.plen = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr);
-	LOG_LEASE (LOGD_DHCP4, "plen %d", address.plen);
+	options = out_options ? create_options_dict () : NULL;
+
+	if (sd_dhcp_lease_get_address (lease, &a_address) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get address from lease");
+		return NULL;
+	}
+	nm_utils_inet4_ntop (a_address.s_addr, addr_str);
+	LOG_LEASE (LOGD_DHCP4, "address %s", addr_str);
+	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, addr_str);
+
+	if (sd_dhcp_lease_get_netmask (lease, &a_netmask) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get netmask from lease");
+		return NULL;
+	}
+	a_plen = nm_utils_ip4_netmask_to_prefix (a_netmask.s_addr);
+	LOG_LEASE (LOGD_DHCP4, "plen %u", (guint) a_plen);
 	add_option (options,
 	            dhcp4_requests,
 	            SD_DHCP_OPTION_SUBNET_MASK,
-	            nm_utils_inet4_ntop (tmp_addr.s_addr, NULL));
-
-	/* Lease time */
-	sd_dhcp_lease_get_lifetime (lease, &lifetime);
-	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
-	address.lifetime = address.preferred = lifetime;
-	end_time = (guint64) time (NULL) + lifetime;
-	LOG_LEASE (LOGD_DHCP4, "expires in %" G_GUINT32_FORMAT " seconds", lifetime);
+	            nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
+
+	if (sd_dhcp_lease_get_lifetime (lease, &a_lifetime) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get lifetime from lease");
+		return NULL;
+	}
+	LOG_LEASE (LOGD_DHCP4, "expires in %u seconds (at %lld)",
+	           (guint) a_lifetime,
+	           (long long) (ts_time + a_lifetime));
 	add_option_u64 (options,
 	                dhcp4_requests,
 	                SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
-	                end_time);
-
-	address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
-	nm_ip4_config_add_address (ip4_config, &address);
+	                (guint64) (ts_time + a_lifetime));
+
+	nm_ip4_config_add_address (ip4_config,
+	                           &((const NMPlatformIP4Address) {
+	                               .address      = a_address.s_addr,
+	                               .peer_address = a_address.s_addr,
+	                               .plen         = a_plen,
+	                               .addr_source  = NM_IP_CONFIG_SOURCE_DHCP,
+	                               .timestamp    = ts,
+	                               .lifetime     = a_lifetime,
+	                               .preferred    = a_lifetime,
+	                           }));
 
-	/* DNS Servers */
 	num = sd_dhcp_lease_get_dns (lease, &addr_list);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
 			if (addr_list[i].s_addr) {
 				nm_ip4_config_add_nameserver (ip4_config, addr_list[i].s_addr);
-				s = nm_utils_inet4_ntop (addr_list[i].s_addr, NULL);
+				s = nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
 				LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", s);
 				g_string_append_printf (str, "%s%s", str->len ? " " : "", s);
 			}
@@ -300,7 +336,6 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, str->str);
 	}
 
-	/* Search domains */
 	num = sd_dhcp_lease_get_search_domains (lease, (char ***) &search_domains);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
@@ -312,141 +347,191 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_SEARCH_LIST, str->str);
 	}
 
-	/* Domain Name */
-	r = sd_dhcp_lease_get_domainname (lease, &s);
-	if (r == 0) {
-		/* Multiple domains sometimes stuffed into option 15 "Domain Name".
-		 * As systemd escapes such characters, split them at \\032. */
-		char **domains = g_strsplit (s, "\\032", 0);
+	if (   sd_dhcp_lease_get_domainname (lease, &s) >= 0
+	    && s) {
+		gs_strfreev char **domains = NULL;
 		char **d;
 
+		/* Multiple domains sometimes stuffed into option 15 "Domain Name".
+		 * As systemd escapes such characters, split them at \\032. */
+		domains = g_strsplit (s, "\\032", 0);
 		for (d = domains; *d; d++) {
 			LOG_LEASE (LOGD_DHCP4, "domain name '%s'", *d);
 			nm_ip4_config_add_domain (ip4_config, *d);
 		}
-		g_strfreev (domains);
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME, s);
 	}
 
-	/* Hostname */
-	r = sd_dhcp_lease_get_hostname (lease, &s);
-	if (r == 0) {
+	if (sd_dhcp_lease_get_hostname (lease, &s) >= 0) {
 		LOG_LEASE (LOGD_DHCP4, "hostname '%s'", s);
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_HOST_NAME, s);
 	}
 
-	/* Routes */
 	num = sd_dhcp_lease_get_routes (lease, &routes);
 	if (num > 0) {
-		nm_gstring_prepare (&str);
+		nm_auto_free_gstring GString *str_classless = NULL;
+		nm_auto_free_gstring GString *str_static = NULL;
+		guint32 default_route_metric = route_metric;
+
 		for (i = 0; i < num; i++) {
-			NMPlatformIP4Route route = { 0 };
-			const char *gw_str;
-			guint8 plen;
-			struct in_addr a;
+			switch (sd_dhcp_route_get_option (routes[i])) {
+			case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
+				has_classless_route = TRUE;
+				break;
+			case SD_DHCP_OPTION_STATIC_ROUTE:
+				has_static_route = TRUE;
+				break;
+			}
+		}
+
+		if (has_classless_route)
+			str_classless = g_string_sized_new (30);
+		if (has_static_route)
+			str_static = g_string_sized_new (30);
 
-			if (sd_dhcp_route_get_destination (routes[i], &a) < 0)
+		for (i = 0; i < num; i++) {
+			char network_net_str[NM_UTILS_INET_ADDRSTRLEN];
+			char gateway_str[NM_UTILS_INET_ADDRSTRLEN];
+			guint8 r_plen;
+			struct in_addr r_network;
+			struct in_addr r_gateway;
+			in_addr_t network_net;
+			int option;
+			guint32 m;
+
+			option = sd_dhcp_route_get_option (routes[i]);
+			if (!NM_IN_SET (option, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,
+			                        SD_DHCP_OPTION_STATIC_ROUTE))
 				continue;
 
-			if (   sd_dhcp_route_get_destination_prefix_length (routes[i], &plen) < 0
-			    || plen > 32)
+			if (sd_dhcp_route_get_destination (routes[i], &r_network) < 0)
+				continue;
+			if (   sd_dhcp_route_get_destination_prefix_length (routes[i], &r_plen) < 0
+			    || r_plen > 32)
+				continue;
+			if (sd_dhcp_route_get_gateway (routes[i], &r_gateway) < 0)
 				continue;
 
-			route.plen = plen;
-			route.network = nm_utils_ip4_address_clear_host_address (a.s_addr, plen);
+			network_net = nm_utils_ip4_address_clear_host_address (r_network.s_addr,
+			                                                       r_plen);
+			nm_utils_inet4_ntop (network_net, network_net_str);
+			nm_utils_inet4_ntop (r_gateway.s_addr, gateway_str);
+
+			LOG_LEASE (LOGD_DHCP4,
+			           "%sstatic route %s/%d gw %s",
+			             option == SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE
+			           ? "classless "
+			           : "",
+			           network_net_str,
+			           (int) r_plen,
+			           gateway_str);
+			g_string_append_printf (nm_gstring_add_space_delimiter (  option == SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE
+			                                                        ? str_classless
+			                                                        : str_static),
+			                        "%s/%d %s",
+			                        network_net_str,
+			                        (int) r_plen,
+			                        gateway_str);
+
+			if (   option == SD_DHCP_OPTION_STATIC_ROUTE
+			    && has_classless_route) {
+				/* RFC 3443: if the DHCP server returns both a Classless Static Routes
+				 * option and a Static Routes option, the DHCP client MUST ignore the
+				 * Static Routes option. */
+				continue;
+			}
 
-			if (sd_dhcp_route_get_gateway (routes[i], &a) < 0)
+			if (   r_plen == 0
+			    && option == SD_DHCP_OPTION_STATIC_ROUTE) {
+				/* for option 33 (static route), RFC 2132 says:
+				 *
+				 * The default route (0.0.0.0) is an illegal destination for a static
+				 * route. */
 				continue;
-			route.gateway = a.s_addr;
-
-			if (route.plen) {
-				route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
-				route.metric = route_metric;
-				route.table_coerced = nm_platform_route_table_coerce (route_table);
-				nm_ip4_config_add_route (ip4_config, &route, NULL);
-
-				s = nm_utils_inet4_ntop (route.network, buf);
-				gw_str = nm_utils_inet4_ntop (route.gateway, NULL);
-				LOG_LEASE (LOGD_DHCP4, "static route %s/%d gw %s", s, route.plen, gw_str);
-
-				g_string_append_printf (str, "%s%s/%d %s", str->len ? " " : "", s, route.plen, gw_str);
-			} else {
-				if (!static_default_gateway) {
-					static_default_gateway = TRUE;
-					gateway_has = TRUE;
-					gateway = route.gateway;
-
-					s = nm_utils_inet4_ntop (route.gateway, NULL);
-					LOG_LEASE (LOGD_DHCP4, "gateway %s", s);
-					add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, s);
-				}
 			}
-		}
-		if (str->len)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, str->str);
-	}
 
-	/* If the DHCP server returns both a Classless Static Routes option and a
-	 * Router option, the DHCP client MUST ignore the Router option [RFC 3442].
-	 * Be more lenient and ignore the Router option only if Classless Static
-	 * Routes contain a default gateway (as other DHCP backends do).
-	 */
-	/* Gateway */
-	if (!static_default_gateway) {
-		r = sd_dhcp_lease_get_router (lease, &tmp_addr);
-		if (r == 0) {
-			gateway_has = TRUE;
-			gateway = tmp_addr.s_addr;
-			s = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
-			LOG_LEASE (LOGD_DHCP4, "gateway %s", s);
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, s);
+			if (r_plen == 0) {
+				/* if there are multiple default routes, we add them with differing
+				 * metrics. */
+				m = default_route_metric;
+				if (default_route_metric < G_MAXUINT32)
+					default_route_metric++;
+
+				has_router_from_classless = TRUE;
+			} else
+				m = route_metric;
+
+			nm_ip4_config_add_route (ip4_config,
+			                         &((const NMPlatformIP4Route) {
+			                             .network       = network_net,
+			                             .plen          = r_plen,
+			                             .gateway       = r_gateway.s_addr,
+			                             .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
+			                             .metric        = m,
+			                             .table_coerced = nm_platform_route_table_coerce (route_table),
+			                         }),
+			                         NULL);
 		}
-	}
 
-	if (gateway_has) {
-		const NMPlatformIP4Route rt = {
-			.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
-			.gateway = gateway,
-			.table_coerced = nm_platform_route_table_coerce (route_table),
-			.metric = route_metric,
-		};
+		if (str_classless && str_classless->len > 0)
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, str_classless->str);
+		if (str_static && str_static->len > 0)
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_STATIC_ROUTE, str_static->str);
+	}
 
-		nm_ip4_config_add_route (ip4_config, &rt, NULL);
+	/* FIXME: internal client only supports returing the first router. */
+	if (sd_dhcp_lease_get_router (lease, &a_router) >= 0) {
+		s = nm_utils_inet4_ntop (a_router.s_addr, addr_str);
+		LOG_LEASE (LOGD_DHCP4, "gateway %s", s);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, s);
+
+		/* If the DHCP server returns both a Classless Static Routes option and a
+		 * Router option, the DHCP client MUST ignore the Router option [RFC 3442].
+		 *
+		 * Be more lenient and ignore the Router option only if Classless Static
+		 * Routes contain a default gateway (as other DHCP backends do).
+		 */
+		if (!has_router_from_classless) {
+			nm_ip4_config_add_route (ip4_config,
+			                         &((const NMPlatformIP4Route) {
+			                             .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
+			                             .gateway       = a_router.s_addr,
+			                             .table_coerced = nm_platform_route_table_coerce (route_table),
+			                             .metric        = route_metric,
+			                         }),
+			                         NULL);
+		}
 	}
 
-	/* MTU */
-	r = sd_dhcp_lease_get_mtu (lease, &mtu);
-	if (r == 0 && mtu) {
+	if (   sd_dhcp_lease_get_mtu (lease, &mtu) >= 0
+	    && mtu) {
 		nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
-		add_option_u32 (options, dhcp4_requests, SD_DHCP_OPTION_INTERFACE_MTU, mtu);
+		add_option_u64 (options, dhcp4_requests, SD_DHCP_OPTION_INTERFACE_MTU, mtu);
 		LOG_LEASE (LOGD_DHCP4, "mtu %u", mtu);
 	}
 
-	/* NTP servers */
 	num = sd_dhcp_lease_get_ntp (lease, &addr_list);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
-			s = nm_utils_inet4_ntop (addr_list[i].s_addr, buf);
+			s = nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
 			LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", s);
 			g_string_append_printf (str, "%s%s", str->len ? " " : "", s);
 		}
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_NTP_SERVER, str->str);
 	}
 
-	/* Root path */
-	r = sd_dhcp_lease_get_root_path (lease, &s);
-	if (r >= 0) {
+	if (sd_dhcp_lease_get_root_path (lease, &s) >= 0) {
 		LOG_LEASE (LOGD_DHCP4, "root path '%s'", s);
 		add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROOT_PATH, s);
 	}
 
-	r = sd_dhcp_lease_get_vendor_specific (lease, &data, &data_len);
-	if (r >= 0)
+	if (sd_dhcp_lease_get_vendor_specific (lease, &data, &data_len) >= 0)
 		metered = !!memmem (data, data_len, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
 	nm_ip4_config_set_metered (ip4_config, metered);
 
-	return ip4_config;
+	NM_SET_OUT (out_options, g_steal_pointer (&options));
+	return g_steal_pointer (&ip4_config);
 }
 
 /*****************************************************************************/
@@ -483,34 +568,17 @@ get_leasefile_path (int addr_family, const char *iface, const char *uuid)
 /*****************************************************************************/
 
 static void
-_save_client_id (NMDhcpSystemd *self,
-                 uint8_t type,
-                 const uint8_t *client_id,
-                 size_t len)
-{
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (client_id != NULL);
-	g_return_if_fail (len > 0);
-
-	if (!nm_dhcp_client_get_client_id (NM_DHCP_CLIENT (self))) {
-		nm_dhcp_client_set_client_id_bin (NM_DHCP_CLIENT (self),
-		                                  type, client_id, len);
-	}
-}
-
-static void
 bound4_handle (NMDhcpSystemd *self)
 {
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 	const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
 	sd_dhcp_lease *lease;
-	NMIP4Config *ip4_config;
-	GHashTable *options;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	gs_unref_hashtable GHashTable *options = NULL;
 	GError *error = NULL;
-	int r;
 
-	r = sd_dhcp_client_get_lease (priv->client4, &lease);
-	if (r < 0 || !lease) {
+	if (   sd_dhcp_client_get_lease (priv->client4, &lease) < 0
+	    || !lease) {
 		_LOGW ("no lease!");
 		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
 		return;
@@ -518,40 +586,29 @@ bound4_handle (NMDhcpSystemd *self)
 
 	_LOGD ("lease available");
 
-	options = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
 	ip4_config = lease_to_ip4_config (nm_dhcp_client_get_multi_idx (NM_DHCP_CLIENT (self)),
 	                                  iface,
 	                                  nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
 	                                  lease,
-	                                  options,
 	                                  nm_dhcp_client_get_route_table (NM_DHCP_CLIENT (self)),
 	                                  nm_dhcp_client_get_route_metric (NM_DHCP_CLIENT (self)),
 	                                  TRUE,
+	                                  &options,
 	                                  &error);
-	if (ip4_config) {
-		const uint8_t *client_id = NULL;
-		size_t client_id_len = 0;
-		uint8_t type = 0;
-
-		add_requests_to_options (options, dhcp4_requests);
-		dhcp_lease_save (lease, priv->lease_file);
-
-		sd_dhcp_client_get_client_id (priv->client4, &type, &client_id, &client_id_len);
-		if (client_id)
-			_save_client_id (self, type, client_id, client_id_len);
-
-		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
-		                          NM_DHCP_STATE_BOUND,
-		                          NM_IP_CONFIG_CAST (ip4_config),
-		                          options);
-	} else {
+	if (!ip4_config) {
 		_LOGW ("%s", error->message);
-		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
 		g_clear_error (&error);
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+		return;
 	}
 
-	g_hash_table_destroy (options);
-	g_clear_object (&ip4_config);
+	add_requests_to_options (options, dhcp4_requests);
+	dhcp_lease_save (lease, priv->lease_file);
+
+	nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
+	                          NM_DHCP_STATE_BOUND,
+	                          NM_IP_CONFIG_CAST (ip4_config),
+	                          options);
 }
 
 static void
@@ -582,127 +639,124 @@ dhcp_event_cb (sd_dhcp_client *client, int event, gpointer user_data)
 	}
 }
 
-static guint16
-get_arp_type (GBytes *hwaddr)
-{
-	switch (g_bytes_get_size (hwaddr)) {
-	case ETH_ALEN:
-		return ARPHRD_ETHER;
-	case INFINIBAND_ALEN:
-		return ARPHRD_INFINIBAND;
-	default:
-		return ARPHRD_NONE;
-	}
-}
-
 static gboolean
 ip4_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const char *last_ip4_address,
            GError **error)
 {
+	nm_auto (sd_dhcp_client_unrefp) sd_dhcp_client *sd_client = NULL;
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
-	const char *iface = nm_dhcp_client_get_iface (client);
+	gs_free char *lease_file = NULL;
 	GBytes *hwaddr;
-	sd_dhcp_lease *lease = NULL;
-	GBytes *override_client_id;
-	const uint8_t *client_id = NULL;
-	size_t client_id_len = 0;
+	const uint8_t *hwaddr_arr;
+	gsize hwaddr_len;
+	int arp_type;
+	GBytes *client_id;
+	gs_unref_bytes GBytes *client_id_new = NULL;
+	const uint8_t *client_id_arr;
+	size_t client_id_len;
 	struct in_addr last_addr = { 0 };
 	const char *hostname;
 	int r, i;
-	gboolean success = FALSE;
 
-	g_assert (priv->client4 == NULL);
-	g_assert (priv->client6 == NULL);
+	g_return_val_if_fail (!priv->client4, FALSE);
+	g_return_val_if_fail (!priv->client6, FALSE);
 
-	g_free (priv->lease_file);
-	priv->lease_file = get_leasefile_path (AF_INET, iface, nm_dhcp_client_get_uuid (client));
-
-	r = sd_dhcp_client_new (&priv->client4, FALSE);
+	r = sd_dhcp_client_new (&sd_client, FALSE);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to create dhcp-client: %s");
 		return FALSE;
 	}
 
-	_LOGT ("dhcp-client4: set %p", priv->client4);
+	_LOGT ("dhcp-client4: set %p", sd_client);
 
-	r = sd_dhcp_client_attach_event (priv->client4, NULL, 0);
+	r = sd_dhcp_client_attach_event (sd_client, NULL, 0);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to attach event: %s");
-		goto errout;
+		return FALSE;
 	}
 
 	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_dhcp_client_set_mac (priv->client4,
-		                            data,
-		                            len,
-		                            get_arp_type (hwaddr));
-		if (r < 0) {
-			nm_utils_error_set_errno (error, r, "failed to set MAC address: %s");
-			goto errout;
-		}
+	if (   !hwaddr
+	    || !(hwaddr_arr = g_bytes_get_data (hwaddr, &hwaddr_len))
+	    || (arp_type = nm_utils_arp_type_detect_from_hwaddrlen (hwaddr_len)) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address");
+		return FALSE;
 	}
-
-	r = sd_dhcp_client_set_ifindex (priv->client4, nm_dhcp_client_get_ifindex (client));
+	r = sd_dhcp_client_set_mac (sd_client,
+	                            hwaddr_arr,
+	                            hwaddr_len,
+	                            (guint16) arp_type);
 	if (r < 0) {
-		nm_utils_error_set_errno (error, r, "failed to set ifindex: %s");
-		goto errout;
+		nm_utils_error_set_errno (error, r, "failed to set MAC address: %s");
+		return FALSE;
 	}
 
-	r = sd_dhcp_client_set_callback (priv->client4, dhcp_event_cb, client);
+	r = sd_dhcp_client_set_ifindex (sd_client,
+	                                nm_dhcp_client_get_ifindex (client));
 	if (r < 0) {
-		nm_utils_error_set_errno (error, r, "failed to set callback: %s");
-		goto errout;
+		nm_utils_error_set_errno (error, r, "failed to set ifindex: %s");
+		return FALSE;
 	}
 
-	dhcp_lease_load (&lease, priv->lease_file);
+	lease_file = get_leasefile_path (AF_INET,
+	                                 nm_dhcp_client_get_iface (client),
+	                                 nm_dhcp_client_get_uuid (client));
 
 	if (last_ip4_address)
 		inet_pton (AF_INET, last_ip4_address, &last_addr);
-	else if (lease)
-		sd_dhcp_lease_get_address (lease, &last_addr);
+	else {
+		nm_auto (sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+
+		dhcp_lease_load (&lease, lease_file);
+		if (lease)
+			sd_dhcp_lease_get_address (lease, &last_addr);
+	}
 
 	if (last_addr.s_addr) {
-		r = sd_dhcp_client_set_request_address (priv->client4, &last_addr);
+		r = sd_dhcp_client_set_request_address (sd_client, &last_addr);
 		if (r < 0) {
 			nm_utils_error_set_errno (error, r, "failed to set last IPv4 address: %s");
-			goto errout;
+			return FALSE;
 		}
 	}
 
-	override_client_id = nm_dhcp_client_get_client_id (client);
-	if (override_client_id) {
-		client_id = g_bytes_get_data (override_client_id, &client_id_len);
-		nm_assert (client_id && client_id_len >= 2);
-		sd_dhcp_client_set_client_id (priv->client4,
-		                              client_id[0],
-		                              client_id + 1,
-		                              NM_MIN (client_id_len - 1, _NM_SD_MAX_CLIENT_ID_LEN));
-	} else if (lease) {
-		r = sd_dhcp_lease_get_client_id (lease, (const void **) &client_id, &client_id_len);
-		if (r == 0 && client_id_len >= 2) {
-			sd_dhcp_client_set_client_id (priv->client4,
-			                              client_id[0],
-			                              client_id + 1,
-			                              client_id_len - 1);
-			_save_client_id (NM_DHCP_SYSTEMD (client),
-			                 client_id[0],
-			                 client_id + 1,
-			                 client_id_len - 1);
-		}
+	client_id = nm_dhcp_client_get_client_id (client);
+	if (!client_id) {
+		client_id_new = nm_utils_dhcp_client_id_mac (arp_type, hwaddr_arr, hwaddr_len);
+		client_id = client_id_new;
+	}
+
+	if (   !(client_id_arr = g_bytes_get_data (client_id, &client_id_len))
+	    || client_id_len < 2) {
+
+		/* invalid client-ids are not expected. */
+		nm_assert_not_reached ();
+
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "no valid IPv4 client-id");
+		return FALSE;
+	}
+
+	/* Note that we always set a client-id. In particular for infiniband that is necessary,
+	 * see https://tools.ietf.org/html/rfc4390#section-2.1 . */
+	r = sd_dhcp_client_set_client_id (sd_client,
+	                                  client_id_arr[0],
+	                                  client_id_arr + 1,
+	                                  NM_MIN (client_id_len - 1, _NM_SD_MAX_CLIENT_ID_LEN));
+	if (r < 0) {
+		nm_utils_error_set_errno (error, r, "failed to set IPv4 client-id: %s");
+		return FALSE;
 	}
 
 	/* Add requested options */
 	for (i = 0; dhcp4_requests[i].name; i++) {
-		if (dhcp4_requests[i].include)
-			sd_dhcp_client_set_request_option (priv->client4, dhcp4_requests[i].num);
+		if (dhcp4_requests[i].include) {
+			nm_assert (dhcp4_requests[i].option_num <= 255);
+			r = sd_dhcp_client_set_request_option (sd_client, dhcp4_requests[i].option_num);
+			nm_assert (r >= 0 || r == -EEXIST);
+		}
 	}
 
 	hostname = nm_dhcp_client_get_hostname (client);
@@ -711,28 +765,36 @@ ip4_start (NMDhcpClient *client,
 		 * only based on whether the hostname has a domain part or not. At the
 		 * moment there is no way to force one or another.
 		 */
-		r = sd_dhcp_client_set_hostname (priv->client4, hostname);
+		r = sd_dhcp_client_set_hostname (sd_client, hostname);
 		if (r < 0) {
 			nm_utils_error_set_errno (error, r, "failed to set DHCP hostname: %s");
-			goto errout;
+			return FALSE;
 		}
 	}
 
+	r = sd_dhcp_client_set_callback (sd_client, dhcp_event_cb, client);
+	if (r < 0) {
+		nm_utils_error_set_errno (error, r, "failed to set callback: %s");
+		return FALSE;
+	}
+
+	priv->client4 = g_steal_pointer (&sd_client);
+
+	g_free (priv->lease_file);
+	priv->lease_file = g_steal_pointer (&lease_file);
+
+	nm_dhcp_client_set_client_id (client, client_id);
+
 	r = sd_dhcp_client_start (priv->client4);
 	if (r < 0) {
+		sd_dhcp_client_set_callback (priv->client4, NULL, NULL);
+		nm_clear_pointer (&priv->client4, sd_dhcp_client_unref);
 		nm_utils_error_set_errno (error, r, "failed to start DHCP client: %s");
-		goto errout;
+		return FALSE;
 	}
 
 	nm_dhcp_client_start_timeout (client);
-
-	success = TRUE;
-
-errout:
-	sd_dhcp_lease_unref (lease);
-	if (!success)
-		sd_dhcp_client_unref (g_steal_pointer (&priv->client4));
-	return success;
+	return TRUE;
 }
 
 static NMIP6Config *
@@ -740,41 +802,45 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
                      const char *iface,
                      int ifindex,
                      sd_dhcp6_lease *lease,
-                     GHashTable *options,
                      gboolean log_lease,
                      gboolean info_only,
+                     GHashTable **out_options,
                      GError **error)
 {
+	gs_unref_object NMIP6Config *ip6_config = NULL;
+	gs_unref_hashtable GHashTable *options = NULL;
 	struct in6_addr tmp_addr, *dns;
 	uint32_t lft_pref, lft_valid;
-	NMIP6Config *ip6_config;
-	const char *addr_str;
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
 	char **domains;
 	nm_auto_free_gstring GString *str = NULL;
 	int num, i;
-	gint32 ts;
+	const gint32 ts = nm_utils_get_monotonic_timestamp_s ();
 
 	g_return_val_if_fail (lease, NULL);
+
 	ip6_config = nm_ip6_config_new (multi_idx, ifindex);
-	ts = nm_utils_get_monotonic_timestamp_s ();
 
-	/* Addresses */
+	options = out_options ? create_options_dict () : NULL;
+
 	sd_dhcp6_lease_reset_address_iter (lease);
 	nm_gstring_prepare (&str);
 	while (sd_dhcp6_lease_get_address (lease, &tmp_addr, &lft_pref, &lft_valid) >= 0) {
-		NMPlatformIP6Address address = {
-			.plen = 128,
-			.address = tmp_addr,
-			.timestamp = ts,
-			.lifetime = lft_valid,
-			.preferred = lft_pref,
+		const NMPlatformIP6Address address = {
+			.plen        = 128,
+			.address     = tmp_addr,
+			.timestamp   = ts,
+			.lifetime    = lft_valid,
+			.preferred   = lft_pref,
 			.addr_source = NM_IP_CONFIG_SOURCE_DHCP,
 		};
 
 		nm_ip6_config_add_address (ip6_config, &address);
 
-		addr_str = nm_utils_inet6_ntop (&tmp_addr, NULL);
-		g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);
+		nm_utils_inet6_ntop (&tmp_addr, addr_str);
+		if (str->len)
+			g_string_append_c (str, ' ');
+		g_string_append (str, addr_str);
 
 		LOG_LEASE (LOGD_DHCP6,
 		           "address %s",
@@ -784,8 +850,8 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 	if (str->len)
 		add_option (options, dhcp6_requests, DHCP6_OPTION_IP_ADDRESS, str->str);
 
-	if (!info_only && nm_ip6_config_get_num_addresses (ip6_config) == 0) {
-		g_object_unref (ip6_config);
+	if (   !info_only
+	    && nm_ip6_config_get_num_addresses (ip6_config) == 0) {
 		g_set_error_literal (error,
 		                     NM_MANAGER_ERROR,
 		                     NM_MANAGER_ERROR_FAILED,
@@ -793,20 +859,20 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 		return NULL;
 	}
 
-	/* DNS servers */
 	num = sd_dhcp6_lease_get_dns (lease, &dns);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
 			nm_ip6_config_add_nameserver (ip6_config, &dns[i]);
-			addr_str = nm_utils_inet6_ntop (&dns[i], NULL);
-			g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);
+			nm_utils_inet6_ntop (&dns[i], addr_str);
+			if (str->len)
+				g_string_append_c (str, ' ');
+			g_string_append (str, addr_str);
 			LOG_LEASE (LOGD_DHCP6, "nameserver %s", addr_str);
 		}
 		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DNS_SERVERS, str->str);
 	}
 
-	/* Search domains */
 	num = sd_dhcp6_lease_get_domains (lease, &domains);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
@@ -818,7 +884,8 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DOMAIN_LIST, str->str);
 	}
 
-	return ip6_config;
+	NM_SET_OUT (out_options, g_steal_pointer (&options));
+	return g_steal_pointer (&ip6_config);
 }
 
 static void
@@ -830,10 +897,9 @@ bound6_handle (NMDhcpSystemd *self)
 	gs_unref_hashtable GHashTable *options = NULL;
 	gs_free_error GError *error = NULL;
 	sd_dhcp6_lease *lease;
-	int r;
 
-	r = sd_dhcp6_client_get_lease (priv->client6, &lease);
-	if (r < 0 || !lease) {
+	if (   sd_dhcp6_client_get_lease (priv->client6, &lease) < 0
+	    || !lease) {
 		_LOGW (" no lease!");
 		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
 		return;
@@ -841,25 +907,25 @@ bound6_handle (NMDhcpSystemd *self)
 
 	_LOGD ("lease available");
 
-	options = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
 	ip6_config = lease_to_ip6_config (nm_dhcp_client_get_multi_idx (NM_DHCP_CLIENT (self)),
 	                                  iface,
 	                                  nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
 	                                  lease,
-	                                  options,
 	                                  TRUE,
 	                                  nm_dhcp_client_get_info_only (NM_DHCP_CLIENT (self)),
+	                                  &options,
 	                                  &error);
 
-	if (ip6_config) {
-		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
-		                          NM_DHCP_STATE_BOUND,
-		                          NM_IP_CONFIG_CAST (ip6_config),
-		                          options);
-	} else {
+	if (!ip6_config) {
 		_LOGW ("%s", error->message);
 		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+		return;
 	}
+
+	nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
+	                          NM_DHCP_STATE_BOUND,
+	                          NM_IP_CONFIG_CAST (ip6_config),
+	                          options);
 }
 
 static void
@@ -895,31 +961,34 @@ ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
            guint needed_prefixes,
            GError **error)
 {
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
-	const char *iface = nm_dhcp_client_get_iface (client);
+	nm_auto (sd_dhcp6_client_unrefp) sd_dhcp6_client *sd_client = NULL;
 	GBytes *hwaddr;
 	const char *hostname;
+	const char *iface;
 	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)
+	GBytes *duid;
+	const uint8_t *hwaddr_arr;
+	gsize hwaddr_len;
+	int arp_type;
+
+	g_return_val_if_fail (!priv->client4, FALSE);
+	g_return_val_if_fail (!priv->client6, FALSE);
+
+	if (   !(duid = nm_dhcp_client_get_client_id (client))
+	    || !(duid_arr = g_bytes_get_data (duid, &duid_len))
+	    || duid_len < 2) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "missing DUID");
 		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));
-
-	r = sd_dhcp6_client_new (&priv->client6);
+	r = sd_dhcp6_client_new (&sd_client);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to create dhcp-client: %s");
 		return FALSE;
@@ -930,12 +999,23 @@ ip6_start (NMDhcpClient *client,
 		       needed_prefixes);
 	}
 
-	_LOGT ("dhcp-client6: set %p", priv->client6);
+	_LOGT ("dhcp-client6: set %p", sd_client);
 
 	if (nm_dhcp_client_get_info_only (client))
-		sd_dhcp6_client_set_information_request (priv->client6, 1);
+		sd_dhcp6_client_set_information_request (sd_client, 1);
+
+	iface = nm_dhcp_client_get_iface (client);
 
-	r = sd_dhcp6_client_set_duid (priv->client6,
+	r = sd_dhcp6_client_set_iaid (sd_client,
+	                              nm_utils_create_dhcp_iaid (TRUE,
+	                                                         (const guint8 *) iface,
+	                                                         strlen (iface)));
+	if (r < 0) {
+		nm_utils_error_set_errno (error, r, "failed to set IAID: %s");
+		return FALSE;
+	}
+
+	r = sd_dhcp6_client_set_duid (sd_client,
 	                              unaligned_read_be16 (&duid_arr[0]),
 	                              &duid_arr[2],
 	                              duid_len - 2);
@@ -944,82 +1024,84 @@ ip6_start (NMDhcpClient *client,
 		return FALSE;
 	}
 
-	r = sd_dhcp6_client_attach_event (priv->client6, NULL, 0);
+	r = sd_dhcp6_client_attach_event (sd_client, NULL, 0);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to attach event: %s");
-		goto errout;
+		return FALSE;
 	}
 
 	hwaddr = nm_dhcp_client_get_hw_addr (client);
-	if (hwaddr) {
-		const uint8_t *data;
-		gsize len;
-
-		data = g_bytes_get_data (hwaddr, &len);
-		r = sd_dhcp6_client_set_mac (priv->client6,
-		                             data,
-		                             len,
-		                             get_arp_type (hwaddr));
-		if (r < 0) {
-			nm_utils_error_set_errno (error, r, "failed to set MAC address: %s");
-			goto errout;
-		}
+	if (   !hwaddr
+	    || !(hwaddr_arr = g_bytes_get_data (hwaddr, &hwaddr_len))
+	    || (arp_type = nm_utils_arp_type_detect_from_hwaddrlen (hwaddr_len)) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address");
+		return FALSE;
 	}
-
-	r = sd_dhcp6_client_set_ifindex (priv->client6, nm_dhcp_client_get_ifindex (client));
+	r = sd_dhcp6_client_set_mac (sd_client,
+	                             hwaddr_arr,
+	                             hwaddr_len,
+	                             (guint16) arp_type);
 	if (r < 0) {
-		nm_utils_error_set_errno (error, r, "failed to set ifindex: %s");
-		goto errout;
+		nm_utils_error_set_errno (error, r, "failed to set MAC address: %s");
+		return FALSE;
 	}
 
-	r = sd_dhcp6_client_set_callback (priv->client6, dhcp6_event_cb, client);
+	r = sd_dhcp6_client_set_ifindex (sd_client,
+	                                 nm_dhcp_client_get_ifindex (client));
 	if (r < 0) {
-		nm_utils_error_set_errno (error, r, "failed to set callback: %s");
-		goto errout;
+		nm_utils_error_set_errno (error, r, "failed to set ifindex: %s");
+		return FALSE;
 	}
 
 	/* Add requested options */
 	for (i = 0; dhcp6_requests[i].name; i++) {
-		if (dhcp6_requests[i].include)
-			sd_dhcp6_client_set_request_option (priv->client6, dhcp6_requests[i].num);
+		if (dhcp6_requests[i].include) {
+			r = sd_dhcp6_client_set_request_option (sd_client, dhcp6_requests[i].option_num);
+			nm_assert (r >= 0 || r == -EEXIST);
+		}
 	}
 
-	r = sd_dhcp6_client_set_local_address (priv->client6, ll_addr);
+	r = sd_dhcp6_client_set_local_address (sd_client, ll_addr);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to set local address: %s");
-		goto errout;
+		return FALSE;
 	}
 
 	hostname = nm_dhcp_client_get_hostname (client);
-	r = sd_dhcp6_client_set_fqdn (priv->client6, hostname);
+	r = sd_dhcp6_client_set_fqdn (sd_client, hostname);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to set DHCP hostname: %s");
-		goto errout;
+		return FALSE;
+	}
+
+	r = sd_dhcp6_client_set_callback (sd_client, dhcp6_event_cb, client);
+	if (r < 0) {
+		nm_utils_error_set_errno (error, r, "failed to set callback: %s");
+		return FALSE;
 	}
 
+	priv->client6 = g_steal_pointer (&sd_client);
+
 	r = sd_dhcp6_client_start (priv->client6);
 	if (r < 0) {
+		sd_dhcp6_client_set_callback (priv->client6, NULL, NULL);
+		nm_clear_pointer (&priv->client6, sd_dhcp6_client_unref);
 		nm_utils_error_set_errno (error, r, "failed to start client: %s");
-		goto errout;
+		return FALSE;
 	}
 
 	nm_dhcp_client_start_timeout (client);
-
 	return TRUE;
-
-errout:
-	sd_dhcp6_client_unref (g_steal_pointer (&priv->client6));
-	return FALSE;
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+stop (NMDhcpClient *client, gboolean release)
 {
 	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);
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_systemd_parent_class)->stop (client, release);
 
 	_LOGT ("dhcp-client%d: stop %p",
 	       priv->client4 ? '4' : '6',
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index 6bbc670b..768f9fd7 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -197,7 +197,8 @@ ip4_process_dhclient_rfc3442_routes (const char *iface,
 			/* gateway passed as classless static route */
 			*gwaddr = route.gateway;
 		} else {
-			char addr[INET_ADDRSTRLEN];
+			char b1[INET_ADDRSTRLEN];
+			char b2[INET_ADDRSTRLEN];
 
 			/* normal route */
 			route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
@@ -206,8 +207,9 @@ ip4_process_dhclient_rfc3442_routes (const char *iface,
 			nm_ip4_config_add_route (ip4_config, &route, NULL);
 
 			_LOG2I (LOGD_DHCP4, iface, "  classless static route %s/%d gw %s",
-			        nm_utils_inet4_ntop (route.network, addr), route.plen,
-			        nm_utils_inet4_ntop (route.gateway, NULL));
+			        nm_utils_inet4_ntop (route.network, b1),
+			        route.plen,
+			        nm_utils_inet4_ntop (route.gateway, b2));
 		}
 	}
 
@@ -408,6 +410,7 @@ nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
 	gboolean gateway_has = FALSE;
 	guint32 gateway = 0;
 	guint8 plen = 0;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	g_return_val_if_fail (options != NULL, NULL);
 
@@ -439,7 +442,7 @@ nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
 		process_classful_routes (iface, options, route_table, route_metric, ip4_config);
 
 	if (gateway) {
-		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", nm_utils_inet4_ntop (gateway, NULL));
+		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", nm_utils_inet4_ntop (gateway, sbuf));
 		gateway_has = TRUE;
 	} else {
 		/* If the gateway wasn't provided as a classless static route with a
@@ -726,10 +729,10 @@ nm_dhcp_utils_duid_to_string (GBytes *duid)
 	gconstpointer data;
 	gsize len;
 
-	g_return_val_if_fail (duid != NULL, NULL);
+	g_return_val_if_fail (duid, NULL);
 
 	data = g_bytes_get_data (duid, &len);
-	return _nm_utils_bin2str (data, len, FALSE);
+	return _nm_utils_bin2hexstr_full (data, len, ':', FALSE, NULL);
 }
 
 /**
diff --git a/src/dhcp/tests/meson.build b/src/dhcp/tests/meson.build
index 0fee26b2..d2de4dc4 100644
--- a/src/dhcp/tests/meson.build
+++ b/src/dhcp/tests/meson.build
@@ -1,6 +1,6 @@
 test_units = [
   'test-dhcp-dhclient',
-  'test-dhcp-utils'
+  'test-dhcp-utils',
 ]
 
 foreach test_unit: test_units
@@ -13,6 +13,6 @@ foreach test_unit: test_units
   test(
     'dhcp/' + test_unit,
     test_script,
-    args: test_args + [exe.full_path()]
+    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 edac4257..ab1f5551 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -760,62 +760,74 @@ test_read_commented_duid_from_leasefile (void)
 	g_assert (duid == NULL);
 }
 
+/*****************************************************************************/
+
+static void
+_save_duid (const char *path,
+            const guint8 *duid_bin,
+            gsize duid_len)
+{
+	gs_unref_bytes GBytes *duid = NULL;
+	GError *error = NULL;
+	gboolean success;
+
+	g_assert (path);
+	g_assert (duid_bin);
+	g_assert (duid_len > 0);
+
+	duid = g_bytes_new (duid_bin, duid_len);
+	success = nm_dhcp_dhclient_save_duid (path, duid, &error);
+	nmtst_assert_success (success, error);
+}
+
 static void
 test_write_duid (void)
 {
-	const char *duid = "\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254";
+	const guint8 duid[] = { 000, 001, 000, 001, 027, 'X', 0350, 'X', 0, '#', 025, 010, '~', 0254 };
 	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
 	GError *error = NULL;
-	char *contents = NULL;
+	gs_free char *contents = NULL;
 	gboolean success;
 	const char *path = "test-dhclient-write-duid.leases";
 
-	success = nm_dhcp_dhclient_save_duid (path, duid, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	_save_duid (path, duid, G_N_ELEMENTS (duid));
 
 	success = g_file_get_contents (path, &contents, NULL, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
 	unlink (path);
-	g_assert_cmpstr (expected_contents, ==, contents);
 
-	g_free (contents);
+	g_assert_cmpstr (expected_contents, ==, contents);
 }
 
 static void
 test_write_existing_duid (void)
 {
-	const char *duid = "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302";
+	const guint8 duid[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '\"', 0372, 0214, 0326, 0302 };
 	const char *original_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
 	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n";
 	GError *error = NULL;
-	char *contents = NULL;
+	gs_free char *contents = NULL;
 	gboolean success;
 	const char *path = "test-dhclient-write-existing-duid.leases";
 
 	success = g_file_set_contents (path, original_contents, -1, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
 	/* Save other DUID; should be overwritten */
-	success = nm_dhcp_dhclient_save_duid (path, duid, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	_save_duid (path, duid, G_N_ELEMENTS (duid));
 
 	/* reread original contents */
 	success = g_file_get_contents (path, &contents, NULL, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
 	unlink (path);
 	g_assert_cmpstr (expected_contents, ==, contents);
-
-	g_free (contents);
 }
 
+static const guint8 DUID_BIN[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '\"', 0372, 0214, 0326, 0302 };
 #define DUID "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302"
+
 static void
 test_write_existing_commented_duid (void)
 {
@@ -824,28 +836,22 @@ test_write_existing_commented_duid (void)
 	    "default-duid \"" DUID "\";\n"
 	    ORIG_CONTENTS;
 	GError *error = NULL;
-	char *contents = NULL;
+	gs_free char *contents = NULL;
 	gboolean success;
 	const char *path = "test-dhclient-write-existing-commented-duid.leases";
 
 	success = g_file_set_contents (path, ORIG_CONTENTS, -1, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
 	/* Save other DUID; should be saved on top */
-	success = nm_dhcp_dhclient_save_duid (path, DUID, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	_save_duid (path, DUID_BIN, G_N_ELEMENTS (DUID_BIN));
 
 	/* reread original contents */
 	success = g_file_get_contents (path, &contents, NULL, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
 	unlink (path);
 	g_assert_cmpstr (expected_contents, ==, contents);
-
-	g_free (contents);
 #undef ORIG_CONTENTS
 }
 
@@ -865,8 +871,7 @@ test_write_existing_multiline_duid (void)
 	success = g_file_set_contents (path, ORIG_CONTENTS, -1, &error);
 	nmtst_assert_success (success, error);
 
-	success = nm_dhcp_dhclient_save_duid (path, DUID, &error);
-	nmtst_assert_success (success, error);
+	_save_duid (path, DUID_BIN, G_N_ELEMENTS (DUID_BIN));
 
 	success = g_file_get_contents (path, &contents, NULL, &error);
 	nmtst_assert_success (success, error);