all: implement RFC8910 §3 URI equality check
authorPaul Donald <[email protected]>
Fri, 21 Nov 2025 14:45:20 +0000 (15:45 +0100)
committerÁlvaro Fernández Rojas <[email protected]>
Mon, 24 Nov 2025 09:29:58 +0000 (10:29 +0100)
§3 Precedence of API URIs
"
A device may learn about Captive Portal API URIs through more than one of (or
indeed all of) the above options. Implementations can select their own
precedence order (e.g., prefer one of the IPv6 options before the DHCPv4 option,
or vice versa, et cetera).

If the URIs learned via more than one option described in Section 2 are not all
identical, this condition should be logged for the device owner or
administrator; it is a network configuration error if the learned URIs are not
all identical.
"

We log the 'network configuration error' but proceed in an attempt to be useful
(since the RFC makes no mention of disregarding the URI) and prefer DHCPv6,
since this is the DHCPv6 client.

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

index 71b163cc3109ffd7b29c401fd21dd1b66b42350d..32a5e47df99e9f4c5ed0bb260a3fc8d298b4638f 100644 (file)
@@ -1372,7 +1372,7 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _o_unused const int rc,
                odhcp6c_clear_state(STATE_S46_MAPT);
                odhcp6c_clear_state(STATE_S46_MAPE);
                odhcp6c_clear_state(STATE_S46_LW);
-               odhcp6c_clear_state(STATE_CAPT_PORT);
+               odhcp6c_clear_state(STATE_CAPT_PORT_DHCPV6);
                odhcp6c_clear_state(STATE_PASSTHRU);
                odhcp6c_clear_state(STATE_CUSTOM_OPTS);
 
@@ -1550,7 +1550,7 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _o_unused const int rc,
                                                continue;
                                        memcpy(copy, odata, olen);
                                        copy[uri_len] = '\0';
-                                       odhcp6c_add_state(STATE_CAPT_PORT, odata, olen);
+                                       odhcp6c_add_state(STATE_CAPT_PORT_DHCPV6, odata, olen);
                                        free(copy);
                                }
                                break;
index cda9ef4e2fc5d51674fe6385bbfcad554bf14598..d97ace9c65ea286ad2755ecadf9c9014c4aa73df 100644 (file)
@@ -510,7 +510,7 @@ int main(_o_unused int argc, char* const argv[])
                        odhcp6c_clear_state(STATE_NTP_FQDN);
                        odhcp6c_clear_state(STATE_SIP_IP);
                        odhcp6c_clear_state(STATE_SIP_FQDN);
-                       odhcp6c_clear_state(STATE_CAPT_PORT);
+                       odhcp6c_clear_state(STATE_CAPT_PORT_DHCPV6);
                        bound = false;
 
                        size_t oro_len = 0;
index 0bd686cf4da986d636e703eb0f15ddf77f819905..40b991286f650b2b1a49ea445e2af82fe93ef902 100644 (file)
@@ -435,7 +435,8 @@ enum odhcp6c_state {
        STATE_S46_MAPT,
        STATE_S46_MAPE,
        STATE_S46_LW,
-       STATE_CAPT_PORT,
+       STATE_CAPT_PORT_RA,
+       STATE_CAPT_PORT_DHCPV6,
        STATE_PASSTHRU,
        _STATE_MAX
 };
index d44b9f0066e5e102aec12c67e94c92ffe3bd05bd..0c83e5468c3b54336a7eaba1bd47c9172443b5b9 100644 (file)
--- a/src/ra.c
+++ b/src/ra.c
@@ -582,8 +582,8 @@ bool ra_process(void)
 
                                        memcpy(copy, buf, uri_len);
                                        copy[uri_len] = '\0';
-                                       odhcp6c_clear_state(STATE_CAPT_PORT);
-                                       odhcp6c_add_state(STATE_CAPT_PORT, copy, uri_len);
+                                       odhcp6c_clear_state(STATE_CAPT_PORT_RA);
+                                       odhcp6c_add_state(STATE_CAPT_PORT_RA, copy, uri_len);
                                        free(copy);
                                }
                        }
index 5a6168da2a848863b2fb60eb4cffe7aa5dd72bbb..209f7eef9e9769400e51bfe5a99d9bbc6faf7a8b 100644 (file)
@@ -461,7 +461,8 @@ void script_call(const char *status, int delay, bool resume)
        } else if (pid == 0) {
                size_t dns_len, search_len, custom_len, sntp_ip_len, ntp_ip_len, ntp_dns_len;
                size_t sip_ip_len, sip_fqdn_len, aftr_name_len, addr_len;
-               size_t s46_mapt_len, s46_mape_len, s46_lw_len, capt_port_len, passthru_len;
+               size_t s46_mapt_len, s46_mape_len, s46_lw_len, passthru_len;
+               size_t capt_port_ra_len, capt_port_dhcpv6_len;
 
                signal(SIGTERM, SIG_DFL);
                if (delay > 0) {
@@ -482,7 +483,8 @@ void script_call(const char *status, int delay, bool resume)
                uint8_t *s46_mapt = odhcp6c_get_state(STATE_S46_MAPT, &s46_mapt_len);
                uint8_t *s46_mape = odhcp6c_get_state(STATE_S46_MAPE, &s46_mape_len);
                uint8_t *s46_lw = odhcp6c_get_state(STATE_S46_LW, &s46_lw_len);
-               uint8_t *capt_port = odhcp6c_get_state(STATE_CAPT_PORT, &capt_port_len);
+               uint8_t *capt_port_ra = odhcp6c_get_state(STATE_CAPT_PORT_RA, &capt_port_ra_len);
+               uint8_t *capt_port_dhcpv6 = odhcp6c_get_state(STATE_CAPT_PORT_DHCPV6, &capt_port_dhcpv6_len);
                uint8_t *passthru = odhcp6c_get_state(STATE_PASSTHRU, &passthru_len);
 
                size_t prefix_len, address_len, ra_pref_len,
@@ -494,6 +496,15 @@ void script_call(const char *status, int delay, bool resume)
                uint8_t *ra_dns = odhcp6c_get_state(STATE_RA_DNS, &ra_dns_len);
                uint8_t *ra_search = odhcp6c_get_state(STATE_RA_SEARCH, &ra_search_len);
 
+               /* RFC8910 §3 */
+               if (capt_port_ra_len > 0 && capt_port_dhcpv6_len > 0) {
+                       if (capt_port_ra_len != capt_port_dhcpv6_len ||
+                               !memcmp(capt_port_dhcpv6, capt_port_ra, capt_port_dhcpv6_len))
+                               syslog(LOG_ERR,
+                                       "%s received via different vectors differ: preferring URI from DHCPv6",
+                                       CAPT_PORT_URI_STR);
+               }
+
                ipv6_to_env("SERVER", addr, addr_len / sizeof(*addr));
                ipv6_to_env("RDNSS", dns, dns_len / sizeof(*dns));
                ipv6_to_env("SNTP_IP", sntp, sntp_ip_len / sizeof(*sntp));
@@ -506,7 +517,10 @@ void script_call(const char *status, int delay, bool resume)
                s46_to_env(STATE_S46_MAPE, s46_mape, s46_mape_len);
                s46_to_env(STATE_S46_MAPT, s46_mapt, s46_mapt_len);
                s46_to_env(STATE_S46_LW, s46_lw, s46_lw_len);
-               string_to_env(CAPT_PORT_URI_STR, capt_port, capt_port_len);
+               if (capt_port_dhcpv6_len > 0)
+                       string_to_env(CAPT_PORT_URI_STR, capt_port_dhcpv6, capt_port_dhcpv6_len);
+               else if (capt_port_ra_len > 0)
+                       string_to_env(CAPT_PORT_URI_STR, capt_port_ra, capt_port_ra_len);
                bin_to_env(custom, custom_len);
 
                if (odhcp6c_is_bound()) {
index ab2e362642ff42be3a6004264f7907a77c1f41d6..44349e89dcda753ebc1f4ea98b29dc18829f51d7 100644 (file)
@@ -531,7 +531,8 @@ static int states_to_blob(void)
        char *buf = NULL;
        size_t dns_len, search_len, custom_len, sntp_ip_len, ntp_ip_len, ntp_dns_len;
        size_t sip_ip_len, sip_fqdn_len, aftr_name_len, addr_len;
-       size_t s46_mapt_len, s46_mape_len, s46_lw_len, capt_port_len, passthru_len;
+       size_t s46_mapt_len, s46_mape_len, s46_lw_len, passthru_len;
+       size_t capt_port_ra_len, capt_port_dhcpv6_len;
        struct in6_addr *addr = odhcp6c_get_state(STATE_SERVER_ADDR, &addr_len);
        struct in6_addr *dns = odhcp6c_get_state(STATE_DNS, &dns_len);
        uint8_t *search = odhcp6c_get_state(STATE_SEARCH, &search_len);
@@ -545,7 +546,8 @@ static int states_to_blob(void)
        uint8_t *s46_mapt = odhcp6c_get_state(STATE_S46_MAPT, &s46_mapt_len);
        uint8_t *s46_mape = odhcp6c_get_state(STATE_S46_MAPE, &s46_mape_len);
        uint8_t *s46_lw = odhcp6c_get_state(STATE_S46_LW, &s46_lw_len);
-       uint8_t *capt_port = odhcp6c_get_state(STATE_CAPT_PORT, &capt_port_len);
+       uint8_t *capt_port_ra = odhcp6c_get_state(STATE_CAPT_PORT_RA, &capt_port_ra_len);
+       uint8_t *capt_port_dhcpv6 = odhcp6c_get_state(STATE_CAPT_PORT_DHCPV6, &capt_port_dhcpv6_len);
        uint8_t *passthru = odhcp6c_get_state(STATE_PASSTHRU, &passthru_len);
 
        size_t prefix_len, address_len, ra_pref_len,
@@ -559,6 +561,15 @@ static int states_to_blob(void)
 
        blob_buf_init(&b, BLOBMSG_TYPE_TABLE);
 
+       /* RFC8910 §3 */
+       if (capt_port_ra_len > 0 && capt_port_dhcpv6_len > 0) {
+               if (capt_port_ra_len != capt_port_dhcpv6_len ||
+                       !memcmp(capt_port_dhcpv6, capt_port_ra, capt_port_dhcpv6_len))
+                       syslog(LOG_ERR,
+                               "%s received via different vectors differ: preferring URI from DHCPv6",
+                               CAPT_PORT_URI_STR);
+       }
+
        blobmsg_add_string(&b, "DHCPV6_STATE", dhcpv6_state_to_str(dhcpv6_get_state()));
 
        CHECK(ipv6_to_blob("SERVER", addr, addr_len / sizeof(*addr)));
@@ -573,7 +584,10 @@ static int states_to_blob(void)
        CHECK(s46_to_blob(STATE_S46_MAPE, s46_mape, s46_mape_len));
        CHECK(s46_to_blob(STATE_S46_MAPT, s46_mapt, s46_mapt_len));
        CHECK(s46_to_blob(STATE_S46_LW, s46_lw, s46_lw_len));
-       blobmsg_add_string(&b, CAPT_PORT_URI_STR, (char *)capt_port);
+       if (capt_port_dhcpv6_len > 0)
+               blobmsg_add_string(&b, CAPT_PORT_URI_STR, (char *)capt_port_dhcpv6);
+       else if (capt_port_ra_len > 0)
+               blobmsg_add_string(&b, CAPT_PORT_URI_STR, (char *)capt_port_ra);
        CHECK(bin_to_blob(custom, custom_len));
 
        if (odhcp6c_is_bound()) {