odhcp6c: add -K option to set packet kernel priority
[project/odhcp6c.git] / src / dhcpv6.c
index 17866889e6f06f327c6c48bb81a030e7314e5106..ca5957e3611c7d08ad0e6a6feba37ffea250dd93 100644 (file)
@@ -1,5 +1,6 @@
 /**
  * Copyright (C) 2012-2014 Steven Barth <steven@midlink.org>
+ * Copyright (C) 2017-2018 Hans Dedecker <dedeckeh@gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License v2 as published by
@@ -15,6 +16,7 @@
 #include <time.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <inttypes.h>
 #include <stdlib.h>
 #include <signal.h>
 #include <limits.h>
 #include <unistd.h>
 #include <syslog.h>
 #include <stdbool.h>
+#include <ctype.h>
 #include <sys/time.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
+#include <arpa/inet.h>
 #include <netinet/in.h>
 
 #include <net/if.h>
 #include <net/ethernet.h>
 
 #include "odhcp6c.h"
+#ifdef USE_LIBUBOX
+#include <libubox/md5.h>
+#else
 #include "md5.h"
+#endif
 
 
 #define ALL_DHCPV6_RELAYS {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
@@ -51,9 +59,9 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
                const uint8_t transaction[3], enum dhcpv6_msg type,
                const struct in6_addr *daddr);
 
-static int dhcpv6_parse_ia(void *opt, void *end);
+static unsigned int dhcpv6_parse_ia(void *opt, void *end);
 
-static int dhcpv6_calc_refresh_timers(void);
+static unsigned 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);
@@ -71,8 +79,6 @@ static reply_handler dhcpv6_handle_rebind_reply;
 static reply_handler dhcpv6_handle_reconfigure;
 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, 0, "<POLL>",
@@ -91,16 +97,17 @@ static struct dhcpv6_retx dhcpv6_retx[_DHCPV6_MSG_MAX] = {
                        dhcpv6_handle_reply, NULL},
 };
 
-
 // Sockets
 static int sock = -1;
 static int ifindex = -1;
 static int64_t t1 = 0, t2 = 0, t3 = 0;
 
 // IA states
-static int request_prefix = -1;
 static enum odhcp6c_ia_mode na_mode = IA_MODE_NONE, pd_mode = IA_MODE_NONE;
+static bool stateful_only_mode = false;
 static bool accept_reconfig = false;
+// Server unicast address
+static struct in6_addr server_addr = IN6ADDR_ANY_INIT;
 
 // Reconfigure key
 static uint8_t reconf_key[16];
@@ -108,21 +115,101 @@ static uint8_t reconf_key[16];
 // client options
 static unsigned int client_options = 0;
 
+static uint32_t ntohl_unaligned(const uint8_t *data)
+{
+       uint32_t buf;
+
+       memcpy(&buf, data, sizeof(buf));
+       return ntohl(buf);
+}
+
+static char *dhcpv6_msg_to_str(enum dhcpv6_msg msg)
+{
+       switch (msg) {
+       case DHCPV6_MSG_SOLICIT:
+               return "SOLICIT";
+
+       case DHCPV6_MSG_ADVERT:
+               return "ADVERTISE";
+
+       case DHCPV6_MSG_REQUEST:
+               return "REQUEST";
+
+       case DHCPV6_MSG_RENEW:
+               return "RENEW";
+
+       case DHCPV6_MSG_REBIND:
+               return "REBIND";
+
+       case DHCPV6_MSG_REPLY:
+               return "REPLY";
+
+       case DHCPV6_MSG_RELEASE:
+               return "RELEASE";
+
+       case DHCPV6_MSG_DECLINE:
+               return "DECLINE";
+
+       case DHCPV6_MSG_RECONF:
+               return "RECONFIGURE";
+
+       case DHCPV6_MSG_INFO_REQ:
+               return "INFORMATION REQUEST";
+
+       default:
+               break;
+       }
+
+       return "UNKNOWN";
+}
+
+static char *dhcpv6_status_code_to_str(uint16_t code)
+{
+       switch (code) {
+       case DHCPV6_Success:
+               return "Success";
+
+       case DHCPV6_UnspecFail:
+               return "Unspecified Failure";
+
+       case DHCPV6_NoAddrsAvail:
+               return "No Address Available";
+
+       case DHCPV6_NoBinding:
+               return "No Binding";
+
+       case DHCPV6_NotOnLink:
+               return "Not On Link";
+
+       case DHCPV6_UseMulticast:
+               return "Use Multicast";
+
+       case DHCPV6_NoPrefixAvail:
+               return "No Prefix Available";
+
+       default:
+               break;
+       }
 
-int init_dhcpv6(const char *ifname, unsigned int options, int sol_timeout)
+       return "Unknown";
+}
+
+int init_dhcpv6(const char *ifname, unsigned int options, int sk_prio, int sol_timeout)
 {
        client_options = options;
        dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_timeo = sol_timeout;
 
        sock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
        if (sock < 0)
-               return -1;
+               goto failure;
 
        // Detect interface
        struct ifreq ifr;
-       strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+       memset(&ifr, 0, sizeof(ifr));
+       strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1);
        if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0)
-               return -1;
+               goto failure;
+
        ifindex = ifr.ifr_ifindex;
 
        // Create client DUID
@@ -171,11 +258,6 @@ int init_dhcpv6(const char *ifname, unsigned int options, int sol_timeout)
                        htons(DHCPV6_OPT_NTP_SERVER),
                        htons(DHCPV6_OPT_AFTR_NAME),
                        htons(DHCPV6_OPT_PD_EXCLUDE),
-                       htons(DHCPV6_OPT_SOL_MAX_RT),
-                       htons(DHCPV6_OPT_INF_MAX_RT),
-#ifdef EXT_PREFIX_CLASS
-                       htons(DHCPV6_OPT_PREFIX_CLASS),
-#endif
 #ifdef EXT_CER_ID
                        htons(DHCPV6_OPT_CER_ID),
 #endif
@@ -185,32 +267,52 @@ int init_dhcpv6(const char *ifname, unsigned int options, int sol_timeout)
                };
                odhcp6c_add_state(STATE_ORO, oro, sizeof(oro));
        }
+       // Required oro
+       uint16_t req_oro[] = {
+               htons(DHCPV6_OPT_INF_MAX_RT),
+               htons(DHCPV6_OPT_SOL_MAX_RT),
+               htons(DHCPV6_OPT_INFO_REFRESH),
+       };
+       odhcp6c_add_state(STATE_ORO, req_oro, sizeof(req_oro));
 
        // Configure IPv6-options
        int val = 1;
-       setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
-       setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
-       setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, sizeof(val));
-       setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname));
+       if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)) < 0)
+               goto failure;
+
+       if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0)
+               goto failure;
+
+       if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, sizeof(val)) < 0)
+               goto failure;
+
+       if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)) < 0)
+               goto failure;
+
+       if (setsockopt(sock, SOL_SOCKET, SO_PRIORITY, &sk_prio, sizeof(sk_prio)) < 0)
+               goto failure;
 
        struct sockaddr_in6 client_addr = { .sin6_family = AF_INET6,
                .sin6_port = htons(DHCPV6_CLIENT_PORT), .sin6_flowinfo = 0 };
+
        if (bind(sock, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0)
-               return -1;
+               goto failure;
 
        return 0;
+
+failure:
+       if (sock >= 0)
+               close(sock);
+
+       return -1;
 }
 
 enum {
        IOV_HDR=0,
        IOV_ORO,
-       IOV_ORO_REFRESH,
        IOV_CL_ID,
        IOV_SRV_ID,
-       IOV_VENDOR_CLASS_HDR,
-       IOV_VENDOR_CLASS,
-       IOV_USER_CLASS_HDR,
-       IOV_USER_CLASS,
+       IOV_OPTS,
        IOV_RECONF_ACCEPT,
        IOV_FQDN,
        IOV_HDR_IA_NA,
@@ -219,10 +321,20 @@ enum {
        IOV_TOTAL
 };
 
-void dhcpv6_set_ia_mode(enum odhcp6c_ia_mode na, enum odhcp6c_ia_mode pd)
+int dhcpv6_set_ia_mode(enum odhcp6c_ia_mode na, enum odhcp6c_ia_mode pd, bool stateful_only)
 {
+       int mode = DHCPV6_UNKNOWN;
+
        na_mode = na;
        pd_mode = pd;
+       stateful_only_mode = stateful_only;
+
+       if (na_mode == IA_MODE_NONE && pd_mode == IA_MODE_NONE)
+               mode = DHCPV6_STATELESS;
+       else if (na_mode == IA_MODE_FORCE || pd_mode == IA_MODE_FORCE)
+               mode = DHCPV6_STATEFUL;
+
+       return mode;
 }
 
 static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
@@ -242,7 +354,6 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
        fqdn.len = htons(fqdn_len - 4);
        fqdn.flags = 0;
 
-
        // Build Client ID
        size_t cl_id_len;
        void *cl_id = odhcp6c_get_state(STATE_CLIENT_ID, &cl_id_len);
@@ -266,17 +377,21 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
                for (size_t i = 0; i < n_prefixes; i++) {
                        struct dhcpv6_ia_hdr hdr_ia_pd = {
                                htons(DHCPV6_OPT_IA_PD),
-                               htons(sizeof(hdr_ia_pd) - 4 + sizeof(struct dhcpv6_ia_prefix)),
+                               htons(sizeof(hdr_ia_pd) - 4 +
+                                     sizeof(struct dhcpv6_ia_prefix) * !!request_prefixes[i].length),
                                request_prefixes[i].iaid, 0, 0
                        };
                        struct dhcpv6_ia_prefix pref = {
                                .type = htons(DHCPV6_OPT_IA_PREFIX),
-                               .len = htons(25), .prefix = request_prefixes[i].length
+                               .len = htons(sizeof(pref) - 4),
+                               .prefix = request_prefixes[i].length
                        };
                        memcpy(ia_pd + ia_pd_len, &hdr_ia_pd, sizeof(hdr_ia_pd));
                        ia_pd_len += sizeof(hdr_ia_pd);
-                       memcpy(ia_pd + ia_pd_len, &pref, sizeof(pref));
-                       ia_pd_len += sizeof(pref);
+                       if (request_prefixes[i].length) {
+                               memcpy(ia_pd + ia_pd_len, &pref, sizeof(pref));
+                               ia_pd_len += sizeof(pref);
+                       }
                }
        } else {
                struct odhcp6c_entry *e = odhcp6c_get_state(STATE_IA_PD, &ia_pd_entries);
@@ -357,9 +472,6 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
                }
        }
 
-       if (ia_pd_entries > 0)
-               request_prefix = 1;
-
        // Build IA_NAs
        size_t ia_na_entries, ia_na_len = 0;
        void *ia_na = NULL;
@@ -397,27 +509,39 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
                uint16_t length;
        } reconf_accept = {htons(DHCPV6_OPT_RECONF_ACCEPT), 0};
 
-       // Request Information Refresh
-       uint16_t oro_refresh = htons(DHCPV6_OPT_INFO_REFRESH);
+       // Option list
+       size_t opts_len;
+       void *opts = odhcp6c_get_state(STATE_OPTS, &opts_len);
 
-       // Build vendor-class option
-       size_t vendor_class_len, user_class_len;
-       struct dhcpv6_vendorclass *vendor_class = odhcp6c_get_state(STATE_VENDORCLASS, &vendor_class_len);
-       void *user_class = odhcp6c_get_state(STATE_USERCLASS, &user_class_len);
+       // Option Request List
+       size_t oro_entries, oro_len = 0;
+       uint16_t *oro, *s_oro = odhcp6c_get_state(STATE_ORO, &oro_entries);
 
-       struct {
-               uint16_t type;
-               uint16_t length;
-       } vendor_class_hdr = {htons(DHCPV6_OPT_VENDOR_CLASS), htons(vendor_class_len)};
+       oro_entries /= sizeof(*s_oro);
+       oro = alloca(oro_entries * sizeof(*oro));
 
-       struct {
-               uint16_t type;
-               uint16_t length;
-       } user_class_hdr = {htons(DHCPV6_OPT_USER_CLASS), htons(user_class_len)};
+       for (size_t i = 0; i < oro_entries; i++) {
+               struct odhcp6c_opt *opt = odhcp6c_find_opt(htons(s_oro[i]));
+
+               if (opt) {
+                       if (!(opt->flags & OPT_ORO))
+                               continue;
+
+                       if ((opt->flags & OPT_ORO_SOLICIT) && type != DHCPV6_MSG_SOLICIT)
+                               continue;
+
+                       if ((opt->flags & OPT_ORO_STATELESS) && type != DHCPV6_MSG_INFO_REQ)
+                               continue;
+
+                       if ((opt->flags & OPT_ORO_STATEFUL) && type == DHCPV6_MSG_INFO_REQ)
+                               continue;
+               }
+
+               oro[oro_len++] = s_oro[i];
+       }
+       oro_len *= sizeof(*oro);
 
        // Prepare Header
-       size_t oro_len;
-       void *oro = odhcp6c_get_state(STATE_ORO, &oro_len);
        struct {
                uint8_t type;
                uint8_t trid[3];
@@ -436,13 +560,9 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
        struct iovec iov[IOV_TOTAL] = {
                [IOV_HDR] = {&hdr, sizeof(hdr)},
                [IOV_ORO] = {oro, oro_len},
-               [IOV_ORO_REFRESH] = {&oro_refresh, 0},
                [IOV_CL_ID] = {cl_id, cl_id_len},
                [IOV_SRV_ID] = {srv_id, srv_id_len},
-               [IOV_VENDOR_CLASS_HDR] = {&vendor_class_hdr, vendor_class_len ? sizeof(vendor_class_hdr) : 0},
-               [IOV_VENDOR_CLASS] = {vendor_class, vendor_class_len},
-               [IOV_USER_CLASS_HDR] = {&user_class_hdr, user_class_len ? sizeof(user_class_hdr) : 0},
-               [IOV_USER_CLASS] = {user_class, user_class_len},
+               [IOV_OPTS] = { opts, opts_len },
                [IOV_RECONF_ACCEPT] = {&reconf_accept, sizeof(reconf_accept)},
                [IOV_FQDN] = {&fqdn, fqdn_len},
                [IOV_HDR_IA_NA] = {&hdr_ia_na, sizeof(hdr_ia_na)},
@@ -451,13 +571,8 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
        };
 
        size_t cnt = IOV_TOTAL;
-       if (type == DHCPV6_MSG_INFO_REQ) {
-               cnt = 9;
-               iov[IOV_ORO_REFRESH].iov_len = sizeof(oro_refresh);
-               hdr.oro_len = htons(oro_len + sizeof(oro_refresh));
-       } else if (!request_prefix) {
-               cnt = 13;
-       }
+       if (type == DHCPV6_MSG_INFO_REQ)
+               cnt = IOV_HDR_IA_NA;
 
        // Disable IAs if not used
        if (type != DHCPV6_MSG_SOLICIT && ia_na_len == 0)
@@ -478,18 +593,40 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
        struct msghdr msg = {.msg_name = &srv, .msg_namelen = sizeof(srv),
                        .msg_iov = iov, .msg_iovlen = cnt};
 
-       sendmsg(sock, &msg, 0);
-}
+       switch (type) {
+       case DHCPV6_MSG_REQUEST:
+       case DHCPV6_MSG_RENEW:
+       case DHCPV6_MSG_RELEASE:
+       case DHCPV6_MSG_DECLINE:
+               if (!IN6_IS_ADDR_UNSPECIFIED(&server_addr) &&
+                       odhcp6c_addr_in_scope(&server_addr)) {
+                       srv.sin6_addr = server_addr;
+                       if (!IN6_IS_ADDR_LINKLOCAL(&server_addr))
+                               srv.sin6_scope_id = 0;
+               }
+               break;
+       default:
+               break;
+       }
 
+       if (sendmsg(sock, &msg, 0) < 0) {
+               char in6_str[INET6_ADDRSTRLEN];
+
+               syslog(LOG_ERR, "Failed to send %s message to %s (%s)",
+                       dhcpv6_msg_to_str(type),
+                       inet_ntop(AF_INET6, (const void *)&srv.sin6_addr,
+                               in6_str, sizeof(in6_str)), strerror(errno));
+       }
+}
 
 static int64_t dhcpv6_rand_delay(int64_t time)
 {
        int random;
        odhcp6c_random(&random, sizeof(random));
+
        return (time * ((int64_t)random % 1000LL)) / 10000LL;
 }
 
-
 int dhcpv6_request(enum dhcpv6_msg type)
 {
        uint8_t rc = 0;
@@ -499,6 +636,7 @@ int dhcpv6_request(enum dhcpv6_msg type)
        if (retx->delay) {
                struct timespec ts = {0, 0};
                ts.tv_nsec = (dhcpv6_rand_delay((10000 * DHCPV6_REQ_DELAY) / 2) + (1000 * DHCPV6_REQ_DELAY) / 2) * 1000000;
+
                while (nanosleep(&ts, &ts) < 0 && errno == EINTR);
        }
 
@@ -512,8 +650,8 @@ int dhcpv6_request(enum dhcpv6_msg type)
        if (timeout == 0)
                return -1;
 
-       syslog(LOG_NOTICE, "Starting %s transaction (timeout %llus, max rc %d)",
-                       retx->name, (unsigned long long)timeout, retx->max_rc);
+       syslog(LOG_NOTICE, "Starting %s transaction (timeout %"PRIu64"s, max rc %d)",
+                       retx->name, timeout, retx->max_rc);
 
        uint64_t start = odhcp6c_get_milli_time(), round_start = start, elapsed;
 
@@ -521,6 +659,7 @@ int dhcpv6_request(enum dhcpv6_msg type)
        uint8_t trid[3] = {0, 0, 0};
        if (type != DHCPV6_MSG_UNKNOWN)
                odhcp6c_random(trid, sizeof(trid));
+
        ssize_t len = -1;
        int64_t rto = 0;
 
@@ -533,8 +672,7 @@ int dhcpv6_request(enum dhcpv6_msg type)
                                delay = dhcpv6_rand_delay(retx->init_timeo * 1000);
 
                        rto = (retx->init_timeo * 1000 + delay);
-               }
-               else
+               } else
                        rto = (2 * rto + dhcpv6_rand_delay(rto));
 
                if (retx->max_timeo && (rto >= retx->max_timeo * 1000))
@@ -550,10 +688,15 @@ 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_SOLICIT)
-                               syslog(LOG_NOTICE, "Send %s message (elapsed %llums, rc %d)",
-                                               retx->name, (unsigned long long)elapsed, rc);
+               switch (type) {
+               case DHCPV6_MSG_UNKNOWN:
+                       break;
+               default:
+                       syslog(LOG_NOTICE, "Send %s message (elapsed %"PRIu64"ms, rc %d)",
+                                       retx->name, elapsed, rc);
+                       // Fall through
+               case DHCPV6_MSG_SOLICIT:
+               case DHCPV6_MSG_INFO_REQ:
                        dhcpv6_send(type, trid, elapsed / 10);
                        rc++;
                }
@@ -561,14 +704,18 @@ int dhcpv6_request(enum dhcpv6_msg type)
                // Receive rounds
                for (; len < 0 && (round_start < round_end);
                                round_start = odhcp6c_get_milli_time()) {
-                       uint8_t buf[1536], cmsg_buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+                       uint8_t buf[1536];
+                       union {
+                               struct cmsghdr hdr;
+                               uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+                       } cmsg_buf;
                        struct iovec iov = {buf, sizeof(buf)};
                        struct sockaddr_in6 addr;
                        struct msghdr msg = {.msg_name = &addr, .msg_namelen = sizeof(addr),
-                                       .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsg_buf,
+                                       .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsg_buf.buf,
                                        .msg_controllen = sizeof(cmsg_buf)};
                        struct in6_pktinfo *pktinfo = NULL;
-
+                       const struct dhcpv6_header *hdr = (const struct dhcpv6_header *)buf;
 
                        // Check for pending signal
                        if (odhcp6c_signal_process())
@@ -577,8 +724,10 @@ int dhcpv6_request(enum dhcpv6_msg type)
                        // Set timeout for receiving
                        uint64_t t = round_end - round_start;
                        struct timeval tv = {t / 1000, (t % 1000) * 1000};
-                       setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
-                                       &tv, sizeof(tv));
+                       if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
+                                       &tv, sizeof(tv)) < 0)
+                               syslog(LOG_ERR, "setsockopt SO_RCVTIMEO failed (%s)",
+                                               strerror(errno));
 
                        // Receive cycle
                        len = recvmsg(sock, &msg, 0);
@@ -610,8 +759,8 @@ 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 "
-                                       "%llums", (unsigned long long)elapsed);
+                       syslog(LOG_NOTICE, "Got a valid %s after %"PRIu64"ms",
+                              dhcpv6_msg_to_str(hdr->msg_type), elapsed);
 
                        if (retx->handler_reply)
                                len = retx->handler_reply(type, rc, opt, opt_end, &addr);
@@ -623,7 +772,7 @@ int dhcpv6_request(enum dhcpv6_msg type)
                // Allow
                if (retx->handler_finish)
                        len = retx->handler_finish();
-       } while (len < 0 && ((timeout == UINT32_MAX) || (elapsed / 1000 < timeout)) && 
+       } while (len < 0 && ((timeout == UINT32_MAX) || (elapsed / 1000 < timeout)) &&
                        (!retx->max_rc || rc < retx->max_rc));
        return len;
 }
@@ -642,12 +791,13 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
                if (rep->msg_type != DHCPV6_MSG_ADVERT &&
                                rep->msg_type != DHCPV6_MSG_REPLY)
                        return false;
+
        } else if (type == DHCPV6_MSG_UNKNOWN) {
                if (!accept_reconfig || rep->msg_type != DHCPV6_MSG_RECONF)
                        return false;
-       } else if (rep->msg_type != DHCPV6_MSG_REPLY) {
+
+       } else if (rep->msg_type != DHCPV6_MSG_REPLY)
                return false;
-       }
 
        uint8_t *end = ((uint8_t*)buf) + len, *odata = NULL,
                rcmsg = DHCPV6_MSG_UNKNOWN;
@@ -676,7 +826,8 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
                                continue;
 
                        md5_ctx_t md5;
-                       uint8_t serverhash[16], secretbytes[64], hash[16];
+                       uint8_t serverhash[16], secretbytes[64];
+                       uint32_t hash[4];
                        memcpy(serverhash, r->key, sizeof(serverhash));
                        memset(r->key, 0, sizeof(r->key));
 
@@ -708,12 +859,10 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
                        ia_present = true;
                        if (olen < -4 + sizeof(struct dhcpv6_ia_hdr))
                                options_valid = false;
-               }
-               else if ((otype == DHCPV6_OPT_IA_ADDR) || (otype == DHCPV6_OPT_IA_PREFIX) ||
-                               (otype == DHCPV6_OPT_PD_EXCLUDE)) {
+               } else if ((otype == DHCPV6_OPT_IA_ADDR) || (otype == DHCPV6_OPT_IA_PREFIX) ||
+                               (otype == DHCPV6_OPT_PD_EXCLUDE))
                        // Options are not allowed on global level
                        options_valid = false;
-               }
        }
 
        if (!options_valid || ((odata + olen) > end))
@@ -723,7 +872,7 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
                return false;
 
        if (rep->msg_type == DHCPV6_MSG_RECONF) {
-               if ((rcmsg != DHCPV6_MSG_RENEW && rcmsg != DHCPV6_MSG_INFO_REQ) ||
+               if ((rcmsg != DHCPV6_MSG_RENEW && rcmsg != DHCPV6_MSG_REBIND && rcmsg != DHCPV6_MSG_INFO_REQ) ||
                        (rcmsg == DHCPV6_MSG_INFO_REQ && ia_present) ||
                        !rcauth_ok || IN6_IS_ADDR_MULTICAST(daddr))
                        return false;
@@ -732,32 +881,64 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
        return clientid_ok && serverid_ok;
 }
 
-
 int dhcpv6_poll_reconfigure(void)
 {
        int ret = dhcpv6_request(DHCPV6_MSG_UNKNOWN);
-       if (ret != -1)
+
+       switch (ret) {
+       /*
+        * Only RENEW/REBIND/INFORMATION REQUEST
+        * message transmission can be requested
+        * by a RECONFIGURE
+        */
+       case DHCPV6_MSG_RENEW:
+       case DHCPV6_MSG_REBIND:
+       case DHCPV6_MSG_INFO_REQ:
                ret = dhcpv6_request(ret);
+               break;
+
+       default:
+               break;
+       }
 
        return ret;
 }
 
-
-static int dhcpv6_handle_reconfigure(_unused enum dhcpv6_msg orig, const int rc,
+static int dhcpv6_handle_reconfigure(enum dhcpv6_msg orig, const int rc,
                const void *opt, const void *end, _unused const struct sockaddr_in6 *from)
 {
        uint16_t otype, olen;
-       uint8_t *odata, msg = DHCPV6_MSG_RENEW;
-       dhcpv6_for_each_option(opt, end, otype, olen, odata)
-               if (otype == DHCPV6_OPT_RECONF_MESSAGE && olen == 1 && (
-                               odata[0] == DHCPV6_MSG_RENEW ||
-                               odata[0] == DHCPV6_MSG_INFO_REQ))
-                       msg = odata[0];
-
-       dhcpv6_handle_reply(DHCPV6_MSG_UNKNOWN, rc, NULL, NULL, NULL);
-       return msg;
-}
+       uint8_t *odata;
+       enum dhcpv6_msg msg = DHCPV6_MSG_UNKNOWN;
+
+       dhcpv6_for_each_option(opt, end, otype, olen, odata) {
+               if (otype == DHCPV6_OPT_RECONF_MESSAGE && olen == 1) {
+                       switch (odata[0]) {
+                       case DHCPV6_MSG_REBIND:
+                               if (t2 != UINT32_MAX)
+                                       t2 = 0;
+                       // Fall through
+                       case DHCPV6_MSG_RENEW:
+                               if (t1 != UINT32_MAX)
+                                       t1 = 0;
+                       // Fall through
+                       case DHCPV6_MSG_INFO_REQ:
+                               msg = odata[0];
+                               syslog(LOG_NOTICE, "Need to respond with %s in reply to %s",
+                                      dhcpv6_msg_to_str(msg), dhcpv6_msg_to_str(DHCPV6_MSG_RECONF));
+                               break;
+
+                       default:
+                               break;
+                       }
+               }
+       }
+
+       if (msg != DHCPV6_MSG_UNKNOWN)
+               dhcpv6_handle_reply(orig, rc, NULL, NULL, NULL);
 
+       return (msg == DHCPV6_MSG_UNKNOWN? -1: (int)msg);
+}
 
 // Collect all advertised servers
 static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
@@ -766,14 +947,15 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
        uint16_t olen, otype;
        uint8_t *odata, pref = 0;
        struct dhcpv6_server_cand cand = {false, false, 0, 0, {0},
-                                       DHCPV6_SOL_MAX_RT,
+                                       IN6ADDR_ANY_INIT, DHCPV6_SOL_MAX_RT,
                                        DHCPV6_INF_MAX_RT, NULL, NULL, 0, 0};
        bool have_na = false;
        int have_pd = 0;
 
        dhcpv6_for_each_option(opt, end, otype, olen, odata) {
                if (orig == DHCPV6_MSG_SOLICIT &&
-                               (otype == DHCPV6_OPT_IA_PD || otype == DHCPV6_OPT_IA_NA) &&
+                               ((otype == DHCPV6_OPT_IA_PD && pd_mode != IA_MODE_NONE) ||
+                                (otype == DHCPV6_OPT_IA_NA && na_mode != IA_MODE_NONE)) &&
                                olen > -4 + sizeof(struct dhcpv6_ia_hdr)) {
                        struct dhcpv6_ia_hdr *ia_hdr = (void*)(&odata[-4]);
                        dhcpv6_parse_ia(ia_hdr, odata + olen + sizeof(*ia_hdr));
@@ -782,34 +964,29 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
                if (otype == DHCPV6_OPT_SERVERID && olen <= 130) {
                        memcpy(cand.duid, odata, olen);
                        cand.duid_len = olen;
-               } 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
-                               cand.preference -= 2000;
-                               break;
-
-                       default :
-                               break;
-                       }
                } else if (otype == DHCPV6_OPT_PREF && olen >= 1 &&
                                cand.preference >= 0) {
                        cand.preference = pref = odata[0];
+               } else if (otype == DHCPV6_OPT_UNICAST && olen == sizeof(cand.server_addr)) {
+                       if (!(client_options & DHCPV6_IGNORE_OPT_UNICAST))
+                               cand.server_addr = *(struct in6_addr *)odata;
+
                } else if (otype == DHCPV6_OPT_RECONF_ACCEPT) {
                        cand.wants_reconfigure = true;
                } else if (otype == DHCPV6_OPT_SOL_MAX_RT && olen == 4) {
-                       uint32_t sol_max_rt = ntohl(*((uint32_t *)odata));
+                       uint32_t sol_max_rt = ntohl_unaligned(odata);
                        if (sol_max_rt >= DHCPV6_SOL_MAX_RT_MIN &&
                                        sol_max_rt <= DHCPV6_SOL_MAX_RT_MAX)
                                cand.sol_max_rt = sol_max_rt;
+
                } else if (otype == DHCPV6_OPT_INF_MAX_RT && olen == 4) {
-                       uint32_t inf_max_rt = ntohl(*((uint32_t *)odata));
+                       uint32_t inf_max_rt = ntohl_unaligned(odata);
                        if (inf_max_rt >= DHCPV6_INF_MAX_RT_MIN &&
                                        inf_max_rt <= DHCPV6_INF_MAX_RT_MAX)
                                cand.inf_max_rt = inf_max_rt;
-               } else if (otype == DHCPV6_OPT_IA_PD && request_prefix) {
+
+               } else if (otype == DHCPV6_OPT_IA_PD &&
+                                       olen >= -4 + sizeof(struct dhcpv6_ia_hdr)) {
                        struct dhcpv6_ia_hdr *h = (struct dhcpv6_ia_hdr*)&odata[-4];
                        uint8_t *oend = odata + olen, *d;
                        dhcpv6_for_each_option(&h[1], oend, otype, olen, d) {
@@ -819,17 +996,21 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
                                        have_pd = p->prefix;
                                }
                        }
-               } else if (otype == DHCPV6_OPT_IA_NA) {
+               } else if (otype == DHCPV6_OPT_IA_NA &&
+                                       olen >= -4 + sizeof(struct dhcpv6_ia_hdr)) {
                        struct dhcpv6_ia_hdr *h = (struct dhcpv6_ia_hdr*)&odata[-4];
                        uint8_t *oend = odata + olen, *d;
-                       dhcpv6_for_each_option(&h[1], oend, otype, olen, d)
+
+                       dhcpv6_for_each_option(&h[1], oend, otype, olen, d) {
                                if (otype == DHCPV6_OPT_IA_ADDR &&
                                                olen >= -4 + sizeof(struct dhcpv6_ia_addr))
                                        have_na = true;
+                       }
                }
        }
 
-       if ((!have_na && na_mode == IA_MODE_FORCE) ||
+       if ((stateful_only_mode && !have_na && !have_pd) ||
+                       (!have_na && na_mode == IA_MODE_FORCE) ||
                        (!have_pd && pd_mode == IA_MODE_FORCE)) {
                /*
                 * RFC7083 states to process the SOL_MAX_RT and
@@ -862,13 +1043,11 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
        return (rc > 1 || (pref == 255 && cand.preference > 0)) ? 1 : -1;
 }
 
-
 static int dhcpv6_commit_advert(void)
 {
        return dhcpv6_promote_server_cand();
 }
 
-
 static int dhcpv6_handle_rebind_reply(enum dhcpv6_msg orig, const int rc,
                const void *opt, const void *end, const struct sockaddr_in6 *from)
 {
@@ -879,7 +1058,6 @@ static int dhcpv6_handle_rebind_reply(enum dhcpv6_msg orig, const int rc,
        return dhcpv6_handle_reply(orig, rc, opt, end, from);
 }
 
-
 static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
                const void *opt, const void *end, const struct sockaddr_in6 *from)
 {
@@ -887,9 +1065,11 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
        uint16_t otype, olen;
        uint32_t refresh = 86400;
        int ret = 1;
+       unsigned int state_IAs;
+       unsigned int updated_IAs = 0;
        bool handled_status_codes[_DHCPV6_Status_Max] = { false, };
 
-       odhcp6c_expire();
+       odhcp6c_expire(true);
 
        if (orig == DHCPV6_MSG_UNKNOWN) {
                static time_t last_update = 0;
@@ -937,208 +1117,243 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
                odhcp6c_clear_state(STATE_S46_MAPE);
                odhcp6c_clear_state(STATE_S46_LW);
                odhcp6c_clear_state(STATE_PASSTHRU);
-       }
+               odhcp6c_clear_state(STATE_CUSTOM_OPTS);
 
-       // Parse and find all matching IAs
-       dhcpv6_for_each_option(opt, end, otype, olen, odata) {
-               bool passthru = true;
+               // Parse and find all matching IAs
+               dhcpv6_for_each_option(opt, end, otype, olen, odata) {
+                       struct odhcp6c_opt *dopt = odhcp6c_find_opt(otype);
 
-               if ((otype == DHCPV6_OPT_IA_PD || otype == DHCPV6_OPT_IA_NA)
-                               && olen > -4 + sizeof(struct dhcpv6_ia_hdr)) {
-                       struct dhcpv6_ia_hdr *ia_hdr = (void*)(&odata[-4]);
+                       if ((otype == DHCPV6_OPT_IA_PD || otype == DHCPV6_OPT_IA_NA)
+                                       && olen > -4 + sizeof(struct dhcpv6_ia_hdr)) {
+                               struct dhcpv6_ia_hdr *ia_hdr = (void*)(&odata[-4]);
 
-                       // Test ID
-                       if (ia_hdr->iaid != htonl(1) && otype == DHCPV6_OPT_IA_NA)
-                               continue;
+                               if ((na_mode == IA_MODE_NONE && otype == DHCPV6_OPT_IA_NA) ||
+                                       (pd_mode == IA_MODE_NONE && otype == DHCPV6_OPT_IA_PD))
+                                       continue;
 
-                       uint16_t code = DHCPV6_Success;
-                       uint16_t stype, slen;
-                       uint8_t *sdata;
-                       // Get and handle status code
-                       dhcpv6_for_each_option(&ia_hdr[1], odata + olen,
-                                       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;
+                               // Test ID
+                               if (ia_hdr->iaid != htonl(1) && otype == DHCPV6_OPT_IA_NA)
+                                       continue;
 
-                                       code = ((int)sdata[0]) << 8 | ((int)sdata[1]);
+                               uint16_t code = DHCPV6_Success;
+                               uint16_t stype, slen;
+                               uint8_t *sdata;
+                               // Get and handle status code
+                               dhcpv6_for_each_option(&ia_hdr[1], odata + olen,
+                                               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 (code == DHCPV6_Success)
-                                               continue;
+                                               code = ((int)sdata[0]) << 8 | ((int)sdata[1]);
 
-                                       dhcpv6_handle_ia_status_code(orig, ia_hdr,
-                                               code, mdata, mlen, handled_status_codes, &ret);
+                                               if (code == DHCPV6_Success)
+                                                       continue;
 
+                                               dhcpv6_handle_ia_status_code(orig, ia_hdr,
+                                                       code, mdata, mlen, handled_status_codes, &ret);
 
-                                       if (ret > 0)
-                                               return ret;
-                                       break;
+                                               if (ret > 0)
+                                                       return ret;
+
+                                               break;
+                                       }
                                }
-                       }
 
-                       if (code != DHCPV6_Success)
-                               continue;
+                               if (code != DHCPV6_Success)
+                                       continue;
+
+                               updated_IAs += dhcpv6_parse_ia(ia_hdr, odata + olen);
+                       } else if (otype == DHCPV6_OPT_UNICAST && olen == sizeof(server_addr)) {
+                               if (!(client_options & DHCPV6_IGNORE_OPT_UNICAST))
+                                       server_addr = *(struct in6_addr *)odata;
 
-                       dhcpv6_parse_ia(ia_hdr, odata + olen + sizeof(*ia_hdr));
-                       passthru = false;
-               } 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]);
-
-                       dhcpv6_handle_status_code(orig, code, mdata, mlen, &ret);
-                       passthru = false;
-               }
-               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) {
-                       odhcp6c_add_state(STATE_SEARCH, odata, olen);
-                       passthru = false;
-               } else if (otype == DHCPV6_OPT_SNTP_SERVERS) {
-                       if (olen % 16 == 0)
-                               odhcp6c_add_state(STATE_SNTP_IP, odata, olen);
-               } else if (otype == DHCPV6_OPT_NTP_SERVER) {
-                       uint16_t stype, slen;
-                       uint8_t *sdata;
-                       // Test status and bail if error
-                       dhcpv6_for_each_option(odata, odata + olen,
-                                       stype, slen, sdata) {
-                               if (slen == 16 && (stype == NTP_MC_ADDR ||
-                                               stype == NTP_SRV_ADDR))
-                                       odhcp6c_add_state(STATE_NTP_IP,
-                                                       sdata, slen);
-                               else if (slen > 0 && stype == NTP_SRV_FQDN)
-                                       odhcp6c_add_state(STATE_NTP_FQDN,
-                                                       sdata, slen);
-                       }
-               } else if (otype == DHCPV6_OPT_SIP_SERVER_A) {
-                       if (olen == 16)
-                               odhcp6c_add_state(STATE_SIP_IP, odata, olen);
-               } 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) {
-                       refresh = ntohl(*((uint32_t*)odata));
-                       passthru = false;
-               } else if (otype == DHCPV6_OPT_AUTH) {
-                       if (olen == -4 + sizeof(struct dhcpv6_auth_reconfigure)) {
-                               struct dhcpv6_auth_reconfigure *r = (void*)&odata[-4];
-                               if (r->protocol == 3 && r->algorithm == 1 &&
-                                               r->reconf_type == 1)
-                                       memcpy(reconf_key, r->key, sizeof(r->key));
                        }
-                       passthru = false;
-               } else if (otype == DHCPV6_OPT_AFTR_NAME && olen > 3) {
-                       size_t cur_len;
-                       odhcp6c_get_state(STATE_AFTR_NAME, &cur_len);
-                       if (cur_len == 0)
-                               odhcp6c_add_state(STATE_AFTR_NAME, odata, olen);
-                       passthru = false;
-               } else if (otype == DHCPV6_OPT_SOL_MAX_RT && olen == 4) {
-                       uint32_t sol_max_rt = ntohl(*((uint32_t *)odata));
-                       if (sol_max_rt >= DHCPV6_SOL_MAX_RT_MIN &&
-                                       sol_max_rt <= DHCPV6_SOL_MAX_RT_MAX)
-                               dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_timeo = sol_max_rt;
-                       passthru = false;
-               } else if (otype == DHCPV6_OPT_INF_MAX_RT && olen == 4) {
-                       uint32_t inf_max_rt = ntohl(*((uint32_t *)odata));
-                       if (inf_max_rt >= DHCPV6_INF_MAX_RT_MIN &&
-                                       inf_max_rt <= DHCPV6_INF_MAX_RT_MAX)
-                               dhcpv6_retx[DHCPV6_MSG_INFO_REQ].max_timeo = inf_max_rt;
-                       passthru = false;
-#ifdef EXT_CER_ID
-               } else if (otype == DHCPV6_OPT_CER_ID && olen == -4 +
-                               sizeof(struct dhcpv6_cer_id)) {
-                       struct dhcpv6_cer_id *cer_id = (void*)&odata[-4];
-                       struct in6_addr any = IN6ADDR_ANY_INIT;
-                       if (memcmp(&cer_id->addr, &any, sizeof(any)))
-                               odhcp6c_add_state(STATE_CER, &cer_id->addr, sizeof(any));
-                       passthru = false;
-#endif
-               } else if (otype == DHCPV6_OPT_S46_CONT_MAPT) {
-                       odhcp6c_add_state(STATE_S46_MAPT, odata, olen);
-                       passthru = false;
-               } else if (otype == DHCPV6_OPT_S46_CONT_MAPE) {
-                       size_t mape_len;
-                       odhcp6c_get_state(STATE_S46_MAPE, &mape_len);
-                       if (mape_len == 0)
-                               odhcp6c_add_state(STATE_S46_MAPE, odata, olen);
-                       passthru = false;
-               } else if (otype == DHCPV6_OPT_S46_CONT_LW) {
-                       odhcp6c_add_state(STATE_S46_LW, odata, olen);
-                       passthru = false;
-               } else if (otype == DHCPV6_OPT_CLIENTID ||
-                               otype == DHCPV6_OPT_SERVERID ||
-                               otype == DHCPV6_OPT_IA_TA ||
-                               otype == DHCPV6_OPT_PREF ||
-                               otype == DHCPV6_OPT_UNICAST ||
-                               otype == DHCPV6_OPT_FQDN ||
-                               otype == DHCPV6_OPT_RECONF_ACCEPT) {
-                       passthru = false;
-               } else {
-                       odhcp6c_add_state(STATE_CUSTOM_OPTS, &odata[-4], olen + 4);
+                       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]);
+
+                               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)
+                               odhcp6c_add_state(STATE_SEARCH, odata, olen);
+                       else if (otype == DHCPV6_OPT_SNTP_SERVERS) {
+                               if (olen % 16 == 0)
+                                       odhcp6c_add_state(STATE_SNTP_IP, odata, olen);
+                       } else if (otype == DHCPV6_OPT_NTP_SERVER) {
+                               uint16_t stype, slen;
+                               uint8_t *sdata;
+                               // Test status and bail if error
+                               dhcpv6_for_each_option(odata, odata + olen,
+                                               stype, slen, sdata) {
+                                       if (slen == 16 && (stype == NTP_MC_ADDR ||
+                                                       stype == NTP_SRV_ADDR))
+                                               odhcp6c_add_state(STATE_NTP_IP,
+                                                               sdata, slen);
+                                       else if (slen > 0 && stype == NTP_SRV_FQDN)
+                                               odhcp6c_add_state(STATE_NTP_FQDN,
+                                                               sdata, slen);
+                               }
+                       } else if (otype == DHCPV6_OPT_SIP_SERVER_A) {
+                               if (olen == 16)
+                                       odhcp6c_add_state(STATE_SIP_IP, odata, olen);
+                       } 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) {
+                               refresh = ntohl_unaligned(odata);
+                       } else if (otype == DHCPV6_OPT_AUTH) {
+                               if (olen == -4 + sizeof(struct dhcpv6_auth_reconfigure)) {
+                                       struct dhcpv6_auth_reconfigure *r = (void*)&odata[-4];
+                                       if (r->protocol == 3 && r->algorithm == 1 &&
+                                                       r->reconf_type == 1)
+                                               memcpy(reconf_key, r->key, sizeof(r->key));
+                               }
+                       } else if (otype == DHCPV6_OPT_AFTR_NAME && olen > 3) {
+                               size_t cur_len;
+                               odhcp6c_get_state(STATE_AFTR_NAME, &cur_len);
+                               if (cur_len == 0)
+                                       odhcp6c_add_state(STATE_AFTR_NAME, odata, olen);
+                       } else if (otype == DHCPV6_OPT_SOL_MAX_RT && olen == 4) {
+                               uint32_t sol_max_rt = ntohl_unaligned(odata);
+                               if (sol_max_rt >= DHCPV6_SOL_MAX_RT_MIN &&
+                                               sol_max_rt <= DHCPV6_SOL_MAX_RT_MAX)
+                                       dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_timeo = sol_max_rt;
+                       } else if (otype == DHCPV6_OPT_INF_MAX_RT && olen == 4) {
+                               uint32_t inf_max_rt = ntohl_unaligned(odata);
+                               if (inf_max_rt >= DHCPV6_INF_MAX_RT_MIN &&
+                                               inf_max_rt <= DHCPV6_INF_MAX_RT_MAX)
+                                       dhcpv6_retx[DHCPV6_MSG_INFO_REQ].max_timeo = inf_max_rt;
+       #ifdef EXT_CER_ID
+                       } else if (otype == DHCPV6_OPT_CER_ID && olen == -4 +
+                                       sizeof(struct dhcpv6_cer_id)) {
+                               struct dhcpv6_cer_id *cer_id = (void*)&odata[-4];
+                               struct in6_addr any = IN6ADDR_ANY_INIT;
+                               if (memcmp(&cer_id->addr, &any, sizeof(any)))
+                                       odhcp6c_add_state(STATE_CER, &cer_id->addr, sizeof(any));
+       #endif
+                       } else if (otype == DHCPV6_OPT_S46_CONT_MAPT) {
+                               odhcp6c_add_state(STATE_S46_MAPT, odata, olen);
+                       } else if (otype == DHCPV6_OPT_S46_CONT_MAPE) {
+                               size_t mape_len;
+                               odhcp6c_get_state(STATE_S46_MAPE, &mape_len);
+                               if (mape_len == 0)
+                                       odhcp6c_add_state(STATE_S46_MAPE, odata, olen);
+                       } else if (otype == DHCPV6_OPT_S46_CONT_LW) {
+                               odhcp6c_add_state(STATE_S46_LW, odata, olen);
+                       } else
+                               odhcp6c_add_state(STATE_CUSTOM_OPTS, &odata[-4], olen + 4);
+
+                       if (!dopt || !(dopt->flags & OPT_NO_PASSTHRU))
+                               odhcp6c_add_state(STATE_PASSTHRU, &odata[-4], olen + 4);
                }
-
-               if (passthru)
-                       odhcp6c_add_state(STATE_PASSTHRU, &odata[-4], olen + 4);
        }
 
-       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;
-
-                       case DHCPV6_MSG_REQUEST:
-                               // All server candidates can be cleared if not yet bound
-                               if (!odhcp6c_is_bound())
-                                       dhcpv6_clear_all_server_cand();
+       // Bail out if fatal status code was received
+       if (ret <= 0)
+               return ret;
+
+       switch (orig) {
+       case DHCPV6_MSG_REQUEST:
+       case DHCPV6_MSG_REBIND:
+       case DHCPV6_MSG_RENEW:
+               state_IAs = dhcpv6_calc_refresh_timers();
+               // In case there're no state IA entries
+               // keep sending request/renew/rebind messages
+               if (state_IAs == 0) {
+                       ret = 0;
+                       break;
+               }
 
-                       default :
-                               break;
+               if (orig == DHCPV6_MSG_REQUEST) {
+                       // All server candidates can be cleared if not yet bound
+                       if (!odhcp6c_is_bound())
+                               dhcpv6_clear_all_server_cand();
+
+                       odhcp6c_clear_state(STATE_SERVER_ADDR);
+                       odhcp6c_add_state(STATE_SERVER_ADDR, &from->sin6_addr, 16);
+               } else if (orig == DHCPV6_MSG_RENEW) {
+                       // Send further renews if T1 is not set and if
+                       // there're IAs which were not in the Reply message
+                       if (!t1 && state_IAs != updated_IAs) {
+                               if (updated_IAs)
+                                       // Publish updates
+                                       script_call("updated", 0, false);
+
+                               /*
+                                * RFC8415 states following in §18.2.10.1 :
+                                * Sends a Renew/Rebind if any of the IAs are not in the Reply
+                                * message, but as this likely indicates that the server that
+                                * responded does not support that IA type, sending immediately is
+                                * unlikely to produce a different result.  Therefore, the client
+                                * MUST rate-limit its transmissions (see Section 14.1) and MAY just
+                                * wait for the normal retransmission time (as if the Reply message
+                                * had not been received).  The client continues to use other
+                                * bindings for which the server did return information
+                                */
+                               ret = -1;
                        }
-
-                       if (orig == DHCPV6_MSG_REBIND || orig == DHCPV6_MSG_REQUEST) {
-                               odhcp6c_clear_state(STATE_SERVER_ADDR);
-                               odhcp6c_add_state(STATE_SERVER_ADDR, &from->sin6_addr, 16);
+               } else if (orig == DHCPV6_MSG_REBIND) {
+                       odhcp6c_clear_state(STATE_SERVER_ADDR);
+                       odhcp6c_add_state(STATE_SERVER_ADDR, &from->sin6_addr, 16);
+
+                       // Send further rebinds if T1 and T2 is not set and if
+                       // there're IAs which were not in the Reply message
+                       if (!t1 && !t2 && state_IAs != updated_IAs) {
+                               if (updated_IAs)
+                                       // Publish updates
+                                       script_call("updated", 0, false);
+
+                               /*
+                                * RFC8415 states following in §18.2.10.1 :
+                                * Sends a Renew/Rebind if any of the IAs are not in the Reply
+                                * message, but as this likely indicates that the server that
+                                * responded does not support that IA type, sending immediately is
+                                * unlikely to produce a different result.  Therefore, the client
+                                * MUST rate-limit its transmissions (see Section 14.1) and MAY just
+                                * wait for the normal retransmission time (as if the Reply message
+                                * had not been received).  The client continues to use other
+                                * bindings for which the server did return information
+                                */
+                               ret = -1;
                        }
                }
-       }
-       else if (ret > 0) {
+               break;
+
+       case DHCPV6_MSG_INFO_REQ:
                // All server candidates can be cleared if not yet bound
                if (!odhcp6c_is_bound())
                        dhcpv6_clear_all_server_cand();
 
                t1 = refresh;
+               break;
+
+       default:
+               break;
        }
 
        return ret;
 }
 
-
-static int dhcpv6_parse_ia(void *opt, void *end)
+static unsigned int dhcpv6_parse_ia(void *opt, void *end)
 {
        struct dhcpv6_ia_hdr *ia_hdr = (struct dhcpv6_ia_hdr *)opt;
-       int parsed_ia = 0;
+       unsigned int updated_IAs = 0;
        uint32_t t1, t2;
        uint16_t otype, olen;
        uint8_t *odata;
+       char buf[INET6_ADDRSTRLEN];
 
        t1 = ntohl(ia_hdr->t1);
        t2 = ntohl(ia_hdr->t2);
 
-       if (t1 > t2)
+       if (t1 > t2 && t1 > 0 && t2 > 0)
                return 0;
 
+       syslog(LOG_INFO, "%s %04x T1 %d T2 %d", ntohs(ia_hdr->type) == DHCPV6_OPT_IA_PD ? "IA_PD" : "IA_NA", ntohl(ia_hdr->iaid), t1, t2);
+
        // Update address IA
        dhcpv6_for_each_option(&ia_hdr[1], end, otype, olen, odata) {
                struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0,
@@ -1167,14 +1382,6 @@ static int dhcpv6_parse_ia(void *opt, void *end)
                        uint16_t stype, slen;
                        uint8_t *sdata;
 
-#ifdef EXT_PREFIX_CLASS
-                       // Find prefix class, if any
-                       dhcpv6_for_each_option(&prefix[1], odata + olen,
-                                       stype, slen, sdata)
-                               if (stype == DHCPV6_OPT_PREFIX_CLASS && slen == 2)
-                                       entry.class = sdata[0] << 8 | sdata[1];
-#endif
-
                        // Parse PD-exclude
                        bool ok = true;
                        dhcpv6_for_each_option(odata + sizeof(*prefix) - 4U,
@@ -1186,12 +1393,11 @@ static int dhcpv6_parse_ia(void *opt, void *end)
                                if (elen > 64)
                                        elen = 64;
 
-                               if (elen <= 32 || elen <= entry.length) {
+                               if (entry.length < 32 || elen <= entry.length) {
                                        ok = false;
                                        continue;
                                }
 
-
                                uint8_t bytes = ((elen - entry.length - 1) / 8) + 1;
                                if (slen <= bytes) {
                                        ok = false;
@@ -1213,8 +1419,12 @@ static int dhcpv6_parse_ia(void *opt, void *end)
                        }
 
                        if (ok) {
-                               odhcp6c_update_entry(STATE_IA_PD, &entry, 0, false);
-                               parsed_ia++;
+                               if (odhcp6c_update_entry(STATE_IA_PD, &entry, 0, 0))
+                                       updated_IAs++;
+
+                               syslog(LOG_INFO, "%s/%d preferred %d valid %d",
+                                      inet_ntop(AF_INET6, &entry.target, buf, sizeof(buf)),
+                                      entry.length, entry.preferred , entry.valid);
                        }
 
                        entry.priority = 0;
@@ -1238,33 +1448,35 @@ static int dhcpv6_parse_ia(void *opt, void *end)
                        entry.length = 128;
                        entry.target = addr->addr;
 
-#ifdef EXT_PREFIX_CLASS
-                       uint16_t stype, slen;
-                       uint8_t *sdata;
-                       // Find prefix class, if any
-                       dhcpv6_for_each_option(&addr[1], odata + olen,
-                                       stype, slen, sdata)
-                               if (stype == DHCPV6_OPT_PREFIX_CLASS && slen == 2)
-                                       entry.class = sdata[0] << 8 | sdata[1];
-#endif
+                       if (odhcp6c_update_entry(STATE_IA_NA, &entry, 0, 0))
+                               updated_IAs++;
 
-                       odhcp6c_update_entry(STATE_IA_NA, &entry, 0, false);
-                       parsed_ia++;
+                       syslog(LOG_INFO, "%s preferred %d valid %d",
+                              inet_ntop(AF_INET6, &entry.target, buf, sizeof(buf)),
+                              entry.preferred , entry.valid);
                }
        }
-       return parsed_ia;
-}
 
+       return updated_IAs;
+}
 
-static int dhcpv6_calc_refresh_timers(void)
+static unsigned int dhcpv6_calc_refresh_timers(void)
 {
        struct odhcp6c_entry *e;
        size_t ia_na_entries, ia_pd_entries, i;
+       size_t invalid_entries = 0;
        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++) {
+               /* Exclude invalid IA_NA entries */
+               if (!e[i].valid) {
+                       invalid_entries++;
+                       continue;
+               }
+
                if (e[i].t1 < l_t1)
                        l_t1 = e[i].t1;
 
@@ -1277,7 +1489,14 @@ static int dhcpv6_calc_refresh_timers(void)
 
        e = odhcp6c_get_state(STATE_IA_PD, &ia_pd_entries);
        ia_pd_entries /= sizeof(*e);
+
        for (i = 0; i < ia_pd_entries; i++) {
+               /* Exclude invalid IA_PD entries */
+               if (!e[i].valid) {
+                       invalid_entries++;
+                       continue;
+               }
+
                if (e[i].t1 < l_t1)
                        l_t1 = e[i].t1;
 
@@ -1288,34 +1507,39 @@ static int dhcpv6_calc_refresh_timers(void)
                        l_t3 = e[i].valid;
        }
 
-       if (ia_pd_entries || ia_na_entries) {
+       if (ia_pd_entries + ia_na_entries - invalid_entries) {
                t1 = l_t1;
                t2 = l_t2;
                t3 = l_t3;
-       } else {
-               t1 = 600;
+
+               syslog(LOG_INFO, "T1 %"PRId64"s, T2 %"PRId64"s, T3 %"PRId64"s", t1, t2, t3);
        }
 
-       return (int)(ia_pd_entries + ia_na_entries);
+       return (unsigned 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)
+               const void *status_msg, int len)
 {
-       uint8_t buf[len + 3];
+       const char *src = status_msg;
+       char buf[len + 3];
+       char *dst = buf;
 
-       memset(buf, 0, sizeof(buf));
        if (len) {
-               buf[0] = '(';
-               memcpy(&buf[1], status_msg, len);
-               buf[len + 1] = ')';
+               *dst++ = '(';
+               while (len--) {
+                       *dst = isprint((unsigned char)*src) ? *src : '?';
+                       src++;
+                       dst++;
+               }
+               *dst++ = ')';
        }
 
-       syslog(LOG_WARNING, "Server returned %s status %i %s",
-               scope, code, buf);
-}
+       *dst = 0;
 
+       syslog(LOG_WARNING, "Server returned %s status '%s %s'",
+               scope, dhcpv6_status_code_to_str(code), buf);
+}
 
 static void dhcpv6_handle_status_code(const enum dhcpv6_msg orig,
                const uint16_t code, const void *status_msg, const int len,
@@ -1330,7 +1554,18 @@ static void dhcpv6_handle_status_code(const enum dhcpv6_msg orig,
                break;
 
        case DHCPV6_UseMulticast:
-               // TODO handle multicast status code
+               switch(orig) {
+               case DHCPV6_MSG_REQUEST:
+               case DHCPV6_MSG_RENEW:
+               case DHCPV6_MSG_RELEASE:
+               case DHCPV6_MSG_DECLINE:
+                       // Message needs to be retransmitted according to RFC3315 chapter 18.1.8
+                       server_addr = in6addr_any;
+                       *ret = 0;
+                       break;
+               default:
+                       break;
+               }
                break;
 
        case DHCPV6_NoAddrsAvail:
@@ -1344,7 +1579,6 @@ static void dhcpv6_handle_status_code(const enum dhcpv6_msg orig,
        }
 }
 
-
 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,
@@ -1373,6 +1607,7 @@ static void dhcpv6_handle_ia_status_code(const enum dhcpv6_msg orig,
        }
 }
 
+// Note this always takes ownership of cand->ia_na and cand->ia_pd
 static void dhcpv6_add_server_cand(const struct dhcpv6_server_cand *cand)
 {
        size_t cand_len, i;
@@ -1395,7 +1630,10 @@ static void dhcpv6_add_server_cand(const struct dhcpv6_server_cand *cand)
                        break;
        }
 
-       odhcp6c_insert_state(STATE_SERVER_CAND, i * sizeof(*c), cand, sizeof(*cand));
+       if (odhcp6c_insert_state(STATE_SERVER_CAND, i * sizeof(*c), cand, sizeof(*cand))) {
+               free(cand->ia_na);
+               free(cand->ia_pd);
+       }
 }
 
 static void dhcpv6_clear_all_server_cand(void)
@@ -1440,16 +1678,18 @@ int dhcpv6_promote_server_cand(void)
        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)
+               if (pd_mode != IA_MODE_NONE)
                        ret = DHCPV6_STATEFUL;
        }