Support RFC4361.

RFC4361 requires clients to send a clientid, and specifies that by default
that clientid should be a combination of a machine-static DUID and an
interface-static IAID.

There are several RFC-compliant DUIDs.  ndhc uses RFC6355's DUID-UUID,
but chooses not to follow RFC4122 for the UUID and instead simply uses
random bytes from its combined Tausworthe PRNG.

RFC4122 is excessively complex, and 128-bit random values are more than
sufficiently collision-resistant on even large DHCP segments.

ndhc requires a read/writable directory to store the DUID/IAID states.  By
default this directory is /etc/ndhc.  It exists outside the chroot.  The DUID
will be stored in a single file, DUID.  The IAIDs exist per-interface and are
stored in files with names similar to IAID-xx:xx:xx:xx:xx:xx, where the xx
values are replaced by the Ethernet hardware address of the interface.

If it is impossible to read or store the DUIDs or IAIDs, ndhc will
fail at start time before it performs any network activity or forks any
subprocesses.

If the host system lacks volatile storage, then a clientid should manually
be specified using the -c or --clientid command arguments.
This commit is contained in:
Nicholas J. Kain 2014-03-19 00:42:32 -04:00
parent aad83608d1
commit 15598c9207
4 changed files with 306 additions and 7 deletions

263
ndhc/duiaid.c Normal file
View File

@ -0,0 +1,263 @@
/* duiaid.c - DUID/IAID storage and generation for clientids
*
* Copyright (c) 2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
#include "duiaid.h"
#include "strl.h"
#include "log.h"
#include "random.h"
#include "io.h"
#include "ndhc.h"
static char clientid_path[PATH_MAX] = "/etc/ndhc";
void set_clientid_path(char *df)
{
strnkcpy(clientid_path, df, sizeof clientid_path);
}
static void fail_if_clientid_path_dne(void)
{
if (strlen(clientid_path) == 0) {
log_error("clientid path is empty; it must be specified");
exit(EXIT_FAILURE);
}
struct stat st;
if (stat(clientid_path, &st) < 0) {
log_error("failed to stat clientid path '%s': %s",
clientid_path, strerror(errno));
exit(EXIT_FAILURE);
}
if (!S_ISDIR(st.st_mode)) {
log_error("clientid path '%s' does not specify a directory",
clientid_path);
exit(EXIT_FAILURE);
}
}
static void get_duid_path(char *duidfile, size_t dlen)
{
fail_if_clientid_path_dne();
int splen = snprintf(duidfile, sizeof dlen, "%s/DUID", clientid_path);
if (splen < 0) {
log_line("%s: snprintf failed; return=%d", __func__, splen);
exit(EXIT_FAILURE);
}
if ((size_t)splen >= sizeof dlen) {
log_line("%s: snprintf dest buffer too small %d >= %u",
__func__, splen, sizeof dlen);
exit(EXIT_FAILURE);
}
}
static void get_iaid_path(char *iaidfile, size_t ilen, uint8_t *hwaddr,
size_t hwaddrlen)
{
fail_if_clientid_path_dne();
if (hwaddrlen != 6) {
log_line("%s: Hardware address length=%u != 6 bytes",
__func__, hwaddrlen);
exit(EXIT_FAILURE);
}
int splen = snprintf
(iaidfile, sizeof ilen,
"%s/IAID-%.2hhx:%.2hhx:%.2hhx:%.2hhx:%.2hhx:%.2hhx",
clientid_path, hwaddr[0], hwaddr[1], hwaddr[2],
hwaddr[3], hwaddr[4], hwaddr[5]);
if (splen < 0) {
log_line("%s: snprintf failed; return=%d", __func__, splen);
exit(EXIT_FAILURE);
}
if ((size_t)splen >= sizeof ilen) {
log_line("%s: snprintf dest buffer too small %d >= %u",
__func__, splen, sizeof ilen);
exit(EXIT_FAILURE);
}
}
static int open_duidfile_read(void)
{
char duidfile[PATH_MAX];
get_duid_path(duidfile, sizeof duidfile);
int fd = -1;
fd = open(duidfile, O_RDONLY, 0);
if (fd < 0) {
log_line("Failed to open duidfile '%s' for reading: %s",
duidfile, strerror(errno));
}
return fd;
}
static int open_duidfile_write(void)
{
char duidfile[PATH_MAX];
get_duid_path(duidfile, sizeof duidfile);
int fd = -1;
fd = open(duidfile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
if (fd < 0) {
log_line("Failed to open duidfile '%s' for writing: %s",
duidfile, strerror(errno));
exit(EXIT_FAILURE);
}
return fd;
}
static int open_iaidfile_read(uint8_t *hwaddr, size_t hwaddrlen)
{
char iaidfile[PATH_MAX];
get_iaid_path(iaidfile, sizeof iaidfile, hwaddr, hwaddrlen);
int fd = -1;
fd = open(iaidfile, O_RDONLY, 0);
if (fd < 0) {
log_line("Failed to open iaidfile '%s' for reading: %s",
iaidfile, strerror(errno));
}
return fd;
}
static int open_iaidfile_write(uint8_t *hwaddr, size_t hwaddrlen)
{
char iaidfile[PATH_MAX];
get_iaid_path(iaidfile, sizeof iaidfile, hwaddr, hwaddrlen);
int fd = -1;
fd = open(iaidfile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
if (fd < 0) {
log_line("Failed to open iaidfile '%s' for writing: %s",
iaidfile, strerror(errno));
exit(EXIT_FAILURE);
}
return fd;
}
// We use DUID-UUID (RFC6355)
// It is a 16-bit type=4 in network byte order followed by a 128-byte UUID.
// RFC6355 specifies a RFC4122 UUID, but I simply use a 128-byte random
// value, as the complexity of RFC4122 UUID generation is completely
// unwarranted for DHCPv4.
static size_t generate_duid(struct nk_random_state_u32 *s, char *dest,
size_t dlen)
{
const size_t tlen = sizeof(uint16_t) + 4 * sizeof(uint32_t);
if (dlen < tlen) {
log_error("%s: dlen < %u", __func__, tlen);
exit(EXIT_FAILURE);
}
size_t off = 0;
uint16_t typefield = htons(4);
memcpy(dest+off, &typefield, sizeof typefield);
off += sizeof typefield;
for (size_t i = 0; i < 4; ++i) {
uint32_t r32 = nk_random_u32(s);
memcpy(dest+off, &r32, sizeof r32);
off += sizeof r32;
}
return dlen - off;
}
// RFC6355 specifies the IAID as a 32-bit value that uniquely identifies
// a hardware link for a given host.
static size_t generate_iaid(struct nk_random_state_u32 *s, char *dest,
size_t dlen)
{
if (dlen < sizeof(uint32_t)) {
log_error("%s: dlen < %u", __func__, sizeof(uint32_t));
exit(EXIT_FAILURE);
}
size_t off = 0;
uint32_t r32 = nk_random_u32(s);
memcpy(dest+off, &r32, sizeof r32);
off += sizeof r32;
return dlen - off;
}
// Failures are all fatal.
void get_clientid(struct client_state_t *cs, struct client_config_t *cc)
{
if (cc->clientid_len > 0)
return;
char iaid[sizeof cc->clientid];
char duid[sizeof cc->clientid];
size_t iaid_len;
size_t duid_len;
int fd = open_iaidfile_read(cc->arp, sizeof cc->arp);
if (fd < 0) {
iaid_len = generate_iaid(&cs->rnd32_state, iaid, sizeof iaid);
fd = open_iaidfile_write(cc->arp, sizeof cc->arp);
int r = safe_write(fd, iaid, iaid_len);
if (r < 0 || (size_t)r != iaid_len) {
log_error("%s: (%s) failed to write generated IAID.",
cc->interface, __func__);
exit(EXIT_FAILURE);
}
} else {
iaid_len = safe_read(fd, iaid, sizeof iaid);
}
close(fd);
fd = open_duidfile_read();
if (fd < 0) {
duid_len = generate_duid(&cs->rnd32_state, duid, sizeof duid);
fd = open_duidfile_write();
int r = safe_write(fd, duid, duid_len);
if (r < 0 || (size_t)r != duid_len) {
log_error("%s: (%s) failed to write generated DUID.",
cc->interface, __func__);
exit(EXIT_FAILURE);
}
} else {
duid_len = safe_read(fd, duid, sizeof duid);
}
close(fd);
const uint8_t cid_type = 255;
if (sizeof cid_type + iaid_len + duid_len > sizeof cc->clientid) {
log_error("%s: (%s) clientid length > %s",
cc->interface, __func__, sizeof cc->clientid);
exit(EXIT_FAILURE);
}
uint8_t cid_len = 0;
memcpy(&cc->clientid + cid_len, &cid_type, sizeof cid_type);
cid_len += sizeof cid_type;
memcpy(&cc->clientid + cid_len, iaid, iaid_len);
cid_len += iaid_len;
memcpy(&cc->clientid + cid_len, duid, duid_len);
cid_len += duid_len;
cc->clientid_len = cid_len;
}

36
ndhc/duiaid.h Normal file
View File

@ -0,0 +1,36 @@
/* duiaid.h - DUID/IAID storage and generation for clientids
*
* Copyright (c) 2014 Nicholas J. Kain <njkain at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef NJK_NDHC_DUIAID_H_
#define NJK_NDHC_DUIAID_H_
#include "ndhc.h"
void set_clientid_path(char *df);
void get_clientid(struct client_state_t *cs, struct client_config_t *cc);
#endif /* NJK_NDHC_DUIAID_H_ */

View File

@ -61,7 +61,6 @@
#include "netlink.h" #include "netlink.h"
#include "leasefile.h" #include "leasefile.h"
#include "ifset.h" #include "ifset.h"
#include "log.h" #include "log.h"
#include "chroot.h" #include "chroot.h"
#include "cap.h" #include "cap.h"
@ -70,6 +69,7 @@
#include "io.h" #include "io.h"
#include "seccomp.h" #include "seccomp.h"
#include "ifchd.h" #include "ifchd.h"
#include "duiaid.h"
struct client_state_t cs = { struct client_state_t cs = {
.ifchWorking = 0, .ifchWorking = 0,
@ -578,6 +578,8 @@ int main(int argc, char **argv)
} }
} }
nk_random_u32_init(&cs.rnd32_state);
if (getuid()) if (getuid())
suicide("FATAL - I need to be started as root."); suicide("FATAL - I need to be started as root.");
if (!strncmp(chroot_dir, "", sizeof chroot_dir)) if (!strncmp(chroot_dir, "", sizeof chroot_dir))
@ -588,6 +590,8 @@ int main(int argc, char **argv)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
get_clientid(&cs, &client_config);
switch (perform_ifup()) { switch (perform_ifup()) {
case 1: case 1:
cs.ifsPrevState = IFS_UP; cs.ifsPrevState = IFS_UP;
@ -603,11 +607,12 @@ int main(int argc, char **argv)
if (ifch_pid == 0) { if (ifch_pid == 0) {
close(pToNdhcR); close(pToNdhcR);
close(pToIfchW); close(pToIfchW);
// Don't share the RNG state with the master process.
nk_random_u32_init(&cs.rnd32_state);
ifch_main(); ifch_main();
} else if (ifch_pid > 0) { } else if (ifch_pid > 0) {
close(pToIfchR); close(pToIfchR);
close(pToNdhcW); close(pToNdhcW);
nk_random_u32_init(&cs.rnd32_state);
ndhc_main(); ndhc_main();
} else { } else {
log_line("FATAL - failed to fork ndhc-ifch: %s", strerror(errno)); log_line("FATAL - failed to fork ndhc-ifch: %s", strerror(errno));

View File

@ -121,11 +121,6 @@ static int get_if_index_and_mac(const struct nlmsghdr *nlh,
client_config.interface, client_config.interface,
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
memcpy(client_config.arp, mac, 6); memcpy(client_config.arp, mac, 6);
if (client_config.clientid_len < 2) {
client_config.clientid[0] = 1; // Ethernet MAC type
memcpy(&client_config.clientid + 1, mac, 6);
client_config.clientid_len = 7;
}
return 1; return 1;
} }
return 0; return 0;