]> git.decadent.org.uk Git - odhcp6c.git/blobdiff - src/dhcpv6.c
odhcp6c: preference and status code support in advertise
[odhcp6c.git] / src / dhcpv6.c
index f905a7836d2ba601d5b98c5a10d72e77bd53da34..d212decaa614ea9413fa7d77670b6a0a9564c5b9 100644 (file)
@@ -58,20 +58,20 @@ 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, "<POLL>",
-                       dhcpv6_handle_reconfigure, NULL},
-       [DHCPV6_MSG_SOLICIT] = {true, 1, 120, "SOLICIT",
-                       dhcpv6_handle_advert, dhcpv6_commit_advert},
-       [DHCPV6_MSG_REQUEST] = {true, 1, 30, "REQUEST",
-                       dhcpv6_handle_reply, NULL},
-       [DHCPV6_MSG_RENEW] = {false, 10, 600, "RENEW",
-                       dhcpv6_handle_reply, NULL},
-       [DHCPV6_MSG_REBIND] = {false, 10, 600, "REBIND",
-                       dhcpv6_handle_rebind_reply, NULL},
-       [DHCPV6_MSG_RELEASE] = {false, 1, 600, "RELEASE", NULL, NULL},
-       [DHCPV6_MSG_DECLINE] = {false, 1, 3, "DECLINE", NULL, NULL},
-       [DHCPV6_MSG_INFO_REQ] = {true, 1, 120, "INFOREQ",
-                       dhcpv6_handle_reply, NULL},
+       [DHCPV6_MSG_UNKNOWN] = {false, 1, 120, 0, "<POLL>",
+                       dhcpv6_handle_reconfigure, NULL},
+       [DHCPV6_MSG_SOLICIT] = {true, 1, 3600, 0, "SOLICIT",
+                       dhcpv6_handle_advert, dhcpv6_commit_advert},
+       [DHCPV6_MSG_REQUEST] = {true, 1, 30, 10, "REQUEST",
+                       dhcpv6_handle_reply, NULL},
+       [DHCPV6_MSG_RENEW] = {false, 10, 600, 0, "RENEW",
+                       dhcpv6_handle_reply, NULL},
+       [DHCPV6_MSG_REBIND] = {false, 10, 600, 0, "REBIND",
+                       dhcpv6_handle_rebind_reply, NULL},
+       [DHCPV6_MSG_RELEASE] = {false, 1, 0, 5, "RELEASE", NULL, NULL},
+       [DHCPV6_MSG_DECLINE] = {false, 1, 0, 5, "DECLINE", NULL, NULL},
+       [DHCPV6_MSG_INFO_REQ] = {true, 1, 120, 0, "INFOREQ",
+                       dhcpv6_handle_reply, NULL},
 };
 
 
@@ -157,6 +157,8 @@ int init_dhcpv6(const char *ifname, int request_pd, int sol_timeout)
        int val = 1;
        setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+       val = 0;
+       setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, sizeof(val));
        setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname));
 
        struct sockaddr_in6 client_addr = { .sin6_family = AF_INET6,
@@ -360,7 +362,7 @@ static int64_t dhcpv6_rand_delay(int64_t time)
 
 int dhcpv6_request(enum dhcpv6_msg type)
 {
-       uint8_t buf[1536];
+       uint8_t buf[1536], rc = 0;
        uint64_t timeout = UINT32_MAX;
        struct dhcpv6_retx *retx = &dhcpv6_retx[type];
 
@@ -370,11 +372,7 @@ int dhcpv6_request(enum dhcpv6_msg type)
                nanosleep(&ts, NULL);
        }
 
-       if (type == DHCPV6_MSG_REQUEST)
-               timeout = 60;
-       else if (type == DHCPV6_MSG_RELEASE || type == DHCPV6_MSG_DECLINE)
-               timeout = 3;
-       else if (type == DHCPV6_MSG_UNKNOWN)
+       if (type == DHCPV6_MSG_UNKNOWN)
                timeout = t1;
        else if (type == DHCPV6_MSG_RENEW)
                timeout = (t2 > t1) ? t2 - t1 : 0;
@@ -384,7 +382,7 @@ int dhcpv6_request(enum dhcpv6_msg type)
        if (timeout == 0)
                return -1;
 
-       syslog(LOG_NOTICE, "Sending %s (timeout %us)", retx->name, (unsigned)timeout);
+       syslog(LOG_NOTICE, "Starting %s transaction (timeout %llus, max rc %d)", retx->name, timeout, retx->max_rc);
 
        uint64_t start = odhcp6c_get_milli_time(), round_start = start, elapsed;
 
@@ -396,11 +394,19 @@ int dhcpv6_request(enum dhcpv6_msg type)
        int64_t rto = 0;
 
        do {
-               rto = (rto == 0) ? (retx->init_timeo * 1000 +
-                               dhcpv6_rand_delay(retx->init_timeo * 1000)) :
-                               (2 * rto + dhcpv6_rand_delay(rto));
+               if (rto == 0) {
+                       int64_t delay = dhcpv6_rand_delay(retx->init_timeo * 1000);
 
-               if (rto >= retx->max_timeo * 1000)
+                       // First RT MUST be strictly greater than IRT for solicit messages (RFC3313 17.1.2)
+                       while (type == DHCPV6_MSG_SOLICIT && delay <= 0)
+                               delay = dhcpv6_rand_delay(retx->init_timeo * 1000);
+
+                       rto = (retx->init_timeo * 1000 + delay);
+               }
+               else
+                       rto = (2 * rto + dhcpv6_rand_delay(rto));
+
+               if (retx->max_timeo && (rto >= retx->max_timeo * 1000))
                        rto = retx->max_timeo * 1000 +
                                dhcpv6_rand_delay(retx->max_timeo * 1000);
 
@@ -413,8 +419,11 @@ int dhcpv6_request(enum dhcpv6_msg type)
                        round_end = timeout * 1000 + start;
 
                // Built and send package
-               if (type != DHCPV6_MSG_UNKNOWN)
+               if (type != DHCPV6_MSG_UNKNOWN) {
+                       syslog(LOG_NOTICE, "Send %s message (elapsed %llums, rc %d)", retx->name, elapsed, rc);
                        dhcpv6_send(type, trid, elapsed / 10);
+                       rc++;
+               }
 
                // Receive rounds
                for (; len < 0 && round_start < round_end;
@@ -442,11 +451,11 @@ int dhcpv6_request(enum dhcpv6_msg type)
                                round_start = odhcp6c_get_milli_time();
                                elapsed = round_start - start;
                                syslog(LOG_NOTICE, "Got a valid reply after "
-                                               "%ums", (unsigned)elapsed);
+                                               "%llums", elapsed);
 
                                if (retx->handler_reply)
                                        len = retx->handler_reply(
-                                                       type, opt, opt_end);
+                                                       type, rc, opt, opt_end);
 
                                if (len > 0 && round_end - round_start > 1000)
                                        round_end = 1000 + round_start;
@@ -456,7 +465,7 @@ int dhcpv6_request(enum dhcpv6_msg type)
                // Allow
                if (retx->handler_finish)
                        len = retx->handler_finish();
-       } while (len < 0 && elapsed / 1000 < timeout);
+       } while (len < 0 && ((elapsed / 1000 < timeout) && (!retx->max_rc || rc < retx->max_rc)));
 
        return len;
 }
@@ -547,7 +556,7 @@ int dhcpv6_poll_reconfigure(void)
 }
 
 
-static int dhcpv6_handle_reconfigure(_unused enum dhcpv6_msg orig,
+static int dhcpv6_handle_reconfigure(_unused enum dhcpv6_msg orig, const int rc,
                const void *opt, const void *end)
 {
        // TODO: should verify the reconfigure message
@@ -559,17 +568,17 @@ static int dhcpv6_handle_reconfigure(_unused enum dhcpv6_msg orig,
                                odata[0] == DHCPV6_MSG_INFO_REQ))
                        msg = odata[0];
 
-       dhcpv6_handle_reply(DHCPV6_MSG_UNKNOWN, NULL, NULL);
+       dhcpv6_handle_reply(DHCPV6_MSG_UNKNOWN, rc, NULL, NULL);
        return msg;
 }
 
 
 // Collect all advertised servers
-static int dhcpv6_handle_advert(enum dhcpv6_msg orig,
+static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
                const void *opt, const void *end)
 {
        uint16_t olen, otype;
-       uint8_t *odata;
+       uint8_t *odata, pref = 0;
        struct dhcpv6_server_cand cand = {false, false, 0, 0, {0}, NULL, NULL, 0, 0};
        bool have_na = false;
        int have_pd = 0;
@@ -585,12 +594,29 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig,
                if (otype == DHCPV6_OPT_SERVERID && olen <= 130) {
                        memcpy(cand.duid, odata, olen);
                        cand.duid_len = olen;
-               } else if (otype == DHCPV6_OPT_STATUS && olen >= 2 && !odata[0]
-                               && odata[1] == DHCPV6_NoPrefixAvail) {
-                       cand.preference -= 2000;
+               } else if (otype == DHCPV6_OPT_STATUS && olen >= 2) {
+                       int error = ((int)odata[0] << 8 | (int)odata[1]);
+
+                       switch (error) {
+                       case DHCPV6_NoPrefixAvail:
+                               // Status code on global level
+                               if (pd_mode == IA_MODE_FORCE)
+                                       return -1;
+                               cand.preference -= 2000;
+                               break;
+
+                       case DHCPV6_NoAddrsAvail:
+                               // Status code on global level
+                               if (na_mode == IA_MODE_FORCE)
+                                       return -1;
+                               break;
+
+                       default :
+                               break;
+                       }
                } else if (otype == DHCPV6_OPT_PREF && olen >= 1 &&
                                cand.preference >= 0) {
-                       cand.preference = odata[0];
+                       cand.preference = pref = odata[0];
                } else if (otype == DHCPV6_OPT_RECONF_ACCEPT) {
                        cand.wants_reconfigure = true;
                } else if (otype == DHCPV6_OPT_IA_PD && request_prefix) {
@@ -639,7 +665,7 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig,
                odhcp6c_clear_state(STATE_IA_PD);
        }
 
-       return -1;
+       return (rc > 1 || (pref == 255 && cand.preference > 0)) ? 1 : -1;
 }
 
 
@@ -670,8 +696,10 @@ static int dhcpv6_commit_advert(void)
                odhcp6c_add_state(STATE_SERVER_ID, hdr, sizeof(hdr));
                odhcp6c_add_state(STATE_SERVER_ID, c->duid, c->duid_len);
                accept_reconfig = c->wants_reconfigure;
-               odhcp6c_add_state(STATE_IA_NA, c->ia_na, c->ia_na_len);
-               odhcp6c_add_state(STATE_IA_PD, c->ia_pd, c->ia_pd_len);
+               if (c->ia_na_len)
+                       odhcp6c_add_state(STATE_IA_NA, c->ia_na, c->ia_na_len);
+               if (c->ia_pd_len)
+                       odhcp6c_add_state(STATE_IA_PD, c->ia_pd, c->ia_pd_len);
        }
 
        for (size_t i = 0; i < cand_len / sizeof(*c); ++i) {
@@ -682,27 +710,27 @@ static int dhcpv6_commit_advert(void)
 
        if (!c)
                return -1;
-       else if (request_prefix || na_mode != IA_MODE_NONE)
+       else if ((request_prefix && c->ia_pd_len) || (na_mode != IA_MODE_NONE && c->ia_na_len))
                return DHCPV6_STATEFUL;
        else
                return DHCPV6_STATELESS;
 }
 
 
-static int dhcpv6_handle_rebind_reply(enum dhcpv6_msg orig,
+static int dhcpv6_handle_rebind_reply(enum dhcpv6_msg orig, const int rc,
                const void *opt, const void *end)
 {
-       dhcpv6_handle_advert(orig, opt, end);
+       dhcpv6_handle_advert(orig, rc, opt, end);
        if (dhcpv6_commit_advert() < 0) {
-               dhcpv6_handle_reply(DHCPV6_MSG_UNKNOWN, NULL, NULL);
+               dhcpv6_handle_reply(DHCPV6_MSG_UNKNOWN, rc, NULL, NULL);
                return -1;
        }
 
-       return dhcpv6_handle_reply(orig, opt, end);
+       return dhcpv6_handle_reply(orig, rc, opt, end);
 }
 
 
-static int dhcpv6_handle_reply(enum dhcpv6_msg orig,
+static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
                const void *opt, const void *end)
 {
        uint8_t *odata;