Skip to content

Commit

Permalink
feat(esp_netif): add support for DHCP Option 114 captive portal URI
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jkingsman committed Apr 12, 2024
1 parent 5a40bb8 commit 593194d
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 18 deletions.
5 changes: 3 additions & 2 deletions components/esp_netif/include/esp_netif_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;

Expand Down
4 changes: 4 additions & 0 deletions components/esp_netif/lwip/esp_netif_lwip.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
26 changes: 25 additions & 1 deletion components/lwip/apps/dhcpserver/dhcpserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
19 changes: 6 additions & 13 deletions components/lwip/include/apps/dhcpserver/dhcpserver_options.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand Down
34 changes: 33 additions & 1 deletion examples/protocols/http_server/captive_portal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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'
Expand Down Expand Up @@ -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
```
Original file line number Diff line number Diff line change
Expand Up @@ -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
29 changes: 29 additions & 0 deletions examples/protocols/http_server/captive_portal/main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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();

Expand Down
1 change: 0 additions & 1 deletion tools/ci/check_copyright_ignore.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 593194d

Please sign in to comment.