dhcpv6-ia: rework reconfigure accept logic
authorHans Dedecker <dedeckeh@gmail.com>
Thu, 10 Aug 2017 13:44:09 +0000 (15:44 +0200)
committerHans Dedecker <dedeckeh@gmail.com>
Thu, 10 Aug 2017 16:46:57 +0000 (18:46 +0200)
Rework reconfigure accept retransmission logic; every DHCPv6 assignment
has its own reconfigure accept timer now making sure the timeout and
retransmission behavior is in line with RFC3315 paragraph 19.1.2.

While at it rename the original reconf_timer into valid_until_timeout as
it will only check now the lifetime of the assignments.

Set reconf_cnt to INT_MAX for every assignment which belongs to the same
client identifier; before the reconf_cnt of the first handled assignment
was set by mistake breaking the retransmission logic.

Signed-off-by: Hans Dedecker <dedeckeh@gmail.com>
src/dhcpv6-ia.c
src/dhcpv6.h

index 847ede4329c62211fb90b8f25510e0da622d4b9b..04d92fe918524be0199782cff16b581a3ee11509 100644 (file)
      (addrs)[(i)].prefix > 64)
 
 static void free_dhcpv6_assignment(struct dhcpv6_assignment *c);
-static void reconf_timer(struct uloop_timeout *event);
-static struct uloop_timeout reconf_event = {.cb = reconf_timer};
+static void stop_reconf(struct dhcpv6_assignment *a);
+static void valid_until_cb(struct uloop_timeout *event);
+
+static struct uloop_timeout valid_until_timeout = {.cb = valid_until_cb};
 static uint32_t serial = 0;
 static uint8_t statemd5[16];
 
 int dhcpv6_ia_init(void)
 {
-       uloop_timeout_set(&reconf_event, 2000);
+       uloop_timeout_set(&valid_until_timeout, 1000);
        return 0;
 }
 
@@ -120,6 +122,7 @@ int setup_dhcpv6_ia_interface(struct interface *iface, bool enable)
                        }
 
                        if (a->head.next) {
+                               a->iface = iface;
                                if (lease->hostname[0]) {
                                        free(a->hostname);
                                        a->hostname = strdup(lease->hostname);
@@ -141,6 +144,9 @@ static void free_dhcpv6_assignment(struct dhcpv6_assignment *c)
        if (c->head.next)
                list_del(&c->head);
 
+       if (c->reconf_cnt)
+               stop_reconf(c);
+
        free(c->managed);
        free(c->hostname);
        free(c);
@@ -170,7 +176,7 @@ static size_t get_preferred_addr(const struct odhcpd_ipaddr *addrs, const size_t
        return m;
 }
 
-static int send_reconf(struct interface *iface, struct dhcpv6_assignment *assign)
+static int send_reconf(struct dhcpv6_assignment *assign)
 {
        struct {
                struct dhcpv6_client_header hdr;
@@ -202,6 +208,7 @@ static int send_reconf(struct interface *iface, struct dhcpv6_assignment *assign
                .clid_len = htons(assign->clid_len),
                .clid_data = {0},
        };
+       struct interface *iface = assign->iface;
 
        odhcpd_get_mac(iface, reconf_msg.mac);
        memcpy(reconf_msg.clid_data, assign->clid_data, assign->clid_len);
@@ -662,6 +669,36 @@ void dhcpv6_ia_preupdate(struct interface *iface)
                        apply_lease(iface, c, false);
 }
 
+static void reconf_timeout_cb(struct uloop_timeout *event)
+{
+       struct dhcpv6_assignment *a = container_of(event, struct dhcpv6_assignment, reconf_timer);
+
+       if (a->reconf_cnt > 0 && a->reconf_cnt < DHCPV6_REC_MAX_RC) {
+               send_reconf(a);
+               uloop_timeout_set(&a->reconf_timer,
+                                       DHCPV6_REC_TIMEOUT << a->reconf_cnt);
+               a->reconf_cnt++;
+       } else
+               stop_reconf(a);
+}
+
+static void start_reconf(struct dhcpv6_assignment *a)
+{
+       uloop_timeout_set(&a->reconf_timer,
+                               DHCPV6_REC_TIMEOUT << a->reconf_cnt);
+       a->reconf_timer.cb = reconf_timeout_cb;
+       a->reconf_cnt++;
+
+       send_reconf(a);
+}
+
+static void stop_reconf(struct dhcpv6_assignment *a)
+{
+       uloop_timeout_cancel(&a->reconf_timer);
+       a->reconf_cnt = 0;
+       a->reconf_timer.cb = NULL;
+}
+
 void dhcpv6_ia_postupdate(struct interface *iface)
 {
        if (iface->dhcpv6 != RELAYD_SERVER)
@@ -697,16 +734,14 @@ void dhcpv6_ia_postupdate(struct interface *iface)
                        apply_lease(iface, c, true);
 
                if (c->accept_reconf && c->reconf_cnt == 0) {
-                       c->reconf_cnt = 1;
-                       c->reconf_sent = now;
-                       send_reconf(iface, c);
+                       start_reconf(c);
 
                        /* Leave all other assignments of that client alone */
                        struct dhcpv6_assignment *a;
                        list_for_each_entry(a, &iface->ia_assignments, head)
                                if (a != c && a->clid_len == c->clid_len &&
                                                !memcmp(a->clid_data, c->clid_data, a->clid_len))
-                                       c->reconf_cnt = INT_MAX;
+                                       a->reconf_cnt = INT_MAX;
                }
        }
 
@@ -722,7 +757,7 @@ void dhcpv6_ia_postupdate(struct interface *iface)
        dhcpv6_write_statefile();
 }
 
-static void reconf_timer(struct uloop_timeout *event)
+static void valid_until_cb(struct uloop_timeout *event)
 {
        time_t now = odhcpd_time();
        struct interface *iface;
@@ -737,15 +772,10 @@ static void reconf_timer(struct uloop_timeout *event)
                                                (a->length == 128 && a->clid_len == 0))
                                        free_dhcpv6_assignment(a);
 
-                       } else if (a->reconf_cnt > 0 && a->reconf_cnt < 8 &&
-                                       now > a->reconf_sent + (1 << a->reconf_cnt)) {
-                               ++a->reconf_cnt;
-                               a->reconf_sent = now;
-                               send_reconf(iface, a);
                        }
                }
        }
-       uloop_timeout_set(event, 2000);
+       uloop_timeout_set(event, 1000);
 }
 
 static size_t append_reply(uint8_t *buf, size_t buflen, uint16_t status,
@@ -1124,8 +1154,7 @@ ssize_t dhcpv6_handle_ia(uint8_t *buf, size_t buflen, struct interface *iface,
                                a->clid_len = clid_len;
                                a->iaid = ia->iaid;
                                a->peer = *addr;
-                               a->reconf_cnt = 0;
-                               a->reconf_sent = 0;
+                               stop_reconf(a);
                                break;
                        }
                }
@@ -1152,6 +1181,7 @@ ssize_t dhcpv6_handle_ia(uint8_t *buf, size_t buflen, struct interface *iface,
                                        /* Set valid time to current time indicating  */
                                        /* assignment is not having infinite lifetime */
                                        a->valid_until = now;
+                                       a->iface = iface;
 
                                        if (first)
                                                memcpy(a->key, first->key, sizeof(a->key));
index e58cb69ad9144058f0a41157954cdc4755bfffef..de815cf6494f40bc76e631551a6fef4de683f6b3 100644 (file)
@@ -76,6 +76,9 @@
 
 #define DHCPV6_HOP_COUNT_LIMIT 32
 
+#define DHCPV6_REC_TIMEOUT     2000 /* msec */
+#define DHCPV6_REC_MAX_RC      8
+
 struct dhcpv6_client_header {
        uint8_t msg_type;
        uint8_t transaction_id[3];
@@ -139,17 +142,21 @@ struct dhcpv6_ia_addr {
 
 struct dhcpv6_assignment {
        struct list_head head;
+       struct interface *iface;
+
        struct sockaddr_in6 peer;
        time_t valid_until;
-       time_t reconf_sent;
+
+       struct uloop_timeout reconf_timer;
+       bool accept_reconf;
        int reconf_cnt;
-       char *hostname;
        uint8_t key[16];
+
+       char *hostname;
        uint32_t assigned;
        uint32_t iaid;
        uint8_t mac[6];
        uint8_t length; // length == 128 -> IA_NA, length <= 64 -> IA_PD
-       bool accept_reconf;
 
        struct odhcpd_ipaddr *managed;
        ssize_t managed_size;