summary refs log tree commit diff
path: root/src/dhcp-manager
diff options
context:
space:
mode:
Diffstat (limited to 'src/dhcp-manager')
-rw-r--r--src/dhcp-manager/Makefile.am1
-rw-r--r--src/dhcp-manager/Makefile.in18
-rw-r--r--src/dhcp-manager/nm-dhcp-client.c141
-rw-r--r--src/dhcp-manager/nm-dhcp-client.h18
-rw-r--r--src/dhcp-manager/nm-dhcp-dhclient-utils.c163
-rw-r--r--src/dhcp-manager/nm-dhcp-dhclient-utils.h10
-rw-r--r--src/dhcp-manager/nm-dhcp-dhclient.c77
-rw-r--r--src/dhcp-manager/nm-dhcp-dhcpcd.c7
-rw-r--r--src/dhcp-manager/nm-dhcp-manager.c11
-rw-r--r--src/dhcp-manager/nm-dhcp-manager.h2
-rw-r--r--src/dhcp-manager/tests/Makefile.am8
-rw-r--r--src/dhcp-manager/tests/Makefile.in24
-rw-r--r--src/dhcp-manager/tests/test-dhclient-commented-duid.leases2
-rw-r--r--src/dhcp-manager/tests/test-dhclient-duid.leases2
-rw-r--r--src/dhcp-manager/tests/test-dhcp-dhclient.c176
15 files changed, 613 insertions, 47 deletions
diff --git a/src/dhcp-manager/Makefile.am b/src/dhcp-manager/Makefile.am
index 04ae1489..40a05834 100644
--- a/src/dhcp-manager/Makefile.am
+++ b/src/dhcp-manager/Makefile.am
@@ -52,6 +52,7 @@ libdhcp_manager_la_SOURCES = \
 libdhcp_manager_la_CPPFLAGS = \
 	$(DBUS_CFLAGS) \
 	$(GLIB_CFLAGS) \
+	-DSYSCONFDIR=\"$(sysconfdir)\" \
 	-DLIBEXECDIR=\"$(libexecdir)\" \
 	-DLOCALSTATEDIR=\"$(localstatedir)\" \
 	-DDHCLIENT_PATH=\"$(DHCLIENT_PATH)\" \
diff --git a/src/dhcp-manager/Makefile.in b/src/dhcp-manager/Makefile.in
index f161c210..acd13114 100644
--- a/src/dhcp-manager/Makefile.in
+++ b/src/dhcp-manager/Makefile.in
@@ -61,12 +61,11 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/compiler_warnings.m4 \
 	$(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/intltool.m4 \
 	$(top_srcdir)/m4/introspection.m4 $(top_srcdir)/m4/lib-ld.m4 \
 	$(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
-	$(top_srcdir)/m4/libnl-check.m4 $(top_srcdir)/m4/libtool.m4 \
-	$(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
-	$(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
-	$(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \
-	$(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/vapigen.m4 \
-	$(top_srcdir)/configure.ac
+	$(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+	$(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+	$(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+	$(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+	$(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
 	$(ACLOCAL_M4)
 mkinstalldirs = $(install_sh) -d
@@ -269,12 +268,6 @@ LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
-LIBNL1_CFLAGS = @LIBNL1_CFLAGS@
-LIBNL1_LIBS = @LIBNL1_LIBS@
-LIBNL2_CFLAGS = @LIBNL2_CFLAGS@
-LIBNL2_LIBS = @LIBNL2_LIBS@
-LIBNL3_CFLAGS = @LIBNL3_CFLAGS@
-LIBNL3_LIBS = @LIBNL3_LIBS@
 LIBNL_CFLAGS = @LIBNL_CFLAGS@
 LIBNL_LIBS = @LIBNL_LIBS@
 LIBOBJS = @LIBOBJS@
@@ -466,6 +459,7 @@ libdhcp_manager_la_SOURCES = \
 libdhcp_manager_la_CPPFLAGS = \
 	$(DBUS_CFLAGS) \
 	$(GLIB_CFLAGS) \
+	-DSYSCONFDIR=\"$(sysconfdir)\" \
 	-DLIBEXECDIR=\"$(libexecdir)\" \
 	-DLOCALSTATEDIR=\"$(localstatedir)\" \
 	-DDHCLIENT_PATH=\"$(DHCLIENT_PATH)\" \
diff --git a/src/dhcp-manager/nm-dhcp-client.c b/src/dhcp-manager/nm-dhcp-client.c
index f721adf5..2cdd304d 100644
--- a/src/dhcp-manager/nm-dhcp-client.c
+++ b/src/dhcp-manager/nm-dhcp-client.c
@@ -25,9 +25,8 @@
 #include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
 #include <stdlib.h>
+#include <uuid/uuid.h>
 
 #include "nm-utils.h"
 #include "nm-logging.h"
@@ -36,9 +35,11 @@
 
 typedef struct {
 	char *       iface;
+	GByteArray * hwaddr;
 	gboolean     ipv6;
 	char *       uuid;
 	guint32      timeout;
+	GByteArray * duid;
 
 	guchar       state;
 	GPid         pid;
@@ -67,6 +68,7 @@ static guint signals[LAST_SIGNAL] = { 0 };
 enum {
 	PROP_0,
 	PROP_IFACE,
+	PROP_HWADDR,
 	PROP_IPV6,
 	PROP_UUID,
 	PROP_TIMEOUT,
@@ -182,7 +184,7 @@ nm_dhcp_client_stop_pid (GPid pid, const char *iface, guint timeout_secs)
 }
 
 static void
-stop (NMDHCPClient *self, gboolean release)
+stop (NMDHCPClient *self, gboolean release, const GByteArray *duid)
 {
 	NMDHCPClientPrivate *priv;
 
@@ -321,6 +323,96 @@ nm_dhcp_client_start_ip4 (NMDHCPClient *self,
 	return priv->pid ? TRUE : FALSE;
 }
 
+static GByteArray *
+generate_duid_from_machine_id (void)
+{
+	GByteArray *duid;
+	char *contents = NULL;
+	GError *error = NULL;
+	GChecksum *sum;
+	guint8 buffer[32]; /* SHA256 digest size */
+	gsize sumlen = sizeof (buffer);
+	const guint16 duid_type = g_htons (4);
+	uuid_t uuid;
+	int ret;
+
+	/* Get the machine ID from /etc/machine-id; it's always in /etc no matter
+	 * where our configured SYSCONFDIR is.
+	 */
+	if (!g_file_get_contents ("/etc/machine-id", &contents, NULL, &error)) {
+		nm_log_warn (LOGD_DHCP6, "Failed to read " SYSCONFDIR "/machine-id to generate DHCPv6 DUID: (%d) %s",
+			         error ? error->code : -1,
+			         error ? error->message : "(unknown)");
+		g_clear_error (&error);
+		return NULL;
+	}
+
+	contents = g_strstrip (contents);
+	ret = uuid_parse (contents, uuid);
+	g_free (contents);
+
+	if (ret != 0) {
+		nm_log_warn (LOGD_DHCP6, "Failed to parse " SYSCONFDIR "/machine-id to generate DHCPv6 DUID.");
+		return NULL;
+	}
+
+	/* Hash the machine ID so it's not leaked to the network */
+	sum = g_checksum_new (G_CHECKSUM_SHA256);
+	g_checksum_update (sum, (const guchar *) &uuid, sizeof (uuid));
+	g_checksum_get_digest (sum, buffer, &sumlen);
+	g_checksum_free (sum);
+
+	/* Generate a DHCP Unique Identifier for DHCPv6 using the
+	 * DUID-UUID method (see RFC 6355 section 4).  Format is:
+	 *
+	 * u16: type (DUID-UUID = 4)
+	 * u8[16]: UUID bytes
+	 */
+	duid = g_byte_array_sized_new (18);
+	g_byte_array_append (duid, (guint8 *) &duid_type, sizeof (duid_type));
+
+	/* Since SHA256 is 256 bits, but UUID is 128 bits, we just take the first
+	 * 128 bits of the SHA256 as the DUID-UUID.
+	 */
+	g_byte_array_append (duid, buffer, 16);
+
+	return duid;
+}
+
+static GByteArray *
+get_duid (NMDHCPClient *self)
+{
+	static GByteArray *duid = NULL;
+	GByteArray *copy = NULL;
+
+	if (G_UNLIKELY (duid == NULL))
+		duid = generate_duid_from_machine_id ();
+
+	if (G_LIKELY (duid)) {
+		copy = g_byte_array_sized_new (duid->len);
+		g_byte_array_append (copy, duid->data, duid->len);
+	}
+
+	return copy;
+}
+
+static char *
+escape_duid (const GByteArray *duid)
+{
+	guint32 i = 0;
+	GString *s;
+
+	g_return_val_if_fail (duid != NULL, NULL);
+
+	s = g_string_sized_new (40);
+	while (i < duid->len) {
+		if (s->len)
+			g_string_append_c (s, ':');
+		g_string_append_printf (s, "%02x", duid->data[i++]);
+	}
+	return g_string_free (s, FALSE);
+}
+
 gboolean
 nm_dhcp_client_start_ip6 (NMDHCPClient *self,
                           NMSettingIP6Config *s_ip6,
@@ -329,6 +421,7 @@ nm_dhcp_client_start_ip6 (NMDHCPClient *self,
                           gboolean info_only)
 {
 	NMDHCPClientPrivate *priv;
+	char *escaped;
 
 	g_return_val_if_fail (self != NULL, FALSE);
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
@@ -338,12 +431,29 @@ nm_dhcp_client_start_ip6 (NMDHCPClient *self,
 	g_return_val_if_fail (priv->ipv6 == TRUE, FALSE);
 	g_return_val_if_fail (priv->uuid != NULL, FALSE);
 
+	/* If we don't have one yet, read the default DUID for this DHCPv6 client
+	 * from the client-specific persistent configuration.
+	 */
+	if (!priv->duid)
+		priv->duid = NM_DHCP_CLIENT_GET_CLASS (self)->get_duid (self);
+
+	if (nm_logging_level_enabled (LOGL_DEBUG)) {
+		escaped = escape_duid (priv->duid);
+		nm_log_dbg (LOGD_DHCP, "(%s): DHCPv6 DUID is '%s'", priv->iface, escaped);
+		g_free (escaped);
+	}
+
 	priv->info_only = info_only;
 
 	nm_log_info (LOGD_DHCP, "Activation (%s) Beginning DHCPv6 transaction (timeout in %d seconds)",
 	             priv->iface, priv->timeout);
 
-	priv->pid = NM_DHCP_CLIENT_GET_CLASS (self)->ip6_start (self, s_ip6, dhcp_anycast_addr, hostname, info_only);
+	priv->pid = NM_DHCP_CLIENT_GET_CLASS (self)->ip6_start (self,
+	                                                        s_ip6,
+	                                                        dhcp_anycast_addr,
+	                                                        hostname,
+	                                                        info_only,
+	                                                        priv->duid);
 	if (priv->pid > 0)
 		start_monitor (self);
 
@@ -397,7 +507,7 @@ nm_dhcp_client_stop (NMDHCPClient *self, gboolean release)
 
 	/* Kill the DHCP client */
 	if (!priv->dead) {
-		NM_DHCP_CLIENT_GET_CLASS (self)->stop (self, release);
+		NM_DHCP_CLIENT_GET_CLASS (self)->stop (self, release, priv->duid);
 		priv->dead = TRUE;
 
 		nm_log_info (LOGD_DHCP, "(%s): canceled DHCP transaction, DHCP client pid %d",
@@ -1323,6 +1433,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_IFACE:
 		g_value_set_string (value, priv->iface);
 		break;
+	case PROP_HWADDR:
+		g_value_set_boxed (value, priv->hwaddr);
+		break;
 	case PROP_IPV6:
 		g_value_set_boolean (value, priv->ipv6);
 		break;
@@ -1349,6 +1462,10 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		priv->iface = g_strdup (g_value_get_string (value));
 		break;
+	case PROP_HWADDR:
+		/* construct only */
+		priv->hwaddr = g_value_dup_boxed (value);
+		break;
 	case PROP_IPV6:
 		/* construct-only */
 		priv->ipv6 = g_value_get_boolean (value);
@@ -1382,6 +1499,11 @@ dispose (GObject *object)
 
 	g_hash_table_destroy (priv->options);
 	g_free (priv->iface);
+	if (priv->hwaddr)
+		g_byte_array_free (priv->hwaddr, TRUE);
+
+	if (priv->duid)
+		g_byte_array_free (priv->duid, TRUE);
 
 	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
 }
@@ -1399,6 +1521,7 @@ nm_dhcp_client_class_init (NMDHCPClientClass *client_class)
 	object_class->set_property = set_property;
 
 	client_class->stop = stop;
+	client_class->get_duid = get_duid;
 
 	g_object_class_install_property
 		(object_class, PROP_IFACE,
@@ -1409,6 +1532,14 @@ nm_dhcp_client_class_init (NMDHCPClientClass *client_class)
 		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
 	g_object_class_install_property
+		(object_class, PROP_HWADDR,
+		 g_param_spec_boxed (NM_DHCP_CLIENT_HWADDR,
+		                     "hwaddr",
+		                     "hardware address",
+		                     G_TYPE_BYTE_ARRAY,
+		                     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_object_class_install_property
 		(object_class, PROP_IPV6,
 		 g_param_spec_boolean (NM_DHCP_CLIENT_IPV6,
 		                       "ipv6",
diff --git a/src/dhcp-manager/nm-dhcp-client.h b/src/dhcp-manager/nm-dhcp-client.h
index b93515f4..930ec1a5 100644
--- a/src/dhcp-manager/nm-dhcp-client.h
+++ b/src/dhcp-manager/nm-dhcp-client.h
@@ -35,6 +35,7 @@
 #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_HWADDR    "hwaddr"
 #define NM_DHCP_CLIENT_IPV6      "ipv6"
 #define NM_DHCP_CLIENT_UUID      "uuid"
 #define NM_DHCP_CLIENT_TIMEOUT   "timeout"
@@ -85,10 +86,23 @@ typedef struct {
 	                       NMSettingIP6Config *s_ip6,
 	                       guint8 *anycast_addr,
 	                       const char *hostname,
-	                       gboolean info_only);
+	                       gboolean info_only,
+	                       const GByteArray *duid);
 
 	void (*stop)          (NMDHCPClient *self,
-	                       gboolean release);
+	                       gboolean release,
+	                       const GByteArray *duid);
+
+	/**
+	 * get_duid:
+	 * @self: the #NMDHCPClient
+	 *
+	 * Attempts to find an existing DHCPv6 DUID for this client in the DHCP
+	 * client's persistent configuration.  Returned DUID should be the binary
+	 * representation of the DUID.  If no DUID is found, %NULL should be
+	 * returned.
+	 */
+	GByteArray * (*get_duid) (NMDHCPClient *self);
 
 	/* Signals */
 	void (*state_changed) (NMDHCPClient *self, NMDHCPState state);
diff --git a/src/dhcp-manager/nm-dhcp-dhclient-utils.c b/src/dhcp-manager/nm-dhcp-dhclient-utils.c
index f3d3b1b6..8dc7902f 100644
--- a/src/dhcp-manager/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp-manager/nm-dhcp-dhclient-utils.c
@@ -22,6 +22,7 @@
 #include <glib.h>
 #include <glib/gi18n.h>
 #include <string.h>
+#include <ctype.h>
 
 #include "nm-dhcp-dhclient-utils.h"
 
@@ -255,3 +256,165 @@ nm_dhcp_dhclient_create_config (const char *interface,
 	return g_string_free (new_contents, FALSE);
 }
 
+/* Roughly follow what dhclient's quotify_buf() and pretty_escape() functions do */
+char *
+nm_dhcp_dhclient_escape_duid (const GByteArray *duid)
+{
+	char *escaped;
+	const guint8 *s = duid->data;
+	char *d;
+
+	d = escaped = g_malloc0 ((duid->len * 4) + 1);
+	while (s < (duid->data + duid->len)) {
+		if (!g_ascii_isprint (*s)) {
+			*d++ = '\\';
+			*d++ = '0' + ((*s >> 6) & 0x7);
+			*d++ = '0' + ((*s >> 3) & 0x7);
+			*d++ = '0' + (*s++ & 0x7);
+		} else if (*s == '"' || *s == '\'' || *s == '$' ||
+		           *s == '`' || *s == '\\' || *s == '|' ||
+		           *s == '&') {
+			*d++ = '\\';
+			*d++ = *s++;
+		} else
+			*d++ = *s++;
+	}
+	return escaped;
+}
+
+static inline gboolean
+isoctal (const guint8 *p)
+{
+	return (   p[0] >= '0' && p[0] <= '3'
+	        && p[1] >= '0' && p[1] <= '7'
+	        && p[2] >= '0' && p[2] <= '7');
+}
+
+GByteArray *
+nm_dhcp_dhclient_unescape_duid (const char *duid)
+{
+	GByteArray *unescaped;
+	const guint8 *p = (const guint8 *) duid;
+	guint i, len;
+	guint8 octal;
+
+	len = strlen (duid);
+	unescaped = g_byte_array_sized_new (len);
+	for (i = 0; i < len; i++) {
+		if (p[i] == '\\') {
+			i++;
+			if (isdigit (p[i])) {
+				/* Octal escape sequence */
+				if (i + 2 >= len || !isoctal (p + i))
+					goto error;
+				octal = ((p[i] - '0') << 6) + ((p[i + 1] - '0') << 3) + (p[i + 2] - '0');
+				g_byte_array_append (unescaped, &octal, 1);
+				i += 2;
+			} else {
+				/* One of ", ', $, `, \, |, or & */
+				g_warn_if_fail (p[i] == '"' || p[i] == '\'' || p[i] == '$' ||
+				                p[i] == '`' || p[i] == '\\' || p[i] == '|' ||
+				                p[i] == '&');
+				g_byte_array_append (unescaped, &p[i], 1);
+			}
+		} else
+			g_byte_array_append (unescaped, &p[i], 1);
+	}
+
+	return unescaped;
+
+error:
+	g_byte_array_free (unescaped, TRUE);
+	return NULL;
+}
+
+#define DUID_PREFIX "default-duid \""
+
+GByteArray *
+nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
+{
+	GByteArray *duid = NULL;
+	char *contents;
+	char **line, **split, *p, *e;
+
+	if (!g_file_test (leasefile, G_FILE_TEST_EXISTS))
+		return NULL;
+
+	if (!g_file_get_contents (leasefile, &contents, NULL, error))
+		return NULL;
+
+	split = g_strsplit_set (contents, "\n\r", -1);
+	for (line = split; line && *line && (duid == NULL); line++) {
+		p = g_strstrip (*line);
+		if (g_str_has_prefix (p, DUID_PREFIX)) {
+			p += strlen (DUID_PREFIX);
+
+			/* look for trailing "; */
+			e = p + strlen (p) - 2;
+			if (strcmp (e, "\";") != 0)
+				continue;
+			*e = '\0';
+
+			duid = nm_dhcp_dhclient_unescape_duid (p);
+		}
+	}
+	g_free (contents);
+	g_strfreev (split);
+
+	return duid;
+}
+
+gboolean
+nm_dhcp_dhclient_save_duid (const char *leasefile,
+                            const char *escaped_duid,
+                            GError **error)
+{
+	char **lines = NULL, **iter, *l;
+	GString *s;
+	gboolean success;
+	gsize len = 0;
+
+	g_return_val_if_fail (leasefile != NULL, FALSE);
+	g_return_val_if_fail (escaped_duid != NULL, FALSE);
+
+	if (g_file_test (leasefile, G_FILE_TEST_EXISTS)) {
+		char *contents = NULL;
+
+		if (!g_file_get_contents (leasefile, &contents, &len, error)) {
+			g_prefix_error (error, "failed to read lease file %s: ", leasefile);
+			return FALSE;
+		}
+
+		/* If the file already contains an uncommented DUID, leave it */
+		g_assert (contents);
+		lines = g_strsplit_set (contents, "\n\r", -1);
+		g_free (contents);
+		for (iter = lines; iter && *iter; iter++) {
+			l = *iter;
+			while (g_ascii_isspace (*l))
+				l++;
+			if (g_str_has_prefix (l, DUID_PREFIX)) {
+				g_strfreev (lines);
+				return TRUE;
+			}
+		}
+	}
+
+	s = g_string_sized_new (len + 50);
+	g_string_append_printf (s, DUID_PREFIX "%s\";\n", escaped_duid);
+
+	/* Preserve existing leasefile contents */
+	if (lines) {
+		for (iter = lines; iter && *iter; iter++)
+			g_string_append (s, *iter[0] ? *iter : "\n");
+		g_strfreev (lines);
+	}
+
+	success = g_file_set_contents (leasefile, s->str, -1, error);
+	if (!success)
+		g_prefix_error (error, "failed to set DUID in lease file %s: ", leasefile);
+
+	g_string_free (s, TRUE);
+	return success;
+}
+
diff --git a/src/dhcp-manager/nm-dhcp-dhclient-utils.h b/src/dhcp-manager/nm-dhcp-dhclient-utils.h
index 2f9f7c45..95cb0568 100644
--- a/src/dhcp-manager/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp-manager/nm-dhcp-dhclient-utils.h
@@ -34,5 +34,15 @@ char *nm_dhcp_dhclient_create_config (const char *interface,
                                       const char *orig_path,
                                       const char *orig_contents);
 
+char *nm_dhcp_dhclient_escape_duid (const GByteArray *duid);
+
+GByteArray *nm_dhcp_dhclient_unescape_duid (const char *duid);
+
+GByteArray *nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error);
+
+gboolean nm_dhcp_dhclient_save_duid (const char *leasefile,
+                                     const char *escaped_duid,
+                                     GError **error);
+
 #endif /* NM_DHCP_DHCLIENT_UTILS_H */
 
diff --git a/src/dhcp-manager/nm-dhcp-dhclient.c b/src/dhcp-manager/nm-dhcp-dhclient.c
index 347d97c4..d9f51352 100644
--- a/src/dhcp-manager/nm-dhcp-dhclient.c
+++ b/src/dhcp-manager/nm-dhcp-dhclient.c
@@ -50,6 +50,7 @@ G_DEFINE_TYPE (NMDHCPDhclient, nm_dhcp_dhclient, NM_TYPE_DHCP_CLIENT)
 typedef struct {
 	const char *path;
 	char *conf_file;
+	const char *def_leasefile;
 	char *lease_file;
 	char *pid_file;
 } NMDHCPDhclientPrivate;
@@ -445,6 +446,7 @@ dhclient_child_setup (gpointer user_data G_GNUC_UNUSED)
 static GPid
 dhclient_start (NMDHCPClient *client,
                 const char *mode_opt,
+                const GByteArray *duid,
                 gboolean release)
 {
 	NMDHCPDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (client);
@@ -575,7 +577,7 @@ ip4_start (NMDHCPClient *client,
 		return -1;
 	}
 
-	return dhclient_start (client, NULL, FALSE);
+	return dhclient_start (client, NULL, NULL, FALSE);
 }
 
 static GPid
@@ -583,7 +585,8 @@ ip6_start (NMDHCPClient *client,
            NMSettingIP6Config *s_ip6,
            guint8 *dhcp_anycast_addr,
            const char *hostname,
-           gboolean info_only)
+           gboolean info_only,
+           const GByteArray *duid)
 {
 	NMDHCPDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (client);
 	const char *iface;
@@ -596,16 +599,16 @@ ip6_start (NMDHCPClient *client,
 		return -1;
 	}
 
-	return dhclient_start (client, info_only ? "-S" : "-N", FALSE);
+	return dhclient_start (client, info_only ? "-S" : "-N", duid, FALSE);
 }
 
 static void
-stop (NMDHCPClient *client, gboolean release)
+stop (NMDHCPClient *client, gboolean release, const GByteArray *duid)
 {
 	NMDHCPDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (client);
 
 	/* Chain up to parent */
-	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->stop (client, release);
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->stop (client, release, duid);
 
 	if (priv->conf_file)
 		remove (priv->conf_file);
@@ -618,7 +621,7 @@ stop (NMDHCPClient *client, gboolean release)
 	if (release) {
 		GPid rpid;
 
-		rpid = dhclient_start (client, NULL, TRUE);
+		rpid = dhclient_start (client, NULL, duid, TRUE);
 		if (rpid > 0) {
 			/* Wait a few seconds for the release to happen */
 			nm_dhcp_client_stop_pid (rpid, nm_dhcp_client_get_iface (client), 5);
@@ -626,14 +629,75 @@ stop (NMDHCPClient *client, gboolean release)
 	}
 }
 
+static GByteArray *
+get_duid (NMDHCPClient *client)
+{
+	NMDHCPDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (client);
+	GByteArray *duid = NULL;
+	char *leasefile;
+	GError *error = NULL;
+
+	/* Look in interface-specific leasefile first for backwards compat */
+	leasefile = get_dhclient_leasefile (nm_dhcp_client_get_iface (client),
+	                                    nm_dhcp_client_get_uuid (client),
+	                                    TRUE);
+	nm_log_dbg (LOGD_DHCP, "Looking for DHCPv6 DUID in '%s'.", leasefile);
+	duid = nm_dhcp_dhclient_read_duid (leasefile, &error);
+	g_free (leasefile);
+
+	if (error) {
+		nm_log_warn (LOGD_DHCP, "Failed to read leasefile '%s': (%d) %s",
+		             leasefile,
+		             error ? error->code : -1,
+		             error ? error->message : "(unknown)");
+		g_clear_error (&error);
+	}
+
+	if (!duid && priv->def_leasefile) {
+		/* Otherwise read the default machine-wide DUID */
+		nm_log_dbg (LOGD_DHCP, "Looking for default DHCPv6 DUID in '%s'.", priv->def_leasefile);
+		duid = nm_dhcp_dhclient_read_duid (priv->def_leasefile, &error);
+		if (error) {
+			nm_log_warn (LOGD_DHCP, "Failed to read leasefile '%s': (%d) %s",
+			             priv->def_leasefile,
+			             error ? error->code : -1,
+			             error ? error->message : "(unknown)");
+			g_clear_error (&error);
+		}
+	}
+
+	/* return our DUID, otherwise let the parent class make a default DUID */
+	return duid ? duid : NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->get_duid (client);
+}
+
 /***************************************************/
 
+static const char *def_leasefiles[] = {
+	SYSCONFDIR "/dhclient6.leases",
+	LOCALSTATEDIR "/lib/dhcp/dhclient6.leases",
+	LOCALSTATEDIR "/lib/dhclient/dhclient6.leases",
+	NULL
+};
+
 static void
 nm_dhcp_dhclient_init (NMDHCPDhclient *self)
 {
 	NMDHCPDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
+	const char **iter = &def_leasefiles[0];
 
 	priv->path = nm_dhcp_dhclient_get_path (DHCLIENT_PATH);
+
+	while (iter && *iter) {
+		if (g_file_test (*iter, G_FILE_TEST_EXISTS)) {
+			priv->def_leasefile = *iter;
+			break;
+		}
+		iter++;
+	}
+
+	/* Fallback option */
+	if (!priv->def_leasefile)
+		priv->def_leasefile = SYSCONFDIR "/dhclient6.leases";
 }
 
 static void
@@ -662,5 +726,6 @@ nm_dhcp_dhclient_class_init (NMDHCPDhclientClass *dhclient_class)
 	client_class->ip4_start = ip4_start;
 	client_class->ip6_start = ip6_start;
 	client_class->stop = stop;
+	client_class->get_duid = get_duid;
 }
 
diff --git a/src/dhcp-manager/nm-dhcp-dhcpcd.c b/src/dhcp-manager/nm-dhcp-dhcpcd.c
index dda96e2a..f3245ee5 100644
--- a/src/dhcp-manager/nm-dhcp-dhcpcd.c
+++ b/src/dhcp-manager/nm-dhcp-dhcpcd.c
@@ -170,19 +170,20 @@ ip6_start (NMDHCPClient *client,
            NMSettingIP6Config *s_ip6,
            guint8 *dhcp_anycast_addr,
            const char *hostname,
-           gboolean info_only)
+           gboolean info_only,
+           const GByteArray *duid)
 {
 	nm_log_warn (LOGD_DHCP6, "the dhcpcd backend does not support IPv6.");
 	return -1;
 }
 
 static void
-stop (NMDHCPClient *client, gboolean release)
+stop (NMDHCPClient *client, gboolean release, const GByteArray *duid)
 {
 	NMDHCPDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (client);
 
 	/* Chain up to parent */
-	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcd_parent_class)->stop (client, release);
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcd_parent_class)->stop (client, release, duid);
 
 	if (priv->pid_file)
 		remove (priv->pid_file);
diff --git a/src/dhcp-manager/nm-dhcp-manager.c b/src/dhcp-manager/nm-dhcp-manager.c
index 995bff47..1bd7f984 100644
--- a/src/dhcp-manager/nm-dhcp-manager.c
+++ b/src/dhcp-manager/nm-dhcp-manager.c
@@ -384,6 +384,7 @@ add_client (NMDHCPManager *self, NMDHCPClient *client)
 static NMDHCPClient *
 client_start (NMDHCPManager *self,
               const char *iface,
+              const GByteArray *hwaddr,
               const char *uuid,
               gboolean ipv6,
               NMSettingIP4Config *s_ip4,
@@ -417,6 +418,7 @@ client_start (NMDHCPManager *self,
 	/* And make a new one */
 	client = g_object_new (priv->client_type,
 	                       NM_DHCP_CLIENT_INTERFACE, iface,
+	                       NM_DHCP_CLIENT_HWADDR, hwaddr,
 	                       NM_DHCP_CLIENT_IPV6, ipv6,
 	                       NM_DHCP_CLIENT_UUID, uuid,
 	                       NM_DHCP_CLIENT_TIMEOUT, timeout ? timeout : DHCP_TIMEOUT,
@@ -442,13 +444,13 @@ client_start (NMDHCPManager *self,
 NMDHCPClient *
 nm_dhcp_manager_start_ip4 (NMDHCPManager *self,
                            const char *iface,
+                           const GByteArray *hwaddr,
                            const char *uuid,
                            NMSettingIP4Config *s_ip4,
                            guint32 timeout,
                            guint8 *dhcp_anycast_addr)
 {
 	NMDHCPManagerPrivate *priv;
-	NMDHCPClient *client = NULL;
 	const char *hostname = NULL;
 	gboolean send_hostname = TRUE;
 
@@ -485,15 +487,14 @@ nm_dhcp_manager_start_ip4 (NMDHCPManager *self,
 		}
 	}
 
-	client = client_start (self, iface, uuid, FALSE, s_ip4, NULL, timeout, dhcp_anycast_addr, hostname, FALSE);
-
-	return client;
+	return client_start (self, iface, hwaddr, uuid, FALSE, s_ip4, NULL, timeout, dhcp_anycast_addr, hostname, FALSE);
 }
 
 /* Caller owns a reference to the NMDHCPClient on return */
 NMDHCPClient *
 nm_dhcp_manager_start_ip6 (NMDHCPManager *self,
                            const char *iface,
+                           const GByteArray *hwaddr,
                            const char *uuid,
                            NMSettingIP6Config *s_ip6,
                            guint32 timeout,
@@ -516,7 +517,7 @@ nm_dhcp_manager_start_ip6 (NMDHCPManager *self,
 			hostname = NULL;
 	}
 
-	return client_start (self, iface, uuid, TRUE, NULL, s_ip6, timeout, dhcp_anycast_addr, hostname, info_only);
+	return client_start (self, iface, hwaddr, uuid, TRUE, NULL, s_ip6, timeout, dhcp_anycast_addr, hostname, info_only);
 }
 
 static void
diff --git a/src/dhcp-manager/nm-dhcp-manager.h b/src/dhcp-manager/nm-dhcp-manager.h
index 68db7e37..a29a33fd 100644
--- a/src/dhcp-manager/nm-dhcp-manager.h
+++ b/src/dhcp-manager/nm-dhcp-manager.h
@@ -67,6 +67,7 @@ void           nm_dhcp_manager_set_hostname_provider(NMDHCPManager *manager,
 
 NMDHCPClient * nm_dhcp_manager_start_ip4     (NMDHCPManager *manager,
                                               const char *iface,
+                                              const GByteArray *hwaddr,
                                               const char *uuid,
                                               NMSettingIP4Config *s_ip4,
                                               guint32 timeout,
@@ -74,6 +75,7 @@ NMDHCPClient * nm_dhcp_manager_start_ip4     (NMDHCPManager *manager,
 
 NMDHCPClient * nm_dhcp_manager_start_ip6     (NMDHCPManager *manager,
                                               const char *iface,
+                                              const GByteArray *hwaddr,
                                               const char *uuid,
                                               NMSettingIP6Config *s_ip6,
                                               guint32 timeout,
diff --git a/src/dhcp-manager/tests/Makefile.am b/src/dhcp-manager/tests/Makefile.am
index 251aa224..8c9d06c1 100644
--- a/src/dhcp-manager/tests/Makefile.am
+++ b/src/dhcp-manager/tests/Makefile.am
@@ -15,7 +15,8 @@ test_dhcp_dhclient_SOURCES = \
 	test-dhcp-dhclient.c
 
 test_dhcp_dhclient_CPPFLAGS = \
-	$(GLIB_CFLAGS)
+	$(GLIB_CFLAGS) \
+	-DTESTDIR="\"$(abs_srcdir)\""
 
 test_dhcp_dhclient_LDADD = \
 	-ldl \
@@ -27,3 +28,8 @@ check-local: test-dhcp-dhclient
 	$(abs_builddir)/test-dhcp-dhclient
 
 endif
+
+EXTRA_DIST = \
+	test-dhclient-duid.leases \
+	test-dhclient-commented-duid.leases
+
diff --git a/src/dhcp-manager/tests/Makefile.in b/src/dhcp-manager/tests/Makefile.in
index 32cdf4db..921ccaf3 100644
--- a/src/dhcp-manager/tests/Makefile.in
+++ b/src/dhcp-manager/tests/Makefile.in
@@ -62,12 +62,11 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/compiler_warnings.m4 \
 	$(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/intltool.m4 \
 	$(top_srcdir)/m4/introspection.m4 $(top_srcdir)/m4/lib-ld.m4 \
 	$(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
-	$(top_srcdir)/m4/libnl-check.m4 $(top_srcdir)/m4/libtool.m4 \
-	$(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
-	$(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
-	$(top_srcdir)/m4/nls.m4 $(top_srcdir)/m4/po.m4 \
-	$(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/vapigen.m4 \
-	$(top_srcdir)/configure.ac
+	$(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+	$(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+	$(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+	$(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+	$(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
 	$(ACLOCAL_M4)
 mkinstalldirs = $(install_sh) -d
@@ -219,12 +218,6 @@ LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
-LIBNL1_CFLAGS = @LIBNL1_CFLAGS@
-LIBNL1_LIBS = @LIBNL1_LIBS@
-LIBNL2_CFLAGS = @LIBNL2_CFLAGS@
-LIBNL2_LIBS = @LIBNL2_LIBS@
-LIBNL3_CFLAGS = @LIBNL3_CFLAGS@
-LIBNL3_LIBS = @LIBNL3_LIBS@
 LIBNL_CFLAGS = @LIBNL_CFLAGS@
 LIBNL_LIBS = @LIBNL_LIBS@
 LIBOBJS = @LIBOBJS@
@@ -377,7 +370,8 @@ with_resolvconf = @with_resolvconf@
 @ENABLE_TESTS_TRUE@	test-dhcp-dhclient.c
 
 @ENABLE_TESTS_TRUE@test_dhcp_dhclient_CPPFLAGS = \
-@ENABLE_TESTS_TRUE@	$(GLIB_CFLAGS)
+@ENABLE_TESTS_TRUE@	$(GLIB_CFLAGS) \
+@ENABLE_TESTS_TRUE@	-DTESTDIR="\"$(abs_srcdir)\""
 
 @ENABLE_TESTS_TRUE@test_dhcp_dhclient_LDADD = \
 @ENABLE_TESTS_TRUE@	-ldl \
@@ -385,6 +379,10 @@ with_resolvconf = @with_resolvconf@
 @ENABLE_TESTS_TRUE@	$(top_builddir)/libnm-util/libnm-util.la \
 @ENABLE_TESTS_TRUE@	$(GLIB_LIBS)
 
+EXTRA_DIST = \
+	test-dhclient-duid.leases \
+	test-dhclient-commented-duid.leases
+
 all: all-am
 
 .SUFFIXES:
diff --git a/src/dhcp-manager/tests/test-dhclient-commented-duid.leases b/src/dhcp-manager/tests/test-dhclient-commented-duid.leases
new file mode 100644
index 00000000..3e46ae7d
--- /dev/null
+++ b/src/dhcp-manager/tests/test-dhclient-commented-duid.leases
@@ -0,0 +1,2 @@
+#default-duid "\000\001\000\001\030y\246\023`g \354Lp";
+
diff --git a/src/dhcp-manager/tests/test-dhclient-duid.leases b/src/dhcp-manager/tests/test-dhclient-duid.leases
new file mode 100644
index 00000000..229331d4
--- /dev/null
+++ b/src/dhcp-manager/tests/test-dhclient-duid.leases
@@ -0,0 +1,2 @@
+default-duid "\000\001\000\001\030y\246\023`g \354Lp";
+
diff --git a/src/dhcp-manager/tests/test-dhcp-dhclient.c b/src/dhcp-manager/tests/test-dhcp-dhclient.c
index a87c29aa..7eb2d358 100644
--- a/src/dhcp-manager/tests/test-dhcp-dhclient.c
+++ b/src/dhcp-manager/tests/test-dhcp-dhclient.c
@@ -20,6 +20,7 @@
 
 #include <glib.h>
 #include <string.h>
+#include <unistd.h>
 
 #include "nm-dhcp-dhclient-utils.h"
 #include "nm-utils.h"
@@ -225,6 +226,173 @@ test_existing_multiline_alsoreq (void)
 
 /*******************************************/
 
+static void
+test_one_duid (const char *escaped, const guint8 *unescaped, guint len)
+{
+	GByteArray *t;
+	char *w;
+
+	t = nm_dhcp_dhclient_unescape_duid (escaped);
+	g_assert (t);
+	g_assert_cmpint (t->len, ==, len);
+	g_assert_cmpint (memcmp (t->data, unescaped, len), ==, 0);
+	g_byte_array_free (t, TRUE);
+
+	t = g_byte_array_sized_new (len);
+	g_byte_array_append (t, unescaped, len);
+	w = nm_dhcp_dhclient_escape_duid (t);
+	g_assert (w);
+	g_assert_cmpint (strlen (escaped), ==, strlen (w));
+	g_assert_cmpstr (escaped, ==, w);
+}
+
+static void
+test_duids (void)
+{
+	const guint8 test1_u[] = { 0x00, 0x01, 0x00, 0x01, 0x13, 0x6f, 0x13, 0x6e,
+	                           0x00, 0x22, 0xfa, 0x8c, 0xd6, 0xc2 };
+	const char *test1_s = "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302";
+
+	const guint8 test2_u[] = { 0x00, 0x01, 0x00, 0x01, 0x17, 0x57, 0xee, 0x39,
+	                           0x00, 0x23, 0x15, 0x08, 0x7E, 0xac };
+	const char *test2_s = "\\000\\001\\000\\001\\027W\\3569\\000#\\025\\010~\\254";
+
+	const guint8 test3_u[] = { 0x00, 0x01, 0x00, 0x01, 0x17, 0x58, 0xe8, 0x58,
+	                           0x00, 0x23, 0x15, 0x08, 0x7e, 0xac };
+	const char *test3_s = "\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254";
+
+	const guint8 test4_u[] = { 0x00, 0x01, 0x00, 0x01, 0x15, 0xd5, 0x31, 0x97,
+	                           0x00, 0x16, 0xeb, 0x04, 0x45, 0x18 };
+	const char *test4_s = "\\000\\001\\000\\001\\025\\3251\\227\\000\\026\\353\\004E\\030";
+
+	const char *bad_s = "\\000\\001\\000\\001\\425\\3251\\227\\000\\026\\353\\004E\\030";
+
+	test_one_duid (test1_s, test1_u, sizeof (test1_u));
+	test_one_duid (test2_s, test2_u, sizeof (test2_u));
+	test_one_duid (test3_s, test3_u, sizeof (test3_u));
+	test_one_duid (test4_s, test4_u, sizeof (test4_u));
+
+	/* Invalid octal digit */
+	g_assert (nm_dhcp_dhclient_unescape_duid (bad_s) == NULL);
+}
+
+static void
+test_read_duid_from_leasefile (void)
+{
+	const guint8 expected[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0x79, 0xa6,
+	                            0x13, 0x60, 0x67, 0x20, 0xec, 0x4c, 0x70 };
+	GByteArray *duid;
+	GError *error = NULL;
+
+	duid = nm_dhcp_dhclient_read_duid (TESTDIR "/test-dhclient-duid.leases", &error);
+	g_assert_no_error (error);
+	g_assert (duid);
+	g_assert_cmpint (duid->len, ==, sizeof (expected));
+	g_assert_cmpint (memcmp (duid->data, expected, duid->len), ==, 0);
+
+	g_byte_array_free (duid, TRUE);
+}
+
+static void
+test_read_commented_duid_from_leasefile (void)
+{
+	GByteArray *duid;
+	GError *error = NULL;
+
+	duid = nm_dhcp_dhclient_read_duid (TESTDIR "/test-dhclient-commented-duid.leases", &error);
+	g_assert_no_error (error);
+	g_assert (duid == NULL);
+}
+
+static void
+test_write_duid (void)
+{
+	const char *duid = "\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254";
+	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
+	GError *error = NULL;
+	char *contents = NULL;
+	gboolean success;
+	const char *path = "test-dhclient-write-duid.leases";
+
+	success = nm_dhcp_dhclient_save_duid (path, duid, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	success = g_file_get_contents (path, &contents, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	unlink (path);
+	g_assert_cmpstr (expected_contents, ==, contents);
+
+	g_free (contents);
+}
+
+static void
+test_write_existing_duid (void)
+{
+	const char *duid = "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302";
+	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
+	GError *error = NULL;
+	char *contents = NULL;
+	gboolean success;
+	const char *path = "test-dhclient-write-existing-duid.leases";
+
+	success = g_file_set_contents (path, expected_contents, -1, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* Save other DUID; should be a no-op */
+	success = nm_dhcp_dhclient_save_duid (path, duid, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* reread original contents */
+	success = g_file_get_contents (path, &contents, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	unlink (path);
+	g_assert_cmpstr (expected_contents, ==, contents);
+
+	g_free (contents);
+}
+
+static void
+test_write_existing_commented_duid (void)
+{
+	#define DUID "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302"
+	#define ORIG_CONTENTS "#default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n"
+	const char *expected_contents = \
+		"default-duid \"" DUID "\";\n"
+		ORIG_CONTENTS;
+	GError *error = NULL;
+	char *contents = NULL;
+	gboolean success;
+	const char *path = "test-dhclient-write-existing-commented-duid.leases";
+
+	success = g_file_set_contents (path, ORIG_CONTENTS, -1, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* Save other DUID; should be a no-op */
+	success = nm_dhcp_dhclient_save_duid (path, DUID, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* reread original contents */
+	success = g_file_get_contents (path, &contents, NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	unlink (path);
+	g_assert_cmpstr (expected_contents, ==, contents);
+
+	g_free (contents);
+}
+
+/*******************************************/
+
 #if GLIB_CHECK_VERSION(2,25,12)
 typedef GTestFixtureFunc TCFunc;
 #else
@@ -248,6 +416,14 @@ int main (int argc, char **argv)
 	g_test_suite_add (suite, TESTCASE (test_override_hostname, NULL));
 	g_test_suite_add (suite, TESTCASE (test_existing_alsoreq, NULL));
 	g_test_suite_add (suite, TESTCASE (test_existing_multiline_alsoreq, NULL));
+	g_test_suite_add (suite, TESTCASE (test_duids, NULL));
+
+	g_test_suite_add (suite, TESTCASE (test_read_duid_from_leasefile, NULL));
+	g_test_suite_add (suite, TESTCASE (test_read_commented_duid_from_leasefile, NULL));
+
+	g_test_suite_add (suite, TESTCASE (test_write_duid, NULL));
+	g_test_suite_add (suite, TESTCASE (test_write_existing_duid, NULL));
+	g_test_suite_add (suite, TESTCASE (test_write_existing_commented_duid, NULL));
 
 	return g_test_run ();
 }