Fine tune server candidate selection logic
authorHans Dedecker <dedeckeh@gmail.com>
Sun, 1 Dec 2013 17:57:06 +0000 (18:57 +0100)
committerHans Dedecker <dedeckeh@gmail.com>
Sun, 1 Dec 2013 20:07:04 +0000 (21:07 +0100)
DHCPv6 server info from ADVERTISE messages is inserted in the server
candidate list sorted according to the preference value (highest values
first in the list).
Server candidate list can only contain one entry from a given server.
Server with highest preference value is selected; in case the server
does not respond the next server is selected from the list.
When the server candidate list is empty and no servers responded;
the client restarts the DHCPv6 server discovery process

src/dhcpv6.c
src/odhcp6c.c
src/odhcp6c.h

index e078867..e44be48 100644 (file)
@@ -59,6 +59,7 @@ static void dhcpv6_handle_ia_status_code(const enum dhcpv6_msg orig,
                const void *status_msg, const int len,
                bool handled_status_codes[_DHCPV6_Status_Max],
                int *ret);
+static void dhcpv6_add_server_cand(const struct dhcpv6_server_cand *cand);
 
 static reply_handler dhcpv6_handle_reply;
 static reply_handler dhcpv6_handle_advert;
@@ -751,7 +752,7 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
        if (cand.duid_len > 0) {
                cand.ia_na = odhcp6c_move_state(STATE_IA_NA, &cand.ia_na_len);
                cand.ia_pd = odhcp6c_move_state(STATE_IA_PD, &cand.ia_pd_len);
-               odhcp6c_add_state(STATE_SERVER_CAND, &cand, sizeof(cand));
+               dhcpv6_add_server_cand(&cand);
        }
 
        return (rc > 1 || (pref == 255 && cand.preference > 0)) ? 1 : -1;
@@ -760,50 +761,7 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
 
 static int dhcpv6_commit_advert(void)
 {
-       size_t cand_len;
-       struct dhcpv6_server_cand *c = NULL, *cand =
-                       odhcp6c_get_state(STATE_SERVER_CAND, &cand_len);
-
-       bool retry = false;
-       for (size_t i = 0; i < cand_len / sizeof(*c); ++i) {
-               if (cand[i].has_noaddravail)
-                       retry = true; // We want to try again
-
-               if (!c || c->preference < cand[i].preference)
-                       c = &cand[i];
-       }
-
-       if (retry && na_mode == IA_MODE_TRY) {
-               // We give it a second try without the IA_NA
-               na_mode = IA_MODE_NONE;
-               return dhcpv6_request(DHCPV6_MSG_SOLICIT);
-       }
-
-       if (c) {
-               uint16_t hdr[2] = {htons(DHCPV6_OPT_SERVERID),
-                               htons(c->duid_len)};
-               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;
-               server_addr = c->server_addr;
-               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) {
-               free(cand[i].ia_na);
-               free(cand[i].ia_pd);
-       }
-       odhcp6c_clear_state(STATE_SERVER_CAND);
-
-       if (!c)
-               return -1;
-       else if ((request_prefix && c->ia_pd_len) || (na_mode != IA_MODE_NONE && c->ia_na_len))
-               return DHCPV6_STATEFUL;
-       else
-               return DHCPV6_STATELESS;
+       return dhcpv6_promote_server_cand();
 }
 
 
@@ -973,6 +931,11 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
                                        ret = -1;
                                break;
 
+                       case DHCPV6_MSG_REQUEST:
+                               // All server candidates can be cleared if not yet bound
+                               if (!odhcp6c_is_bound())
+                                       dhcpv6_clear_all_server_cand();
+
                        default :
                                break;
                        }
@@ -1254,3 +1217,86 @@ static void dhcpv6_handle_ia_status_code(const enum dhcpv6_msg orig,
                break;
        }
 }
+
+static void dhcpv6_add_server_cand(const struct dhcpv6_server_cand *cand)
+{
+       size_t cand_len, i;
+       struct dhcpv6_server_cand *c = odhcp6c_get_state(STATE_SERVER_CAND, &cand_len);
+
+       // Remove identical duid server candidate
+       for (i = 0; i < cand_len / sizeof(*c); ++i) {
+               if (cand->duid_len == c[i].duid_len &&
+                       !memcmp(cand->duid, c[i].duid, cand->duid_len)) {
+                       free(c[i].ia_na);
+                       free(c[i].ia_pd);
+                       odhcp6c_remove_state(STATE_SERVER_CAND, i * sizeof(*c), sizeof(*c));
+                       break;
+               }
+       }
+
+       for (i = 0, c = odhcp6c_get_state(STATE_SERVER_CAND, &cand_len);
+               i < cand_len / sizeof(*c); ++i) {
+               if (c[i].preference < cand->preference)
+                       break;
+       }
+
+       odhcp6c_insert_state(STATE_SERVER_CAND, i * sizeof(*c), cand, sizeof(*cand));
+}
+
+int dhcpv6_promote_server_cand(void)
+{
+       size_t cand_len;
+       struct dhcpv6_server_cand *cand = odhcp6c_get_state(STATE_SERVER_CAND, &cand_len);
+       uint16_t hdr[2];
+       int ret = DHCPV6_STATELESS;
+
+       // Clear lingering candidate state info
+       odhcp6c_clear_state(STATE_SERVER_ID);
+       odhcp6c_clear_state(STATE_IA_NA);
+       odhcp6c_clear_state(STATE_IA_PD);
+
+       if (!cand)
+               return -1;
+
+       if (cand->has_noaddravail && na_mode == IA_MODE_TRY) {
+               na_mode = IA_MODE_NONE;
+               return dhcpv6_request(DHCPV6_MSG_SOLICIT);
+       }
+
+       hdr[0] = htons(DHCPV6_OPT_SERVERID);
+       hdr[1] = htons(cand->duid_len);
+       odhcp6c_add_state(STATE_SERVER_ID, hdr, sizeof(hdr));
+       odhcp6c_add_state(STATE_SERVER_ID, cand->duid, cand->duid_len);
+       accept_reconfig = cand->wants_reconfigure;
+       if (cand->ia_na_len) {
+               odhcp6c_add_state(STATE_IA_NA, cand->ia_na, cand->ia_na_len);
+               free(cand->ia_na);
+               if (na_mode != IA_MODE_NONE)
+                       ret = DHCPV6_STATEFUL;
+       }
+       if (cand->ia_pd_len) {
+               odhcp6c_add_state(STATE_IA_PD, cand->ia_pd, cand->ia_pd_len);
+               free(cand->ia_pd);
+               if (request_prefix)
+                       ret = DHCPV6_STATEFUL;
+       }
+
+       odhcp6c_remove_state(STATE_SERVER_CAND, 0, sizeof(*cand));
+
+       return ret;
+}
+
+void dhcpv6_clear_all_server_cand(void)
+{
+       size_t cand_len, i;
+       struct dhcpv6_server_cand *c = odhcp6c_get_state(STATE_SERVER_CAND, &cand_len);
+
+       // Server candidates need deep delete for IA_NA/IA_PD
+       for (i = 0; i < cand_len / sizeof(*c); ++i) {
+               if (c[i].ia_na)
+                       free(c[i].ia_na);
+               if (c[i].ia_pd)
+                       free(c[i].ia_pd);
+       }
+       odhcp6c_clear_state(STATE_SERVER_CAND);
+}
index 460f230..c8d40f6 100644 (file)
@@ -236,26 +236,53 @@ int main(_unused int argc, char* const argv[])
                dhcpv6_set_ia_mode(ia_na_mode, ia_pd_mode);
                bound = false;
 
-               // Server candidates need deep-delete
-               size_t cand_len;
-               struct dhcpv6_server_cand *cand = odhcp6c_get_state(STATE_SERVER_CAND, &cand_len);
-               for (size_t i = 0; i < cand_len / sizeof(*cand); ++i) {
-                       free(cand[i].ia_na);
-                       free(cand[i].ia_pd);
-               }
-               odhcp6c_clear_state(STATE_SERVER_CAND);
-
                syslog(LOG_NOTICE, "(re)starting transaction on %s", ifname);
 
                do_signal = 0;
-               int res = dhcpv6_request(DHCPV6_MSG_SOLICIT);
+               int mode = dhcpv6_request(DHCPV6_MSG_SOLICIT);
                odhcp6c_signal_process();
 
-               if (res <= 0) {
-                       continue; // Might happen if we got a signal
-               } else if (res == DHCPV6_STATELESS) { // Stateless mode
+               if (mode < 0)
+                       continue;
+
+               do {
+                       int res = dhcpv6_request(mode == DHCPV6_STATELESS ?
+                                       DHCPV6_MSG_INFO_REQ : DHCPV6_MSG_REQUEST);
+
+                       odhcp6c_signal_process();
+                       if (res > 0)
+                               break;
+                       else if (do_signal > 0) {
+                               mode = -1;
+                               break;
+                       }
+
+                       mode = dhcpv6_promote_server_cand();
+               } while (mode > DHCPV6_UNKNOWN);
+
+               if (mode < 0)
+                       continue;
+
+               switch (mode) {
+               case DHCPV6_STATELESS:
+                       bound = true;
+                       syslog(LOG_NOTICE, "entering stateless-mode on %s", ifname);
+
                        while (do_signal == 0 || do_signal == SIGUSR1) {
                                do_signal = 0;
+                               script_call("informed");                                        
+
+                               int res = dhcpv6_poll_reconfigure();
+                               odhcp6c_signal_process();
+
+                               if (res > 0)
+                                       continue;
+
+                               if (do_signal == SIGUSR1) {
+                                       do_signal = 0; // Acknowledged
+                                       continue;
+                               } else if (do_signal > 0)
+                                       break;
 
                                res = dhcpv6_request(DHCPV6_MSG_INFO_REQ);
                                odhcp6c_signal_process();
@@ -263,84 +290,63 @@ int main(_unused int argc, char* const argv[])
                                        continue;
                                else if (res < 0)
                                        break;
-                               else if (res > 0)
-                                       script_call("informed");
-
-                               bound = true;
-                               syslog(LOG_NOTICE, "entering stateless-mode on %s", ifname);
-
-                               if (dhcpv6_poll_reconfigure() > 0)
-                                       script_call("informed");
                        }
+                       break;
 
-                       continue;
-               }
-
-               // Stateful mode
-               if (dhcpv6_request(DHCPV6_MSG_REQUEST) <= 0)
-                       continue;
-
-               odhcp6c_signal_process();
-               script_call("bound");
-               bound = true;
-               syslog(LOG_NOTICE, "entering stateful-mode on %s", ifname);
+               case DHCPV6_STATEFUL:
+                       script_call("bound");
+                       bound = true;
+                       syslog(LOG_NOTICE, "entering stateful-mode on %s", ifname);
 #ifdef EXT_BFD_PING
-               if (bfd_interval > 0)
-                       bfd_start(ifname, bfd_loss, bfd_interval);
+                       if (bfd_interval > 0)
+                               bfd_start(ifname, bfd_loss, bfd_interval);
 #endif
 
-               while (do_signal == 0 || do_signal == SIGUSR1) {
-                       // Renew Cycle
-                       // Wait for T1 to expire or until we get a reconfigure
-                       int res = dhcpv6_poll_reconfigure();
-                       odhcp6c_signal_process();
-                       if (res > 0) {
-                               script_call("updated");
-                               continue;
-                       }
-
-                       // Handle signal, if necessary
-                       if (do_signal == SIGUSR1)
-                               do_signal = 0; // Acknowledged
-                       else if (do_signal > 0)
-                               break; // Other signal type
-
-                       size_t ia_pd_len, ia_na_len, ia_pd_new, ia_na_new;
-                       odhcp6c_get_state(STATE_IA_PD, &ia_pd_len);
-                       odhcp6c_get_state(STATE_IA_NA, &ia_na_len);
-
-                       // If we have any IAs, send renew, otherwise request
-                       int r;
-                       if (ia_pd_len == 0 && ia_na_len == 0)
-                               r = dhcpv6_request(DHCPV6_MSG_REQUEST);
-                       else
-                               r = dhcpv6_request(DHCPV6_MSG_RENEW);
-                       odhcp6c_signal_process();
-                       if (r > 0) { // Renew was succesfull
-                               // Publish updates
-                               script_call("updated");
-                               continue; // Renew was successful
-                       }
-
-                       odhcp6c_clear_state(STATE_SERVER_ID); // Remove binding
+                       while (do_signal == 0 || do_signal == SIGUSR1) {
+                               // Renew Cycle
+                               // Wait for T1 to expire or until we get a reconfigure
+                               int res = dhcpv6_poll_reconfigure();
+                               odhcp6c_signal_process();
+                               if (res > 0) {
+                                       script_call("updated");
+                                       continue;
+                               }
 
-                       // If we have IAs, try rebind otherwise restart
-                       res = dhcpv6_request(DHCPV6_MSG_REBIND);
-                       odhcp6c_signal_process();
+                               // Handle signal, if necessary
+                               if (do_signal == SIGUSR1)
+                                       do_signal = 0; // Acknowledged
+                               else if (do_signal > 0)
+                                       break; // Other signal type
 
-                       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) ||
-                                       (ia_na_new == 0 && ia_na_len))
-                               break; // We lost all our IAs, restart
-                       else if (res > 0)
-                               script_call("rebound");
-               }
+                               // If we have any IAs, send renew, otherwise request
+                               res = dhcpv6_request(DHCPV6_MSG_RENEW);
+                               odhcp6c_signal_process();
+                               if (res > 0) { // Renew was succesfull
+                                       // Publish updates
+                                       script_call("updated");
+                                       continue; // Renew was successful
+                               }
+                               
+                               odhcp6c_clear_state(STATE_SERVER_ID); // Remove binding
+
+                               // If we have IAs, try rebind otherwise restart
+                               res = dhcpv6_request(DHCPV6_MSG_REBIND);
+                               odhcp6c_signal_process();
 
+                               if (res > 0)
+                                       script_call("rebound");
+                               else {
 #ifdef EXT_BFD_PING
-               bfd_stop();
+                                       bfd_stop();
 #endif
+                                       break;
+                               }
+                       }
+                       break;
 
+               default:
+                       break;
+               }
 
                size_t ia_pd_len, ia_na_len, server_id_len;
                odhcp6c_get_state(STATE_IA_PD, &ia_pd_len);
@@ -454,6 +460,20 @@ void odhcp6c_add_state(enum odhcp6c_state state, const void *data, size_t len)
                memcpy(n, data, len);
 }
 
+void odhcp6c_insert_state(enum odhcp6c_state state, size_t offset, const void *data, size_t len)
+{
+       ssize_t len_after = state_len[state] - offset;
+       if (len_after < 0)
+               return;
+
+       uint8_t *n = odhcp6c_resize_state(state, len);
+       if (n) {
+               uint8_t *sdata = state_data[state];
+               
+               memmove(sdata + offset + len, sdata + offset, len_after);
+               memcpy(sdata + offset, data, len);
+       }
+}
 
 size_t odhcp6c_remove_state(enum odhcp6c_state state, size_t offset, size_t len)
 {
index cf8ff74..2c28c80 100644 (file)
@@ -203,12 +203,11 @@ struct icmp6_opt {
 
 
 enum dhcpv6_mode {
-       DHCPV6_UNKNOWN,
+       DHCPV6_UNKNOWN = -1,
        DHCPV6_STATELESS,
        DHCPV6_STATEFUL
 };
 
-
 enum odhcp6c_ia_mode {
        IA_MODE_NONE,
        IA_MODE_TRY,
@@ -233,6 +232,8 @@ int init_dhcpv6(const char *ifname, int request_pd, int sol_timeout);
 void dhcpv6_set_ia_mode(enum odhcp6c_ia_mode na, enum odhcp6c_ia_mode pd);
 int dhcpv6_request(enum dhcpv6_msg type);
 int dhcpv6_poll_reconfigure(void);
+int dhcpv6_promote_server_cand(void);
+void dhcpv6_clear_all_server_cand(void);
 
 int init_rtnetlink(void);
 int set_rtnetlink_addr(int ifindex, const struct in6_addr *addr,
@@ -252,6 +253,7 @@ bool odhcp6c_addr_in_scope(const struct in6_addr *addr);
 // State manipulation
 void odhcp6c_clear_state(enum odhcp6c_state state);
 void odhcp6c_add_state(enum odhcp6c_state state, const void *data, size_t len);
+void odhcp6c_insert_state(enum odhcp6c_state state, size_t offset, const void *data, size_t len);
 size_t odhcp6c_remove_state(enum odhcp6c_state state, size_t offset, size_t len);
 void* odhcp6c_move_state(enum odhcp6c_state state, size_t *len);
 void* odhcp6c_get_state(enum odhcp6c_state state, size_t *len);