From dc518cf86bb474ad98711745df76add7213e5959 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 15 Jun 2012 16:26:53 -0500 Subject: [PATCH] dhcp: add generic DUID infrastructure Add infrastructure for generating DUID-LLT from a given device MAC and passing it around to the DHCP client implementations. Thanks to Mathieu Trudel-Lapierre for bug fixes in the unescaping code, which were merged into this commit. --- src/dhcp-manager/nm-dhcp-client.c | 90 ++++++++++++++++++++- src/dhcp-manager/nm-dhcp-client.h | 17 +++- src/dhcp-manager/nm-dhcp-dhclient-utils.c | 73 +++++++++++++++++ src/dhcp-manager/nm-dhcp-dhclient-utils.h | 4 + src/dhcp-manager/nm-dhcp-dhclient.c | 7 +- src/dhcp-manager/nm-dhcp-dhcpcd.c | 7 +- src/dhcp-manager/tests/test-dhcp-dhclient.c | 53 ++++++++++++ 7 files changed, 240 insertions(+), 11 deletions(-) diff --git a/src/dhcp-manager/nm-dhcp-client.c b/src/dhcp-manager/nm-dhcp-client.c index faba27612d..cb23ffb310 100644 --- a/src/dhcp-manager/nm-dhcp-client.c +++ b/src/dhcp-manager/nm-dhcp-client.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "nm-utils.h" #include "nm-logging.h" @@ -40,6 +41,7 @@ typedef struct { gboolean ipv6; char * uuid; guint32 timeout; + GByteArray * duid; guchar state; GPid pid; @@ -184,7 +186,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; @@ -323,6 +325,66 @@ nm_dhcp_client_start_ip4 (NMDHCPClient *self, return priv->pid ? TRUE : FALSE; } +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 + +/* Generate a DHCP Unique Identifier for DHCPv6 using the + * DUID-LLT method (see RFC 3315 s9.2), following Debian's + * netcfg DUID-LL generation method. + */ +static GByteArray * +generate_duid (const GByteArray *hwaddr) +{ + GByteArray *duid; + struct duid_header p; + int arptype; + + g_return_val_if_fail (hwaddr != NULL, NULL); + + memset (&p, 0, sizeof (p)); + p.duid_type = g_htons(1); + arptype = nm_utils_hwaddr_type (hwaddr->len); + g_assert (arptype == ARPHRD_ETHER || arptype == ARPHRD_INFINIBAND); + p.hw_type = g_htons (arptype); + p.time = g_htonl (time (NULL) - DUID_TIME_EPOCH); + + duid = g_byte_array_sized_new (sizeof (p) + hwaddr->len); + g_byte_array_append (duid, (const guint8 *) &p, sizeof (p)); + g_byte_array_append (duid, hwaddr->data, hwaddr->len); + + return duid; +} + +static GByteArray * +get_duid (NMDHCPClient *self) +{ + /* generate a default DUID */ + return generate_duid (NM_DHCP_CLIENT_GET_PRIVATE (self)->hwaddr); +} + +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, @@ -331,6 +393,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); @@ -340,12 +403,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); @@ -399,7 +479,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", @@ -1394,6 +1474,9 @@ dispose (GObject *object) 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); } @@ -1410,6 +1493,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, diff --git a/src/dhcp-manager/nm-dhcp-client.h b/src/dhcp-manager/nm-dhcp-client.h index 0df23415e1..930ec1a5e5 100644 --- a/src/dhcp-manager/nm-dhcp-client.h +++ b/src/dhcp-manager/nm-dhcp-client.h @@ -86,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 f3d3b1b6c6..73c097360b 100644 --- a/src/dhcp-manager/nm-dhcp-dhclient-utils.c +++ b/src/dhcp-manager/nm-dhcp-dhclient-utils.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "nm-dhcp-dhclient-utils.h" @@ -255,3 +256,75 @@ 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; +} + diff --git a/src/dhcp-manager/nm-dhcp-dhclient-utils.h b/src/dhcp-manager/nm-dhcp-dhclient-utils.h index 2f9f7c454d..7fc25672aa 100644 --- a/src/dhcp-manager/nm-dhcp-dhclient-utils.h +++ b/src/dhcp-manager/nm-dhcp-dhclient-utils.h @@ -34,5 +34,9 @@ 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); + #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 347d97c4fe..544c08ebd9 100644 --- a/src/dhcp-manager/nm-dhcp-dhclient.c +++ b/src/dhcp-manager/nm-dhcp-dhclient.c @@ -583,7 +583,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; @@ -600,12 +601,12 @@ ip6_start (NMDHCPClient *client, } 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); diff --git a/src/dhcp-manager/nm-dhcp-dhcpcd.c b/src/dhcp-manager/nm-dhcp-dhcpcd.c index dda96e2a09..f3245ee5ef 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/tests/test-dhcp-dhclient.c b/src/dhcp-manager/tests/test-dhcp-dhclient.c index a87c29aa0c..c77337f574 100644 --- a/src/dhcp-manager/tests/test-dhcp-dhclient.c +++ b/src/dhcp-manager/tests/test-dhcp-dhclient.c @@ -225,6 +225,58 @@ 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); +} + +/*******************************************/ + #if GLIB_CHECK_VERSION(2,25,12) typedef GTestFixtureFunc TCFunc; #else @@ -248,6 +300,7 @@ 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)); return g_test_run (); }