dhcpv6: restart DHCPv6 on receipt of RA containing a new prefix
authorPaul Donald <[email protected]>
Tue, 18 Nov 2025 01:07:58 +0000 (02:07 +0100)
committerÁlvaro Fernández Rojas <[email protected]>
Tue, 18 Nov 2025 07:23:20 +0000 (08:23 +0100)
When the upstream DHCPv6 server does not provide IA_NA or IA_PD options, odhcp6c
enters into stateless mode, which will not be exited from until SIGUSR2 signal
is received.

This change enforces DHCPv6 restart on receipt of a RA that:
  a) advertises the presence of DHCPv6 server on the network, containing
  either M or O flags
  b) has a PI (prefix information) option that contains a new prefix

thus allowing the switch to DHCPv6 stateful mode when RA PI options suggest that
the upstream DHCPv6 server now manages a new prefix.

Restart is useful even when DHCPv6 client is already in stateful mode, so the
DHCPv6 server will be able to refresh the client's IA_NA and IA_PD options
before renewal timeout is triggered, hence avoiding the usage of potentially
deprecated addresses.

Signed-off-by: Alin Nastac <[email protected]>
Signed-off-by: Paul Donald <[email protected]>
Link: https://github.com/openwrt/odhcp6c/pull/119
Signed-off-by: Álvaro Fernández Rojas <[email protected]>
src/dhcpv6.c
src/odhcp6c.c
src/odhcp6c.h
src/ra.c

index 68c04f40522088ffa3364d8acbeba29e5f0b5d69..49ed052bcc99671f7cf675cecee90d0bd9e2ea77 100644 (file)
@@ -1661,10 +1661,19 @@ static unsigned int dhcpv6_parse_ia(void *opt, void *end, int *ret)
 
        // Update address IA
        dhcpv6_for_each_option(&ia_hdr[1], end, otype, olen, odata) {
-               struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0,
-                               IN6ADDR_ANY_INIT, 0, 0, 0, 0, 0, 0};
-
-               entry.iaid = ia_hdr->iaid;
+               struct odhcp6c_entry entry = {
+                       .router = IN6ADDR_ANY_INIT,
+                       .auxlen = 0,
+                       .length = 0,
+                       .ra_flags = 0,
+                       .target = IN6ADDR_ANY_INIT,
+                       .priority = 0,
+                       .valid = 0,
+                       .preferred = 0,
+                       .t1 = 0,
+                       .t2 = 0,
+                       .iaid = ia_hdr->iaid,
+               };
 
                switch (otype) {
                case DHCPV6_OPT_IA_PREFIX: {
index 7b1a387c04ad5d2a81d50434c84f127d0115bc5e..43ec72f27af39dc3b89e8469f6564fadfaa47f5b 100644 (file)
@@ -20,6 +20,7 @@
 #include <limits.h>
 #include <linux/if_addr.h>
 #include <net/if.h>
+#include <netinet/icmp6.h>
 #include <poll.h>
 #include <resolv.h>
 #include <signal.h>
@@ -834,16 +835,43 @@ static uint8_t* odhcp6c_resize_state(enum odhcp6c_state state, ssize_t len)
        return n;
 }
 
+static bool odhcp6c_server_advertised()
+{
+       size_t len;
+       uint8_t *start = odhcp6c_get_state(STATE_RA_ROUTE, &len);
+
+       for (struct odhcp6c_entry *c = (struct odhcp6c_entry*)start;
+                       (uint8_t*)c < &start[len] &&
+                       (uint8_t*)odhcp6c_next_entry(c) <= &start[len];
+                       c = odhcp6c_next_entry(c)) {
+               // Only default route entries have flags
+               if (c->length != 0 || IN6_IS_ADDR_UNSPECIFIED(&c->router))
+                       continue;
+
+               if (c->ra_flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))
+                       return true;
+       }
+
+       return false;
+}
+
 bool odhcp6c_signal_process(void)
 {
        while (signal_io) {
                signal_io = false;
 
+               size_t old_ra_prefix_size = state_len[STATE_RA_PREFIX];
                bool ra_updated = ra_process();
 
                if (ra_link_up()) {
                        signal_usr2 = true;
                        ra = false;
+               } else if (old_ra_prefix_size != state_len[STATE_RA_PREFIX] &&
+                               odhcp6c_server_advertised()) {
+                       // Restart DHCPv6 transaction when router advertisement flags
+                       // show presence of a DHCPv6 server and new prefixes were
+                       // added to STATE_RA_PREFIX state
+                       signal_usr2 = true;
                }
 
                if (ra_updated && (bound || config_dhcp->allow_slaac_only >= 0)) {
@@ -953,6 +981,8 @@ bool odhcp6c_update_entry(enum odhcp6c_state state, struct odhcp6c_entry *new,
                        return false;
 
                x->valid = new->valid;
+               x->ra_flags = new->ra_flags;
+               x->priority = new->priority;
                x->preferred = new->preferred;
                x->t1 = new->t1;
                x->t2 = new->t2;
index b229412c65371b97b015167859c371cb242bd22b..410da1e4c4ed97abe11fa6bf02b49c1d14ddbf69 100644 (file)
@@ -481,6 +481,7 @@ struct odhcp6c_entry {
        struct in6_addr router;
        uint8_t auxlen;
        uint8_t length;
+       uint8_t ra_flags;
        struct in6_addr target;
        int16_t priority;
        uint32_t valid;
index ca9e691449d689134a87d8314bfce6a757398d1a..b1011be1a171ef689905d6efacf341c807d264cf 100644 (file)
--- a/src/ra.c
+++ b/src/ra.c
@@ -430,6 +430,7 @@ bool ra_process(void)
                entry->target = any;
                entry->length = 0;
                entry->router = from.sin6_addr;
+               entry->ra_flags = adv->nd_ra_flags_reserved & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER);
                entry->priority = pref_to_priority(adv->nd_ra_flags_reserved);
                if (entry->priority < 0)
                        entry->priority = pref_to_priority(0);
@@ -438,6 +439,7 @@ bool ra_process(void)
                entry->preferred = entry->valid;
                changed |= odhcp6c_update_entry(STATE_RA_ROUTE, entry,
                                                ra_holdoff_interval);
+               entry->ra_flags = 0; // other STATE_RA_* entries don't have flags
 
                // Parse hop limit
                changed |= ra_set_hoplimit(adv->nd_ra_curhoplimit);