summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans Dedecker2013-10-23 12:04:38 +0000
committerSteven Barth2013-10-23 17:22:01 +0000
commitd77cbea57d1b7dd9c25421bb13c90e65bb54a6bc (patch)
tree2febdfc6997d785c8b233433f7d20d298f459f9a
parent0c764d13ba834d6be53c4312007584613dec3a1e (diff)
downloadodhcp6c-d77cbea57d1b7dd9c25421bb13c90e65bb54a6bc.tar.gz
odhpc6c: status code support in reply
The patch implements support for status code handling in reply messages as described in RFC3313 paragraph 18.1.8. The client will *log the status codes returned by the client *send a request if no binding status code is returned for a given IA *send further renew/rebind if the IA was not present in the reply *terminate message exchange when no prefix/no address status code is returned in reponse to a request *terminate message exchange when unspec fail status code is returned *calculate t1/t2/t3 when all IA's have been processed and based on recorded t1/t2/valid timer values per IA Without this patch I have seen issues with request messages send without any IA_PD/IA_NA and t1/t2/t3 timer values which were not correct. These changes have been tested intensive against an ISC DHCPv6 server Signed-off-by: Hans Dedecker <hans.dedecker@gmail.com>
-rw-r--r--src/dhcpv6.c276
-rw-r--r--src/odhcp6c.c34
-rw-r--r--src/odhcp6c.h9
-rw-r--r--src/ra.c2
4 files changed, 258 insertions, 63 deletions
diff --git a/src/dhcpv6.c b/src/dhcpv6.c
index d212dec..a609d4d 100644
--- a/src/dhcpv6.c
+++ b/src/dhcpv6.c
@@ -46,7 +46,17 @@
static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
const uint8_t transaction[3], enum dhcpv6_msg type);
-static uint32_t dhcpv6_parse_ia(void *opt, void *end);
+static int dhcpv6_parse_ia(void *opt, void *end);
+
+static 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 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,
+ bool handled_status_codes[_DHCPV6_Status_Max],
+ int *ret);
static reply_handler dhcpv6_handle_reply;
static reply_handler dhcpv6_handle_advert;
@@ -588,7 +598,7 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
(otype == DHCPV6_OPT_IA_PD || otype == DHCPV6_OPT_IA_NA) &&
olen > sizeof(struct dhcpv6_ia_hdr)) {
struct dhcpv6_ia_hdr *ia_hdr = (void*)(&odata[-4]);
- dhcpv6_parse_ia(&ia_hdr[1], odata + olen);
+ dhcpv6_parse_ia(ia_hdr, odata + olen + sizeof(*ia_hdr));
}
if (otype == DHCPV6_OPT_SERVERID && olen <= 130) {
@@ -735,6 +745,9 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
{
uint8_t *odata;
uint16_t otype, olen;
+ uint32_t refresh = UINT32_MAX;
+ int ret = 1;
+ bool handled_status_codes[_DHCPV6_Status_Max] = { false, };
odhcp6c_expire();
@@ -757,11 +770,9 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
if (t3 < 0)
t3 = 0;
- } else {
- t1 = t2 = t3 = UINT32_MAX;
}
- if (orig == DHCPV6_MSG_REQUEST) {
+ if (orig == DHCPV6_MSG_REQUEST && !odhcp6c_is_bound()) {
// Delete NA and PD we have in the state from the Advert
odhcp6c_clear_state(STATE_IA_NA);
odhcp6c_clear_state(STATE_IA_PD);
@@ -782,48 +793,48 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
if ((otype == DHCPV6_OPT_IA_PD || otype == DHCPV6_OPT_IA_NA)
&& olen > sizeof(struct dhcpv6_ia_hdr)) {
struct dhcpv6_ia_hdr *ia_hdr = (void*)(&odata[-4]);
- uint32_t l_t1 = ntohl(ia_hdr->t1);
- uint32_t l_t2 = ntohl(ia_hdr->t2);
- // Test ID and T1-T2 validity
- if (ia_hdr->iaid != 1 || l_t2 < l_t1)
+ // Test ID
+ if (ia_hdr->iaid != 1)
continue;
- int error = 0;
+ uint16_t code = DHCPV6_Success;
uint16_t stype, slen;
uint8_t *sdata;
- // Test status and bail if error
+ // Get and handle status code
dhcpv6_for_each_option(&ia_hdr[1], odata + olen,
- stype, slen, sdata)
- if (stype == DHCPV6_OPT_STATUS && slen >= 2)
- error = ((int)sdata[0]) << 8 | ((int)sdata[1]);
+ stype, slen, sdata) {
+ if (stype == DHCPV6_OPT_STATUS && slen >= 2) {
+ uint8_t *mdata = (slen > 2) ? &sdata[2] : NULL;
+ uint16_t mlen = (slen > 2) ? slen - 2 : 0;
- if (error) {
- syslog(LOG_WARNING, "Server returned IAID status %i!", error);
- if (error != 2)
- raise(SIGUSR2);
- break;
- }
+ code = ((int)sdata[0]) << 8 | ((int)sdata[1]);
- uint32_t n = dhcpv6_parse_ia(&ia_hdr[1], odata + olen);
+ if (code == DHCPV6_Success)
+ continue;
- if (!l_t1)
- l_t1 = 300;
+ dhcpv6_handle_ia_status_code(orig, ia_hdr,
+ code, mdata, mlen, handled_status_codes, &ret);
- if (!l_t2)
- l_t2 = 600;
- if (n < t3)
- t3 = n;
+ if (ret > 0)
+ return ret;
+ break;
+ }
+ }
- // Update times
- if (l_t1 > 0 && t1 > l_t1)
- t1 = l_t1;
+ if (code != DHCPV6_Success)
+ continue;
- if (l_t2 > 0 && t2 > l_t2)
- t2 = l_t2;
+ dhcpv6_parse_ia(ia_hdr, odata + olen + sizeof(*ia_hdr));
+ } 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]);
- } else if (otype == DHCPV6_OPT_DNS_SERVERS) {
+ dhcpv6_handle_status_code(orig, code, mdata, mlen, &ret);
+ }
+ 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) {
@@ -848,9 +859,7 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
} 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) {
- uint32_t refresh = ntohl(*((uint32_t*)odata));
- if (refresh < (uint32_t)t1)
- t1 = refresh;
+ refresh = ntohl(*((uint32_t*)odata));
} else if (otype == DHCPV6_OPT_AUTH && olen == -4 +
sizeof(struct dhcpv6_auth_reconfigure)) {
struct dhcpv6_auth_reconfigure *r = (void*)&odata[-4];
@@ -869,29 +878,51 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
}
}
- if (t1 == UINT32_MAX)
- t1 = 300;
-
- if (t2 == UINT32_MAX)
- t2 = 600;
+ 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;
- if (t3 == UINT32_MAX)
- t3 = 3600;
+ default :
+ break;
+ }
+ }
+ }
+ else if (ret > 0)
+ t1 = refresh;
- return true;
+ return ret;
}
-static uint32_t dhcpv6_parse_ia(void *opt, void *end)
+static int dhcpv6_parse_ia(void *opt, void *end)
{
- uint32_t timeout = UINT32_MAX; // Minimum timeout
+ struct dhcpv6_ia_hdr *ia_hdr = (struct dhcpv6_ia_hdr *)opt;
+ int parsed_ia = 0;
+ uint32_t t1, t2;
uint16_t otype, olen;
uint8_t *odata;
+ t1 = ntohl(ia_hdr->t1);
+ t2 = ntohl(ia_hdr->t2);
+
+ if (t1 > t2)
+ return 0;
+
// Update address IA
- dhcpv6_for_each_option(opt, end, otype, olen, odata) {
+ dhcpv6_for_each_option(&ia_hdr[1], end, otype, olen, odata) {
struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0,
- IN6ADDR_ANY_INIT, 0, 0, 0};
+ IN6ADDR_ANY_INIT, 0, 0, 0, 0, 0};
if (otype == DHCPV6_OPT_IA_PREFIX) {
struct dhcpv6_ia_prefix *prefix = (void*)&odata[-4];
@@ -904,6 +935,11 @@ static uint32_t dhcpv6_parse_ia(void *opt, void *end)
if (entry.preferred > entry.valid)
continue;
+ entry.t1 = (t1 ? t1 : (entry.preferred != UINT32_MAX ? 0.5 * entry.preferred : UINT32_MAX));
+ entry.t2 = (t2 ? t2 : (entry.preferred != UINT32_MAX ? 0.8 * entry.preferred : UINT32_MAX));
+ if (entry.t1 > entry.t2)
+ entry.t1 = entry.t2;
+
entry.length = prefix->prefix;
entry.target = prefix->addr;
uint16_t stype, slen;
@@ -954,8 +990,10 @@ static uint32_t dhcpv6_parse_ia(void *opt, void *end)
entry.priority = elen;
}
- if (ok)
+ if (ok) {
odhcp6c_update_entry(STATE_IA_PD, &entry);
+ parsed_ia++;
+ }
entry.priority = 0;
memset(&entry.router, 0, sizeof(entry.router));
@@ -970,6 +1008,11 @@ static uint32_t dhcpv6_parse_ia(void *opt, void *end)
if (entry.preferred > entry.valid)
continue;
+ entry.t1 = (t1 ? t1 : (entry.preferred != UINT32_MAX ? 0.5 * entry.preferred : UINT32_MAX));
+ entry.t2 = (t2 ? t2 : (entry.preferred != UINT32_MAX ? 0.8 * entry.preferred : UINT32_MAX));
+ if (entry.t1 > entry.t2)
+ entry.t1 = entry.t2;
+
entry.length = 128;
entry.target = addr->addr;
@@ -984,10 +1027,139 @@ static uint32_t dhcpv6_parse_ia(void *opt, void *end)
#endif
odhcp6c_update_entry(STATE_IA_NA, &entry);
+ parsed_ia++;
}
- if (entry.valid > 0 && timeout > entry.valid)
- timeout = entry.valid;
}
+ return parsed_ia;
+}
+
+
+static int dhcpv6_calc_refresh_timers(void)
+{
+ struct odhcp6c_entry *e;
+ size_t ia_na_entries, ia_pd_entries, i;
+ 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++) {
+ if (e[i].t1 < l_t1)
+ l_t1 = e[i].t1;
- return timeout;
+ if (e[i].t2 < l_t2)
+ l_t2 = e[i].t2;
+
+ if (e[i].valid > l_t3)
+ l_t3 = e[i].valid;
+ }
+
+ 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;
+
+ if (e[i].t2 < l_t2)
+ l_t2 = e[i].t2;
+
+ if (e[i].valid > l_t3)
+ l_t3 = e[i].valid;
+ }
+
+ if (ia_pd_entries || ia_na_entries) {
+ t1 = l_t1;
+ t2 = l_t2;
+ t3 = l_t3;
+ }
+
+ return (int)(ia_pd_entries + ia_na_entries);
+}
+
+
+static void dhcpv6_log_status_code(const uint16_t code, const char *scope,
+ const void *status_msg, const int len)
+{
+ uint8_t buf[len + 3];
+
+ memset(buf, 0, sizeof(buf));
+ if (len) {
+ buf[0] = '(';
+ memcpy(&buf[1], status_msg, len);
+ buf[len + 1] = ')';
+ }
+
+ syslog(LOG_WARNING, "Server returned %s status %i %s",
+ scope, 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)
+{
+ dhcpv6_log_status_code(code, "message", status_msg, len);
+
+ switch (code) {
+ case DHCPV6_UnspecFail:
+ // Generic failure
+ *ret = 0;
+ break;
+
+ case DHCPV6_UseMulticast:
+ // TODO handle multicast status code
+ break;
+
+ case DHCPV6_NoAddrsAvail:
+ case DHCPV6_NoPrefixAvail:
+ if (orig == DHCPV6_MSG_REQUEST)
+ *ret = 0; // Failure
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+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,
+ bool handled_status_codes[_DHCPV6_Status_Max], int *ret)
+{
+ dhcpv6_log_status_code(code, ia_hdr->type == DHCPV6_OPT_IA_NA ?
+ "IA_NA" : "IA_PD", status_msg, len);
+
+ switch (code) {
+ case DHCPV6_NoBinding:
+ switch (orig) {
+ case DHCPV6_MSG_RENEW:
+ case DHCPV6_MSG_REBIND:
+ if ((*ret > 0) && !handled_status_codes[code])
+ *ret = dhcpv6_request(DHCPV6_MSG_REQUEST);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case DHCPV6_NoAddrsAvail:
+ case DHCPV6_NoPrefixAvail:
+ switch (orig) {
+ case DHCPV6_MSG_REQUEST:
+ if (*ret != 0)
+ *ret = 0;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case DHCPV6_NotOnLink:
+ // TODO handle not onlink in case of confirm
+ break;
+
+ default:
+ break;
+ }
}
diff --git a/src/odhcp6c.c b/src/odhcp6c.c
index e81b15f..2063935 100644
--- a/src/odhcp6c.c
+++ b/src/odhcp6c.c
@@ -36,7 +36,6 @@
static void sighandler(int signal);
static int usage(void);
-
static uint8_t *state_data[_STATE_MAX] = {NULL};
static size_t state_len[_STATE_MAX] = {0};
@@ -231,7 +230,7 @@ int main(_unused int argc, char* const argv[])
int res = dhcpv6_request(DHCPV6_MSG_SOLICIT);
odhcp6c_signal_process();
- if (res < 0) {
+ if (res <= 0) {
continue; // Might happen if we got a signal
} else if (res == DHCPV6_STATELESS) { // Stateless mode
while (do_signal == 0 || do_signal == SIGUSR1) {
@@ -257,7 +256,7 @@ int main(_unused int argc, char* const argv[])
}
// Stateful mode
- if (dhcpv6_request(DHCPV6_MSG_REQUEST) < 0)
+ if (dhcpv6_request(DHCPV6_MSG_REQUEST) <= 0)
continue;
odhcp6c_signal_process();
@@ -270,10 +269,8 @@ int main(_unused int argc, char* const argv[])
// Wait for T1 to expire or until we get a reconfigure
int res = dhcpv6_poll_reconfigure();
odhcp6c_signal_process();
- if (res >= 0) {
- if (res > 0)
- script_call("updated");
-
+ if (res > 0) {
+ script_call("updated");
continue;
}
@@ -294,10 +291,11 @@ int main(_unused int argc, char* const argv[])
else
r = dhcpv6_request(DHCPV6_MSG_RENEW);
odhcp6c_signal_process();
- if (r > 0) // Publish updates
+ if (r > 0) { // Renew was succesfull
+ // Publish updates
script_call("updated");
- if (r >= 0)
continue; // Renew was successful
+ }
odhcp6c_clear_state(STATE_SERVER_ID); // Remove binding
@@ -307,7 +305,7 @@ int main(_unused int argc, char* const argv[])
odhcp6c_get_state(STATE_IA_PD, &ia_pd_new);
odhcp6c_get_state(STATE_IA_NA, &ia_na_new);
- if (res < 0 || (ia_pd_new == 0 && ia_pd_len) ||
+ if (res <= 0 || (ia_pd_new == 0 && ia_pd_len) ||
(ia_na_new == 0 && ia_na_len))
break; // We lost all our IAs, restart
else if (res > 0)
@@ -482,6 +480,8 @@ bool odhcp6c_update_entry_safe(enum odhcp6c_state state, struct odhcp6c_entry *n
changed = false;
x->valid = new->valid;
x->preferred = new->preferred;
+ x->t1 = new->t1;
+ x->t2 = new->t2;
x->class = new->class;
} else {
odhcp6c_add_state(state, new, sizeof(*new));
@@ -504,6 +504,16 @@ static void odhcp6c_expire_list(enum odhcp6c_state state, uint32_t elapsed)
size_t len;
struct odhcp6c_entry *start = odhcp6c_get_state(state, &len);
for (struct odhcp6c_entry *c = start; c < &start[len / sizeof(*c)]; ++c) {
+ if (c->t1 < elapsed)
+ c->t1 = 0;
+ else if (c->t1 != UINT32_MAX)
+ c->t1 -= elapsed;
+
+ if (c->t2 < elapsed)
+ c->t2 = 0;
+ else if (c->t2 != UINT32_MAX)
+ c->t2 -= elapsed;
+
if (c->preferred < elapsed)
c->preferred = 0;
else if (c->preferred != UINT32_MAX)
@@ -545,6 +555,10 @@ void odhcp6c_random(void *buf, size_t len)
read(urandom_fd, buf, len);
}
+bool odhcp6c_is_bound(void)
+{
+ return bound;
+}
static void sighandler(int signal)
{
diff --git a/src/odhcp6c.h b/src/odhcp6c.h
index 5b9b78f..183ed44 100644
--- a/src/odhcp6c.h
+++ b/src/odhcp6c.h
@@ -81,8 +81,14 @@ enum dhcpv6_msg {
};
enum dhcpv6_status {
+ DHCPV6_Success = 0,
+ DHCPV6_UnspecFail = 1,
DHCPV6_NoAddrsAvail = 2,
+ DHCPV6_NoBinding = 3,
+ DHCPV6_NotOnLink = 4,
+ DHCPV6_UseMulticast = 5,
DHCPV6_NoPrefixAvail = 6,
+ _DHCPV6_Status_Max
};
typedef int(reply_handler)(enum dhcpv6_msg orig, const int rc,
@@ -219,6 +225,8 @@ struct odhcp6c_entry {
struct in6_addr target;
uint32_t valid;
uint32_t preferred;
+ uint32_t t1;
+ uint32_t t2;
uint16_t class;
};
@@ -240,6 +248,7 @@ void script_delay_call(const char *status, int timeout);
bool odhcp6c_signal_process(void);
uint64_t odhcp6c_get_milli_time(void);
void odhcp6c_random(void *buf, size_t len);
+bool odhcp6c_is_bound(void);
// State manipulation
void odhcp6c_clear_state(enum odhcp6c_state state);
diff --git a/src/ra.c b/src/ra.c
index f41602a..c870279 100644
--- a/src/ra.c
+++ b/src/ra.c
@@ -123,7 +123,7 @@ bool ra_process(void)
bool changed = false;
uint8_t buf[1500], cmsg_buf[128];
struct nd_router_advert *adv = (struct nd_router_advert*)buf;
- struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0, IN6ADDR_ANY_INIT, 0, 0, 0};
+ struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0, IN6ADDR_ANY_INIT, 0, 0, 0, 0, 0};
const struct in6_addr any = IN6ADDR_ANY_INIT;
if (IN6_IS_ADDR_UNSPECIFIED(&lladdr)) {