| prefix_filter |string |`::/0` | Only advertise on-link prefixes within the provided IPv6 prefix; others are filtered out. [IPv6 prefix] |
| ntp |list |`<local address>`| NTP servers to announce accepts IPv4 and IPv6 |
| upstream |list | - | A list of interfaces which can be used as a source of configuration information (e.g. for NTP servers, if not set explicitly). |
+| captive_portal_uri |string | no | The API URI to be sent in RFC8910 captive portal options, via DHCPv6 and ICMPv6 RA. |
[//]: # "dhcpv6_raw - string - not documented, may change when generic DHCPv4/DHCPv6 options are added"
IFACE_ATTR_MAX_PREFERRED_LIFETIME,
IFACE_ATTR_MAX_VALID_LIFETIME,
IFACE_ATTR_NTP,
+ IFACE_ATTR_CAPTIVE_PORTAL_URI,
IFACE_ATTR_MAX
};
[IFACE_ATTR_MAX_PREFERRED_LIFETIME] = { .name = "max_preferred_lifetime", .type = BLOBMSG_TYPE_STRING },
[IFACE_ATTR_MAX_VALID_LIFETIME] = { .name = "max_valid_lifetime", .type = BLOBMSG_TYPE_STRING },
[IFACE_ATTR_NTP] = { .name = "ntp", .type = BLOBMSG_TYPE_ARRAY },
+ [IFACE_ATTR_CAPTIVE_PORTAL_URI] = { .name = "captive_portal_uri", .type = BLOBMSG_TYPE_STRING },
};
const struct uci_blob_param_list interface_attr_list = {
iface->dhcp_leasetime = 43200;
iface->max_preferred_lifetime = ND_PREFERRED_LIMIT;
iface->max_valid_lifetime = ND_VALID_LIMIT;
+ iface->captive_portal_uri = NULL;
iface->dhcpv4_start.s_addr = htonl(START_DEFAULT);
iface->dhcpv4_end.s_addr = htonl(START_DEFAULT + LIMIT_DEFAULT - 1);
iface->dhcpv6_assignall = true;
free(iface->dhcpv4_ntp);
free(iface->dhcpv6_ntp);
free(iface->dhcpv6_sntp);
+ free(iface->captive_portal_uri);
for (unsigned i = 0; i < iface->dnr_cnt; i++) {
free(iface->dnr[i].adn);
free(iface->dnr[i].addr4);
}
}
+ if ((c = tb[IFACE_ATTR_CAPTIVE_PORTAL_URI])) {
+ iface->captive_portal_uri = strdup(blobmsg_get_string(c));
+ iface->captive_portal_uri_len = strlen(iface->captive_portal_uri);
+ debug("Set RFC8910 captive portal URI: '%s' for interface '%s'",
+ iface->captive_portal_uri, iface->name);
+ }
+
if ((c = tb[IFACE_ATTR_DNS])) {
struct blob_attr *cur;
unsigned rem;
IOV_POSIX_TZ_STR,
IOV_TZDB_TZ,
IOV_TZDB_TZ_STR,
+ IOV_CAPT_PORTAL,
+ IOV_CAPT_PORTAL_URI,
IOV_TOTAL
};
struct dhcpv6_dnr *dnrs = NULL;
size_t dnrs_len = 0;
+ /* RFC8910 Captive-Portal URI */
+ uint8_t *capt_portal_ptr = (uint8_t *)iface->captive_portal_uri;
+ size_t capt_portal_len = iface->captive_portal_uri_len;
+ struct {
+ uint16_t type;
+ uint16_t len;
+ } capt_portal;
+
+ /* RFC8910 §2:
+ * DHCP servers MAY send the Captive Portal option without any explicit request
+ * If it is configured, send it.
+ */
+ capt_portal.type = htons(DHCPV6_OPT_CAPTIVE_PORTAL);
+ capt_portal.len = htons(capt_portal_len);
+
uint16_t otype, olen;
uint8_t *odata;
uint16_t *reqopts = NULL;
[IOV_DNR] = {dnrs, dnrs_len},
[IOV_RELAY_MSG] = {NULL, 0},
[IOV_DHCPV4O6_SERVER] = {&dhcpv4o6_server, 0},
+ [IOV_CAPT_PORTAL] = {&capt_portal, capt_portal_len ? sizeof(capt_portal) : 0},
+ [IOV_CAPT_PORTAL_URI] = {capt_portal_ptr, capt_portal_len ? capt_portal_len : 0},
[IOV_BOOTFILE_URL] = {NULL, 0}
};
iov[IOV_SNTP].iov_len + iov[IOV_SNTP_ADDR].iov_len +
iov[IOV_POSIX_TZ].iov_len + iov[IOV_POSIX_TZ_STR].iov_len +
iov[IOV_TZDB_TZ].iov_len + iov[IOV_TZDB_TZ_STR].iov_len +
+ iov[IOV_CAPT_PORTAL].iov_len + iov[IOV_CAPT_PORTAL_URI].iov_len +
iov[IOV_DNR].iov_len + iov[IOV_BOOTFILE_URL].iov_len -
(4 + opts_end - opts));
#define DHCPV6_OPT_INF_MAX_RT 83
#define DHCPV6_OPT_DHCPV4_MSG 87
#define DHCPV6_OPT_4O6_SERVER 88
+/* RFC8910 */
+#define DHCPV6_OPT_CAPTIVE_PORTAL 103
#define DHCPV6_OPT_DNR 144
#define DHCPV6_DUID_VENDOR 2
#define ND_OPT_RECURSIVE_DNS 25
#define ND_OPT_DNS_SEARCH 31
+// RFC 8910 defines captive portal option
+#define ND_OPT_CAPTIVE_PORTAL 37
+
// RFC 8781 defines PREF64 option
#define ND_OPT_PREF64 38
struct avl_tree dhcpv4_leases;
struct list_head dhcpv4_fr_ips;
+ // RFC8910
+ char *captive_portal_uri;
+ size_t captive_portal_uri_len;
+
// Services
enum odhcpd_mode ra;
enum odhcpd_mode dhcpv6;
IOV_RA_PREF64,
IOV_RA_DNR,
IOV_RA_ADV_INTERVAL,
+ IOV_RA_CAPT_PORTAL,
IOV_RA_TOTAL,
};
uint8_t body[];
};
+struct nd_opt_capt_portal {
+ uint8_t type;
+ uint8_t len;
+ uint8_t data[];
+};
+
/* IPv6 RA PIOs */
static struct ra_pio *router_find_ra_pio(struct interface *iface,
struct odhcpd_ipaddr *addr)
struct nd_opt_pref64_info *pref64 = NULL;
struct nd_opt_dnr_info *dnrs = NULL;
struct nd_opt_adv_interval adv_interval;
+ struct nd_opt_capt_portal *capt_portal = NULL;
struct iovec iov[IOV_RA_TOTAL];
struct sockaddr_in6 dest;
size_t dns_sz = 0, search_sz = 0, pref64_sz = 0, dnrs_sz = 0;
size_t pfxs_cnt = 0, routes_cnt = 0;
size_t total_addr_cnt = 0, valid_addr_cnt = 0;
+ size_t capt_portal_sz = 0;
/*
* lowest_found_lifetime stores the lowest lifetime of all prefixes;
* necessary to find longest adv interval necessary
iov[IOV_RA_ADV_INTERVAL].iov_base = (char *)&adv_interval;
iov[IOV_RA_ADV_INTERVAL].iov_len = adv_interval.nd_opt_adv_interval_len * 8;
+ /* RFC 8910 Captive Portal */
+ uint8_t *captive_portal_uri = (uint8_t *)iface->captive_portal_uri;
+ if (iface->captive_portal_uri_len > 0) {
+ /* compute pad so that (header + data + pad) is a multiple of 8 */
+ capt_portal_sz = (sizeof(struct nd_opt_capt_portal) + iface->captive_portal_uri_len + 7) & ~7;
+
+ capt_portal = alloca(capt_portal_sz);
+ memset(capt_portal, 0, capt_portal_sz);
+
+ capt_portal->type = ND_OPT_CAPTIVE_PORTAL;
+ capt_portal->len = capt_portal_sz / 8;
+
+ memcpy(capt_portal->data, captive_portal_uri, iface->captive_portal_uri_len);
+ /* remaining padding bytes already set to 0x00 */
+ }
+
+ iov[IOV_RA_CAPT_PORTAL].iov_base = capt_portal;
+ iov[IOV_RA_CAPT_PORTAL].iov_len = capt_portal_sz;
+
memset(&dest, 0, sizeof(dest));
dest.sin6_family = AF_INET6;