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:
@@ -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,
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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 */
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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 ();
|
||||
}
|
||||
|
Reference in New Issue
Block a user