** Abstract **
- odhcp6c is a minimalistic DHCPv6 client for use in embedded Linux systems.
+ odhcp6c is a minimal DHCPv6 and RA-client for use in embedded Linux systems
+ especially routers. It compiles to only about 30 KB (-Os -s).
** Features **
- 1. Handling of non-temporary addresses
- a) assignment of addresses to source interface
- b) handling of valid and preferred lifetimes
- c) duplicate address detection
+ 1. IPv6 bootstrap from different environments with autodetection
+ a) RA only
+ b) RA + stateless DHCPv6
+ c) RA + stateful DHCPv6 (either IA_NA or IA_PD or both)
- 2. Handling of IPv6-Prefixes (Prefix Delegation)
- a) requesting of prefixes
+ 2. Handling of non-temporary addresses (IA_NA)
+ a) handling of valid and preferred lifetimes
+ b) duplicate address detection
+ c) automatic fallback to stateless or PD-only mode
- 3. Stateless fallback-support
+ 3. Support for DHCPv6 extension
+ a) Reconfigure-Messages
+ b) Prefix Delegation (including handling of valid and preferred lifetimes)
+ c) Prefix Exclusion
+ d) DNS Configuration Options
+ e) NTP Options
+ f) SIP Options
+ g) Information-Refresh Options
+ h) SOL_MAX_RT default to 3600
+ i) DS-Lite AFTR-Name Option
- 4. State script support
+ 4. Support for requesting and parsing Router Advertisements
+ a) parsing of prefixes, routes, MTU and RDNSS options
** Compiling **
* bound A suitable server was found and addresses or prefixes acquired
* informed A stateless information request returned updated information
* updated Updated information was received from the DHCPv6 server
- * ra-updated Updated information was received from via Router Advertisement
+ * ra-updated Updated information was received from via Router Advertisement
* rebound The DHCPv6 client switched to another server
* unbound The DHCPv6 client lost all DHCPv6 servers and will restart
* stopped The DHCPv6 client has been stopped
* SNTP_FQDN A space-separated list of SNTP server FQDNs
* SIP_IP A space-separated list of SIP servers
* SIP_DOMAIN A space-separated list of SIP domains
- * OPTION_<num> Custom option received as base-16
+ * OPTION_<num> Custom option received as base-16
* PREFIXES A space-separated list of prefixes currently assigned
- Format: <prefix>/<length>,preferred,valid[,cls]
- Format: <prefix>/<length>,preferred,valid[,excluded=<excluded-prefix>/<length>]
++ Format: <prefix>/<length>,preferred,valid[,excluded=<excluded-prefix>/<length>][,class=<prefix class #>]
* ADDRESSES A space-separated list of addresses currently assigned
Format: <address>/<length>,preferred,valid
- * RA_ADDRESSES A space-separated list of addresses from RA-prefixes
+ * RA_ADDRESSES A space-separated list of addresses from RA-prefixes
Format: <address>/<length>,preferred,valid
* RA_ROUTES A space-separated list of routes from the RA
Format: <address>/<length>,gateway,valid,metric
* RA_DNS A space-separated list of recursive DNS servers from the RA
-
-
-
-
- ** Wishlist **
-
- Features that I would like to see implemented in the near or far future:
-
- * Reconfigure Authentication
+ * AFTR The DS-Lite AFTR domain name
+ * AFTR_IP The DS-Lite AFTR resolved IPv6 address
/**
- * Copyright (C) 2012 Steven Barth <steven@midlink.org>
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License v2 as published by
#include <time.h>
#include <fcntl.h>
#include <errno.h>
+ #include <stdlib.h>
#include <signal.h>
#include <limits.h>
#include <resolv.h>
#include <net/ethernet.h>
#include "odhcp6c.h"
+ #include "md5.h"
#define ALL_DHCPV6_RELAYS {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
static struct dhcpv6_retx dhcpv6_retx[_DHCPV6_MSG_MAX] = {
[DHCPV6_MSG_UNKNOWN] = {false, 1, 120, "<POLL>",
dhcpv6_handle_reconfigure, NULL},
- [DHCPV6_MSG_SOLICIT] = {true, 1, 120, "SOLICIT",
+ [DHCPV6_MSG_SOLICIT] = {true, 1, 3600, "SOLICIT",
dhcpv6_handle_advert, dhcpv6_commit_advert},
[DHCPV6_MSG_REQUEST] = {true, 30, 10, "REQUEST",
dhcpv6_handle_reply, NULL},
static enum odhcp6c_ia_mode na_mode = IA_MODE_NONE;
static bool accept_reconfig = false;
+ // Reconfigure key
+ static uint8_t reconf_key[16];
+
int init_dhcpv6(const char *ifname, int request_pd)
}
// Create ORO
- uint16_t oro[] = {htons(DHCPV6_OPT_DNS_SERVERS),
+ uint16_t oro[] = {
+ htons(DHCPV6_OPT_SIP_SERVER_D),
+ htons(DHCPV6_OPT_SIP_SERVER_A),
+ htons(DHCPV6_OPT_DNS_SERVERS),
htons(DHCPV6_OPT_DNS_DOMAIN),
htons(DHCPV6_OPT_NTP_SERVER),
- htons(DHCPV6_OPT_PREFIX_CLASS)
- };
+ htons(DHCPV6_OPT_SIP_SERVER_A),
+ htons(DHCPV6_OPT_AFTR_NAME),
+ htons(DHCPV6_OPT_PD_EXCLUDE),
++ htons(DHCPV6_OPT_PREFIX_CLASS),
+ };
odhcp6c_add_state(STATE_ORO, oro, sizeof(oro));
// Build IA_PDs
size_t ia_pd_entries, ia_pd_len = 0;
- void *ia_pd = NULL;
struct odhcp6c_entry *e = odhcp6c_get_state(STATE_IA_PD, &ia_pd_entries);
ia_pd_entries /= sizeof(*e);
struct dhcpv6_ia_hdr hdr_ia_pd = {
1, 0, 0
};
- struct dhcpv6_ia_prefix pref = {
- .type = htons(DHCPV6_OPT_IA_PREFIX),
- .len = htons(25), .prefix = request_prefix
- };
-
- struct dhcpv6_ia_prefix p[ia_pd_entries];
+ uint8_t *ia_pd = alloca(ia_pd_entries * (sizeof(struct dhcpv6_ia_prefix) + 10));
for (size_t i = 0; i < ia_pd_entries; ++i) {
- p[i].type = htons(DHCPV6_OPT_IA_PREFIX);
- p[i].len = htons(sizeof(p[i]) - 4U);
- p[i].preferred = 0;
- p[i].valid = 0;
- p[i].prefix = e[i].length;
- p[i].addr = e[i].target;
+ uint8_t ex_len = 0;
+ if (e[i].priority > 0)
+ ex_len = ((e[i].priority - e[i].length - 1) / 8) + 6;
+
+ struct dhcpv6_ia_prefix p = {
+ .type = htons(DHCPV6_OPT_IA_PREFIX),
+ .len = htons(sizeof(p) - 4U + ex_len),
+ .prefix = e[i].length,
+ .addr = e[i].target
+ };
+
+ memcpy(ia_pd + ia_pd_len, &p, sizeof(p));
+ ia_pd_len += sizeof(p);
+
+ if (ex_len) {
+ ia_pd[ia_pd_len++] = 0;
+ ia_pd[ia_pd_len++] = DHCPV6_OPT_PD_EXCLUDE;
+ ia_pd[ia_pd_len++] = 0;
+ ia_pd[ia_pd_len++] = ex_len - 4;
+ ia_pd[ia_pd_len++] = e[i].priority;
+
+ uint32_t excl = ntohl(e[i].router.s6_addr32[1]);
+ excl >>= (64 - e[i].priority);
+ excl <<= 8 - ((e[i].priority - e[i].length) % 8);
+
+ for (size_t i = ex_len - 5; i > 0; --i, excl >>= 8)
+ ia_pd[ia_pd_len + i] = excl & 0xff;
+ ia_pd_len += ex_len - 5;
+ }
}
- ia_pd = p;
- ia_pd_len = sizeof(p);
- hdr_ia_pd.len = htons(ntohs(hdr_ia_pd.len) + ia_pd_len);
- if (request_prefix > 0 &&
+ struct dhcpv6_ia_prefix pref = {
+ .type = htons(DHCPV6_OPT_IA_PREFIX),
+ .len = htons(25), .prefix = request_prefix
+ };
+ if (request_prefix > 0 && ia_pd_len == 0 &&
(type == DHCPV6_MSG_SOLICIT ||
type == DHCPV6_MSG_REQUEST)) {
- ia_pd = &pref;
+ ia_pd = (uint8_t*)&pref;
ia_pd_len = sizeof(pref);
- hdr_ia_pd.len = htons(ntohs(hdr_ia_pd.len) + ia_pd_len);
}
+ hdr_ia_pd.len = htons(ntohs(hdr_ia_pd.len) + ia_pd_len);
// Build IA_NAs
size_t ia_na_entries, ia_na_len = 0;
{&oro_refresh, 0},
{cl_id, cl_id_len},
{srv_id, srv_id_len},
- {&reconf_accept, 0},
+ {&reconf_accept, sizeof(reconf_accept)},
{&fqdn, fqdn_len},
{&hdr_ia_na, sizeof(hdr_ia_na)},
{ia_na, ia_na_len},
}
// Disable IAs if not used
- if (type == DHCPV6_MSG_SOLICIT) {
- iov[5].iov_len = sizeof(reconf_accept);
- } else if (type != DHCPV6_MSG_REQUEST) {
+ if (type != DHCPV6_MSG_REQUEST && type != DHCPV6_MSG_SOLICIT) {
+ iov[5].iov_len = 0;
if (ia_na_len == 0)
iov[7].iov_len = 0;
if (ia_pd_len == 0)
else if (type == DHCPV6_MSG_UNKNOWN)
timeout = t1;
else if (type == DHCPV6_MSG_RENEW)
- timeout = t2 - t1;
+ timeout = (t2 > t1) ? t2 - t1 : 0;
else if (type == DHCPV6_MSG_REBIND)
- timeout = t3 - t2;
+ timeout = (t3 > t2) ? t3 - t2 : 0;
if (timeout == 0)
return -1;
uint64_t start = odhcp6c_get_milli_time(), round_start = start, elapsed;
// Generate transaction ID
- uint8_t trid[3];
- odhcp6c_random(trid, sizeof(trid));
+ uint8_t trid[3] = {0, 0, 0};
+ if (type != DHCPV6_MSG_UNKNOWN)
+ odhcp6c_random(trid, sizeof(trid));
ssize_t len = -1;
int64_t rto = 0;
uint8_t *end = ((uint8_t*)buf) + len, *odata;
uint16_t otype, olen;
- bool clientid_ok = false, serverid_ok = false;
+ bool clientid_ok = false, serverid_ok = false, rcauth_ok = false;
size_t client_id_len, server_id_len;
void *client_id = odhcp6c_get_state(STATE_CLIENT_ID, &client_id_len);
void *server_id = odhcp6c_get_state(STATE_SERVER_ID, &server_id_len);
- dhcpv6_for_each_option(&rep[1], end, otype, olen, odata)
- if (otype == DHCPV6_OPT_CLIENTID)
+ dhcpv6_for_each_option(&rep[1], end, otype, olen, odata) {
+ if (otype == DHCPV6_OPT_CLIENTID) {
clientid_ok = (olen + 4U == client_id_len) && !memcmp(
&odata[-4], client_id, client_id_len);
- else if (otype == DHCPV6_OPT_SERVERID)
+ } else if (otype == DHCPV6_OPT_SERVERID) {
serverid_ok = (olen + 4U == server_id_len) && !memcmp(
&odata[-4], server_id, server_id_len);
+ } else if (otype == DHCPV6_OPT_AUTH && olen == -4 +
+ sizeof(struct dhcpv6_auth_reconfigure)) {
+ struct dhcpv6_auth_reconfigure *r = (void*)&odata[-4];
+ if (r->protocol != 3 || r->algorithm != 1 || r->reconf_type != 2)
+ continue;
+
+ md5_state_t md5;
+ uint8_t serverhash[16], secretbytes[16], hash[16];
+ memcpy(serverhash, r->key, sizeof(serverhash));
+ memset(r->key, 0, sizeof(r->key));
+ memcpy(secretbytes, reconf_key, sizeof(secretbytes));
+
+ for (size_t i = 0; i < sizeof(secretbytes); ++i)
+ secretbytes[i] ^= 0x36;
+
+ md5_init(&md5);
+ md5_append(&md5, secretbytes, sizeof(secretbytes));
+ md5_append(&md5, buf, len);
+ md5_finish(&md5, hash);
+
+ for (size_t i = 0; i < sizeof(secretbytes); ++i) {
+ secretbytes[i] ^= 0x36;
+ secretbytes[i] ^= 0x5c;
+ }
+
+ md5_init(&md5);
+ md5_append(&md5, secretbytes, sizeof(secretbytes));
+ md5_append(&md5, hash, 16);
+ md5_finish(&md5, hash);
+
+ rcauth_ok = !memcmp(hash, serverhash, sizeof(hash));
+ }
+ }
+
+ if (rep->msg_type == DHCPV6_MSG_RECONF && !rcauth_ok)
+ return false;
return clientid_ok && (serverid_ok || server_id_len == 0);
}
} else if (otype == DHCPV6_OPT_RECONF_ACCEPT) {
cand.wants_reconfigure = true;
} else if (otype == DHCPV6_OPT_IA_PD && request_prefix) {
- struct dhcpv6_ia_hdr *h = (void*)odata;
+ struct dhcpv6_ia_hdr *h = (struct dhcpv6_ia_hdr*)&odata[-4];
uint8_t *oend = odata + olen, *d;
dhcpv6_for_each_option(&h[1], oend, otype, olen, d) {
if (otype == DHCPV6_OPT_IA_PREFIX)
uint8_t *odata;
uint16_t otype, olen;
- static time_t last_update = 0;
- time_t now = odhcp6c_get_milli_time() / 1000;
-
- uint32_t elapsed = now - last_update;
odhcp6c_expire();
if (orig == DHCPV6_MSG_UNKNOWN) {
+ static time_t last_update = 0;
+ time_t now = odhcp6c_get_milli_time() / 1000;
+
+ uint32_t elapsed = (last_update > 0) ? now - last_update : 0;
+ last_update = now;
+
t1 -= elapsed;
t2 -= elapsed;
t3 -= elapsed;
odhcp6c_clear_state(STATE_SNTP_FQDN);
odhcp6c_clear_state(STATE_SIP_IP);
odhcp6c_clear_state(STATE_SIP_FQDN);
+ odhcp6c_clear_state(STATE_AFTR_NAME);
}
// Parse and find all matching IAs
if (error)
continue;
- // Update times
- if (l_t1 > 0 && t1 > l_t1)
- t1 = l_t1;
-
- if (l_t2 > 0 && t2 > l_t2)
- t2 = l_t2;
-
uint32_t n = dhcpv6_parse_ia(&ia_hdr[1], odata + olen);
- if (n < t1)
- t1 = n;
+ if (!l_t1)
+ l_t1 = 300;
- if (n < t2)
- t2 = n;
+ if (!l_t2)
+ l_t2 = 600;
if (n < t3)
t3 = n;
- if (t2 >= t3)
- t2 = 8 * t3 / 10;
+ // Update times
+ if (l_t1 > 0 && t1 > l_t1)
+ t1 = l_t1;
- if (t1 >= t2)
- t1 = 5 * t2 / 8;
+ if (l_t2 > 0 && t2 > l_t2)
+ t2 = l_t2;
} else if (otype == DHCPV6_OPT_DNS_SERVERS) {
if (olen % 16 == 0)
uint32_t refresh = ntohl(*((uint32_t*)odata));
if (refresh < (uint32_t)t1)
t1 = refresh;
+ } else if (otype == DHCPV6_OPT_AUTH && olen == -4 +
+ sizeof(struct dhcpv6_auth_reconfigure)) {
+ struct dhcpv6_auth_reconfigure *r = (void*)&odata[-4];
+ if (r->protocol == 3 && r->algorithm == 1 &&
+ r->reconf_type == 1)
+ memcpy(reconf_key, r->key, sizeof(r->key));
+ } else if (otype == DHCPV6_OPT_AFTR_NAME && olen > 3) {
+ size_t cur_len;
+ odhcp6c_get_state(STATE_AFTR_NAME, &cur_len);
+ if (cur_len == 0)
+ odhcp6c_add_state(STATE_AFTR_NAME, odata, olen);
} else if (otype != DHCPV6_OPT_CLIENTID &&
otype != DHCPV6_OPT_SERVERID) {
odhcp6c_add_state(STATE_CUSTOM_OPTS,
uint16_t otype, olen;
uint8_t *odata;
- struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT,
- 0, 0, IN6ADDR_ANY_INIT, 0, 0};
-
// Update address IA
dhcpv6_for_each_option(opt, end, otype, olen, odata) {
+ struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT,
+ 0, 0, IN6ADDR_ANY_INIT, 0, 0, 0};
+
if (otype == DHCPV6_OPT_IA_PREFIX) {
struct dhcpv6_ia_prefix *prefix = (void*)&odata[-4];
if (olen + 4U < sizeof(*prefix))
entry.length = prefix->prefix;
entry.target = prefix->addr;
+ uint16_t stype, slen;
+ uint8_t *sdata;
+
+ // Find prefix class, if any
+ dhcpv6_for_each_option(&prefix[1], odata + olen,
+ stype, slen, sdata)
+ if (stype == DHCPV6_OPT_PREFIX_CLASS && slen == 2)
+ entry.prefix_class = ntohs(*((uint16_t*)sdata));
- odhcp6c_update_entry(STATE_IA_PD, &entry);
+ // Parse PD-exclude
+ bool ok = true;
- uint16_t stype, slen;
- uint8_t *sdata;
+ dhcpv6_for_each_option(odata + sizeof(*prefix) - 4U,
+ odata + olen, stype, slen, sdata) {
+ if (stype != DHCPV6_OPT_PD_EXCLUDE || slen < 2)
+ continue;
+
+ uint8_t elen = sdata[0];
+ if (elen > 64)
+ elen = 64;
+
+ if (elen <= 32 || elen <= entry.length) {
+ ok = false;
+ continue;
+ }
+
+
+ uint8_t bytes = ((elen - entry.length - 1) / 8) + 1;
+ if (slen <= bytes) {
+ ok = false;
+ continue;
+ }
+
+ uint32_t exclude = 0;
+ do {
+ exclude = exclude << 8 | sdata[bytes];
+ } while (--bytes);
+
+ exclude >>= 8 - ((elen - entry.length) % 8);
+ exclude <<= 64 - elen;
+
+ // Abusing router & priority fields for exclusion
+ entry.router = entry.target;
+ entry.router.s6_addr32[1] |= htonl(exclude);
+ entry.priority = elen;
+ }
+
+ if (ok)
+ odhcp6c_update_entry(STATE_IA_PD, &entry);
+
+ entry.priority = 0;
+ memset(&entry.router, 0, sizeof(entry.router));
} else if (otype == DHCPV6_OPT_IA_ADDR) {
struct dhcpv6_ia_addr *addr = (void*)&odata[-4];
if (olen + 4U < sizeof(*addr))
/**
- * Copyright (C) 2012 Steven Barth <steven@midlink.org>
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License v2 as published by
#include <net/if.h>
#include <sys/wait.h>
#include <sys/syscall.h>
+ #include <arpa/inet.h>
#include "odhcp6c.h"
#include "ra.h"
static size_t state_len[_STATE_MAX] = {0};
static volatile int do_signal = 0;
- static int urandom_fd = -1;
- static bool bound = false, allow_slaac_only = true, release = true;
+ static int urandom_fd = -1, allow_slaac_only = 0;
+ static bool bound = false, release = true;
+ static time_t last_update = 0;
int main(_unused int argc, char* const argv[])
char *optpos;
uint16_t opttype;
enum odhcp6c_ia_mode ia_na_mode = IA_MODE_TRY;
+ static struct in6_addr ifid = IN6ADDR_ANY_INIT;
bool help = false, daemonize = false;
int logopt = LOG_PID;
int c, request_pd = 0;
- while ((c = getopt(argc, argv, "SN:P:c:r:s:khedp:")) != -1) {
+ while ((c = getopt(argc, argv, "S::N:P:c:i:r:s:khedp:")) != -1) {
switch (c) {
case 'S':
- allow_slaac_only = false;
+ allow_slaac_only = (optarg) ? atoi(optarg) : -1;
break;
case 'N':
break;
case 'P':
- allow_slaac_only = false;
+ if (allow_slaac_only >= 0 && allow_slaac_only < 10)
+ allow_slaac_only = 10;
+
request_pd = strtoul(optarg, NULL, 10);
if (request_pd == 0)
request_pd = -1;
}
break;
+ case 'i':
+ if (inet_pton(AF_INET6, optarg, &ifid) != 1)
+ help = true;
+ break;
+
case 'r':
optpos = optarg;
while (optpos[0]) {
signal(SIGUSR2, sighandler);
if ((urandom_fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY)) < 0 ||
- init_dhcpv6(ifname, request_pd) || ra_init(ifname) ||
+ init_dhcpv6(ifname, request_pd) || ra_init(ifname, &ifid) ||
script_init(script, ifname)) {
syslog(LOG_ERR, "failed to initialize: %s", strerror(errno));
return 3;
const char buf[] =
"Usage: odhcp6c [options] <interface>\n"
"\nFeature options:\n"
- " -S Don't allow SLAAC-only (implied by -P)\n"
+ " -S <time> Wait at least <time> sec for a DHCP-server (0)\n"
" -N <mode> Mode for requesting addresses [try|force|none]\n"
" -P <length> Request IPv6-Prefix (0 = auto)\n"
" -c <clientid> Override client-ID (base-16 encoded)\n"
+ " -i <iface-id> Use a custom interface identifier for RA handling\n"
" -r <options> Options to be requested (comma-separated)\n"
" -s <script> Status update script (/usr/sbin/odhcp6c-update)\n"
" -k Don't send a RELEASE when stopping\n"
{
if (do_signal == SIGIO) {
do_signal = 0;
- bool updated = ra_process();
- updated |= ra_rtnl_process();
- if (updated && (bound || allow_slaac_only)) {
- odhcp6c_expire();
- script_call("ra-updated");
- }
+ bool ra_rtnled = ra_rtnl_process();
+ bool ra_updated = ra_process();
+
+ if (ra_rtnled || (ra_updated && (bound || allow_slaac_only == 0)))
+ script_call("ra-updated"); // Immediate process urgent events
+ else if (ra_updated && !bound && allow_slaac_only > 0)
+ script_delay_call("ra-updated", allow_slaac_only);
}
return do_signal != 0;
if (x) {
x->valid = new->valid;
x->preferred = new->preferred;
+ x->prefix_class = new->prefix_class;
} else {
odhcp6c_add_state(state, new, sizeof(*new));
}
void odhcp6c_expire(void)
{
- static time_t last_update = 0;
time_t now = odhcp6c_get_milli_time() / 1000;
-
- uint32_t elapsed = now - last_update;
+ uint32_t elapsed = (last_update > 0) ? now - last_update : 0;
last_update = now;
odhcp6c_expire_list(STATE_RA_PREFIX, elapsed);
}
+ uint32_t odhcp6c_elapsed(void)
+ {
+ return odhcp6c_get_milli_time() / 1000 - last_update;
+ }
+
+
void odhcp6c_random(void *buf, size_t len)
{
read(urandom_fd, buf, len);
/**
- * Copyright (C) 2012 Steven Barth <steven@midlink.org>
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License v2 as published by
DHCPV6_OPT_NTP_SERVER = 56,
DHCPV6_OPT_SIP_SERVER_D = 21,
DHCPV6_OPT_SIP_SERVER_A = 22,
-
+ DHCPV6_OPT_AFTR_NAME = 64,
+ DHCPV6_OPT_PD_EXCLUDE = 67,
+ /* draft-bhandari-dhc-class-based-prefix */
+ DHCPV6_OPT_PREFIX_CLASS = 200, /* NOT STANDARDIZED! */
};
enum dhcpv6_opt_npt {
uint8_t data[128];
} _packed;
+ struct dhcpv6_auth_reconfigure {
+ uint16_t type;
+ uint16_t len;
+ uint8_t protocol;
+ uint8_t algorithm;
+ uint8_t rdm;
+ uint64_t replay;
+ uint8_t reconf_type;
+ uint8_t key[16];
+ } _packed;
+
+
#define dhcpv6_for_each_option(start, end, otype, olen, odata)\
for (uint8_t *_o = (uint8_t*)(start); _o + 4 <= (uint8_t*)(end) &&\
((otype) = _o[0] << 8 | _o[1]) && ((odata) = (void*)&_o[4]) &&\
STATE_RA_ROUTE,
STATE_RA_PREFIX,
STATE_RA_DNS,
+ STATE_AFTR_NAME,
_STATE_MAX
};
struct in6_addr target;
uint32_t valid;
uint32_t preferred;
+ uint32_t prefix_class;
};
int script_init(const char *path, const char *ifname);
ssize_t script_unhexlify(uint8_t *dst, size_t len, const char *src);
void script_call(const char *status);
+ void script_delay_call(const char *status, int timeout);
bool odhcp6c_signal_process(void);
uint64_t odhcp6c_get_milli_time(void);
void odhcp6c_update_entry_safe(enum odhcp6c_state state, struct odhcp6c_entry *new, uint32_t safe);
void odhcp6c_expire(void);
+ uint32_t odhcp6c_elapsed(void);
+ /**
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stddef.h>
#include <stdbool.h>
+ #include <syslog.h>
#include <unistd.h>
#include <net/if.h>
+ #include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/icmp6.h>
static void ra_send_rs(int signal __attribute__((unused)));
- int ra_init(const char *ifname)
+ int ra_init(const char *ifname, const struct in6_addr *ifid)
{
sock = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6);
if_index = if_nametoindex(ifname);
strncpy(if_name, ifname, sizeof(if_name) - 1);
+ lladdr = *ifid;
// Filter ICMPv6 package types
struct icmp6_filter filt;
fcntl(sock, F_SETOWN, ourpid);
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_ASYNC);
- // Get LL-addr
- FILE *fp = fopen("/proc/net/if_inet6", "r");
- if (fp) {
- char addrbuf[33], ifbuf[16];
- while (fscanf(fp, "%32s %*x %*x %*x %*x %15s", addrbuf, ifbuf) == 2) {
- if (!strcmp(ifbuf, if_name)) {
- script_unhexlify((uint8_t*)&lladdr, sizeof(lladdr), addrbuf);
- break;
+ if (IN6_IS_ADDR_UNSPECIFIED(&lladdr)) {
+ // Autodetect interface-id if not specified
+ FILE *fp = fopen("/proc/net/if_inet6", "r");
+ if (fp) {
+ char addrbuf[33], ifbuf[16];
+ while (fscanf(fp, "%32s %*x %*x %*x %*x %15s", addrbuf, ifbuf) == 2) {
+ if (!strcmp(ifbuf, if_name)) {
+ script_unhexlify((uint8_t*)&lladdr, sizeof(lladdr), addrbuf);
+ break;
+ }
}
+ fclose(fp);
}
- fclose(fp);
}
// Open rtnetlink socket
static bool ra_deduplicate(const struct in6_addr *any, uint8_t length)
{
- struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, length, 0, *any, 0, 0};
+ struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, length, 0, *any, 0, 0, 0};
struct odhcp6c_entry *x = odhcp6c_find_entry(STATE_RA_PREFIX, &entry);
if (x && IN6_ARE_ADDR_EQUAL(&x->target, any)) {
odhcp6c_random(&x->target.s6_addr32[2], 2 * sizeof(uint32_t));
bool ra_rtnl_process(void)
{
bool found = false;
+ uint32_t elapsed = odhcp6c_elapsed();
uint8_t buf[8192];
while (true) {
ssize_t len = recv(rtnl_sock, buf, sizeof(buf), MSG_DONTWAIT);
if (len < 0)
break;
+ if (elapsed > 10)
+ continue;
+
for (struct nlmsghdr *nh = (struct nlmsghdr*)buf; NLMSG_OK(nh, (size_t)len);
nh = NLMSG_NEXT(nh, len)) {
struct ifaddrmsg *ifa = NLMSG_DATA(nh);
struct in6_addr *addr = NULL;
if (NLMSG_PAYLOAD(nh, 0) < sizeof(*ifa) || ifa->ifa_index != if_index ||
(nh->nlmsg_type == RTM_NEWADDR && !(ifa->ifa_flags & IFA_F_DADFAILED)) ||
- (nh->nlmsg_type == RTM_DELADDR && !(ifa->ifa_flags & IFA_F_TENTATIVE)))
+ (nh->nlmsg_type == RTM_DELADDR && !(ifa->ifa_flags & IFA_F_TENTATIVE)) ||
+ (nh->nlmsg_type != RTM_NEWADDR && nh->nlmsg_type != RTM_DELADDR))
continue;
ssize_t alen = NLMSG_PAYLOAD(nh, sizeof(*ifa));
if (rta->rta_type == IFA_ADDRESS && RTA_PAYLOAD(rta) >= sizeof(*addr))
addr = RTA_DATA(rta);
- if (addr)
+ if (addr) {
+ char ipbuf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
+ syslog(LOG_WARNING, "duplicate address detected: %s (code: %u:%x)",
+ ipbuf, (unsigned)nh->nlmsg_type, (unsigned)ifa->ifa_flags);
found |= ra_deduplicate(addr, ifa->ifa_prefixlen);
+ }
}
}
return found;
bool found = false;
uint8_t buf[1500];
struct nd_router_advert *adv = (struct nd_router_advert*)buf;
- struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0, IN6ADDR_ANY_INIT, 0, 0};
+ struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0, IN6ADDR_ANY_INIT, 0, 0, 0};
const struct in6_addr any = IN6ADDR_ANY_INIT;
- odhcp6c_expire();
while (true) {
struct sockaddr_in6 from;
rs_attempt = 0;
}
- found = true;
+ if (!found) {
+ odhcp6c_expire();
+ found = true;
+ }
uint32_t router_valid = ntohs(adv->nd_ra_router_lifetime);
// Parse default route
+ entry.target = any;
+ entry.length = 0;
entry.router = from.sin6_addr;
entry.priority = pref_to_priority(adv->nd_ra_flags_reserved);
if (entry.priority < 0)
odhcp6c_update_entry(STATE_RA_ROUTE, &entry);
// Parse ND parameters
- if (adv->nd_ra_reachable)
+ if (ntohl(adv->nd_ra_reachable) <= 3600000)
update_proc("neigh", "base_reachable_time_ms", ntohl(adv->nd_ra_reachable));
- if (adv->nd_ra_retransmit)
+ if (ntohl(adv->nd_ra_retransmit) <= 60000)
update_proc("neigh", "retrans_time_ms", ntohl(adv->nd_ra_retransmit));
struct icmpv6_opt *opt;
icmpv6_for_each_option(opt, &adv[1], &buf[len]) {
if (opt->type == ND_OPT_MTU) {
- update_proc("conf", "mtu", ntohl(*((uint32_t*)&opt->data[2])));
+ uint32_t *mtu = (uint32_t*)&opt->data[2];
+ if (ntohl(*mtu) >= 1280 && ntohl(*mtu) <= 65535)
+ update_proc("conf", "mtu", ntohl(*mtu));
} else if (opt->type == ND_OPT_ROUTE_INFORMATION && opt->len <= 3) {
entry.router = from.sin6_addr;
entry.target = any;
entry.priority = pref_to_priority(opt->data[1]);
entry.length = opt->data[0];
- entry.valid = ntohl(*((uint32_t*)&opt->data[2]));
+ uint32_t *valid = (uint32_t*)&opt->data[2];
+ entry.valid = ntohl(*valid);
memcpy(&entry.target, &opt->data[6], (opt->len - 1) * 8);
if (entry.length > 128 || IN6_IS_ADDR_LINKLOCAL(&entry.target)
entry.router = from.sin6_addr;
entry.priority = 0;
entry.length = 128;
- entry.valid = ntohl(*((uint32_t*)&opt->data[2]));
+ uint32_t *valid = (uint32_t*)&opt->data[2];
+ entry.valid = ntohl(*valid);
entry.preferred = 0;
for (ssize_t i = 0; i < (opt->len - 1) / 2; ++i) {
entry[i].valid = router_valid;
}
- odhcp6c_expire();
+ if (found)
+ odhcp6c_expire();
+
return found;
}
/**
- * Copyright (C) 2012 Steven Barth <steven@midlink.org>
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License v2 as published by
*/
#include <stdio.h>
+ #include <netdb.h>
#include <resolv.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
+ #include <signal.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
static char *argv[4] = {NULL, NULL, NULL, NULL};
+ static volatile char *delayed_call = NULL;
+ static bool dont_delay = false;
int script_init(const char *path, const char *ifname)
static void fqdn_to_env(const char *name, const uint8_t *fqdn, size_t len)
{
size_t buf_len = strlen(name);
+ size_t buf_size = len + buf_len + 2;
const uint8_t *fqdn_end = fqdn + len;
char *buf = realloc(NULL, len + buf_len + 2);
memcpy(buf, name, buf_len);
buf[buf_len++] = '=';
int l = 1;
while (l > 0 && fqdn < fqdn_end) {
- l = dn_expand(fqdn, &fqdn[len], fqdn, &buf[buf_len], len);
+ l = dn_expand(fqdn, fqdn_end, fqdn, &buf[buf_len], buf_size - buf_len);
fqdn += l;
buf_len += strlen(&buf[buf_len]);
buf[buf_len++] = ' ';
}
+ static void fqdn_to_ip_env(const char *name, const uint8_t *fqdn, size_t len)
+ {
+ size_t buf_len = strlen(name);
+ char *buf = realloc(NULL, INET6_ADDRSTRLEN + buf_len + 3);
+ memcpy(buf, name, buf_len);
+ buf[buf_len++] = '=';
+
+ char namebuf[256];
+ if (dn_expand(fqdn, fqdn + len, fqdn, namebuf, sizeof(namebuf)) <= 0)
+ return;
+
+ struct addrinfo hints = {.ai_family = AF_INET6}, *r;
+ if (getaddrinfo(namebuf, NULL, &hints, &r))
+ return;
+
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)r->ai_addr;
+ inet_ntop(AF_INET6, &sin6->sin6_addr, &buf[buf_len], INET6_ADDRSTRLEN);
+
+ freeaddrinfo(r);
+ putenv(buf);
+ }
+
+
static void bin_to_env(uint8_t *opts, size_t len)
{
uint8_t *oend = opts + len, *odata;
}
}
+ enum entry_type {
+ ENTRY_ADDRESS,
+ ENTRY_HOST,
+ ENTRY_ROUTE,
+ ENTRY_PREFIX
+ };
- static void entry_to_env(const char *name, const void *data, size_t len, bool host, bool route)
+ static void entry_to_env(const char *name, const void *data, size_t len, enum entry_type type)
{
size_t buf_len = strlen(name);
const struct odhcp6c_entry *e = data;
for (size_t i = 0; i < len / sizeof(*e); ++i) {
inet_ntop(AF_INET6, &e[i].target, &buf[buf_len], INET6_ADDRSTRLEN);
buf_len += strlen(&buf[buf_len]);
- if (!host) {
+ if (type != ENTRY_HOST) {
buf_len += snprintf(&buf[buf_len], 6, "/%hhu", e[i].length);
- if (route) {
+ if (type == ENTRY_ROUTE) {
buf[buf_len++] = ',';
if (!IN6_IS_ADDR_UNSPECIFIED(&e[i].router)) {
inet_ntop(AF_INET6, &e[i].router, &buf[buf_len], INET6_ADDRSTRLEN);
buf_len += snprintf(&buf[buf_len], 12, ",%u", e[i].priority);
} else {
buf_len += snprintf(&buf[buf_len], 24, ",%u,%u", e[i].preferred, e[i].valid);
- if (e[i].prefix_class)
- buf_len += snprintf(&buf[buf_len], 12, ",%u", e[i].prefix_class);
+ }
++ if (type == ENTRY_PREFIX && e[i].prefix_class) {
++ buf_len += snprintf(&buf[buf_len], 12, ",class=%u", e[i].prefix_class);
++ }
+
+ if (type == ENTRY_PREFIX && e[i].priority) {
+ // priority and router are abused for prefix exclusion
+ buf_len += snprintf(&buf[buf_len], 12, ",excluded=");
+ inet_ntop(AF_INET6, &e[i].router, &buf[buf_len], INET6_ADDRSTRLEN);
+ buf_len += strlen(&buf[buf_len]);
+ buf_len += snprintf(&buf[buf_len], 24, "/%u", e[i].priority);
}
}
buf[buf_len++] = ' ';
}
+ static void script_call_delayed(int signal __attribute__((unused)))
+ {
+ if (delayed_call)
+ script_call((char*)delayed_call);
+ }
+
+
+ void script_delay_call(const char *status, int timeout)
+ {
+ if (dont_delay) {
+ script_call(status);
+ } else if (!delayed_call) {
+ delayed_call = strdup(status);
+ signal(SIGALRM, script_call_delayed);
+ alarm(timeout);
+ }
+ }
+
+
void script_call(const char *status)
{
size_t dns_len, search_len, custom_len, sntp_ip_len, sntp_dns_len;
- size_t sip_ip_len, sip_fqdn_len;
+ size_t sip_ip_len, sip_fqdn_len, aftr_name_len;
+
+ odhcp6c_expire();
+ if (delayed_call) {
+ alarm(0);
+ dont_delay = true;
+ }
struct in6_addr *dns = odhcp6c_get_state(STATE_DNS, &dns_len);
uint8_t *search = odhcp6c_get_state(STATE_SEARCH, &search_len);
uint8_t *sntp_dns = odhcp6c_get_state(STATE_SNTP_FQDN, &sntp_dns_len);
struct in6_addr *sip = odhcp6c_get_state(STATE_SIP_IP, &sip_ip_len);
uint8_t *sip_fqdn = odhcp6c_get_state(STATE_SIP_FQDN, &sip_fqdn_len);
+ uint8_t *aftr_name = odhcp6c_get_state(STATE_AFTR_NAME, &aftr_name_len);
size_t prefix_len, address_len, ra_pref_len, ra_route_len, ra_dns_len;
uint8_t *prefix = odhcp6c_get_state(STATE_IA_PD, &prefix_len);
fqdn_to_env("DOMAINS", search, search_len);
fqdn_to_env("SNTP_FQDN", sntp_dns, sntp_dns_len);
fqdn_to_env("SIP_DOMAIN", sip_fqdn, sip_fqdn_len);
+ fqdn_to_env("AFTR", aftr_name, aftr_name_len);
+ fqdn_to_ip_env("AFTR_IP", aftr_name, aftr_name_len);
bin_to_env(custom, custom_len);
- entry_to_env("PREFIXES", prefix, prefix_len, false, false);
- entry_to_env("ADDRESSES", address, address_len, false, false);
- entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len, false, false);
- entry_to_env("RA_ROUTES", ra_route, ra_route_len, false, true);
- entry_to_env("RA_DNS", ra_dns, ra_dns_len, true, false);
+ entry_to_env("PREFIXES", prefix, prefix_len, ENTRY_PREFIX);
+ entry_to_env("ADDRESSES", address, address_len, ENTRY_ADDRESS);
+ entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len, ENTRY_ADDRESS);
+ entry_to_env("RA_ROUTES", ra_route, ra_route_len, ENTRY_ROUTE);
+ entry_to_env("RA_DNS", ra_dns, ra_dns_len, ENTRY_HOST);
argv[2] = (char*)status;
execv(argv[0], argv);