[PATCH] DUID support



Hi,

This is a patch that adds capabilities to NM to handle DUIDs itself
(for dhclient), to get the original DUID from dhclient6.leases as an
authoritative source, making sure this value is copied over in the new
lease files for each connection as it gets activated.

See attached file.

Regards,

Mathieu Trudel-Lapierre <mathieu tl gmail com>
Freenode: cyphermox, Jabber: mathieu tl gmail com
4096R/EE018C93 1967 8F7D 03A1 8F38 732E  FF82 C126 33E1 EE01 8C93
From: Mathieu Trudel-Lapierre <mathieu trudel-lapierre canonical com>
Subject: add DUID handling support and DUID generation.
Bug-Ubuntu: https://bugs.launchpad.net/bugs/849994

This patch watches the DUID already provided by dhclient if it's available
and attempts to make sure it is recorded in the future lease files when
necessary. If the DUID isn't present anywhere, it will be generated as a
DUID-LLT, and the values from dhclient always takes precedence.

Index: network-manager-0.9.0/src/dhcp-manager/nm-dhcp-client.c
===================================================================
--- network-manager-0.9.0.orig/src/dhcp-manager/nm-dhcp-client.c	2011-08-22 19:16:06.000000000 -0400
+++ network-manager-0.9.0/src/dhcp-manager/nm-dhcp-client.c	2011-08-30 12:32:00.097042017 -0400
@@ -29,6 +29,12 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <stdlib.h>
+#include <stdint.h>
+
+#include <net/if.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
 
 #include "nm-utils.h"
 #include "nm-logging.h"
@@ -40,6 +46,7 @@
 	gboolean     ipv6;
 	char *       uuid;
 	guint32      timeout;
+	char *       duid;
 
 	guchar       state;
 	GPid         pid;
@@ -70,12 +77,119 @@
 	PROP_IFACE,
 	PROP_IPV6,
 	PROP_UUID,
+	PROP_DUID,
 	PROP_TIMEOUT,
 	LAST_PROP
 };
 
 /********************************************/
 
+struct duid_header {
+	uint16_t duid_type;
+	uint16_t hw_type;
+	uint32_t time;
+	/* link-layer address follows */
+} __attribute__((__packed__));
+
+#define DUID_TIME_EPOCH 946684800
+#define DUID_ETHER_LEN 6
+
+static char *
+escape_duid (const guint8 * duid, guint32 len)
+{
+        static char escaped[(sizeof(struct duid_header*) + DUID_ETHER_LEN) * 4 + 1];
+        const guint8 *s = duid;
+        char *d = escaped;
+
+        while (len--) {
+                if (*s == '\0') {
+                        *d++ = '\\';
+                        *d++ = '0';
+                        *d++ = '0';
+                        *d++ = '0';
+                } else if (*s == '\a') {
+                        *d++ = '\\';
+                        *d++ = '0';
+                        *d++ = '0';
+                        *d++ = '7';
+                } else if (*s == '\b') {
+                        *d++ = '\\';
+                        *d++ = '0';
+                        *d++ = '1';
+                        *d++ = '0';
+                } else if (*s == '\t') {
+                        *d++ = '\\';
+                        *d++ = '0';
+                        *d++ = '1';
+                        *d++ = '1';
+                } else if (*s == '\n') {
+                        *d++ = '\\';
+                        *d++ = '0';
+                        *d++ = '1';
+                        *d++ = '2';
+                } else if (*s == '\f') {
+                        *d++ = '\\';
+                        *d++ = '0';
+                        *d++ = '1';
+                        *d++ = '4';
+                } else if (*s == '\r') {
+                        *d++ = '\\';
+                        *d++ = '0';
+                        *d++ = '1';
+                        *d++ = '5';
+                } else {
+                        *d++ = *s;
+                }
+                s++;
+        }
+        *d = '\0';
+        return g_strescape (escaped, "\\");
+}
+
+/* Get a DHCP Unique Identifier for DHCPv6.
+ * We use the DUID-LLT method (see RFC 3315 s9.2) using code based on Debian's
+ * netcfg code to generate DUID-LL numbers.
+ */
+gboolean
+nm_dhcp_client_generate_duid (const char *iface, char **duid)
+{
+	GByteArray *raw_duid;
+	struct sockaddr sa;
+	struct ifreq ifr;
+	struct duid_header *duid_header;
+	struct timeval tv;
+	int s;
+
+	s = socket(AF_INET, SOCK_DGRAM, 0); /* doesn't matter what kind */
+	if (s < 0) {
+		return FALSE;
+	}
+
+	strncpy(ifr.ifr_name, iface, IFNAMSIZ);
+	if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0) {
+		nm_log_err (LOGD_HW, "Couldn't get hardware address of %s: %s",
+		            iface, strerror(errno));
+		return FALSE;
+	}
+	memcpy(&sa, &ifr.ifr_hwaddr, sizeof((void *)&sa));
+
+	raw_duid = g_byte_array_sized_new (sizeof(duid_header) + DUID_ETHER_LEN);
+
+	duid_header = malloc (sizeof(duid_header));
+	duid_header->duid_type = g_htons(1);
+	duid_header->hw_type = g_htons(sa.sa_family);
+	gettimeofday(&tv, NULL);
+	duid_header->time = g_htonl(tv.tv_sec - DUID_TIME_EPOCH);
+
+	g_byte_array_append (raw_duid, (guint8*) duid_header, sizeof(duid_header));
+	g_byte_array_append (raw_duid, (guint8*) sa.sa_data, DUID_ETHER_LEN);
+
+	nm_log_warn (LOGD_HW, "MATT: DUID: '%s':%d", escape_duid(raw_duid->data, raw_duid->len), raw_duid->len);
+	*duid = escape_duid (raw_duid->data, raw_duid->len);
+
+	return TRUE;
+}
+
 GPid
 nm_dhcp_client_get_pid (NMDHCPClient *self)
 {
@@ -112,6 +226,15 @@
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->uuid;
 }
 
+const char *
+nm_dhcp_client_get_duid (NMDHCPClient *self)
+{
+       g_return_val_if_fail (self != NULL, NULL);
+       g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+       return NM_DHCP_CLIENT_GET_PRIVATE (self)->duid;
+}
+
 /********************************************/
 
 static void
@@ -322,6 +445,19 @@
 	return priv->pid ? TRUE : FALSE;
 }
 
+void
+nm_dhcp_client_guess_duid (NMDHCPClient *self)
+{
+	NMDHCPClientPrivate *priv;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
+
+	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	priv->duid = NM_DHCP_CLIENT_GET_CLASS (self)->guess_duid (self);
+}
+
 gboolean
 nm_dhcp_client_start_ip6 (NMDHCPClient *self,
                           NMSettingIP6Config *s_ip6,
@@ -339,6 +475,16 @@
 	g_return_val_if_fail (priv->ipv6 == TRUE, FALSE);
 	g_return_val_if_fail (priv->uuid != NULL, FALSE);
 
+        /* Dealing with IPv6 and following RFCs mean we need a default DUID for
+         * a new connection. DUIDs can be generated, but we usually wouldn't
+         * unless it's not available anywhere else.
+         */
+        if (priv->duid == NULL)
+                nm_dhcp_client_guess_duid (self);
+
+        if (priv->duid != NULL)
+                nm_log_info (LOGD_DHCP, "MATT: nm_dhcp_client_start_ip6: DUID is '%s'", priv->duid);
+
 	priv->info_only = info_only;
 
 	nm_log_info (LOGD_DHCP, "Activation (%s) Beginning DHCPv6 transaction (timeout in %d seconds)",
@@ -1326,6 +1472,7 @@
 
 	priv->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 	priv->pid = -1;
+	priv->duid = NULL;
 }
 
 static void
@@ -1344,6 +1491,9 @@
 	case PROP_UUID:
 		g_value_set_string (value, priv->uuid);
 		break;
+	case PROP_DUID:
+		g_value_set_string (value, priv->duid);
+		break;
 	case PROP_TIMEOUT:
 		g_value_set_uint (value, priv->timeout);
 		break;
@@ -1372,6 +1522,9 @@
 		/* construct-only */
 		priv->uuid = g_value_dup_string (value);
 		break;
+	case PROP_DUID:
+		priv->duid = g_value_dup_string (value);
+		break;
 	case PROP_TIMEOUT:
 		priv->timeout = g_value_get_uint (value);
 		break;
@@ -1440,6 +1593,14 @@
 		                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
 	g_object_class_install_property
+		(object_class, PROP_DUID,
+		 g_param_spec_string (NM_DHCP_CLIENT_DUID,
+		                      "duid",
+		                      "DUID",
+		                      NULL,
+		                      G_PARAM_READWRITE));
+
+	g_object_class_install_property
 		(object_class, PROP_TIMEOUT,
 		 g_param_spec_uint (NM_DHCP_CLIENT_TIMEOUT,
 		                    "timeout",
Index: network-manager-0.9.0/src/dhcp-manager/nm-dhcp-client.h
===================================================================
--- network-manager-0.9.0.orig/src/dhcp-manager/nm-dhcp-client.h	2011-08-22 19:16:06.000000000 -0400
+++ network-manager-0.9.0/src/dhcp-manager/nm-dhcp-client.h	2011-08-30 12:32:00.097042017 -0400
@@ -37,6 +37,7 @@
 #define NM_DHCP_CLIENT_INTERFACE "iface"
 #define NM_DHCP_CLIENT_IPV6      "ipv6"
 #define NM_DHCP_CLIENT_UUID      "uuid"
+#define NM_DHCP_CLIENT_DUID      "duid"
 #define NM_DHCP_CLIENT_TIMEOUT   "timeout"
 
 typedef enum {
@@ -90,12 +91,16 @@
 	void (*stop)          (NMDHCPClient *self,
 	                       gboolean release);
 
+	char * (*guess_duid)    (NMDHCPClient *self);
+
 	/* Signals */
 	void (*state_changed) (NMDHCPClient *self, NMDHCPState state);
 	void (*timeout)       (NMDHCPClient *self);
 	void (*remove)        (NMDHCPClient *self);
 } NMDHCPClientClass;
 
+gboolean nm_dhcp_client_generate_duid (const char *iface, char **duid);
+
 GType nm_dhcp_client_get_type (void);
 
 GPid nm_dhcp_client_get_pid (NMDHCPClient *self);
@@ -106,6 +111,8 @@
 
 const char *nm_dhcp_client_get_uuid (NMDHCPClient *self);
 
+void nm_dhcp_client_guess_duid (NMDHCPClient *self);
+
 gboolean nm_dhcp_client_start_ip4 (NMDHCPClient *self,
                                    NMSettingIP4Config *s_ip4,
                                    guint8 *dhcp_anycast_addr,
Index: network-manager-0.9.0/src/dhcp-manager/nm-dhcp-dhclient.c
===================================================================
--- network-manager-0.9.0.orig/src/dhcp-manager/nm-dhcp-dhclient.c	2011-08-22 19:16:06.000000000 -0400
+++ network-manager-0.9.0/src/dhcp-manager/nm-dhcp-dhclient.c	2011-08-30 12:39:19.247042852 -0400
@@ -33,6 +33,7 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <ctype.h>
+#include <fcntl.h>
 
 #include <config.h>
 
@@ -48,11 +49,14 @@
 #if defined(TARGET_DEBIAN) || defined(TARGET_SUSE) || defined(TARGET_MANDRIVA)
 #if defined(DHCLIENT_V3)
 #define NM_DHCLIENT_LEASE_DIR			LOCALSTATEDIR "/lib/dhcp3"
+#define NM_DHCLIENT_DEFAULT_LEASE_FILE  NM_DHCLIENT_LEASE_DIR "/dhclient6.leases"
 #else
 #define NM_DHCLIENT_LEASE_DIR           LOCALSTATEDIR "/lib/dhcp"
+#define NM_DHCLIENT_DEFAULT_LEASE_FILE  NM_DHCLIENT_LEASE_DIR "/dhclient6.leases"
 #endif
 #else
 #define NM_DHCLIENT_LEASE_DIR           LOCALSTATEDIR "/lib/dhclient"
+#define NM_DHCLIENT_DEFAULT_LEASE_FILE  NM_DHCLIENT_LEASE_DIR "/dhclient6.leases"
 #endif
 
 #define ACTION_SCRIPT_PATH	LIBEXECDIR "/nm-dhcp-client.action"
@@ -337,6 +341,103 @@
 	return success;
 }
 
+static gboolean
+duid_in_leasefile (char *leasefile, char **duid)
+{
+	char *found_duid = NULL;
+	char *contents;
+	char **line, **split, **params;
+
+	nm_log_warn (LOGD_DHCP, "MATT: Looking for duid in '%s'.", leasefile);
+
+       	if (!g_file_test (leasefile, G_FILE_TEST_EXISTS))
+		return FALSE;
+
+	if (!g_file_get_contents (leasefile, &contents, NULL, NULL))
+		return FALSE;
+
+	split = g_strsplit_set (contents, "\n\r", -1);
+	g_free (contents);
+
+	if (!split)
+		return FALSE;
+
+	for (line = split; line && *line; line++) {
+		*line = g_strstrip (*line);
+
+		if (g_strstr_len (*line, -1, "default-duid")) {
+			nm_log_warn (LOGD_DHCP, "MATT: Found duid in '%s'.", leasefile);
+			params = g_strsplit_set (*line, " \t\"", -1);
+			nm_log_warn (LOGD_DHCP, "MATT: Found DUID: '%s'", params[2]);
+			found_duid = g_strdup (params[2]);
+			g_strfreev (params);
+			break;
+		}
+	}
+	g_strfreev (split);
+
+	if (found_duid != NULL) {
+		*duid = found_duid;
+		return TRUE;
+	}
+	else
+		return FALSE;
+}
+
+static char *
+real_guess_duid (NMDHCPClient *self)
+{
+	char *duid = NULL;
+	GPtrArray *leasefiles;
+	const char *iface, *uuid;
+	char *leasefile, *contents;
+	char **line, **split, **params;
+	gboolean ipv6;
+	int i;
+
+	iface = nm_dhcp_client_get_iface (self);
+	uuid = nm_dhcp_client_get_uuid (self);
+	ipv6 = nm_dhcp_client_get_ipv6 (self);
+
+	if (!ipv6)
+		return NULL;
+
+	leasefiles = g_ptr_array_new ();
+
+	/* Always add the "default" lease file from dhclient as first place
+         * to look for the DUID.
+         */
+	g_ptr_array_add (leasefiles, g_strdup(NM_DHCLIENT_DEFAULT_LEASE_FILE));
+
+	leasefile = get_leasefile_for_iface (iface, uuid, ipv6);
+
+	if (!leasefile)
+		return NULL;
+
+        if (g_file_test (leasefile, G_FILE_TEST_EXISTS))
+		g_ptr_array_add (leasefiles, leasefile);
+
+	for (i = 0; i < leasefiles->len; i++) {
+		gboolean found = FALSE;
+
+		leasefile = g_ptr_array_index (leasefiles, i);
+
+		found = duid_in_leasefile (leasefile, &duid);
+
+		if (found && duid != NULL)
+			break;
+	}
+	if (duid != NULL)
+		nm_log_warn (LOGD_DHCP, "MATT: again, DUID: '%s'", duid);
+
+	for (i = 0; i < leasefiles->len; i++)
+		g_free (g_ptr_array_index (leasefiles, i));
+	g_ptr_array_free (leasefiles, TRUE);
+
+out:
+	return duid;
+}
+
 /* NM provides interface-specific options; thus the same dhclient config
  * file cannot be used since DHCP transactions can happen in parallel.
  * Since some distros don't have default per-interface dhclient config files,
@@ -419,7 +520,7 @@
 	GPid pid = -1;
 	GError *error = NULL;
 	const char *iface, *uuid;
-	char *binary_name, *cmd_str, *pid_file = NULL;
+	char *binary_name, *cmd_str, *pid_file = NULL, *duid = NULL;
 	gboolean ipv6;
 	guint log_domain;
 
@@ -428,6 +529,7 @@
 	iface = nm_dhcp_client_get_iface (client);
 	uuid = nm_dhcp_client_get_uuid (client);
 	ipv6 = nm_dhcp_client_get_ipv6 (client);
+	duid = nm_dhcp_client_get_duid (client);
 
 	log_domain = ipv6 ? LOGD_DHCP6 : LOGD_DHCP4;
 
@@ -469,6 +571,110 @@
 		return -1;
 	}
 
+#if !defined(DHCLIENT_V3)
+        /* Dealing with IPv6 and following RFCs mean we need a default DUID for
+         * a new connection. DUIDs can be generated, but we usually wouldn't
+         * unless it's not available anywhere else.
+         */
+	if (ipv6) {
+		FILE *leasef;
+		gboolean found = FALSE;
+		char *found_duid = NULL;
+		char *contents = NULL, *new_contents = NULL;
+
+		if (!duid) {
+			nm_log_warn (log_domain, "MATT: oops, no DUID yet... try to guess it again.");
+			duid = real_guess_duid (client);
+		}
+
+		if (!duid) {
+			/* We failed all tries to get a DUID from elsewhere, so generate a DUID-LLT
+			 * to pass to the dhclient lease file.
+			 */
+			nm_dhcp_client_generate_duid (iface, &duid);
+			nm_log_warn (log_domain, "MATT: done generation: '%s'", duid);
+
+			/* In all likelihood if there is no DUID before it gets generated
+			 * above, then the default dhclient lease file is also missing,
+			 * so it's safe to create it if we verify it doesn't exist.
+			 */
+			if (!g_file_test (NM_DHCLIENT_DEFAULT_LEASE_FILE, G_FILE_TEST_EXISTS)) {
+				if ((leasef = g_fopen (NM_DHCLIENT_DEFAULT_LEASE_FILE, "w")) == NULL)
+					nm_log_warn (log_domain, "Could not create default DHCPv6 lease file '%s'.",
+					             NM_DHCLIENT_DEFAULT_LEASE_FILE);
+				g_chmod (NM_DHCLIENT_DEFAULT_LEASE_FILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+				g_fprintf (leasef, "default-duid \"%s\";\n", duid);
+				fclose (leasef);
+			}
+		}
+
+		if (!g_file_test (priv->lease_file, G_FILE_TEST_EXISTS)) {
+			found = FALSE;
+			if (g_creat (priv->lease_file, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)
+				nm_log_warn (log_domain, "Could not create DHCPv6 lease file '%s'.", priv->lease_file);
+			g_chmod (priv->lease_file, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+		}
+		else {
+			nm_log_warn (log_domain, "MATT: check if the DUID is available in lease file.");
+			found = duid_in_leasefile (priv->lease_file, &found_duid);
+		}
+
+		/* Write the DUID to lease file if it's not already there.
+		 * We need not care about writing it at the end of the file, because it will be
+		 * rewritten / sorted by dhclient when it will run.
+		 */
+		if (!found) {
+			nm_log_warn (log_domain, "MATT: oops, no DUID in lease file, write it.");
+			if ((leasef = g_fopen (priv->lease_file, "w+")) == NULL) {
+				nm_log_warn (log_domain, "Can't open lease file for writing DUID.");
+				return -1;
+			}
+			g_fprintf (leasef, "default-duid \"%s\";\n", duid);
+			fclose (leasef);
+		}
+		else {
+			nm_log_warn (log_domain, "MATT: DUID in lease file, let's make sure it's the right one.");
+			if (!g_file_get_contents (priv->lease_file, &contents, NULL, &error)) {
+				nm_log_warn (log_domain, "couldn't read current DUID from lease file: '%s'", error->message);
+				g_error_free (error);
+				return -1;
+			}
+
+			/* We may have found a DUID in the file, and received a different value from
+			 * dhclient's default lease file. In this case, let's use the value from dhclient
+			 * as the good one, in case dhclient was run manually for a valid reason.
+			 */
+			if (g_strcmp0 (duid, found_duid) != 0) {
+				gchar **split = NULL, **line = NULL;
+
+				nm_log_warn (log_domain, "MATT: replacing DUID in lease files.");
+
+			        split = g_strsplit_set (contents, "\n\r", -1);
+				g_free (contents);
+
+				if (!split)
+					return -1;
+
+				if ((leasef = g_fopen (priv->lease_file, "w")) == NULL) {
+					nm_log_warn (log_domain, "Can't open lease file for writing DUID.");
+					return -1;
+				}
+
+				g_fprintf (leasef, "default-duid \"%s\";\n", duid);
+				for (line = split; line && *line; line++) {
+					*line = g_strstrip (*line);
+
+					if (!g_strstr_len (*line, -1, "default-duid"))
+						g_fprintf (leasef, "%s\n", *line);
+				}
+				g_strfreev (split);
+				fclose (leasef);
+			}
+			nm_log_warn (log_domain, "MATT: DUID found in lease file...");
+		}
+	}
+#endif
+
 	argv = g_ptr_array_new ();
 	g_ptr_array_add (argv, (gpointer) priv->path);
 
@@ -617,5 +823,6 @@
 	client_class->ip4_start = real_ip4_start;
 	client_class->ip6_start = real_ip6_start;
 	client_class->stop = real_stop;
+	client_class->guess_duid = real_guess_duid;
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]