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);
// IA states
static enum odhcp6c_ia_mode na_mode = IA_MODE_NONE, pd_mode = IA_MODE_NONE;
+static bool stateful_only_mode = false;
static bool accept_reconfig = false;
// Server unicast address
static struct in6_addr server_addr = IN6ADDR_ANY_INIT;
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;
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
IOV_TOTAL
};
-int dhcpv6_set_ia_mode(enum odhcp6c_ia_mode na, enum odhcp6c_ia_mode pd)
+int dhcpv6_set_ia_mode(enum odhcp6c_ia_mode na, enum odhcp6c_ia_mode pd, bool stateful_only)
{
int mode = DHCPV6_UNKNOWN;
na_mode = na;
pd_mode = pd;
+ stateful_only_mode = stateful_only;
if (na_mode == IA_MODE_NONE && pd_mode == IA_MODE_NONE)
mode = DHCPV6_STATELESS;
if (sendmsg(sock, &msg, 0) < 0) {
char in6_str[INET6_ADDRSTRLEN];
- syslog(LOG_ERR, "Failed to send DHCPV6 message to %s (%s)",
+ 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));
}
.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())
round_start = odhcp6c_get_milli_time();
elapsed = round_start - start;
- syslog(LOG_NOTICE, "Got a valid reply after %"PRIu64"ms",
- 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);
{
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;
}
{
uint16_t otype, olen;
uint8_t *odata;
- int msg = -1;
+ enum dhcpv6_msg msg = DHCPV6_MSG_UNKNOWN;
dhcpv6_for_each_option(opt, end, otype, olen, odata) {
if (otype == DHCPV6_OPT_RECONF_MESSAGE && olen == 1) {
// 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:
}
}
- dhcpv6_handle_reply(orig, rc, NULL, NULL, NULL);
+ if (msg != DHCPV6_MSG_UNKNOWN)
+ dhcpv6_handle_reply(orig, rc, NULL, NULL, NULL);
- return msg;
+ return (msg == DHCPV6_MSG_UNKNOWN? -1: (int)msg);
}
// Collect all advertised servers
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));
}
}
- if ((!have_na && na_mode == IA_MODE_FORCE) ||
+ if ((stateful_only_mode && !have_na && !have_pd) ||
+ (!have_na && na_mode == IA_MODE_FORCE) ||
(!have_pd && pd_mode == IA_MODE_FORCE)) {
/*
* RFC7083 states to process the SOL_MAX_RT and
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();
+ odhcp6c_expire(true);
if (orig == DHCPV6_MSG_UNKNOWN) {
static time_t last_update = 0;
}
}
+ // 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:
- // Update refresh timers if no fatal status code was received
- if ((ret > 0) && (ret = dhcpv6_calc_refresh_timers())) {
- 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
- // no updated IAs
- if (!t1) {
- if (!updated_IAs)
- ret = -1;
- else if ((t2 - t1) > 1)
- // Grace period of 1 second
- t1 = 1;
- }
+ 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;
+ }
- } else if (orig == DHCPV6_MSG_REBIND) {
- // Send further rebinds if T1 and T2 is not set and
- // no updated IAs
- if (!t1 && !t2) {
- if (!updated_IAs)
- ret = -1;
- else if ((t3 - t2) > 1)
- // Grace period of 1 second
- t2 = 1;
- }
+ 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);
+ 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;
+ }
+ } 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;
}
}
break;
case DHCPV6_MSG_INFO_REQ:
- if (ret > 0) {
- // All server candidates can be cleared if not yet bound
- if (!odhcp6c_is_bound())
- dhcpv6_clear_all_server_cand();
+ // All server candidates can be cleared if not yet bound
+ if (!odhcp6c_is_bound())
+ dhcpv6_clear_all_server_cand();
- t1 = refresh;
- }
+ t1 = refresh;
break;
default:
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)
+ if (t1 > t2 && t1 > 0 && t2 > 0)
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;
if (ok) {
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;
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 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;
+ size_t invalid_entries = 0;
int64_t l_t1 = UINT32_MAX, l_t2 = UINT32_MAX, l_t3 = 0;
e = odhcp6c_get_state(STATE_IA_NA, &ia_na_entries);
ia_na_entries /= sizeof(*e);
for (i = 0; i < ia_na_entries; i++) {
+ /* Exclude invalid IA_NA entries */
+ if (!e[i].valid) {
+ invalid_entries++;
+ continue;
+ }
+
if (e[i].t1 < l_t1)
l_t1 = e[i].t1;
ia_pd_entries /= sizeof(*e);
for (i = 0; i < ia_pd_entries; i++) {
+ /* Exclude invalid IA_PD entries */
+ if (!e[i].valid) {
+ invalid_entries++;
+ continue;
+ }
+
if (e[i].t1 < l_t1)
l_t1 = e[i].t1;
l_t3 = e[i].valid;
}
- if (ia_pd_entries || ia_na_entries) {
+ if (ia_pd_entries + ia_na_entries - invalid_entries) {
t1 = l_t1;
t2 = l_t2;
t3 = l_t3;
+
+ 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,
*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,