odhpc6c: status code support in reply
authorHans Dedecker <dedeckeh@gmail.com>
Wed, 23 Oct 2013 12:04:38 +0000 (12:04 +0000)
committerSteven Barth <steven@midlink.org>
Wed, 23 Oct 2013 17:22:01 +0000 (19:22 +0200)
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>
src/dhcpv6.c
src/odhcp6c.c
src/odhcp6c.h
src/ra.c

index d212decaa614ea9413fa7d77670b6a0a9564c5b9..a609d4d06059840e677876ff0ba3a76a9748795a 100644 (file)
 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;
+       }
 }
index e81b15f92a5bcfc0a8b54895a336cdf46a8d6ace..20639352fde8979b00bc5aadfda2db14ce4e4028 100644 (file)
@@ -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)
 {
index 5b9b78fd1faa200c73e9f6f8e4ca9dbf70158bdc..183ed44a36725acf24ad2fb5fe77561642fb31c9 100644 (file)
@@ -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);
index f41602a587e00b87d10299bbac62df785b873f11..c870279abae3f70730dc3aee17719a25168df726 100644 (file)
--- 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)) {