From 24485bb4b35ab84c17c2e87bd561d026d4c15c00 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= Date: Fri, 23 Jan 2026 17:29:24 +0100 Subject: [PATCH] dhcpv6: add config for strict RFC7550 MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 --- src/config.c | 5 +++++ src/config.h | 2 ++ src/dhcpv6.c | 61 ++++++++++++++++++++++++++++++++++++--------------- src/odhcp6c.c | 19 +++++++++++++++- src/odhcp6c.h | 4 ++++ 5 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/config.c b/src/config.c index 4a1b6d9..54e2d35 100644 --- a/src/config.c +++ b/src/config.c @@ -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); diff --git a/src/config.h b/src/config.h index cce1c04..91713de 100644 --- a/src/config.h +++ b/src/config.h @@ -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); diff --git a/src/dhcpv6.c b/src/dhcpv6.c index 05d55d3..b82dc30 100644 --- a/src/dhcpv6.c +++ b/src/dhcpv6.c @@ -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); diff --git a/src/odhcp6c.c b/src/odhcp6c.c index 1966d27..235632d 100644 --- a/src/odhcp6c.c +++ b/src/odhcp6c.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -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 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 Set pidfile (/var/run/odhcp6c.pid)\n" " -d Daemonize\n" diff --git a/src/odhcp6c.h b/src/odhcp6c.h index bd3c515..a626c52 100644 --- a/src/odhcp6c.h +++ b/src/odhcp6c.h @@ -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); -- 2.30.2