From 593194d6228e7aac6e16577f8dc7dff0978877f5 Mon Sep 17 00:00:00 2001 From: jkingsman Date: Mon, 8 Apr 2024 00:18:58 -0700 Subject: [PATCH] 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. --- .../esp_netif/include/esp_netif_types.h | 5 +-- components/esp_netif/lwip/esp_netif_lwip.c | 4 +++ components/lwip/apps/dhcpserver/dhcpserver.c | 26 +++++++++++++- .../apps/dhcpserver/dhcpserver_options.h | 19 ++++------- .../http_server/captive_portal/README.md | 34 ++++++++++++++++++- .../captive_portal/main/Kconfig.projbuild | 6 ++++ .../http_server/captive_portal/main/main.c | 29 ++++++++++++++++ tools/ci/check_copyright_ignore.txt | 1 - 8 files changed, 106 insertions(+), 18 deletions(-) diff --git a/components/esp_netif/include/esp_netif_types.h b/components/esp_netif/include/esp_netif_types.h index 7560fb18cd..f70f32f4dc 100644 --- a/components/esp_netif/include/esp_netif_types.h +++ b/components/esp_netif/include/esp_netif_types.h @@ -37,7 +37,7 @@ extern "C" { /** - * @brief Definition of ESP-NETIF bridge controll + * @brief Definition of ESP-NETIF bridge control */ #define ESP_NETIF_BR_FLOOD -1 #define ESP_NETIF_BR_DROP 0 @@ -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 */ @@ -127,7 +128,7 @@ typedef struct { */ typedef struct { esp_netif_t *esp_netif; /*!< Pointer to corresponding esp-netif object */ - esp_netif_ip_info_t ip_info; /*!< IP address, netmask, gatway IP address */ + esp_netif_ip_info_t ip_info; /*!< IP address, netmask, gateway IP address */ bool ip_changed; /*!< Whether the assigned IP has changed or not */ } ip_event_got_ip_t; diff --git a/components/esp_netif/lwip/esp_netif_lwip.c b/components/esp_netif/lwip/esp_netif_lwip.c index 02af33fd68..ec9f5003cb 100644 --- a/components/esp_netif/lwip/esp_netif_lwip.c +++ b/components/esp_netif/lwip/esp_netif_lwip.c @@ -2334,6 +2334,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; diff --git a/components/lwip/apps/dhcpserver/dhcpserver.c b/components/lwip/apps/dhcpserver/dhcpserver.c index 112bdf070b..402e765053 100644 --- a/components/lwip/apps/dhcpserver/dhcpserver.c +++ b/components/lwip/apps/dhcpserver/dhcpserver.c @@ -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 { diff --git a/components/lwip/include/apps/dhcpserver/dhcpserver_options.h b/components/lwip/include/apps/dhcpserver/dhcpserver_options.h index 31a6799283..5a563a1eaa 100644 --- a/components/lwip/include/apps/dhcpserver/dhcpserver_options.h +++ b/components/lwip/include/apps/dhcpserver/dhcpserver_options.h @@ -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, diff --git a/examples/protocols/http_server/captive_portal/README.md b/examples/protocols/http_server/captive_portal/README.md index e0b1e77098..0c23cdaa0c 100644 --- a/examples/protocols/http_server/captive_portal/README.md +++ b/examples/protocols/http_server/captive_portal/README.md @@ -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 +``` \ No newline at end of file diff --git a/examples/protocols/http_server/captive_portal/main/Kconfig.projbuild b/examples/protocols/http_server/captive_portal/main/Kconfig.projbuild index 959cd4287d..9f2a7f1be5 100644 --- a/examples/protocols/http_server/captive_portal/main/Kconfig.projbuild +++ b/examples/protocols/http_server/captive_portal/main/Kconfig.projbuild @@ -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 diff --git a/examples/protocols/http_server/captive_portal/main/main.c b/examples/protocols/http_server/captive_portal/main/main.c index 6111a5b37a..81cb4d0b92 100644 --- a/examples/protocols/http_server/captive_portal/main/main.c +++ b/examples/protocols/http_server/captive_portal/main/main.c @@ -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(); diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index d2e8394f3e..cb8ecab240 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -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