dhcpv6: fix regression
[project/odhcp6c.git] / src / dhcpv6.c
index 81887c409203ab6ff7c51c5918a3f5128cfc8e67..e81918935756b08fd0801f3f2778008cb57710a6 100644 (file)
@@ -61,7 +61,7 @@ static bool dhcpv6_response_is_valid(const void *buf, ssize_t len,
 
 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);
@@ -103,7 +103,6 @@ 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 accept_reconfig = false;
 // Server unicast address
@@ -123,6 +122,27 @@ static uint32_t ntohl_unaligned(const uint8_t *data)
        return ntohl(buf);
 }
 
+static char *dhcpv6_msg_to_str(enum dhcpv6_msg msg)
+{
+       static char *dhcpv6_msg_str[] = {
+               "UNKNOWN",
+               "SOLICIT",
+               "ADVERTISE",
+               "REQUEST",
+               "RENEW",
+               "REBIND",
+               "REPLY",
+               "DECLINE",
+               "RECONFIGURE",
+               "INFORMATION REQUEST",
+       };
+
+       if (msg < _DHCPV6_MSG_MAX)
+               return dhcpv6_msg_str[msg];
+
+       return "Unknown";
+}
+
 int init_dhcpv6(const char *ifname, unsigned int options, int sol_timeout)
 {
        client_options = options;
@@ -187,8 +207,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_CER_ID
                        htons(DHCPV6_OPT_CER_ID),
 #endif
@@ -399,9 +417,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;
@@ -502,9 +517,7 @@ 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 = 8;
-       else if (!request_prefix)
-               cnt = 12;
+               cnt = IOV_HDR_IA_NA;
 
        // Disable IAs if not used
        if (type != DHCPV6_MSG_SOLICIT && ia_na_len == 0)
@@ -544,7 +557,8 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs)
        if (sendmsg(sock, &msg, 0) < 0) {
                char in6_str[INET6_ADDRSTRLEN];
 
-               syslog(LOG_ERR, "Failed to send DHCPV6 message to %s (%s)",
+               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));
        }
@@ -646,6 +660,7 @@ int dhcpv6_request(enum dhcpv6_msg type)
                                        .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())
@@ -689,8 +704,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 %"PRIu64"ms",
-                                       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);
@@ -826,7 +841,7 @@ static int dhcpv6_handle_reconfigure(enum dhcpv6_msg orig, const int rc,
 {
        uint16_t otype, olen;
        uint8_t *odata;
-       int msg = -1;
+       enum dhcpv6_msg msg = DHCPV6_MSG_UNKNOWN;
 
        dhcpv6_for_each_option(opt, end, otype, olen, odata) {
                if (otype == DHCPV6_OPT_RECONF_MESSAGE && olen == 1) {
@@ -841,6 +856,8 @@ static int dhcpv6_handle_reconfigure(enum dhcpv6_msg orig, const int rc,
                        // 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:
@@ -849,9 +866,10 @@ static int dhcpv6_handle_reconfigure(enum dhcpv6_msg orig, const int rc,
                }
        }
 
-       dhcpv6_handle_reply(orig, rc, NULL, NULL, NULL);
+       if (msg != DHCPV6_MSG_UNKNOWN)
+               dhcpv6_handle_reply(orig, rc, NULL, NULL, NULL);
 
-       return msg;
+       return (msg == DHCPV6_MSG_UNKNOWN? -1: (int)msg);
 }
 
 // Collect all advertised servers
@@ -868,7 +886,8 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
 
        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));
@@ -898,7 +917,7 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
                                        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;
@@ -977,6 +996,7 @@ 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, };
 
@@ -1162,55 +1182,83 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
                }
        }
 
+       // 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:
-               // Update refresh timers if no fatal status code was received
-               if ((ret > 0) && (ret = dhcpv6_calc_refresh_timers())) {
-                       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
-                               // no updated IAs
-                               if (!t1) {
-                                       if (!updated_IAs)
-                                               ret = -1;
-                                       else if ((t2 - t1) > 1)
-                                               // Grace period of 1 second
-                                               t1 = 1;
-                               }
+               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;
+               }
 
-                       } else if (orig == DHCPV6_MSG_REBIND) {
-                               // Send further rebinds if T1 and T2 is not set and
-                               // no updated IAs
-                               if (!t1 && !t2) {
-                                       if (!updated_IAs)
-                                               ret = -1;
-                                       else if ((t3 - t2) > 1)
-                                               // Grace period of 1 second
-                                               t2 = 1;
-                               }
+               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);
+                       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;
+                       }
+               } 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;
                        }
                }
                break;
 
        case DHCPV6_MSG_INFO_REQ:
-               if (ret > 0) {
-                       // All server candidates can be cleared if not yet bound
-                       if (!odhcp6c_is_bound())
-                               dhcpv6_clear_all_server_cand();
+               // All server candidates can be cleared if not yet bound
+               if (!odhcp6c_is_bound())
+                       dhcpv6_clear_all_server_cand();
 
-                       t1 = refresh;
-               }
+               t1 = refresh;
                break;
 
        default:
@@ -1227,6 +1275,7 @@ static unsigned int dhcpv6_parse_ia(void *opt, void *end)
        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);
@@ -1234,10 +1283,12 @@ static unsigned int dhcpv6_parse_ia(void *opt, void *end)
        if (t1 > t2)
                return 0;
 
+       syslog(LOG_INFO, "IAID %04x T1 %d T2 %d", htonl(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, 0,
-                               IN6ADDR_ANY_INIT, 0, 0, 0, 0, 0};
+               struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0,
+                               IN6ADDR_ANY_INIT, 0, 0, 0, 0, 0, 0};
 
                entry.iaid = ia_hdr->iaid;
 
@@ -1301,6 +1352,10 @@ static unsigned int dhcpv6_parse_ia(void *opt, void *end)
                        if (ok) {
                                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;
@@ -1326,12 +1381,17 @@ static unsigned int dhcpv6_parse_ia(void *opt, void *end)
 
                        if (odhcp6c_update_entry(STATE_IA_NA, &entry, 0, 0))
                                updated_IAs++;
+
+                       syslog(LOG_INFO, "%s preferred %d valid %d",
+                              inet_ntop(AF_INET6, &entry.target, buf, sizeof(buf)),
+                              entry.preferred , entry.valid);
                }
        }
+
        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;
@@ -1369,9 +1429,11 @@ static int dhcpv6_calc_refresh_timers(void)
                t1 = l_t1;
                t2 = l_t2;
                t3 = l_t3;
+
+               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,
@@ -1545,7 +1607,7 @@ int dhcpv6_promote_server_cand(void)
        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;
        }