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.
This commit is contained in:
Dan Williams
2012-06-15 16:26:53 -05:00
parent c4e519bd78
commit dc518cf86b
7 changed files with 240 additions and 11 deletions

View File

@@ -28,6 +28,7 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <net/if_arp.h>
#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,

View File

@@ -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);

View File

@@ -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,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;
}

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 ();
}