/**
* Copyright (C) 2012-2014 Steven Barth <steven@midlink.org>
+ * Copyright (C) 2017-2018 Hans Dedecker <dedeckeh@gmail.com>
*
* 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 <inttypes.h>
#include <stdlib.h>
#include <signal.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
+#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <net/ethernet.h>
#include "odhcp6c.h"
+#ifdef USE_LIBUBOX
+#include <libubox/md5.h>
+#else
#include "md5.h"
+#endif
#define ALL_DHCPV6_RELAYS {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
const uint8_t transaction[3], enum dhcpv6_msg type,
const struct in6_addr *daddr);
-static int dhcpv6_parse_ia(void *opt, void *end);
+static unsigned int dhcpv6_parse_ia(void *opt, void *end);
-static int dhcpv6_calc_refresh_timers(void);
+static unsigned int dhcpv6_calc_refresh_timers(void);
static void dhcpv6_handle_status_code(_unused const enum dhcpv6_msg orig,
const uint16_t code, const void *status_msg, const int len,
int *ret);
static reply_handler dhcpv6_handle_reconfigure;
static int dhcpv6_commit_advert(void);
-
-
// RFC 3315 - 5.5 Timeout and Delay values
static struct dhcpv6_retx dhcpv6_retx[_DHCPV6_MSG_MAX] = {
[DHCPV6_MSG_UNKNOWN] = {false, 1, 120, 0, "<POLL>",
dhcpv6_handle_reply, NULL},
};
-
// Sockets
static int sock = -1;
static int ifindex = -1;
static int64_t t1 = 0, t2 = 0, t3 = 0;
// IA states
-static int request_prefix = -1;
static enum odhcp6c_ia_mode na_mode = IA_MODE_NONE, pd_mode = IA_MODE_NONE;
static bool accept_reconfig = false;
+// Server unicast address
+static struct in6_addr server_addr = IN6ADDR_ANY_INIT;
// Reconfigure key
static uint8_t reconf_key[16];
// client options
static unsigned int client_options = 0;
-
static uint32_t ntohl_unaligned(const uint8_t *data)
{
uint32_t buf;
return ntohl(buf);
}
+static char *dhcpv6_msg_to_str(enum dhcpv6_msg msg)
+{
+ switch (msg) {
+ case DHCPV6_MSG_SOLICIT:
+ return "SOLICIT";
+
+ case DHCPV6_MSG_ADVERT:
+ return "ADVERTISE";
+
+ case DHCPV6_MSG_REQUEST:
+ return "REQUEST";
+
+ case DHCPV6_MSG_RENEW:
+ return "RENEW";
+
+ case DHCPV6_MSG_REBIND:
+ return "REBIND";
+
+ case DHCPV6_MSG_REPLY:
+ return "REPLY";
+
+ case DHCPV6_MSG_RELEASE:
+ return "RELEASE";
+
+ case DHCPV6_MSG_DECLINE:
+ return "DECLINE";
+
+ case DHCPV6_MSG_RECONF:
+ return "RECONFIGURE";
+
+ case DHCPV6_MSG_INFO_REQ:
+ return "INFORMATION REQUEST";
+
+ default:
+ break;
+ }
+
+ return "UNKNOWN";
+}
+
+static char *dhcpv6_status_code_to_str(uint16_t code)
+{
+ switch (code) {
+ case DHCPV6_Success:
+ return "Success";
+
+ case DHCPV6_UnspecFail:
+ return "Unspecified Failure";
+
+ case DHCPV6_NoAddrsAvail:
+ return "No Address Available";
+
+ case DHCPV6_NoBinding:
+ return "No Binding";
+
+ case DHCPV6_NotOnLink:
+ return "Not On Link";
+
+ case DHCPV6_UseMulticast:
+ return "Use Multicast";
+
+ case DHCPV6_NoPrefixAvail:
+ return "No Prefix Available";
+
+ default:
+ break;
+ }
+
+ return "Unknown";
+}
+
int init_dhcpv6(const char *ifname, unsigned int options, int sol_timeout)
{
client_options = options;
sock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
if (sock < 0)
- return -1;
+ goto failure;
// Detect interface
struct ifreq ifr;
- strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1);
if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0)
- return -1;
+ goto failure;
+
ifindex = ifr.ifr_ifindex;
// Create client DUID
htons(DHCPV6_OPT_NTP_SERVER),
htons(DHCPV6_OPT_AFTR_NAME),
htons(DHCPV6_OPT_PD_EXCLUDE),
- htons(DHCPV6_OPT_SOL_MAX_RT),
- htons(DHCPV6_OPT_INF_MAX_RT),
#ifdef EXT_CER_ID
htons(DHCPV6_OPT_CER_ID),
#endif
};
odhcp6c_add_state(STATE_ORO, oro, sizeof(oro));
}
+ // Required oro
+ uint16_t req_oro[] = {
+ htons(DHCPV6_OPT_INF_MAX_RT),
+ htons(DHCPV6_OPT_SOL_MAX_RT),
+ htons(DHCPV6_OPT_INFO_REFRESH),
+ };
+ odhcp6c_add_state(STATE_ORO, req_oro, sizeof(req_oro));
// Configure IPv6-options
int val = 1;
- setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
- setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, sizeof(val));
- setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname));
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) < 0)
+ goto failure;
+
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0)
+ goto failure;
+
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, sizeof(val)) < 0)
+ goto failure;
+
+ if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)) < 0)
+ goto failure;
struct sockaddr_in6 client_addr = { .sin6_family = AF_INET6,
.sin6_port = htons(DHCPV6_CLIENT_PORT), .sin6_flowinfo = 0 };
+
if (bind(sock, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0)
- return -1;
+ goto failure;
return 0;
+
+failure:
+ if (sock >= 0)
+ close(sock);
+
+ return -1;
}
enum {
IOV_HDR=0,
IOV_ORO,
- IOV_ORO_REFRESH,
IOV_CL_ID,
IOV_SRV_ID,
- IOV_VENDOR_CLASS_HDR,
- IOV_VENDOR_CLASS,
- IOV_USER_CLASS_HDR,
- IOV_USER_CLASS,
+ IOV_OPTS,
IOV_RECONF_ACCEPT,
IOV_FQDN,
IOV_HDR_IA_NA,
fqdn.len = htons(fqdn_len - 4);
fqdn.flags = 0;
-
// Build Client ID
size_t cl_id_len;
void *cl_id = odhcp6c_get_state(STATE_CLIENT_ID, &cl_id_len);
}
}
- if (ia_pd_entries > 0)
- request_prefix = 1;
-
// Build IA_NAs
size_t ia_na_entries, ia_na_len = 0;
void *ia_na = NULL;
uint16_t length;
} reconf_accept = {htons(DHCPV6_OPT_RECONF_ACCEPT), 0};
- // Request Information Refresh
- uint16_t oro_refresh = htons(DHCPV6_OPT_INFO_REFRESH);
+ // Option list
+ size_t opts_len;
+ void *opts = odhcp6c_get_state(STATE_OPTS, &opts_len);
- // Build vendor-class option
- size_t vendor_class_len, user_class_len;
- struct dhcpv6_vendorclass *vendor_class = odhcp6c_get_state(STATE_VENDORCLASS, &vendor_class_len);
- void *user_class = odhcp6c_get_state(STATE_USERCLASS, &user_class_len);
+ // Option Request List
+ size_t oro_entries, oro_len = 0;
+ uint16_t *oro, *s_oro = odhcp6c_get_state(STATE_ORO, &oro_entries);
- struct {
- uint16_t type;
- uint16_t length;
- } vendor_class_hdr = {htons(DHCPV6_OPT_VENDOR_CLASS), htons(vendor_class_len)};
+ oro_entries /= sizeof(*s_oro);
+ oro = alloca(oro_entries * sizeof(*oro));
- struct {
- uint16_t type;
- uint16_t length;
- } user_class_hdr = {htons(DHCPV6_OPT_USER_CLASS), htons(user_class_len)};
+ for (size_t i = 0; i < oro_entries; i++) {
+ struct odhcp6c_opt *opt = odhcp6c_find_opt(htons(s_oro[i]));
+
+ if (opt) {
+ if (!(opt->flags & OPT_ORO))
+ continue;
+
+ if ((opt->flags & OPT_ORO_SOLICIT) && type != DHCPV6_MSG_SOLICIT)
+ continue;
+
+ if ((opt->flags & OPT_ORO_STATELESS) && type != DHCPV6_MSG_INFO_REQ)
+ continue;
+
+ if ((opt->flags & OPT_ORO_STATEFUL) && type == DHCPV6_MSG_INFO_REQ)
+ continue;
+ }
+
+ oro[oro_len++] = s_oro[i];
+ }
+ oro_len *= sizeof(*oro);
// Prepare Header
- size_t oro_len;
- void *oro = odhcp6c_get_state(STATE_ORO, &oro_len);
struct {
uint8_t type;
uint8_t trid[3];
struct iovec iov[IOV_TOTAL] = {
[IOV_HDR] = {&hdr, sizeof(hdr)},
[IOV_ORO] = {oro, oro_len},
- [IOV_ORO_REFRESH] = {&oro_refresh, 0},
[IOV_CL_ID] = {cl_id, cl_id_len},
[IOV_SRV_ID] = {srv_id, srv_id_len},
- [IOV_VENDOR_CLASS_HDR] = {&vendor_class_hdr, vendor_class_len ? sizeof(vendor_class_hdr) : 0},
- [IOV_VENDOR_CLASS] = {vendor_class, vendor_class_len},
- [IOV_USER_CLASS_HDR] = {&user_class_hdr, user_class_len ? sizeof(user_class_hdr) : 0},
- [IOV_USER_CLASS] = {user_class, user_class_len},
+ [IOV_OPTS] = { opts, opts_len },
[IOV_RECONF_ACCEPT] = {&reconf_accept, sizeof(reconf_accept)},
[IOV_FQDN] = {&fqdn, fqdn_len},
[IOV_HDR_IA_NA] = {&hdr_ia_na, sizeof(hdr_ia_na)},
};
size_t cnt = IOV_TOTAL;
- if (type == DHCPV6_MSG_INFO_REQ) {
- cnt = 9;
- iov[IOV_ORO_REFRESH].iov_len = sizeof(oro_refresh);
- hdr.oro_len = htons(oro_len + sizeof(oro_refresh));
- } else if (!request_prefix) {
- cnt = 13;
- }
+ if (type == DHCPV6_MSG_INFO_REQ)
+ cnt = IOV_HDR_IA_NA;
// Disable IAs if not used
if (type != DHCPV6_MSG_SOLICIT && ia_na_len == 0)
struct msghdr msg = {.msg_name = &srv, .msg_namelen = sizeof(srv),
.msg_iov = iov, .msg_iovlen = cnt};
- sendmsg(sock, &msg, 0);
-}
+ switch (type) {
+ case DHCPV6_MSG_REQUEST:
+ case DHCPV6_MSG_RENEW:
+ case DHCPV6_MSG_RELEASE:
+ case DHCPV6_MSG_DECLINE:
+ if (!IN6_IS_ADDR_UNSPECIFIED(&server_addr) &&
+ odhcp6c_addr_in_scope(&server_addr)) {
+ srv.sin6_addr = server_addr;
+ if (!IN6_IS_ADDR_LINKLOCAL(&server_addr))
+ srv.sin6_scope_id = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ if (sendmsg(sock, &msg, 0) < 0) {
+ char in6_str[INET6_ADDRSTRLEN];
+
+ syslog(LOG_ERR, "Failed to send %s message to %s (%s)",
+ dhcpv6_msg_to_str(type),
+ inet_ntop(AF_INET6, (const void *)&srv.sin6_addr,
+ in6_str, sizeof(in6_str)), strerror(errno));
+ }
+}
static int64_t dhcpv6_rand_delay(int64_t time)
{
int random;
odhcp6c_random(&random, sizeof(random));
+
return (time * ((int64_t)random % 1000LL)) / 10000LL;
}
-
int dhcpv6_request(enum dhcpv6_msg type)
{
uint8_t rc = 0;
if (retx->delay) {
struct timespec ts = {0, 0};
ts.tv_nsec = (dhcpv6_rand_delay((10000 * DHCPV6_REQ_DELAY) / 2) + (1000 * DHCPV6_REQ_DELAY) / 2) * 1000000;
+
while (nanosleep(&ts, &ts) < 0 && errno == EINTR);
}
if (timeout == 0)
return -1;
- syslog(LOG_NOTICE, "Starting %s transaction (timeout %llus, max rc %d)",
- retx->name, (unsigned long long)timeout, retx->max_rc);
+ syslog(LOG_NOTICE, "Starting %s transaction (timeout %"PRIu64"s, max rc %d)",
+ retx->name, timeout, retx->max_rc);
uint64_t start = odhcp6c_get_milli_time(), round_start = start, elapsed;
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;
delay = dhcpv6_rand_delay(retx->init_timeo * 1000);
rto = (retx->init_timeo * 1000 + delay);
- }
- else
+ } else
rto = (2 * rto + dhcpv6_rand_delay(rto));
if (retx->max_timeo && (rto >= retx->max_timeo * 1000))
case DHCPV6_MSG_UNKNOWN:
break;
default:
- syslog(LOG_NOTICE, "Send %s message (elapsed %llums, rc %d)",
- retx->name, (unsigned long long)elapsed, rc);
+ syslog(LOG_NOTICE, "Send %s message (elapsed %"PRIu64"ms, rc %d)",
+ retx->name, elapsed, rc);
// Fall through
case DHCPV6_MSG_SOLICIT:
case DHCPV6_MSG_INFO_REQ:
.msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsg_buf.buf,
.msg_controllen = sizeof(cmsg_buf)};
struct in6_pktinfo *pktinfo = NULL;
-
+ const struct dhcpv6_header *hdr = (const struct dhcpv6_header *)buf;
// Check for pending signal
if (odhcp6c_signal_process())
// Set timeout for receiving
uint64_t t = round_end - round_start;
struct timeval tv = {t / 1000, (t % 1000) * 1000};
- setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
- &tv, sizeof(tv));
+ if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
+ &tv, sizeof(tv)) < 0)
+ syslog(LOG_ERR, "setsockopt SO_RCVTIMEO failed (%s)",
+ strerror(errno));
// Receive cycle
len = recvmsg(sock, &msg, 0);
round_start = odhcp6c_get_milli_time();
elapsed = round_start - start;
- syslog(LOG_NOTICE, "Got a valid reply after "
- "%llums", (unsigned long long)elapsed);
+ syslog(LOG_NOTICE, "Got a valid %s after %"PRIu64"ms",
+ dhcpv6_msg_to_str(hdr->msg_type), elapsed);
if (retx->handler_reply)
len = retx->handler_reply(type, rc, opt, opt_end, &addr);
// Allow
if (retx->handler_finish)
len = retx->handler_finish();
- } while (len < 0 && ((timeout == UINT32_MAX) || (elapsed / 1000 < timeout)) &&
+ } while (len < 0 && ((timeout == UINT32_MAX) || (elapsed / 1000 < timeout)) &&
(!retx->max_rc || rc < retx->max_rc));
return len;
}
if (rep->msg_type != DHCPV6_MSG_ADVERT &&
rep->msg_type != DHCPV6_MSG_REPLY)
return false;
+
} else if (type == DHCPV6_MSG_UNKNOWN) {
if (!accept_reconfig || rep->msg_type != DHCPV6_MSG_RECONF)
return false;
- } else if (rep->msg_type != DHCPV6_MSG_REPLY) {
+
+ } else if (rep->msg_type != DHCPV6_MSG_REPLY)
return false;
- }
uint8_t *end = ((uint8_t*)buf) + len, *odata = NULL,
rcmsg = DHCPV6_MSG_UNKNOWN;
ia_present = true;
if (olen < -4 + sizeof(struct dhcpv6_ia_hdr))
options_valid = false;
- }
- else if ((otype == DHCPV6_OPT_IA_ADDR) || (otype == DHCPV6_OPT_IA_PREFIX) ||
- (otype == DHCPV6_OPT_PD_EXCLUDE)) {
+ } else if ((otype == DHCPV6_OPT_IA_ADDR) || (otype == DHCPV6_OPT_IA_PREFIX) ||
+ (otype == DHCPV6_OPT_PD_EXCLUDE))
// Options are not allowed on global level
options_valid = false;
- }
}
if (!options_valid || ((odata + olen) > end))
return false;
if (rep->msg_type == DHCPV6_MSG_RECONF) {
- if ((rcmsg != DHCPV6_MSG_RENEW && rcmsg != DHCPV6_MSG_INFO_REQ) ||
+ if ((rcmsg != DHCPV6_MSG_RENEW && rcmsg != DHCPV6_MSG_REBIND && rcmsg != DHCPV6_MSG_INFO_REQ) ||
(rcmsg == DHCPV6_MSG_INFO_REQ && ia_present) ||
!rcauth_ok || IN6_IS_ADDR_MULTICAST(daddr))
return false;
return clientid_ok && serverid_ok;
}
-
int dhcpv6_poll_reconfigure(void)
{
int ret = dhcpv6_request(DHCPV6_MSG_UNKNOWN);
- if (ret != -1)
+
+ switch (ret) {
+ /*
+ * Only RENEW/REBIND/INFORMATION REQUEST
+ * message transmission can be requested
+ * by a RECONFIGURE
+ */
+ case DHCPV6_MSG_RENEW:
+ case DHCPV6_MSG_REBIND:
+ case DHCPV6_MSG_INFO_REQ:
ret = dhcpv6_request(ret);
+ break;
+
+ default:
+ break;
+ }
return ret;
}
-
-static int dhcpv6_handle_reconfigure(_unused enum dhcpv6_msg orig, const int rc,
+static int dhcpv6_handle_reconfigure(enum dhcpv6_msg orig, const int rc,
const void *opt, const void *end, _unused const struct sockaddr_in6 *from)
{
uint16_t otype, olen;
- uint8_t *odata, msg = DHCPV6_MSG_RENEW;
- dhcpv6_for_each_option(opt, end, otype, olen, odata)
- if (otype == DHCPV6_OPT_RECONF_MESSAGE && olen == 1 && (
- odata[0] == DHCPV6_MSG_RENEW ||
- odata[0] == DHCPV6_MSG_INFO_REQ))
- msg = odata[0];
-
- dhcpv6_handle_reply(DHCPV6_MSG_UNKNOWN, rc, NULL, NULL, NULL);
- return msg;
-}
+ uint8_t *odata;
+ enum dhcpv6_msg msg = DHCPV6_MSG_UNKNOWN;
+ dhcpv6_for_each_option(opt, end, otype, olen, odata) {
+ if (otype == DHCPV6_OPT_RECONF_MESSAGE && olen == 1) {
+ switch (odata[0]) {
+ case DHCPV6_MSG_REBIND:
+ if (t2 != UINT32_MAX)
+ t2 = 0;
+ // Fall through
+ case DHCPV6_MSG_RENEW:
+ if (t1 != UINT32_MAX)
+ t1 = 0;
+ // Fall through
+ case DHCPV6_MSG_INFO_REQ:
+ msg = odata[0];
+ syslog(LOG_NOTICE, "Need to respond with %s in reply to %s",
+ dhcpv6_msg_to_str(msg), dhcpv6_msg_to_str(DHCPV6_MSG_RECONF));
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ if (msg != DHCPV6_MSG_UNKNOWN)
+ dhcpv6_handle_reply(orig, rc, NULL, NULL, NULL);
+
+ return (msg == DHCPV6_MSG_UNKNOWN? -1: (int)msg);
+}
// Collect all advertised servers
static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
uint16_t olen, otype;
uint8_t *odata, pref = 0;
struct dhcpv6_server_cand cand = {false, false, 0, 0, {0},
- DHCPV6_SOL_MAX_RT,
+ IN6ADDR_ANY_INIT, DHCPV6_SOL_MAX_RT,
DHCPV6_INF_MAX_RT, NULL, NULL, 0, 0};
bool have_na = false;
int have_pd = 0;
dhcpv6_for_each_option(opt, end, otype, olen, odata) {
if (orig == DHCPV6_MSG_SOLICIT &&
- (otype == DHCPV6_OPT_IA_PD || otype == DHCPV6_OPT_IA_NA) &&
+ ((otype == DHCPV6_OPT_IA_PD && pd_mode != IA_MODE_NONE) ||
+ (otype == DHCPV6_OPT_IA_NA && na_mode != IA_MODE_NONE)) &&
olen > -4 + sizeof(struct dhcpv6_ia_hdr)) {
struct dhcpv6_ia_hdr *ia_hdr = (void*)(&odata[-4]);
dhcpv6_parse_ia(ia_hdr, odata + olen + sizeof(*ia_hdr));
} else if (otype == DHCPV6_OPT_PREF && olen >= 1 &&
cand.preference >= 0) {
cand.preference = pref = odata[0];
+ } else if (otype == DHCPV6_OPT_UNICAST && olen == sizeof(cand.server_addr)) {
+ if (!(client_options & DHCPV6_IGNORE_OPT_UNICAST))
+ cand.server_addr = *(struct in6_addr *)odata;
+
} else if (otype == DHCPV6_OPT_RECONF_ACCEPT) {
cand.wants_reconfigure = true;
} else if (otype == DHCPV6_OPT_SOL_MAX_RT && olen == 4) {
if (sol_max_rt >= DHCPV6_SOL_MAX_RT_MIN &&
sol_max_rt <= DHCPV6_SOL_MAX_RT_MAX)
cand.sol_max_rt = sol_max_rt;
+
} else if (otype == DHCPV6_OPT_INF_MAX_RT && olen == 4) {
uint32_t inf_max_rt = ntohl_unaligned(odata);
if (inf_max_rt >= DHCPV6_INF_MAX_RT_MIN &&
inf_max_rt <= DHCPV6_INF_MAX_RT_MAX)
cand.inf_max_rt = inf_max_rt;
- } else if (otype == DHCPV6_OPT_IA_PD && request_prefix &&
+
+ } else if (otype == DHCPV6_OPT_IA_PD &&
olen >= -4 + sizeof(struct dhcpv6_ia_hdr)) {
struct dhcpv6_ia_hdr *h = (struct dhcpv6_ia_hdr*)&odata[-4];
uint8_t *oend = odata + olen, *d;
olen >= -4 + sizeof(struct dhcpv6_ia_hdr)) {
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)
+
+ dhcpv6_for_each_option(&h[1], oend, otype, olen, d) {
if (otype == DHCPV6_OPT_IA_ADDR &&
olen >= -4 + sizeof(struct dhcpv6_ia_addr))
have_na = true;
+ }
}
}
return (rc > 1 || (pref == 255 && cand.preference > 0)) ? 1 : -1;
}
-
static int dhcpv6_commit_advert(void)
{
return dhcpv6_promote_server_cand();
}
-
static int dhcpv6_handle_rebind_reply(enum dhcpv6_msg orig, const int rc,
const void *opt, const void *end, const struct sockaddr_in6 *from)
{
return dhcpv6_handle_reply(orig, rc, opt, end, from);
}
-
static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
const void *opt, const void *end, const struct sockaddr_in6 *from)
{
uint16_t otype, olen;
uint32_t refresh = 86400;
int ret = 1;
+ unsigned int state_IAs;
+ unsigned int updated_IAs = 0;
bool handled_status_codes[_DHCPV6_Status_Max] = { false, };
odhcp6c_expire();
// Parse and find all matching IAs
dhcpv6_for_each_option(opt, end, otype, olen, odata) {
- bool passthru = true;
+ struct odhcp6c_opt *dopt = odhcp6c_find_opt(otype);
if ((otype == DHCPV6_OPT_IA_PD || otype == DHCPV6_OPT_IA_NA)
&& olen > -4 + sizeof(struct dhcpv6_ia_hdr)) {
dhcpv6_handle_ia_status_code(orig, ia_hdr,
code, mdata, mlen, handled_status_codes, &ret);
-
if (ret > 0)
return ret;
+
break;
}
}
if (code != DHCPV6_Success)
continue;
- dhcpv6_parse_ia(ia_hdr, odata + olen);
- passthru = false;
- } else if (otype == DHCPV6_OPT_STATUS && olen >= 2) {
+ updated_IAs += dhcpv6_parse_ia(ia_hdr, odata + olen);
+ } else if (otype == DHCPV6_OPT_UNICAST && olen == sizeof(server_addr)) {
+ if (!(client_options & DHCPV6_IGNORE_OPT_UNICAST))
+ server_addr = *(struct in6_addr *)odata;
+
+ }
+ else if (otype == DHCPV6_OPT_STATUS && olen >= 2) {
uint8_t *mdata = (olen > 2) ? &odata[2] : NULL;
uint16_t mlen = (olen > 2) ? olen - 2 : 0;
uint16_t code = ((int)odata[0]) << 8 | ((int)odata[1]);
dhcpv6_handle_status_code(orig, code, mdata, mlen, &ret);
- passthru = false;
- }
- else if (otype == DHCPV6_OPT_DNS_SERVERS) {
+ } else if (otype == DHCPV6_OPT_DNS_SERVERS) {
if (olen % 16 == 0)
odhcp6c_add_state(STATE_DNS, odata, olen);
- } else if (otype == DHCPV6_OPT_DNS_DOMAIN) {
+ } else if (otype == DHCPV6_OPT_DNS_DOMAIN)
odhcp6c_add_state(STATE_SEARCH, odata, olen);
- } else if (otype == DHCPV6_OPT_SNTP_SERVERS) {
+ else if (otype == DHCPV6_OPT_SNTP_SERVERS) {
if (olen % 16 == 0)
odhcp6c_add_state(STATE_SNTP_IP, odata, olen);
} else if (otype == DHCPV6_OPT_NTP_SERVER) {
} else if (otype == DHCPV6_OPT_SIP_SERVER_A) {
if (olen == 16)
odhcp6c_add_state(STATE_SIP_IP, odata, olen);
- } else if (otype == DHCPV6_OPT_SIP_SERVER_D) {
+ } else if (otype == DHCPV6_OPT_SIP_SERVER_D)
odhcp6c_add_state(STATE_SIP_FQDN, odata, olen);
- } else if (otype == DHCPV6_OPT_INFO_REFRESH && olen >= 4) {
+ else if (otype == DHCPV6_OPT_INFO_REFRESH && olen >= 4) {
refresh = ntohl_unaligned(odata);
- passthru = false;
} else if (otype == DHCPV6_OPT_AUTH) {
if (olen == -4 + sizeof(struct dhcpv6_auth_reconfigure)) {
struct dhcpv6_auth_reconfigure *r = (void*)&odata[-4];
r->reconf_type == 1)
memcpy(reconf_key, r->key, sizeof(r->key));
}
- passthru = false;
} 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);
- passthru = false;
} else if (otype == DHCPV6_OPT_SOL_MAX_RT && olen == 4) {
uint32_t sol_max_rt = ntohl_unaligned(odata);
if (sol_max_rt >= DHCPV6_SOL_MAX_RT_MIN &&
sol_max_rt <= DHCPV6_SOL_MAX_RT_MAX)
dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_timeo = sol_max_rt;
- passthru = false;
} else if (otype == DHCPV6_OPT_INF_MAX_RT && olen == 4) {
uint32_t inf_max_rt = ntohl_unaligned(odata);
if (inf_max_rt >= DHCPV6_INF_MAX_RT_MIN &&
inf_max_rt <= DHCPV6_INF_MAX_RT_MAX)
dhcpv6_retx[DHCPV6_MSG_INFO_REQ].max_timeo = inf_max_rt;
- passthru = false;
#ifdef EXT_CER_ID
} else if (otype == DHCPV6_OPT_CER_ID && olen == -4 +
sizeof(struct dhcpv6_cer_id)) {
struct in6_addr any = IN6ADDR_ANY_INIT;
if (memcmp(&cer_id->addr, &any, sizeof(any)))
odhcp6c_add_state(STATE_CER, &cer_id->addr, sizeof(any));
- passthru = false;
#endif
} else if (otype == DHCPV6_OPT_S46_CONT_MAPT) {
odhcp6c_add_state(STATE_S46_MAPT, odata, olen);
- passthru = false;
} else if (otype == DHCPV6_OPT_S46_CONT_MAPE) {
size_t mape_len;
odhcp6c_get_state(STATE_S46_MAPE, &mape_len);
if (mape_len == 0)
odhcp6c_add_state(STATE_S46_MAPE, odata, olen);
- passthru = false;
} else if (otype == DHCPV6_OPT_S46_CONT_LW) {
odhcp6c_add_state(STATE_S46_LW, odata, olen);
- passthru = false;
- } else if (otype == DHCPV6_OPT_CLIENTID ||
- otype == DHCPV6_OPT_SERVERID ||
- otype == DHCPV6_OPT_IA_TA ||
- otype == DHCPV6_OPT_PREF ||
- otype == DHCPV6_OPT_UNICAST ||
- otype == DHCPV6_OPT_FQDN ||
- otype == DHCPV6_OPT_RECONF_ACCEPT) {
- passthru = false;
- } else {
+ } else
odhcp6c_add_state(STATE_CUSTOM_OPTS, &odata[-4], olen + 4);
- }
- if (passthru)
+ if (!dopt || !(dopt->flags & OPT_NO_PASSTHRU))
odhcp6c_add_state(STATE_PASSTHRU, &odata[-4], olen + 4);
}
}
- if (orig != DHCPV6_MSG_INFO_REQ) {
- // Update refresh timers if no fatal status code was received
- if ((ret > 0) && dhcpv6_calc_refresh_timers()) {
- switch (orig) {
- case DHCPV6_MSG_RENEW:
- // Send further renews if T1 is not set
- if (!t1)
- ret = -1;
- break;
- case DHCPV6_MSG_REBIND:
- // Send further rebinds if T1 and T2 is not set
- if (!t1 && !t2)
- ret = -1;
- break;
-
- case DHCPV6_MSG_REQUEST:
- // All server candidates can be cleared if not yet bound
- if (!odhcp6c_is_bound())
- dhcpv6_clear_all_server_cand();
+ // Bail out if fatal status code was received
+ if (ret <= 0)
+ return ret;
+
+ switch (orig) {
+ case DHCPV6_MSG_REQUEST:
+ case DHCPV6_MSG_REBIND:
+ case DHCPV6_MSG_RENEW:
+ state_IAs = dhcpv6_calc_refresh_timers();
+ // In case there're no state IA entries
+ // keep sending request/renew/rebind messages
+ if (state_IAs == 0) {
+ ret = 0;
+ break;
+ }
- default :
- break;
+ if (orig == DHCPV6_MSG_REQUEST) {
+ // All server candidates can be cleared if not yet bound
+ if (!odhcp6c_is_bound())
+ dhcpv6_clear_all_server_cand();
+
+ odhcp6c_clear_state(STATE_SERVER_ADDR);
+ odhcp6c_add_state(STATE_SERVER_ADDR, &from->sin6_addr, 16);
+ } else if (orig == DHCPV6_MSG_RENEW) {
+ // Send further renews if T1 is not set and if
+ // there're IAs which were not in the Reply message
+ if (!t1 && state_IAs != updated_IAs) {
+ if (updated_IAs)
+ // Publish updates
+ script_call("updated", 0, false);
+
+ /*
+ * RFC8415 states following in §18.2.10.1 :
+ * Sends a Renew/Rebind if any of the IAs are not in the Reply
+ * message, but as this likely indicates that the server that
+ * responded does not support that IA type, sending immediately is
+ * unlikely to produce a different result. Therefore, the client
+ * MUST rate-limit its transmissions (see Section 14.1) and MAY just
+ * wait for the normal retransmission time (as if the Reply message
+ * had not been received). The client continues to use other
+ * bindings for which the server did return information
+ */
+ ret = -1;
}
-
- if (orig == DHCPV6_MSG_REBIND || orig == DHCPV6_MSG_REQUEST) {
- odhcp6c_clear_state(STATE_SERVER_ADDR);
- odhcp6c_add_state(STATE_SERVER_ADDR, &from->sin6_addr, 16);
+ } else if (orig == DHCPV6_MSG_REBIND) {
+ odhcp6c_clear_state(STATE_SERVER_ADDR);
+ odhcp6c_add_state(STATE_SERVER_ADDR, &from->sin6_addr, 16);
+
+ // Send further rebinds if T1 and T2 is not set and if
+ // there're IAs which were not in the Reply message
+ if (!t1 && !t2 && state_IAs != updated_IAs) {
+ if (updated_IAs)
+ // Publish updates
+ script_call("updated", 0, false);
+
+ /*
+ * RFC8415 states following in §18.2.10.1 :
+ * Sends a Renew/Rebind if any of the IAs are not in the Reply
+ * message, but as this likely indicates that the server that
+ * responded does not support that IA type, sending immediately is
+ * unlikely to produce a different result. Therefore, the client
+ * MUST rate-limit its transmissions (see Section 14.1) and MAY just
+ * wait for the normal retransmission time (as if the Reply message
+ * had not been received). The client continues to use other
+ * bindings for which the server did return information
+ */
+ ret = -1;
}
}
- }
- else if (ret > 0) {
+ break;
+
+ case DHCPV6_MSG_INFO_REQ:
// All server candidates can be cleared if not yet bound
if (!odhcp6c_is_bound())
dhcpv6_clear_all_server_cand();
t1 = refresh;
+ break;
+
+ default:
+ break;
}
return ret;
}
-
-static int dhcpv6_parse_ia(void *opt, void *end)
+static unsigned int dhcpv6_parse_ia(void *opt, void *end)
{
struct dhcpv6_ia_hdr *ia_hdr = (struct dhcpv6_ia_hdr *)opt;
- int parsed_ia = 0;
+ unsigned int updated_IAs = 0;
uint32_t t1, t2;
uint16_t otype, olen;
uint8_t *odata;
+ char buf[INET6_ADDRSTRLEN];
t1 = ntohl(ia_hdr->t1);
t2 = ntohl(ia_hdr->t2);
if (t1 > t2)
return 0;
+ syslog(LOG_INFO, "%s %04x T1 %d T2 %d", ntohs(ia_hdr->type) == DHCPV6_OPT_IA_PD ? "IA_PD" : "IA_NA", ntohl(ia_hdr->iaid), t1, t2);
+
// Update address IA
dhcpv6_for_each_option(&ia_hdr[1], end, otype, olen, odata) {
- struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0, 0,
- IN6ADDR_ANY_INIT, 0, 0, 0, 0, 0};
+ struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0,
+ IN6ADDR_ANY_INIT, 0, 0, 0, 0, 0, 0};
entry.iaid = ia_hdr->iaid;
continue;
}
-
uint8_t bytes = ((elen - entry.length - 1) / 8) + 1;
if (slen <= bytes) {
ok = false;
}
if (ok) {
- odhcp6c_update_entry(STATE_IA_PD, &entry, 0, false);
- parsed_ia++;
+ if (odhcp6c_update_entry(STATE_IA_PD, &entry, 0, 0))
+ updated_IAs++;
+
+ syslog(LOG_INFO, "%s/%d preferred %d valid %d",
+ inet_ntop(AF_INET6, &entry.target, buf, sizeof(buf)),
+ entry.length, entry.preferred , entry.valid);
}
entry.priority = 0;
entry.length = 128;
entry.target = addr->addr;
- odhcp6c_update_entry(STATE_IA_NA, &entry, 0, false);
- parsed_ia++;
+ if (odhcp6c_update_entry(STATE_IA_NA, &entry, 0, 0))
+ updated_IAs++;
+
+ syslog(LOG_INFO, "%s preferred %d valid %d",
+ inet_ntop(AF_INET6, &entry.target, buf, sizeof(buf)),
+ entry.preferred , entry.valid);
}
}
- return parsed_ia;
-}
+ return updated_IAs;
+}
-static int dhcpv6_calc_refresh_timers(void)
+static unsigned int dhcpv6_calc_refresh_timers(void)
{
struct odhcp6c_entry *e;
size_t ia_na_entries, ia_pd_entries, i;
e = odhcp6c_get_state(STATE_IA_NA, &ia_na_entries);
ia_na_entries /= sizeof(*e);
+
for (i = 0; i < ia_na_entries; i++) {
if (e[i].t1 < l_t1)
l_t1 = e[i].t1;
e = odhcp6c_get_state(STATE_IA_PD, &ia_pd_entries);
ia_pd_entries /= sizeof(*e);
+
for (i = 0; i < ia_pd_entries; i++) {
if (e[i].t1 < l_t1)
l_t1 = e[i].t1;
t1 = l_t1;
t2 = l_t2;
t3 = l_t3;
- } else {
- t1 = 600;
+
+ syslog(LOG_INFO, "T1 %"PRId64"s, T2 %"PRId64"s, T3 %"PRId64"s", t1, t2, t3);
}
- return (int)(ia_pd_entries + ia_na_entries);
+ return (unsigned int)(ia_pd_entries + ia_na_entries);
}
-
static void dhcpv6_log_status_code(const uint16_t code, const char *scope,
const void *status_msg, int len)
{
}
*dst++ = ')';
}
+
*dst = 0;
- syslog(LOG_WARNING, "Server returned %s status %i %s",
- scope, code, buf);
+ syslog(LOG_WARNING, "Server returned %s status '%s %s'",
+ scope, dhcpv6_status_code_to_str(code), buf);
}
-
static void dhcpv6_handle_status_code(const enum dhcpv6_msg orig,
const uint16_t code, const void *status_msg, const int len,
int *ret)
break;
case DHCPV6_UseMulticast:
- // TODO handle multicast status code
+ switch(orig) {
+ case DHCPV6_MSG_REQUEST:
+ case DHCPV6_MSG_RENEW:
+ case DHCPV6_MSG_RELEASE:
+ case DHCPV6_MSG_DECLINE:
+ // Message needs to be retransmitted according to RFC3315 chapter 18.1.8
+ server_addr = in6addr_any;
+ *ret = 0;
+ break;
+ default:
+ break;
+ }
break;
case DHCPV6_NoAddrsAvail:
}
}
-
static void dhcpv6_handle_ia_status_code(const enum dhcpv6_msg orig,
const struct dhcpv6_ia_hdr *ia_hdr, const uint16_t code,
const void *status_msg, const int len,
odhcp6c_add_state(STATE_SERVER_ID, hdr, sizeof(hdr));
odhcp6c_add_state(STATE_SERVER_ID, cand->duid, cand->duid_len);
accept_reconfig = cand->wants_reconfigure;
+
if (cand->ia_na_len) {
odhcp6c_add_state(STATE_IA_NA, cand->ia_na, cand->ia_na_len);
free(cand->ia_na);
if (na_mode != IA_MODE_NONE)
ret = DHCPV6_STATEFUL;
}
+
if (cand->ia_pd_len) {
odhcp6c_add_state(STATE_IA_PD, cand->ia_pd, cand->ia_pd_len);
free(cand->ia_pd);
- if (request_prefix)
+ if (pd_mode != IA_MODE_NONE)
ret = DHCPV6_STATEFUL;
}