dhcpv6: add config for strict RFC7550 master
authorÁlvaro Fernández Rojas <noltari@gmail.com>
Fri, 23 Jan 2026 16:29:24 +0000 (17:29 +0100)
committerÁlvaro Fernández Rojas <noltari@gmail.com>
Sun, 25 Jan 2026 13:54:38 +0000 (14:54 +0100)
Some ISPs don't comply with RFC7550 and need workarounds to get proper
IPv6 connectivity.

Link: https://github.com/openwrt/odhcp6c/pull/152
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
src/config.c
src/config.h
src/dhcpv6.c
src/odhcp6c.c
src/odhcp6c.h

index 4a1b6d932a11084762643630616c78ee08fa0b78..54e2d35eda83fa34fb137aab1db48bdf79fce0c8 100644 (file)
@@ -318,6 +318,11 @@ bool config_set_auth_token(const char* token)
        return config_dhcp.auth_token != NULL;
 }
 
+void config_set_client_opt_cfg(struct odhcp6c_opt_cfg *opt_cfg)
+{
+       config_dhcp.strict_rfc7550 = opt_cfg->strict_rfc7550 != 0;
+}
+
 static int config_parse_opt_u8(const char *src, uint8_t **dst)
 {
        int len = strlen(src);
index cce1c0464573b1a72dcf2864b51d11bf6de9333e..91713de04c745ae991b4cd454a839369cc0f00ca 100644 (file)
@@ -100,6 +100,7 @@ struct config_dhcp {
        uint16_t rand_factor;
        enum odhcp6c_auth_protocol auth_protocol;
        char* auth_token;
+       bool strict_rfc7550;
 };
 
 struct config_dhcp *config_dhcp_get(void);
@@ -126,6 +127,7 @@ bool config_set_irt_min(unsigned int value);
 bool config_set_rand_factor(unsigned int value);
 bool config_set_auth_protocol(const char* protocol);
 bool config_set_auth_token(const char* token);
+void config_set_client_opt_cfg(struct odhcp6c_opt_cfg *opt_cfg);
 
 int config_add_opt(const uint16_t code, const uint8_t *data, const uint16_t len);
 int config_parse_opt_data(const char *data, uint8_t **dst, const unsigned int type, const bool array);
index 05d55d34f3f2cee1f6d7d740273064509c78475a..b82dc3056613ba4a109508621df9f24eee836612 100644 (file)
@@ -985,13 +985,15 @@ static void dhcpv6_send(enum dhcpv6_msg req_msg_type, uint8_t trid[3], uint32_t
 
                switch (req_msg_type) {
                case DHCPV6_MSG_REQUEST:
-                       /* Some broken ISPs won't behave properly if IA_NA is
-                        * sent on Requests when they have provided an empty
-                        * IA_NA on Advertise.
-                        * Therefore we don't comply with RFC7550 and omit
-                        * IA_NA as a workaround.
-                        */
-                       iov[IOV_HDR_IA_NA].iov_len = 0;
+                       if (!config_dhcp->strict_rfc7550) {
+                               /* Some broken ISPs won't behave properly if IA_NA is
+                                * sent on Requests when they have provided an empty
+                                * IA_NA on Advertise.
+                                * Therefore we don't comply with RFC7550 and omit
+                                * IA_NA as a workaround.
+                                */
+                               iov[IOV_HDR_IA_NA].iov_len = 0;
+                       }
                        break;
                case DHCPV6_MSG_SOLICIT:
                        break;
@@ -2134,6 +2136,7 @@ int dhcpv6_promote_server_cand(void)
        struct dhcpv6_server_cand *cand = odhcp6c_get_state(STATE_SERVER_CAND, &cand_len);
        uint16_t hdr[2];
        int ret = DHCPV6_STATELESS;
+       bool override_ia = false;
 
        // Clear lingering candidate state info
        odhcp6c_clear_state(STATE_SERVER_ID);
@@ -2143,25 +2146,47 @@ int dhcpv6_promote_server_cand(void)
        if (!cand_len)
                return -1;
 
-       if (!cand->ia_pd_len && cand->has_noaddravail) {
-               bool override = false;
+       if (config_dhcp->strict_rfc7550) {
+               if (!cand->ia_pd_len && cand->has_noaddravail) {
+                       /* Some ISPs provide neither IA_NA nor IA_PD, so we
+                        * should fallback to SLAAC.
+                        */
+
+                       if (na_mode == IA_MODE_TRY) {
+                               na_mode = IA_MODE_NONE;
+                               override_ia = true;
+                       }
 
-               if (na_mode == IA_MODE_TRY) {
+                       if (pd_mode == IA_MODE_TRY) {
+                               pd_mode = IA_MODE_NONE;
+                               override_ia = true;
+                       }
+               }
+       } else {
+               if (cand->has_noaddravail && na_mode == IA_MODE_TRY) {
+                       /* Some broken ISPs require a new Solicit message
+                        * without IA_NA if they haven't provided an address
+                        * on the Advertise message.
+                        */
                        na_mode = IA_MODE_NONE;
-                       override = true;
+                       override_ia = true;
                }
 
-               if (pd_mode == IA_MODE_TRY) {
+               if (!cand->ia_pd_len && pd_mode == IA_MODE_TRY) {
+                       /* Some broken ISPs require a new Solicit message
+                        * without IA_PD if they haven't provided a prefix
+                        * on the Advertise message.
+                        */
                        pd_mode = IA_MODE_NONE;
-                       override = true;
+                       override_ia = true;
                }
+       }
 
-               if (override) {
-                       dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_timeo = cand->sol_max_rt;
-                       dhcpv6_retx[DHCPV6_MSG_INFO_REQ].max_timeo = cand->inf_max_rt;
+       if (override_ia) {
+               dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_timeo = cand->sol_max_rt;
+               dhcpv6_retx[DHCPV6_MSG_INFO_REQ].max_timeo = cand->inf_max_rt;
 
-                       return -1;
-               }
+               return -1;
        }
 
        hdr[0] = htons(DHCPV6_OPT_SERVERID);
index 1966d276727d4a808206201ff95a7d0acd71d64b..235632db87557390a658a70bb4e941967dd4e5ae 100644 (file)
@@ -17,6 +17,7 @@
 #include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <getopt.h>
 #include <limits.h>
 #include <linux/if_addr.h>
 #include <net/if.h>
@@ -201,6 +202,15 @@ static struct odhcp6c_opt opts[] = {
        { .code = 0, .flags = 0, .str = NULL },
 };
 
+static struct odhcp6c_opt_cfg opt_cfg = {
+       .strict_rfc7550 = 0,
+};
+
+static struct option opt_long[] = {
+       { "strict-rfc7550", no_argument, &opt_cfg.strict_rfc7550, 1 },
+       { NULL, 0, NULL, 0 },
+};
+
 int main(_o_unused int argc, char* const argv[])
 {
        static struct in6_addr ifid = IN6ADDR_ANY_INIT;
@@ -209,6 +219,7 @@ int main(_o_unused int argc, char* const argv[])
        const char *script = "/lib/netifd/dhcpv6.script";
        ssize_t l;
        uint8_t buf[134], *o_data;
+       int optidx;
        char *optpos;
        uint16_t opttype;
        struct odhcp6c_opt *opt;
@@ -228,8 +239,11 @@ int main(_o_unused int argc, char* const argv[])
 
        atexit(odhcp6c_cleanup);
 
-       while ((c = getopt(argc, argv, "SDN:V:P:FB:c:i:r:Ru:Ux:s:EkK:t:C:m:Lhedp:favl:")) != -1) {
+       while ((c = getopt_long(argc, argv, "SDN:V:P:FB:c:i:r:Ru:Ux:s:EkK:t:C:m:Lhedp:favl:", opt_long, &optidx)) != -1) {
                switch (c) {
+               case 0:
+                       break;
+
                case 'S':
                        config_set_allow_slaac_only(false);
                        break;
@@ -477,6 +491,8 @@ int main(_o_unused int argc, char* const argv[])
                }
        }
 
+       config_set_client_opt_cfg(&opt_cfg);
+
        openlog("odhcp6c", logopt, LOG_DAEMON);
        setlogmask(LOG_UPTO(config_dhcp->log_level));
 
@@ -858,6 +874,7 @@ static int usage(void)
        "       -m <seconds>    Minimum time between accepting RA updates (3)\n"
        "       -L              Ignore default lifetime for RDNSS records\n"
        "       -U              Ignore Server Unicast option\n"
+       "       --strict-rfc7550 Enforce RFC7550 compliance\n"
        "\nInvocation options:\n"
        "       -p <pidfile>    Set pidfile (/var/run/odhcp6c.pid)\n"
        "       -d              Daemonize\n"
index bd3c515a7a6a2109e5169046312c69e53945d2ba..a626c52d0a03afe85e7554230452be9e8fbe36a8 100644 (file)
@@ -552,6 +552,10 @@ struct odhcp6c_opt {
        const char *str;
 };
 
+struct odhcp6c_opt_cfg {
+       int strict_rfc7550;
+};
+
 uint32_t hash_ifname(const char *s);
 int init_dhcpv6(const char *ifname);
 int dhcpv6_get_ia_mode(void);