Captive-Portal Identification in DHCP and Router Advertisements (RAs)
obsoletes
- rfc-7710 — Captive-Portal Identification Using DHCP or Router Advertisements (RAs)
updates
- rfc-3679 — Unused Dynamic Host Configuration Protocol (DHCP) Option Codes
Extracted elements (25)
DHCPv4 option code was changed from 160 (RFC 7710) to 114 because Polycom devices used code 160 for other purposes, as discovered during the IETF 106 network experiment in Singapore, causing those devices to malfunction when option 160 carried the Captive Portal API URL.
Providing the portal URI via DHCP or RA options is described as a cleaner technique than hijacking TLS connections, and may improve security by making users more reluctant to accept TLS hijacking, which can be performed from beyond the captive portal's network.
The mechanism is designed as one component of a standardized approach; captive portals still need to implement interception techniques for legacy clients, and clients still need to perform probing. The DHCP/RA mechanism provides a more reliable and performant detection path and is the preferred mechanism going forward.
A device may learn Captive Portal API URIs via multiple options (IPv4 DHCP, IPv6 DHCP, IPv6 RA). If URIs learned via more than one option are not identical, this condition should be logged as it represents a network configuration error. Implementations may select their own precedence order.
This document obsoletes RFC 7710 by changing the DHCPv4 option code from 160 to 114, and also updates RFC 3679 (which listed code 160 as unused). Implementations upgrading from RFC 7710 must migrate to code 114.
Clients that support the Captive Portal DHCP option SHOULD include the option in the Parameter Request List in DHCPREQUEST messages. DHCP servers MAY send the option without an explicit request.
In all variants of the Captive-Portal option, the URI MUST be that of the captive portal API endpoint as defined in RFC 8908.
The captive portal operator SHOULD ensure that the URIs provisioned by each method (IPv4 DHCP, IPv6 DHCP, IPv6 RA) are identical to reduce the chance of operational problems.
The URI in the IPv6 RA Captive-Portal option MUST be padded with NUL (0x00) to make the total option length (including Type and Length fields) a multiple of 8 bytes.
The URI SHOULD NOT contain an IP address literal, with exceptions for networks with only one operational IP address family where DNS is either not available or not fully functional until the captive portal has been satisfied.
URIs longer than 255 bytes SHOULD NOT be provisioned via IPv6 DHCP options or IPv6 RA options, because the maximum length of the URI that can be carried in IPv4 DHCP is 255 bytes. In IPv6-only environments, this restriction can be relaxed.
When a captive portal performs content negotiation and the client does not explicitly indicate support for the captive portal API content type, the captive portal SHOULD redirect the client to the value associated with the 'user-portal-url' API key.
When performing content negotiation, implementors SHOULD include an appropriate Vary header field or set Cache-Control to 'private' or more restrictive (e.g., 'no-store') in any responses, because such responses might be cached.
Networks with no captive portals may explicitly signal this by provisioning the Captive-Portal option with the URI 'urn:ietf:params:capport:unrestricted'. Clients observing this URI may forego time-consuming forms of captive portal detection.
The Captive-Portal DHCP/RA option informs the client that it may be behind a captive portal and provides the URI to access the Captive Portal API as defined by RFC 8908. It is defined for DHCPv4 (option 114), DHCPv6 (option 103), and IPv6 RA (type 37).
IANA registered 'capport:unrestricted' in the 'IETF URN Sub-namespace for Registered Protocol Parameter Identifiers' registry (RFC 3553). Only one value is defined; no sub-namespace registrations are possible.
IANA updated DHCPv6 option 103 and IPv6 ND option 37 (both named 'DHCP Captive-Portal', previously registered in RFC 7710) to reference RFC 8910.
IANA updated the 'BOOTP Vendor Extensions and DHCP Options' registry: Tag 114 is assigned as 'DHCP Captive-Portal' (reference RFC 8910), and Tag 160 (previously assigned by RFC 7710) is marked Unassigned due to conflict with Polycom devices.
Because the OS/application knows it is connecting to a captive portal (as opposed to an intercepted connection), it can render the captive portal page in a sandboxed environment and clearly label the page as untrusted, mitigating risks from attacker-injected portal URIs.
By removing the need for captive portals to perform MITM hijacking, this mechanism makes the portal and its actions visible rather than hidden, reduces the likelihood users will disable security safeguards like DNSSEC or VPNs, and allows systems to avoid sending cookies or credentials while in captive portal mode.
Clients processing Captive-Portal options SHOULD validate that the option's contents conform to URI validation requirements including those in RFC 3986. In the absence of RA-Guard (RFC 6105, RFC 7113) or DHCPv6-Shield (RFC 7610), an attacker could inject, modify, or block DHCP messages or RAs to force users to contact an attacker-chosen address.
Devices that automatically connect to open networks could be tracked by forcing repeated captive portal satisfaction or by exposing browser fingerprints via this mechanism; however, similar tracking is already possible with existing captive portal interception techniques.
The IPv4 Captive-Portal DHCPv4 option uses Code=114 (one octet), Len (one octet) for the URI length in octets, and URI (variable length, not null-terminated) encoded per RFC 3986.
The IPv6 Captive-Portal DHCPv6 option uses option-code=103 (two octets), option-len as unsigned 16-bit length in octets, and URI (variable length, not null-terminated) encoded per RFC 3986.
The IPv6 RA Captive-Portal option uses Type=37, Length as an 8-bit unsigned integer in units of 8 bytes (including Type and Length fields), and URI padded with NUL (0x00) to make the total option length a multiple of 8 bytes; URI is not guaranteed null-terminated.