feat(esp_netif): add support for DHCP Option 114 captive portal URI

DHCP Option 114 provides a modern method of indicating a captive
portal redirect to DHCP client. This introduces Option 114 to
the DHCPS component as well as provides examples for usage.
This commit is contained in:
jkingsman 2024-04-08 00:18:58 -07:00 committed by David Čermák
parent ceac33077b
commit 3035ce294d
8 changed files with 104 additions and 16 deletions

View File

@ -89,6 +89,7 @@ typedef enum{
ESP_NETIF_IP_REQUEST_RETRY_TIME = 52, /**< Request IP address retry counter */
ESP_NETIF_VENDOR_CLASS_IDENTIFIER = 60, /**< Vendor Class Identifier of a DHCP client */
ESP_NETIF_VENDOR_SPECIFIC_INFO = 43, /**< Vendor Specific Information of a DHCP server */
ESP_NETIF_CAPTIVEPORTAL_URI = 114, /**< Captive Portal Identification */
} esp_netif_dhcp_option_id_t;
/** IP event declarations */

View File

@ -2398,6 +2398,10 @@ esp_err_t esp_netif_dhcps_option_api(esp_netif_api_msg_t *msg)
}
break;
}
case ESP_NETIF_CAPTIVEPORTAL_URI: {
opt_info = (char *)opt->val;
break;
}
default:
break;

View File

@ -60,6 +60,7 @@
#define DHCP_OPTION_PERFORM_ROUTER_DISCOVERY 31
#define DHCP_OPTION_BROADCAST_ADDRESS 28
#define DHCP_OPTION_REQ_LIST 55
#define DHCP_OPTION_CAPTIVEPORTAL_URI 114
#define DHCP_OPTION_END 255
//#define USE_CLASS_B_NET 1
@ -135,6 +136,7 @@ struct dhcps_t {
dhcps_time_t dhcps_lease_time;
dhcps_offer_t dhcps_offer;
dhcps_offer_t dhcps_dns;
char *dhcps_captiveportal_uri;
dhcps_cb_t dhcps_cb;
void* dhcps_cb_arg;
struct udp_pcb *dhcps_pcb;
@ -164,6 +166,7 @@ dhcps_t *dhcps_new(void)
dhcps->dhcps_lease_time = DHCPS_LEASE_TIME_DEF;
dhcps->dhcps_offer = 0xFF;
dhcps->dhcps_dns = 0x00;
dhcps->dhcps_captiveportal_uri = NULL;
dhcps->dhcps_pcb = NULL;
dhcps->state = DHCPS_HANDLE_CREATED;
return dhcps;
@ -238,6 +241,10 @@ void *dhcps_option_info(dhcps_t *dhcps, u8_t op_id, u32_t opt_len)
option_arg = &dhcps->dhcps_mask;
}
break;
case CAPTIVEPORTAL_URI:
option_arg = &dhcps->dhcps_captiveportal_uri;
break;
default:
break;
@ -292,6 +299,11 @@ err_t dhcps_set_option_info(dhcps_t *dhcps, u8_t op_id, void *opt_info, u32_t op
dhcps->dhcps_mask = *(ip4_addr_t *)opt_info;
}
break;
case CAPTIVEPORTAL_URI:
dhcps->dhcps_captiveportal_uri = (char *)opt_info;
break;
default:
break;
@ -400,6 +412,7 @@ static u8_t *add_msg_type(u8_t *optptr, u8_t type)
*******************************************************************************/
static u8_t *add_offer_options(dhcps_t *dhcps, u8_t *optptr)
{
u32_t i;
ip4_addr_t ipadd;
ipadd.addr = *((u32_t *) &dhcps->server_address);
@ -468,6 +481,17 @@ static u8_t *add_offer_options(dhcps_t *dhcps, u8_t *optptr)
*optptr++ = 0x05;
*optptr++ = 0xdc;
if (dhcps->dhcps_captiveportal_uri) {
size_t length = strlen(dhcps->dhcps_captiveportal_uri);
*optptr++ = DHCP_OPTION_CAPTIVEPORTAL_URI;
*optptr++ = length;
for (i = 0; i < length; i++)
{
*optptr++ = dhcps->dhcps_captiveportal_uri[i];
}
}
*optptr++ = DHCP_OPTION_PERFORM_ROUTER_DISCOVERY;
*optptr++ = 1;
*optptr++ = 0x00;
@ -1006,7 +1030,7 @@ static s16_t parse_msg(dhcps_t *dhcps, struct dhcps_msg *m, u16_t len)
dhcps->client_address.addr = dhcps->client_address_plus.addr;
}
if (flag == false) { // search the fisrt unused ip
if (flag == false) { // search the first unused ip
if (first_address.addr < pdhcps_pool->ip.addr) {
flag = true;
} else {

View File

@ -1,16 +1,8 @@
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
@ -129,6 +121,7 @@ typedef enum
CLIENT_LAST_TRANSACTION_TIME = 91,
ASSOCIATED_IP = 92,
USER_AUTHENTICATION_PROTOCOL = 98,
CAPTIVEPORTAL_URI = 114,
AUTO_CONFIGURE = 116,
NAME_SERVICE_SEARCH = 117,
SUBNET_SELECTION = 118,

View File

@ -5,7 +5,11 @@
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates a simple captive portal that will redirect all DNS IP questions to point to the softAP and redirect all HTTP requests to the captive portal root page. Triggers captive portal (sign in) pop up on Android, iOS and Windows. Note that the example will not redirect HTTPS requests.
This example demonstrates two methods of a captive portal, used to direct users to an authentication page or other necessary starting point before browsing.
One approach response to all DNS queries with the address of the softAP, and redirects all HTTP requests to the captive portal root page. This "funnelling" of DNS and traffic triggers the captive portal (sign in) to appear on Android, iOS, and Windows. Note that the example will not redirect HTTPS requests.
The other approach is a more modern method which includes a field in the DHCP offer (AKA DHCP Option 114), provided when the client is assigned an IP address, which specifies to the client where the captive portal is. This is advantageous because it doesn't require the overhead of DNS redirects and can work more reliably around HTTPS, HTST, and other security systems, as well as being more standards compliant. This feature is toggleable in the `Example Configuration`, but does not conflict with the DNS methodology -- these two methods work towards the same goal and can complement each other.
## How to Use Example
@ -27,6 +31,7 @@ In the `Example Configuration` menu:
* Set `SoftAP SSID`
* Set `SoftAP Password`
* Set `Maximal STA connections`
* Set `DHCP Captive portal` to enable or disable DHCP Option 114
### Build and Flash
@ -42,6 +47,9 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
## Example Output
### ESP32 Output for DNS Redirect
```
I (733) example: Set up softAP with IP: 192.168.4.1
I (743) example: wifi_init_softap finished. SSID:'esp32_ssid' password:'esp32_pwd'
@ -95,3 +103,27 @@ I (23473) example: Serve root
I (23503) example_dns_redirect_server: Received 48 bytes from 192.168.4.2 | DNS reply with len: 64
I (23513) example_dns_redirect_server: Waiting for data
```
### `tcpdump` Output for DHCP Option 114
Note `URL (114)` with the AP address.
```
19:14:20.522698 c8:yy:yy:yy:yy:yy > 74:xx:xx:xx:xx:xx, ethertype IPv4 (0x0800), length 590: (tos 0x0, ttl 64, id 243, offset 0, flags [none], proto UDP (17), length 576)
192.168.4.1.67 > 192.168.4.2.68: [udp sum ok] BOOTP/DHCP, Reply, length 548, xid 0x76a26648, Flags [none] (0x0000)
Your-IP 192.168.4.2
Client-Ethernet-Address 74:xx:xx:xx:xx:xx
Vendor-rfc1048 Extensions
Magic Cookie 0x63825363
DHCP-Message (53), length 1: Offer
Subnet-Mask (1), length 4: 255.255.255.0
Lease-Time (51), length 4: 7200
Server-ID (54), length 4: 192.168.4.1
Default-Gateway (3), length 4: 192.168.4.1
Domain-Name-Server (6), length 4: 192.168.4.1
BR (28), length 4: 192.168.4.255
MTU (26), length 2: 1500
URL (114), length 18: "http://192.168.4.1"
Router-Discovery (31), length 1: N
Vendor-Option (43), length 6: 1.4.0.0.0.2
```

View File

@ -17,4 +17,10 @@ menu "Example Configuration"
default 4
help
Max number of the STA connects to AP.
config ESP_ENABLE_DHCP_CAPTIVEPORTAL
bool "DHCP Captive portal"
default y
help
Enables more modern DHCP-based Option 114 to provide clients with the captive portal URI
endmenu

View File

@ -24,6 +24,7 @@
#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_MAX_STA_CONN CONFIG_ESP_MAX_STA_CONN
#define EXAMPLE_ENABLE_DHCP_CAPTIVEPORTAL_URI CONFIG_ESP_ENABLE_DHCP_CAPTIVEPORTAL
extern const char root_start[] asm("_binary_root_html_start");
extern const char root_end[] asm("_binary_root_html_end");
@ -79,6 +80,29 @@ static void wifi_init_softap(void)
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
}
static void dhcp_set_captiveportal_url(void) {
// get the IP of the access point to redirect to
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);
char ip_addr[16];
inet_ntoa_r(ip_info.ip.addr, ip_addr, 16);
ESP_LOGI(TAG, "Set up softAP with IP: %s", ip_addr);
// turn the IP into a URI
char* captiveportal_uri = (char*) malloc(32 * sizeof(char));
strcpy(captiveportal_uri, "http://");
strcat(captiveportal_uri, ip_addr);
// get a handle to configure DHCP with
esp_netif_t* netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
// set the DHCP option 114
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_dhcps_stop(netif));
ESP_ERROR_CHECK(esp_netif_dhcps_option(netif, ESP_NETIF_OP_SET, ESP_NETIF_CAPTIVEPORTAL_URI, captiveportal_uri, strlen(captiveportal_uri)));
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_dhcps_start(netif));
}
// HTTP GET Handler
static esp_err_t root_get_handler(httpd_req_t *req)
{
@ -155,6 +179,11 @@ void app_main(void)
// Initialise ESP32 in SoftAP mode
wifi_init_softap();
// Configure DNS-based captive portal, if configured
#if CONFIG_ESP_ENABLE_DHCP_CAPTIVEPORTAL
dhcp_set_captiveportal_url();
#endif
// Start the server for the first time
start_webserver();

View File

@ -498,7 +498,6 @@ components/hal/spi_slave_hal_iram.c
components/idf_test/include/idf_performance.h
components/log/host_test/log_test/main/log_test.cpp
components/lwip/apps/ping/ping.c
components/lwip/include/apps/dhcpserver/dhcpserver_options.h
components/lwip/include/apps/esp_ping.h
components/lwip/include/apps/ping/ping.h
components/mbedtls/esp_crt_bundle/test_gen_crt_bundle/test_gen_crt_bundle.py