From d77cbea57d1b7dd9c25421bb13c90e65bb54a6bc Mon Sep 17 00:00:00 2001 From: Hans Dedecker Date: Wed, 23 Oct 2013 12:04:38 +0000 Subject: [PATCH] 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 --- src/dhcpv6.c | 276 ++++++++++++++++++++++++++++++++++++++++---------- src/odhcp6c.c | 34 +++++-- src/odhcp6c.h | 9 ++ src/ra.c | 2 +- 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)) { -- 2.39.5