dhcpv6: Add error checking to handle renew failure for IA_PD operations
authorLuke Deller <[email protected]>
Sun, 1 Dec 2024 02:46:28 +0000 (13:46 +1100)
committerÁlvaro Fernández Rojas <[email protected]>
Sun, 19 Oct 2025 16:40:04 +0000 (18:40 +0200)
Check for error status code in the IA Prefix option in replies to RENEW
messages.

This fixes a problem where odhcp6c thinks that renewal succeeded, when
actually the upstream router is no longer routing this prefix for us.

See https://github.com/openwrt/odhcp6c/issues/61#issuecomment-2509536512

Signed-off-by: Luke Deller <[email protected]>
Link: https://github.com/openwrt/odhcp6c/pull/103
Signed-off-by: Álvaro Fernández Rojas <[email protected]>
src/dhcpv6.c
src/odhcp6c.c

index 3e6c9ba2ff8541903bde2744ad0bbfad7e147bfd..e8dd2aebe3120b9d6723e1427330e50abcc9a986 100644 (file)
@@ -59,7 +59,7 @@ 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 unsigned int dhcpv6_parse_ia(void *opt, void *end);
+static unsigned int dhcpv6_parse_ia(void *opt, void *end, int *ret);
 
 static unsigned int dhcpv6_calc_refresh_timers(void);
 static void dhcpv6_handle_status_code(_unused const enum dhcpv6_msg orig,
@@ -73,6 +73,9 @@ static void dhcpv6_handle_ia_status_code(const enum dhcpv6_msg orig,
 static void dhcpv6_add_server_cand(const struct dhcpv6_server_cand *cand);
 static void dhcpv6_clear_all_server_cand(void);
 
+static void dhcpv6_log_status_code(const uint16_t code, const char *scope,
+               const void *status_msg, int len);
+
 static reply_handler dhcpv6_handle_reply;
 static reply_handler dhcpv6_handle_advert;
 static reply_handler dhcpv6_handle_rebind_reply;
@@ -979,7 +982,7 @@ static int dhcpv6_handle_advert(enum dhcpv6_msg orig, const int rc,
                                 (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));
+                       dhcpv6_parse_ia(ia_hdr, odata + olen + sizeof(*ia_hdr), NULL);
                }
 
                if (otype == DHCPV6_OPT_SERVERID && olen <= 130) {
@@ -1184,7 +1187,7 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
                                if (code != DHCPV6_Success)
                                        continue;
 
-                               updated_IAs += dhcpv6_parse_ia(ia_hdr, odata + olen);
+                               updated_IAs += dhcpv6_parse_ia(ia_hdr, odata + olen, &ret);
                        } else if (otype == DHCPV6_OPT_UNICAST && olen == sizeof(server_addr)) {
                                if (!(client_options & DHCPV6_IGNORE_OPT_UNICAST))
                                        server_addr = *(struct in6_addr *)odata;
@@ -1358,7 +1361,7 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
        return ret;
 }
 
-static unsigned int dhcpv6_parse_ia(void *opt, void *end)
+static unsigned int dhcpv6_parse_ia(void *opt, void *end, int *ret)
 {
        struct dhcpv6_ia_hdr *ia_hdr = (struct dhcpv6_ia_hdr *)opt;
        unsigned int updated_IAs = 0;
@@ -1413,44 +1416,57 @@ static unsigned int dhcpv6_parse_ia(void *opt, void *end)
                        uint16_t stype, slen;
                        uint8_t *sdata;
 
-                       // Parse PD-exclude
-                       bool ok = true;
+                       // Parse sub-options for PD-exclude or error status code
+                       bool update_state = true;
                        dhcpv6_for_each_option(odata + sizeof(*prefix) - 4U,
                                        odata + olen, stype, slen, sdata) {
-                               /*      RFC 6603 §4.2 Prefix Exclude option */
-                               if (stype != DHCPV6_OPT_PD_EXCLUDE || slen < 2)
-                                       continue;
-
-                               uint8_t elen = sdata[0];
-                               if (elen > 64)
-                                       elen = 64;
-
-                               if (entry.length < 32 || elen <= entry.length) {
-                                       ok = false;
-                                       continue;
-                               }
+                               if (stype == DHCPV6_OPT_STATUS && slen > 2) {
+                                       /* RFC 8415 §21.22
+                                       The status of any operations involving this IA Prefix option is
+                                       indicated in a Status Code option (see Section 21.13) in the
+                                       IAprefix-options field. */
+                                       uint8_t *status_msg = (slen > 2) ? &sdata[2] : NULL;
+                                       uint16_t msg_len = (slen > 2) ? slen - 2 : 0;
+                                       uint16_t code = ((int)sdata[0]) << 8 | ((int)sdata[1]);
+
+                                       if (code == DHCPV6_Success)
+                                               continue;
+
+                                       dhcpv6_log_status_code(code, "IA_PREFIX", status_msg, msg_len);
+                                       if (ret) *ret = 0; // renewal failed
+                               } else if (stype == DHCPV6_OPT_PD_EXCLUDE && slen > 2) {
+                                       /*      RFC 6603 §4.2 Prefix Exclude option */
+                                       uint8_t elen = sdata[0];
+                                       if (elen > 64)
+                                               elen = 64;
+
+                                       if (entry.length < 32 || elen <= entry.length) {
+                                               update_state = false;
+                                               continue;
+                                       }
 
-                               uint8_t bytes = ((elen - entry.length - 1) / 8) + 1;
-                               if (slen <= bytes) {
-                                       ok = false;
-                                       continue;
-                               }
+                                       uint8_t bytes = ((elen - entry.length - 1) / 8) + 1;
+                                       if (slen <= bytes) {
+                                               update_state = false;
+                                               continue;
+                                       }
 
-                               uint32_t exclude = 0;
-                               do {
-                                       exclude = exclude << 8 | sdata[bytes];
-                               } while (--bytes);
+                                       uint32_t exclude = 0;
+                                       do {
+                                               exclude = exclude << 8 | sdata[bytes];
+                                       } while (--bytes);
 
-                               exclude >>= 8 - ((elen - entry.length) % 8);
-                               exclude <<= 64 - elen;
+                                       exclude >>= 8 - ((elen - entry.length) % 8);
+                                       exclude <<= 64 - elen;
 
-                               // Abusing router & priority fields for exclusion
-                               entry.router = entry.target;
-                               entry.router.s6_addr32[1] |= htonl(exclude);
-                               entry.priority = elen;
+                                       // Abusing router & priority fields for exclusion
+                                       entry.router = entry.target;
+                                       entry.router.s6_addr32[1] |= htonl(exclude);
+                                       entry.priority = elen;
+                               }
                        }
 
-                       if (ok) {
+                       if (update_state) {
                                if (odhcp6c_update_entry(STATE_IA_PD, &entry, 0, 0))
                                        updated_IAs++;
 
index 4531660c406bd846fafa3d48905ccb253e34e31b..8354820746131b8d0c9d01f7141af635edc0b8fe 100644 (file)
@@ -573,6 +573,9 @@ int main(_unused int argc, char* const argv[])
                                odhcp6c_clear_state(STATE_SERVER_ID); // Remove binding
                                odhcp6c_clear_state(STATE_SERVER_ADDR);
 
+                               // Remove any state invalidated by RENEW reply
+                               odhcp6c_expire(true);
+
                                size_t ia_pd_len, ia_na_len;
                                odhcp6c_get_state(STATE_IA_PD, &ia_pd_len);
                                odhcp6c_get_state(STATE_IA_NA, &ia_na_len);