summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuke Deller2024-12-01 02:46:28 +0000
committerÁlvaro Fernández Rojas2025-10-19 16:40:04 +0000
commit3c7e425169e120b361af0629ba9d76f2bdd0e28c (patch)
tree5bd0497e87cd9a89463fc2da58ac6a2f1e65a68a
parent8de25373a860bc0649f4ae8aadc4010912af4916 (diff)
downloadodhcp6c-3c7e425169e120b361af0629ba9d76f2bdd0e28c.tar.gz
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 <luke@deller.id.au> Link: https://github.com/openwrt/odhcp6c/pull/103 Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
-rw-r--r--src/dhcpv6.c84
-rw-r--r--src/odhcp6c.c3
2 files changed, 53 insertions, 34 deletions
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);