From: Luke Deller Date: Sun, 1 Dec 2024 02:46:28 +0000 (+1100) Subject: dhcpv6: Add error checking to handle renew failure for IA_PD operations X-Git-Url: http://git.openwrt.org/?a=commitdiff_plain;h=3c7e425169e120b361af0629ba9d76f2bdd0e28c;p=project%2Fodhcp6c.git dhcpv6: Add error checking to handle renew failure for IA_PD operations 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 Link: https://github.com/openwrt/odhcp6c/pull/103 Signed-off-by: Álvaro Fernández Rojas --- diff --git a/src/dhcpv6.c b/src/dhcpv6.c index 3e6c9ba..e8dd2ae 100644 --- a/src/dhcpv6.c +++ b/src/dhcpv6.c @@ -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++; diff --git a/src/odhcp6c.c b/src/odhcp6c.c index 4531660..8354820 100644 --- a/src/odhcp6c.c +++ b/src/odhcp6c.c @@ -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);