Merge branch 'feature/example_network_wifi_bridge_provisioning' into 'master'

Examples/Network: Add WiFi to Eth/USB bridge example

See merge request espressif/esp-idf!23978
This commit is contained in:
David Čermák 2023-06-16 01:04:32 +08:00
commit 28c643a56d
25 changed files with 1612 additions and 58 deletions

View File

@ -11,3 +11,6 @@ examples/network/simple_sniffer:
- if: IDF_TARGET not in ["esp32", "esp32c3", "esp32s3"]
temporary: true
reason: lack of runners
examples/network/sta_to_eth:
disable:
- if: SOC_WIFI_SUPPORTED != 1

View File

@ -0,0 +1,10 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
# This example needs a DNS server: let's use the simple DNS server implementation from captive portal example
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/http_server/captive_portal/components/dns_server
$ENV{IDF_PATH}/examples/ethernet/basic/components/ethernet_init)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(sta_to_eth)

View File

@ -0,0 +1,92 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- |
# WiFi station to "Wired" interface L2 forwarder
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example aims to demonstrate 1-1 bridge using WiFi station and one of these interfaces (so called *wired* in this example)
- Ethernet (supported for all targets)
- USB acting as NCM device (supported for ESP32-S2 and ESP32-S3)
It also allows for reconfiguring WiFi settings using a virtual network in the Ethernet. The reconfiguration mode is initialized if the WiFi settings are not available, connection fails or manually by long pressing the Boot button (GPIO0).
It is possible to configure WiFi settings (SSID and password) in a browser on an address `"wifi.settings"` or using unified provisioning.
## How to use example
This example could be used to *bring* wireless connectivity to devices that support only Ethernet (or USB Ethernet implemented as NCM device).
This example also supports runtime configuration of WiFi settings by means of a webpage or unified provisioning.
### Hardware Required
Any board with either Ethernet of USB-OTG supported.
### Configure the project
Open the project configuration menu (`idf.py menuconfig`).
In the `Example Configuration` menu choose the provisioning method:
* `EXAMPLE_WIFI_CONFIGURATION_MANUAL` for manual configuration using a webpage
* `EXAMPLE_WIFI_CONFIGURATION_PROVISIONING` for standard provisioning over the virtual USB network
To provision the device using IDF provisioning tools (if `EXAMPLE_WIFI_CONFIGURATION_PROVISIONING` is selected) you can use idf provisioning utility with transport set to `softap`:
```bash
esp-idf/tools/esp_prov$ python esp_prov.py --transport httpd ...
```
Please refer to the provisioning documentation and `esp_prov` script [documentation](../../../tools/esp_prov/README.md) for more details.
### Build, Flash, and Run
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT build flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
After the flashing you should see the output at idf monitor:
(note that this is the output of USB configuration)
```
I (1740) example_sta2wired: Wi-Fi STA connected
I (1740) example_sta2wired: WiFi station connected successfully
W (1750) TinyUSB: The device's configuration descriptor is not provided by user, using default.
W (1760) TinyUSB: The device's string descriptor is not provided by user, using default.
W (1770) TinyUSB: The device's device descriptor is not provided by user, using default.
I (1770) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (1780) tusb_desc:
┌─────────────────────────────────┐
│ USB Device Descriptor Summary │
├───────────────────┬─────────────┤
│bDeviceClass │ 239 │
├───────────────────┼─────────────┤
│bDeviceSubClass │ 2 │
├───────────────────┼─────────────┤
│bDeviceProtocol │ 1 │
├───────────────────┼─────────────┤
│bMaxPacketSize0 │ 64 │
├───────────────────┼─────────────┤
│idVendor │ 0x303a │
├───────────────────┼─────────────┤
│idProduct │ 0x4002 │
├───────────────────┼─────────────┤
│bcdDevice │ 0x100 │
├───────────────────┼─────────────┤
│iManufacturer │ 0x1 │
├───────────────────┼─────────────┤
│iProduct │ 0x2 │
├───────────────────┼─────────────┤
│iSerialNumber │ 0x3 │
├───────────────────┼─────────────┤
│bNumConfigurations │ 0x1 │
└───────────────────┴─────────────┘
I (915) TinyUSB: TinyUSB Driver installed
```

View File

@ -0,0 +1,16 @@
if(CONFIG_EXAMPLE_WIFI_CONFIGURATION_MANUAL)
set(config_method manual_config.c)
else()
set(config_method provisioning.c scheme_generic_httpd.c)
endif()
if(CONFIG_EXAMPLE_WIRED_INTERFACE_IS_ETHERNET)
set(wired_iface ethernet_iface.c)
else()
set(wired_iface usb_ncm_iface.c)
endif()
idf_component_register(SRCS sta_to_eth_main.c
${wired_iface}
${config_method}
INCLUDE_DIRS "")

View File

@ -0,0 +1,89 @@
menu "Example Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
choice EXAMPLE_WIFI_CONFIGURATION
prompt "WiFi configuration"
default EXAMPLE_WIFI_CONFIGURATION_MANUAL
help
Choose how the WiFi settings should be configured.
config EXAMPLE_WIFI_CONFIGURATION_MANUAL
bool
prompt "Manual configuration via http server"
config EXAMPLE_WIFI_CONFIGURATION_PROVISIONING
bool
prompt "Using unified provisioning"
endchoice
choice EXAMPLE_PROV_SECURITY_VERSION
bool "Protocomm security version"
depends on EXAMPLE_WIFI_CONFIGURATION_PROVISIONING
default EXAMPLE_PROV_SECURITY_VERSION_1
help
Wi-Fi provisioning component offers 3 security versions.
The example offers a choice between security version 1 and 2.
You can also choose version 0, which is recommended only
for testing (not secure, plain text communication)
config EXAMPLE_PROV_SECURITY_VERSION_1
bool "Security version 1"
select ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1
config EXAMPLE_PROV_SECURITY_VERSION_2
bool "Security version 2"
select ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2
config EXAMPLE_PROV_SECURITY_VERSION_0
bool "Plain text communication -- not secure!"
select ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0
endchoice
choice EXAMPLE_PROV_MODE
bool "Security version 2 mode"
depends on EXAMPLE_PROV_SECURITY_VERSION_2
default EXAMPLE_PROV_SEC2_DEV_MODE
config EXAMPLE_PROV_SEC2_DEV_MODE
bool "Security version 2 development mode"
depends on EXAMPLE_PROV_SECURITY_VERSION_2
help
This enables the development mode for
security version 2.
Please note that this mode is NOT recommended for production purpose.
config EXAMPLE_PROV_SEC2_PROD_MODE
bool "Security version 2 production mode"
depends on EXAMPLE_PROV_SECURITY_VERSION_2
help
This enables the production mode for
security version 2.
endchoice
choice EXAMPLE_WIRED_INTERFACE
prompt "Choose the Wired interface"
default EXAMPLE_WIRED_INTERFACE_IS_ETHERNET
help
Choose the wired interface: Ethernet or USB
config EXAMPLE_WIRED_INTERFACE_IS_ETHERNET
bool
prompt "Ethernet"
config EXAMPLE_WIRED_INTERFACE_IS_USB
bool
depends on IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
prompt "USB NCM"
endchoice
config EXAMPLE_RECONFIGURE_BUTTON
int "Button for switching to reconfigure mode"
range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
default 2 if EXAMPLE_WIRED_INTERFACE_IS_ETHERNET
default 0
help
The button on this GPIO is used to reset the board to
the reconfiguration mode, i.e. to restart provisioning
or manual configuration of Wi-Fi settings (ssid, password)
endmenu

View File

@ -0,0 +1,363 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "cc.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "wired_iface.h"
#include "dhcpserver/dhcpserver_options.h"
#include "esp_mac.h"
#include "ethernet_init.h"
#include "esp_eth_netif_glue.h"
/**
* Disable promiscuous mode on Ethernet interface by setting this macro to 0
* if disabled, we'd have to rewrite MAC addressed in frames with the actual Eth interface MAC address
* - this results in better throughput
* - might cause ARP conflicts if the PC is also connected to the same AP with another NIC
*/
#define ETH_BRIDGE_PROMISCUOUS 0
/**
* Set this to 1 to runtime update HW addresses in DHCP messages
* (this is needed if the client uses 61 option and the DHCP server applies strict rules on assigning addresses)
*/
#define MODIFY_DHCP_MSGS 0
static const char *TAG = "example_wired_ethernet";
static esp_eth_handle_t s_eth_handle = NULL;
static bool s_ethernet_is_connected = false;
static uint8_t s_eth_mac[6];
static wired_rx_cb_t s_rx_cb = NULL;
static wired_free_cb_t s_free_cb = NULL;
/**
* @brief Event handler for Ethernet events
*/
void eth_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
uint8_t mac_addr[6] = {0};
/* we can get the ethernet driver handle from event data */
esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;
esp_netif_t *netif = (esp_netif_t*)arg;
switch (event_id) {
case ETHERNET_EVENT_CONNECTED:
ESP_LOGI(TAG, "Ethernet Link Up");
esp_netif_dhcps_start(netif);
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr);
ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
s_ethernet_is_connected = true;
break;
case ETHERNET_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "Ethernet Link Down");
esp_netif_dhcps_stop(netif);
s_ethernet_is_connected = false;
break;
case ETHERNET_EVENT_START:
ESP_LOGI(TAG, "Ethernet Started");
break;
case ETHERNET_EVENT_STOP:
ESP_LOGI(TAG, "Ethernet Stopped");
break;
default:
ESP_LOGI(TAG, "Default Event");
break;
}
}
/**
* In this scenario of WiFi station to Ethernet bridge mode, we have this configuration
*
* (ISP) router ESP32 PC
* [ AP ] <-> [ sta -- eth ] <-> [ eth-NIC ]
*
* From the PC's NIC perspective the L2 forwarding should be transparent and resemble this configuration:
*
* (ISP) router PC
* [ AP ] <----------> [ virtual wifi-NIC ]
*
* In order for the ESP32 to act as L2 bridge it needs to accept all frames on the interface
* - For Ethernet we just enable `PROMISCUOUS` mode
* - For Wifi we could also enable the promiscuous mode, but in that case we'd receive encoded frames
* from 802.11 and we'd have to decode it and process (using wpa-supplicant).
* The easier option (in this scenario of only one client -- eth-NIC) we could simply "pretend"
* that we have the HW mac address of eth-NIC and receive only ethernet frames for "us" from esp_wifi API
* (we could use the same technique for Ethernet and yield better throughput, see ETH_BRIDGE_PROMISCUOUS flag)
*
* This API updates Ethernet frames to swap mac addresses of ESP32 interfaces with those of eth-NIC and AP.
* For that we'd have to parse initial DHCP packets (manually) to record the HW addresses of the AP and eth-NIC
* (note, that it is possible to simply spoof the MAC addresses, but that's not recommended technique)
*/
#define IP_V4 0x40
#define IP_PROTO_UDP 0x11
#define DHCP_PORT_IN 0x43
#define DHCP_PORT_OUT 0x44
#define DHCP_MACIG_COOKIE_OFFSET (8 + 236)
#define DHCP_HW_ADDRESS_OFFSET (36)
#define MIN_DHCP_PACKET_SIZE (285)
#define IP_HEADER_SIZE (20)
#define DHCP_DISCOVER 1
#define DHCP_OFFER 2
#define DHCP_COOKIE_WITH_PKT_TYPE(type) {0x63, 0x82, 0x53, 0x63, 0x35, 1, type};
#if MODIFY_DHCP_MSGS
static void update_udp_checksum(uint16_t *udp_header, uint16_t* ip_header)
{
uint32_t sum = 0;
uint16_t *ptr = udp_header;
ptr[3] = 0; // clear the current checksum
int payload_len = htons(ip_header[1]) - IP_HEADER_SIZE;
// add UDP payload
for (int i = 0; i < payload_len/2; i++) {
sum += htons(*ptr++);
}
// add some IP header data
ptr = ip_header + 6;
for (int i = 0; i < 4; i++) { // IP addresses
sum += htons(*ptr++);
}
sum += IP_PROTO_UDP + payload_len; // protocol + size
do {
sum = (sum & 0xFFFF) + (sum >> 16);
} while (sum & 0xFFFF0000); // process the carry
ptr = udp_header;
ptr[3] = htons(~sum); // update the UDP header with the new checksum
}
#endif // MODIFY_DHCP_MSGS
void mac_spoof(mac_spoof_direction_t direction, uint8_t *buffer, uint16_t len, uint8_t own_mac[6])
{
if (!s_ethernet_is_connected) {
return;
}
static uint8_t eth_nic_mac[6] = {};
static bool eth_nic_mac_found = false;
#if !ETH_BRIDGE_PROMISCUOUS
static uint8_t ap_mac[6] = {};
static bool ap_mac_found = false;
#endif
uint8_t *dest_mac = buffer;
uint8_t *src_mac = buffer + 6;
uint8_t *eth_type = buffer + 12;
if (eth_type[0] == 0x08) { // support only IPv4
// try to find NIC HW address (look for DHCP discovery packet)
if ( (!eth_nic_mac_found || (MODIFY_DHCP_MSGS)) && direction == FROM_WIRED && eth_type[1] == 0x00) { // ETH IP4
uint8_t *ip_header = eth_type + 2;
if (len > MIN_DHCP_PACKET_SIZE && (ip_header[0] & 0xF0) == IP_V4 && ip_header[9] == IP_PROTO_UDP) {
uint8_t *udp_header = ip_header + IP_HEADER_SIZE;
const uint8_t dhcp_ports[] = {0, DHCP_PORT_OUT, 0, DHCP_PORT_IN};
if (memcmp(udp_header, dhcp_ports, sizeof(dhcp_ports)) == 0) {
uint8_t *dhcp_magic = udp_header + DHCP_MACIG_COOKIE_OFFSET;
const uint8_t dhcp_type[] = DHCP_COOKIE_WITH_PKT_TYPE(DHCP_DISCOVER);
if (!eth_nic_mac_found && memcmp(dhcp_magic, dhcp_type, sizeof(dhcp_type)) == 0) {
eth_nic_mac_found = true;
memcpy(eth_nic_mac, src_mac, 6);
}
#if MODIFY_DHCP_MSGS
if (eth_nic_mac_found) {
bool update_checksum = false;
// Replace the BOOTP HW address
uint8_t *dhcp_client_hw_addr = udp_header + DHCP_HW_ADDRESS_OFFSET;
if (memcmp(dhcp_client_hw_addr, eth_nic_mac, 6) == 0) {
memcpy(dhcp_client_hw_addr, own_mac, 6);
update_checksum = true;
}
// Replace the HW address in opt-61
uint8_t *dhcp_opts = dhcp_magic + 4;
while (*dhcp_opts != 0xFF) {
if (dhcp_opts[0] == 61 && dhcp_opts[1] == 7 /* size (type=1 + mac=6) */ && dhcp_opts[2] == 1 /* HW address type*/ &&
memcmp(dhcp_opts + 3, eth_nic_mac, 6) == 0) {
update_checksum = true;
memcpy(dhcp_opts + 3, own_mac, 6);
break;
}
dhcp_opts += dhcp_opts[1]+ 2;
if (dhcp_opts - buffer >= len) {
break;
}
}
if (update_checksum) {
update_udp_checksum((uint16_t *) udp_header, (uint16_t *) ip_header);
}
}
#endif // MODIFY_DHCP_MSGS
} // DHCP
} // UDP/IP
#if !ETH_BRIDGE_PROMISCUOUS || MODIFY_DHCP_MSGS
// try to find AP HW address (look for DHCP offer packet)
} else if ( (!ap_mac_found || (MODIFY_DHCP_MSGS)) && direction == TO_WIRED && eth_type[1] == 0x00) { // ETH IP4
uint8_t *ip_header = eth_type + 2;
if (len > MIN_DHCP_PACKET_SIZE && (ip_header[0] & 0xF0) == IP_V4 && ip_header[9] == IP_PROTO_UDP) {
uint8_t *udp_header = ip_header + IP_HEADER_SIZE;
const uint8_t dhcp_ports[] = {0, DHCP_PORT_IN, 0, DHCP_PORT_OUT};
if (memcmp(udp_header, dhcp_ports, sizeof(dhcp_ports)) == 0) {
uint8_t *dhcp_magic = udp_header + DHCP_MACIG_COOKIE_OFFSET;
#if MODIFY_DHCP_MSGS
if (eth_nic_mac_found) {
uint8_t *dhcp_client_hw_addr = udp_header + DHCP_HW_ADDRESS_OFFSET;
// Replace BOOTP HW address
if (memcmp(dhcp_client_hw_addr, own_mac, 6) == 0) {
memcpy(dhcp_client_hw_addr, eth_nic_mac, 6);
update_udp_checksum((uint16_t*)udp_header, (uint16_t*)ip_header);
}
}
#endif // MODIFY_DHCP_MSGS
const uint8_t dhcp_type[] = DHCP_COOKIE_WITH_PKT_TYPE(DHCP_OFFER);
if (!ap_mac_found && memcmp(dhcp_magic, dhcp_type, sizeof(dhcp_type)) == 0) {
ap_mac_found = true;
memcpy(ap_mac, src_mac, 6);
}
} // DHCP
} // UDP/IP
#endif // !ETH_BRIDGE_PROMISCUOUS || MODIFY_DHCP_MSGS
}
// swap addresses in ARP probes
if (eth_type[1] == 0x06) { // ARP
uint8_t *arp = eth_type + 2 + 8; // points to sender's HW address
if (eth_nic_mac_found && direction == FROM_WIRED && memcmp(arp, eth_nic_mac, 6) == 0) {
/* updates senders HW address to our wireless */
memcpy(arp, own_mac, 6);
#if !ETH_BRIDGE_PROMISCUOUS
} else if (ap_mac_found && direction == TO_WIRED && memcmp(arp, ap_mac, 6) == 0) {
/* updates senders HW address to our wired */
memcpy(arp, s_eth_mac, 6);
#endif // !ETH_BRIDGE_PROMISCUOUS
}
}
// swap HW addresses in ETH frames
#if !ETH_BRIDGE_PROMISCUOUS
if (ap_mac_found && direction == FROM_WIRED && memcmp(dest_mac, s_eth_mac, 6) == 0) {
memcpy(dest_mac, ap_mac, 6);
}
if (ap_mac_found && direction == TO_WIRED && memcmp(src_mac, ap_mac, 6) == 0) {
memcpy(src_mac, s_eth_mac, 6);
}
#endif // !ETH_BRIDGE_PROMISCUOUS
if (eth_nic_mac_found && direction == FROM_WIRED && memcmp(src_mac, eth_nic_mac, 6) == 0) {
memcpy(src_mac, own_mac, 6);
}
if (eth_nic_mac_found && direction == TO_WIRED && memcmp(dest_mac, own_mac, 6) == 0) {
memcpy(dest_mac, eth_nic_mac, 6);
}
} // IP4 section of eth-type (0x08) both ETH-IP4 and ETHARP
}
static esp_err_t wired_recv(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t len, void *priv)
{
esp_err_t ret = s_rx_cb(buffer, len, buffer);
free(buffer);
return ret;
}
esp_err_t wired_bridge_init(wired_rx_cb_t rx_cb, wired_free_cb_t free_cb)
{
uint8_t eth_port_cnt = 0;
esp_eth_handle_t *eth_handles;
ESP_ERROR_CHECK(example_eth_init(&eth_handles, &eth_port_cnt));
// Check for multiple Ethernet interfaces
if (1 < eth_port_cnt) {
ESP_LOGW(TAG, "Multiple Ethernet Interface detected: Only the first initialized interface is going to be used.");
}
s_eth_handle = eth_handles[0];
free(eth_handles);
ESP_ERROR_CHECK(esp_eth_update_input_path(s_eth_handle, wired_recv, NULL));
#if ETH_BRIDGE_PROMISCUOUS
bool eth_promiscuous = true;
ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_S_PROMISCUOUS, &eth_promiscuous));
#endif
ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_G_MAC_ADDR, &s_eth_mac));
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, NULL));
ESP_ERROR_CHECK(esp_eth_start(s_eth_handle));
s_rx_cb = rx_cb;
s_free_cb = free_cb;
return ESP_OK;
}
esp_err_t wired_send(void *buffer, uint16_t len, void *buff_free_arg)
{
if (s_ethernet_is_connected) {
if (esp_eth_transmit(s_eth_handle, buffer, len) != ESP_OK) {
ESP_LOGE(TAG, "Ethernet send packet failed");
return ESP_FAIL;
}
if (s_free_cb) {
s_free_cb(buff_free_arg, NULL);
}
return ESP_OK;
}
return ESP_ERR_INVALID_STATE;
}
/**
* In this scenario of configuring WiFi, we setup Ethernet to create a network and run DHCP server,
* so it could assign an IP address to the PC
*
* ESP32 PC
* | <DHCP server> eth | <-> [ eth-NIC ]
* | <HTTP server> |
* | (wifi-provisioning) |
*
* From the PC's NIC perspective the board acts as a separate network with it's own IP and MAC address
* (this network's MAC address is the native ESP32's Ethernet interface MAC)
*/
esp_err_t wired_netif_init(void)
{
uint8_t eth_port_cnt = 0;
esp_eth_handle_t *eth_handles;
ESP_ERROR_CHECK(example_eth_init(&eth_handles, &eth_port_cnt));
// Check or multiple ethernet interface
if (1 < eth_port_cnt) {
ESP_LOGW(TAG, "Multiple Ethernet Interface detected: Only the first initialized interface is going to be used.");
}
s_eth_handle = eth_handles[0];
free(eth_handles);
// 1) Derive the base config (very similar to IDF's default WiFi AP with DHCP server)
esp_netif_inherent_config_t base_cfg = {
.flags = ESP_NETIF_DHCP_SERVER, // Run DHCP server
.ip_info = &_g_esp_netif_soft_ap_ip, // Use the same IP ranges as IDF's soft AP
.if_key = "wired", // Set mame, key, priority
.if_desc = "ethernet config device",
.route_prio = 10
};
// Config the esp-netif with:
// 1) inherent config (behavioural settings of an interface)
// 2) driver's config -- no need, will use the default ethernet-netif glue and attach it to this netif
// 3) stack config -- will use the default ethernet TCP/IP settings
esp_netif_config_t cfg = {
.base = &base_cfg,
.stack = ESP_NETIF_NETSTACK_DEFAULT_ETH
};
esp_netif_t *netif = esp_netif_new(&cfg);
if (netif == NULL) {
return ESP_FAIL;
}
// Now we attach the constructed network interface to IDF's default ethernet glue
esp_eth_netif_glue_handle_t eth_glue = esp_eth_new_netif_glue(s_eth_handle);
ESP_ERROR_CHECK(esp_netif_attach(netif, eth_glue));
uint8_t mac[6];
ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_G_MAC_ADDR, &mac));
esp_netif_set_mac(netif, mac);
// set the minimum lease time
uint32_t lease_opt = 1;
esp_netif_dhcps_option(netif, ESP_NETIF_OP_SET, IP_ADDRESS_LEASE_TIME, &lease_opt, sizeof(lease_opt));
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, netif));
ESP_ERROR_CHECK(esp_eth_start(s_eth_handle));
return ESP_OK;
}

View File

@ -0,0 +1,9 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "^1.3.0"
rules:
- if: "idf_version >=4.4"
- if: "target in [esp32s2, esp32s3]"
idf: "^5.0"

View File

@ -0,0 +1,120 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "esp_wifi.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_http_server.h"
#include "dns_server.h"
static const char *TAG = "NCM_configuration";
static httpd_handle_t s_web_server = NULL;
static EventGroupHandle_t *s_flags = NULL;
static int s_success_bit;
bool is_provisioned(void)
{
wifi_config_t wifi_cfg;
if (esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK) {
return false;
}
if (strlen((const char *) wifi_cfg.sta.ssid)) {
return true;
}
return false;
}
static esp_err_t http_get_handler(httpd_req_t *req)
{
const char page[] = "<form action=\"/\" method=\"get\"><br><br>\n"
"SSID: <input type=\"text\" id=\"ssid\" name=\"ssid\"><br><br>\n"
"Password: <input type=\"text\" id=\"password\" name=\"password\"><br><br>\n"
" <input type=\"submit\" value=\"Connect\">"
"</form>";
char *buf = NULL;
size_t buf_len;
buf_len = httpd_req_get_url_query_len(req) + 1;
if (buf_len > 1) {
buf = malloc(buf_len);
if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
ESP_LOGI(TAG, "Found URL query => %s", buf);
char param[32];
wifi_config_t wifi_cfg = {};
if (httpd_query_key_value(buf, "ssid", param, sizeof(param)) == ESP_OK) {
ESP_LOGI(TAG, "ssid=%s", param);
strncpy((char *)wifi_cfg.sta.ssid, param, sizeof(wifi_cfg.sta.ssid));
}
if (httpd_query_key_value(buf, "password", param, sizeof(param)) == ESP_OK) {
ESP_LOGI(TAG, "password=%s", param);
strncpy((char *)wifi_cfg.sta.password, param, sizeof(wifi_cfg.sta.password));
}
if (strlen((char *)wifi_cfg.sta.ssid) > 0 && strlen((char *)wifi_cfg.sta.password)) {
httpd_resp_set_type(req, "text/html");
esp_wifi_set_mode(WIFI_MODE_STA);
if (esp_wifi_set_storage(WIFI_STORAGE_FLASH) == ESP_OK &&
esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg) == ESP_OK) {
const char wifi_configured[] = "<h1>Connecting...</h1>";
ESP_LOGI(TAG, "WiFi settings accepted!");
httpd_resp_send(req, wifi_configured, strlen(wifi_configured));
} else {
const char wifi_config_failed[] = "<h1>Failed to configure WiFi settings</h1>";
ESP_LOGE(TAG, "Failed to set WiFi config to flash");
httpd_resp_send(req, wifi_config_failed, strlen(wifi_config_failed));
}
free(buf);
if (s_flags) {
xEventGroupSetBits(*s_flags, s_success_bit);
}
return ESP_OK;
}
}
free(buf);
}
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, page, sizeof(page));
return ESP_OK;
}
static const httpd_uri_t root = {
.uri = "/",
.method = HTTP_GET,
.handler = http_get_handler,
};
static void start_webserver(void)
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_open_sockets = 3;
config.lru_purge_enable = true;
// Start the httpd server
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
if (httpd_start(&s_web_server, &config) == ESP_OK) {
// Set URI handlers
ESP_LOGI(TAG, "Registering URI handlers");
httpd_register_uri_handler(s_web_server, &root);
}
}
esp_err_t start_provisioning(EventGroupHandle_t *flags, int success_bit, int fail_bit)
{
start_webserver();
// Start the DNS server that will reply to "wifi.settings" with "usb" network interface address
dns_server_config_t config = DNS_SERVER_CONFIG_SINGLE("wifi.settings" /* name */, "wired" /* USB netif ID */);
start_dns_server(&config);
s_flags = flags;
s_success_bit = success_bit;
return ESP_OK;
}

View File

@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <wifi_provisioning/manager.h>
#include "esp_log.h"
#include "esp_mac.h"
#include "esp_event.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "dns_server.h"
static const char *TAG = "NCM_provisioning";
#if CONFIG_EXAMPLE_PROV_SECURITY_VERSION_2
#if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE
#define EXAMPLE_PROV_SEC2_USERNAME "wifiprov"
#define EXAMPLE_PROV_SEC2_PWD "abcd1234"
/* This salt,verifier has been generated for username = "wifiprov" and password = "abcd1234"
* IMPORTANT NOTE: For production cases, this must be unique to every device
* and should come from device manufacturing partition.*/
static const char sec2_salt[] = {
0x03, 0x6e, 0xe0, 0xc7, 0xbc, 0xb9, 0xed, 0xa8, 0x4c, 0x9e, 0xac, 0x97, 0xd9, 0x3d, 0xec, 0xf4
};
static const char sec2_verifier[] = {
0x7c, 0x7c, 0x85, 0x47, 0x65, 0x08, 0x94, 0x6d, 0xd6, 0x36, 0xaf, 0x37, 0xd7, 0xe8, 0x91, 0x43,
0x78, 0xcf, 0xfd, 0x61, 0x6c, 0x59, 0xd2, 0xf8, 0x39, 0x08, 0x12, 0x72, 0x38, 0xde, 0x9e, 0x24,
0xa4, 0x70, 0x26, 0x1c, 0xdf, 0xa9, 0x03, 0xc2, 0xb2, 0x70, 0xe7, 0xb1, 0x32, 0x24, 0xda, 0x11,
0x1d, 0x97, 0x18, 0xdc, 0x60, 0x72, 0x08, 0xcc, 0x9a, 0xc9, 0x0c, 0x48, 0x27, 0xe2, 0xae, 0x89,
0xaa, 0x16, 0x25, 0xb8, 0x04, 0xd2, 0x1a, 0x9b, 0x3a, 0x8f, 0x37, 0xf6, 0xe4, 0x3a, 0x71, 0x2e,
0xe1, 0x27, 0x86, 0x6e, 0xad, 0xce, 0x28, 0xff, 0x54, 0x46, 0x60, 0x1f, 0xb9, 0x96, 0x87, 0xdc,
0x57, 0x40, 0xa7, 0xd4, 0x6c, 0xc9, 0x77, 0x54, 0xdc, 0x16, 0x82, 0xf0, 0xed, 0x35, 0x6a, 0xc4,
0x70, 0xad, 0x3d, 0x90, 0xb5, 0x81, 0x94, 0x70, 0xd7, 0xbc, 0x65, 0xb2, 0xd5, 0x18, 0xe0, 0x2e,
0xc3, 0xa5, 0xf9, 0x68, 0xdd, 0x64, 0x7b, 0xb8, 0xb7, 0x3c, 0x9c, 0xfc, 0x00, 0xd8, 0x71, 0x7e,
0xb7, 0x9a, 0x7c, 0xb1, 0xb7, 0xc2, 0xc3, 0x18, 0x34, 0x29, 0x32, 0x43, 0x3e, 0x00, 0x99, 0xe9,
0x82, 0x94, 0xe3, 0xd8, 0x2a, 0xb0, 0x96, 0x29, 0xb7, 0xdf, 0x0e, 0x5f, 0x08, 0x33, 0x40, 0x76,
0x52, 0x91, 0x32, 0x00, 0x9f, 0x97, 0x2c, 0x89, 0x6c, 0x39, 0x1e, 0xc8, 0x28, 0x05, 0x44, 0x17,
0x3f, 0x68, 0x02, 0x8a, 0x9f, 0x44, 0x61, 0xd1, 0xf5, 0xa1, 0x7e, 0x5a, 0x70, 0xd2, 0xc7, 0x23,
0x81, 0xcb, 0x38, 0x68, 0xe4, 0x2c, 0x20, 0xbc, 0x40, 0x57, 0x76, 0x17, 0xbd, 0x08, 0xb8, 0x96,
0xbc, 0x26, 0xeb, 0x32, 0x46, 0x69, 0x35, 0x05, 0x8c, 0x15, 0x70, 0xd9, 0x1b, 0xe9, 0xbe, 0xcc,
0xa9, 0x38, 0xa6, 0x67, 0xf0, 0xad, 0x50, 0x13, 0x19, 0x72, 0x64, 0xbf, 0x52, 0xc2, 0x34, 0xe2,
0x1b, 0x11, 0x79, 0x74, 0x72, 0xbd, 0x34, 0x5b, 0xb1, 0xe2, 0xfd, 0x66, 0x73, 0xfe, 0x71, 0x64,
0x74, 0xd0, 0x4e, 0xbc, 0x51, 0x24, 0x19, 0x40, 0x87, 0x0e, 0x92, 0x40, 0xe6, 0x21, 0xe7, 0x2d,
0x4e, 0x37, 0x76, 0x2f, 0x2e, 0xe2, 0x68, 0xc7, 0x89, 0xe8, 0x32, 0x13, 0x42, 0x06, 0x84, 0x84,
0x53, 0x4a, 0xb3, 0x0c, 0x1b, 0x4c, 0x8d, 0x1c, 0x51, 0x97, 0x19, 0xab, 0xae, 0x77, 0xff, 0xdb,
0xec, 0xf0, 0x10, 0x95, 0x34, 0x33, 0x6b, 0xcb, 0x3e, 0x84, 0x0f, 0xb9, 0xd8, 0x5f, 0xb8, 0xa0,
0xb8, 0x55, 0x53, 0x3e, 0x70, 0xf7, 0x18, 0xf5, 0xce, 0x7b, 0x4e, 0xbf, 0x27, 0xce, 0xce, 0xa8,
0xb3, 0xbe, 0x40, 0xc5, 0xc5, 0x32, 0x29, 0x3e, 0x71, 0x64, 0x9e, 0xde, 0x8c, 0xf6, 0x75, 0xa1,
0xe6, 0xf6, 0x53, 0xc8, 0x31, 0xa8, 0x78, 0xde, 0x50, 0x40, 0xf7, 0x62, 0xde, 0x36, 0xb2, 0xba
};
#endif
static esp_err_t example_get_sec2_salt(const char **salt, uint16_t *salt_len)
{
#if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE
ESP_LOGI(TAG, "Development mode: using hard coded salt");
*salt = sec2_salt;
*salt_len = sizeof(sec2_salt);
return ESP_OK;
#elif CONFIG_EXAMPLE_PROV_SEC2_PROD_MODE
ESP_LOGE(TAG, "Not implemented!");
return ESP_FAIL;
#endif
}
static esp_err_t example_get_sec2_verifier(const char **verifier, uint16_t *verifier_len)
{
#if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE
ESP_LOGI(TAG, "Development mode: using hard coded verifier");
*verifier = sec2_verifier;
*verifier_len = sizeof(sec2_verifier);
return ESP_OK;
#elif CONFIG_EXAMPLE_PROV_SEC2_PROD_MODE
/* This code needs to be updated with appropriate implementation to provide verifier */
ESP_LOGE(TAG, "Not implemented!");
return ESP_FAIL;
#endif
}
#endif // CONFIG_EXAMPLE_PROV_SECURITY_VERSION_2
struct events {
EventGroupHandle_t *flags;
int success_bit;
int fail_bit;
bool success;
};
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
struct events *handler_args = arg;
switch (event_id) {
case WIFI_PROV_START:
ESP_LOGI(TAG, "Provisioning started");
break;
case WIFI_PROV_CRED_RECV: {
wifi_sta_config_t *wifi_sta_cfg = (wifi_sta_config_t *) event_data;
ESP_LOGI(TAG, "Received Wi-Fi credentials"
"\n\tSSID : %s\n\tPassword : %s",
(const char *) wifi_sta_cfg->ssid,
(const char *) wifi_sta_cfg->password);
break;
}
case WIFI_PROV_CRED_FAIL: {
wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *) event_data;
ESP_LOGE(TAG, "Provisioning failed!\n\tReason : %s"
"\n\tPlease reset to factory and retry provisioning",
(*reason == WIFI_PROV_STA_AUTH_ERROR) ?
"Wi-Fi station authentication failed" : "Wi-Fi access-point not found");
handler_args->success = false;
break;
}
case WIFI_PROV_CRED_SUCCESS:
ESP_LOGI(TAG, "Provisioning successful");
handler_args->success = true;
break;
case WIFI_PROV_END:
/* De-initialize manager once provisioning is finished */
wifi_prov_mgr_deinit();
xEventGroupSetBits(*handler_args->flags, handler_args->success ? handler_args->success_bit : handler_args->fail_bit);
free(handler_args);
break;
default:
break;
}
}
extern const wifi_prov_scheme_t wifi_prov_scheme_httpd;
esp_err_t start_provisioning(EventGroupHandle_t *flags, int success_bit, int fail_bit)
{
// Start the DNS server that will reply to "wifi.settings" with "usb" network interface address
dns_server_config_t dns_config = DNS_SERVER_CONFIG_SINGLE("wifi.settings" /* name */, "wired" /* wired netif ID */);
start_dns_server(&dns_config);
struct events *handler_args = malloc(sizeof(struct events));
handler_args->flags = flags;
handler_args->success_bit = success_bit;
handler_args->fail_bit = fail_bit;
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, event_handler, handler_args));
/* Configuration for the provisioning manager */
wifi_prov_mgr_config_t config = {
.scheme = wifi_prov_scheme_httpd,
};
/* Initialize provisioning manager with the
* configuration parameters set above */
ESP_ERROR_CHECK(wifi_prov_mgr_init(config));
/* What is the security level that we want (0, 1, 2):
* - WIFI_PROV_SECURITY_0 is simply plain text communication.
* - WIFI_PROV_SECURITY_1 is secure communication which consists of secure handshake
* - WIFI_PROV_SECURITY_2 SRP6a based authentication and key exchange
* Please check unified provisioning documentation for more details
*/
#ifdef CONFIG_EXAMPLE_PROV_SECURITY_VERSION_0
wifi_prov_security_t security = WIFI_PROV_SECURITY_0;
#elif CONFIG_EXAMPLE_PROV_SECURITY_VERSION_1
wifi_prov_security_t security = WIFI_PROV_SECURITY_1;
const char *pop = "abcd1234"; /* Proof of possession */
wifi_prov_security1_params_t *sec_params = pop;
#elif CONFIG_EXAMPLE_PROV_SECURITY_VERSION_2
wifi_prov_security_t security = WIFI_PROV_SECURITY_2;
/* The username must be the same one, which has been used in the generation of salt and verifier */
#if CONFIG_EXAMPLE_PROV_SEC2_DEV_MODE
/* This pop field represents the password that will be used to generate salt and verifier.
* The field is present here in order to generate the QR code containing password.
* In production this password field shall not be stored on the device */
const char *username = EXAMPLE_PROV_SEC2_USERNAME;
const char *pop = EXAMPLE_PROV_SEC2_PWD;
#elif CONFIG_EXAMPLE_PROV_SEC2_PROD_MODE
/* The username and password shall not be embedded in the firmware */
const char *username = NULL;
const char *pop = NULL;
#endif
/* This is the structure for passing security parameters
* for the protocomm security 2.
* If dynamically allocated, sec2_params pointer and its content
* must be valid till WIFI_PROV_END event is triggered.
*/
wifi_prov_security2_params_t sec2_params = {};
ESP_ERROR_CHECK(example_get_sec2_salt(&sec2_params.salt, &sec2_params.salt_len));
ESP_ERROR_CHECK(example_get_sec2_verifier(&sec2_params.verifier, &sec2_params.verifier_len));
wifi_prov_security2_params_t *sec_params = &sec2_params;
#endif // CONFIG_EXAMPLE_PROV_SECURITY_VERSION_0 (VERSION_1, VERSION_2)
ESP_ERROR_CHECK(wifi_prov_mgr_start_provisioning(security, (const void *) sec_params, NULL, NULL)); // service name and key could be NULL
return ESP_OK;
}
bool is_provisioned(void)
{
bool provisioned = false;
ESP_ERROR_CHECK(wifi_prov_mgr_is_provisioned(&provisioned));
return provisioned;
}

View File

@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
/**
* @brief Checks if the device has been provisioned
* @return true if WiFi is provisioned
*/
bool is_provisioned(void);
/**
* @brief Initiate provisioning
* @param flags Event flags to indicate status of provisioning
* @param success_bit bits set in the event flags on success
* @param fail_bit bits set in the event flags on failure
* @return ESP_OK if provisioning started
*/
esp_err_t start_provisioning(EventGroupHandle_t *flags, int success_bit, int fail_bit);

View File

@ -0,0 +1,91 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "sdkconfig.h"
#include <esp_log.h>
#include <esp_err.h>
#include <wifi_provisioning/manager.h>
#include <protocomm.h>
#include <protocomm_httpd.h>
static const char *TAG = "wifi_prov_scheme_httpd";
static esp_err_t prov_start(protocomm_t *pc, void *config)
{
if (!pc) {
ESP_LOGE(TAG, "Protocomm handle cannot be null");
return ESP_ERR_INVALID_ARG;
}
if (!config) {
ESP_LOGE(TAG, "Cannot start with null configuration");
return ESP_ERR_INVALID_ARG;
}
protocomm_httpd_config_t default_config = {
.data = {
.config = PROTOCOMM_HTTPD_DEFAULT_CONFIG()
}
};
/* Start protocomm server on top of HTTP */
esp_err_t err = protocomm_httpd_start(pc, &default_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start protocomm HTTP server");
return err;
}
return ESP_OK;
}
static esp_err_t prov_stop(protocomm_t *pc)
{
esp_err_t err = protocomm_httpd_stop(pc);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Error occurred while stopping protocomm_httpd");
}
return err;
}
/**
* @brief Creates a configuration for this custom provisioning scheme.
*
* We don't need to pass any config option at this moment, so we create
* a dummy configuration since provisioning manager check for non-nullptr.
* If needed we can extend this scheme to provide some options for httpd
* or wifi provisioning.
*/
static void *new_config(void)
{
return (void *)1;
}
static void delete_config(void *config)
{
}
static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key)
{
return ESP_OK;
}
static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid)
{
return ESP_OK;
}
/**
* @brief Creating a generic HTTPD scheme
*/
const wifi_prov_scheme_t wifi_prov_scheme_httpd = {
.prov_start = prov_start,
.prov_stop = prov_stop,
.new_config = new_config,
.delete_config = delete_config,
.set_config_service = set_config_service,
.set_config_endpoint = set_config_endpoint,
.wifi_mode = WIFI_MODE_STA
};

View File

@ -0,0 +1,212 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include <esp_timer.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "esp_private/wifi.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "provisioning.h"
#include "wired_iface.h"
static const char *TAG = "example_sta2wired";
static EventGroupHandle_t s_event_flags;
static bool s_wifi_is_connected = false;
static uint8_t s_sta_mac[6];
const int CONNECTED_BIT = BIT0;
const int DISCONNECTED_BIT = BIT1;
const int RECONFIGURE_BIT = BIT2;
const int PROV_SUCCESS_BIT = BIT3;
const int PROV_FAIL_BIT = BIT4;
/**
* WiFi -- Wired packet path
*/
static esp_err_t wired_recv_callback(void *buffer, uint16_t len, void *ctx)
{
if (s_wifi_is_connected) {
mac_spoof(FROM_WIRED, buffer, len, s_sta_mac);
if (esp_wifi_internal_tx(ESP_IF_WIFI_STA, buffer, len) != ESP_OK) {
ESP_LOGD(TAG, "Failed to send packet to WiFi!");
}
}
return ESP_OK;
}
static void wifi_buff_free(void *buffer, void *ctx)
{
esp_wifi_internal_free_rx_buffer(buffer);
}
static esp_err_t wifi_recv_callback(void *buffer, uint16_t len, void *eb)
{
mac_spoof(TO_WIRED, buffer, len, s_sta_mac);
if (wired_send(buffer, len, eb) != ESP_OK) {
esp_wifi_internal_free_rx_buffer(eb);
ESP_LOGD(TAG, "Failed to send packet to USB!");
}
return ESP_OK;
}
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Wi-Fi STA disconnected");
s_wifi_is_connected = false;
esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_STA, NULL);
esp_wifi_connect();
xEventGroupClearBits(s_event_flags, CONNECTED_BIT);
xEventGroupSetBits(s_event_flags, DISCONNECTED_BIT);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
ESP_LOGI(TAG, "Wi-Fi STA connected");
esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_STA, wifi_recv_callback);
s_wifi_is_connected = true;
xEventGroupClearBits(s_event_flags, DISCONNECTED_BIT);
xEventGroupSetBits(s_event_flags, CONNECTED_BIT);
}
}
static esp_err_t connect_wifi(void)
{
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, event_handler, NULL));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_start() );
wifi_config_t wifi_cfg;
if (esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK) {
// configuration not available, report error to restart provisioning
return ESP_FAIL;
}
esp_wifi_connect();
EventBits_t status = xEventGroupWaitBits(s_event_flags, CONNECTED_BIT, 0, 1, 10000 / portTICK_PERIOD_MS);
if (status & CONNECTED_BIT) {
ESP_LOGI(TAG, "WiFi station connected successfully");
return ESP_OK;
}
ESP_LOGE(TAG, "WiFi station connected failed");
return ESP_ERR_TIMEOUT;
}
/**
* GPIO button functionality
*/
#define GPIO_INPUT CONFIG_EXAMPLE_RECONFIGURE_BUTTON
#define GPIO_LONG_PUSH_US 2000000 /* push for 2 seconds to reconfigure */
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
static int64_t last_pushed = -1;
if (gpio_get_level(GPIO_INPUT) == 0) {
last_pushed = esp_timer_get_time();
} else {
uint64_t now = esp_timer_get_time();
if (last_pushed != -1 && now - last_pushed > GPIO_LONG_PUSH_US) {
BaseType_t high_task_wakeup;
xEventGroupSetBitsFromISR(s_event_flags, RECONFIGURE_BIT, &high_task_wakeup);
if (high_task_wakeup) {
portYIELD_FROM_ISR();
}
}
last_pushed = -1;
}
}
static void gpio_init(void)
{
gpio_config_t io_conf = { .intr_type = GPIO_INTR_ANYEDGE,
.pin_bit_mask = (1ULL << GPIO_INPUT),
.mode = GPIO_MODE_INPUT,
.pull_up_en = 1
};
gpio_config(&io_conf);
gpio_install_isr_service(0);
//hook isr handler for specific gpio pin
gpio_isr_handler_add(GPIO_INPUT, gpio_isr_handler, NULL);
}
/**
* Application
*/
void app_main(void)
{
static __NOINIT_ATTR uint32_t s_reconfigure_requested;
static const uint32_t RECONFIGURE_REQUEST = 0x1C55AA;
/* Check reset reason and decide if we should re-provision */
bool do_provision = false;
esp_reset_reason_t reason = esp_reset_reason();
ESP_LOGD(TAG, "After restart! %d", reason);
if (reason != ESP_RST_SW) {
s_reconfigure_requested = 0;
} else if (s_reconfigure_requested == RECONFIGURE_REQUEST) {
do_provision = true;
}
/* Initialize NVS and WiFi */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// init the flags and event loop
s_event_flags = xEventGroupCreate();
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* Init the re-provisioning button (long-press with initiate provisioning restart) */
gpio_init();
esp_read_mac(s_sta_mac, ESP_MAC_WIFI_STA);
/* Start the application in configuration mode (to perform provisioning)
* or in a bridge mode (already provisioned) */
if (do_provision || !is_provisioned()) {
ESP_LOGI(TAG, "Starting provisioning");
ESP_ERROR_CHECK(esp_netif_init());
// needed to complete provisioning with getting a valid IP event
esp_netif_create_default_wifi_sta();
// starts the wired interface with virtual network used to configure/provision the example
wired_netif_init();
start_provisioning(&s_event_flags, PROV_SUCCESS_BIT, PROV_FAIL_BIT);
} else {
ESP_LOGI(TAG, "Starting USB-WiFi bridge");
if (connect_wifi() != ESP_OK) {
// if we cannot connect to WiFi we just try to re-provision
xEventGroupSetBits(s_event_flags, RECONFIGURE_BIT);
} else {
// start the wired interface in the bridge mode
wired_bridge_init(wired_recv_callback, wifi_buff_free);
}
}
EventBits_t bits = xEventGroupWaitBits(s_event_flags, RECONFIGURE_BIT | PROV_SUCCESS_BIT | PROV_FAIL_BIT, pdTRUE, pdFALSE, portMAX_DELAY);
if (bits & RECONFIGURE_BIT || bits & PROV_FAIL_BIT) {
// retry provisioning if it previously failed or if requested by the button press
s_reconfigure_requested = RECONFIGURE_REQUEST;
} else {
// provisioning successfully finished, restart to the bridge mode
s_reconfigure_requested = 0;
}
vTaskDelay(pdMS_TO_TICKS(1000)); // to let httpd handle the closure
esp_restart();
}

View File

@ -0,0 +1,185 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* DESCRIPTION:
* This example contains code to make ESP32-S2/S3 as a USB network Device.
*/
#include <stdio.h>
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "tinyusb.h"
#include "tinyusb_net.h"
#include "wired_iface.h"
#include "dhcpserver/dhcpserver_options.h"
#include "lwip/esp_netif_net_stack.h"
#include "esp_mac.h"
static const char *TAG = "example_wired_tusb_ncm";
static esp_netif_t *s_netif = NULL;
/**
* In this scenario of WiFi station to Ethernet bridge mode, we have this configuration
*
* (ISP) router ESP32 PC
* [ AP ] <-> [ sta -- USB ] <-> [ USB-NCM device acting as eth-NIC ]
*
* From the PC's NIC perspective the L2 forwarding should be transparent and resemble this configuration:
*
* (ISP) router PC
* [ AP ] <----------> [ virtual wifi-NIC ]
*
* In order for the ESP32 to act as L2 bridge it needs to accept the frames for the NCM device,
* which we have fully under control, we can modify it's MAC address, as well as the WiFi station
* MAC address, which need to be the same so the AP would see one device (virtual eth-NIC).
* No need to modify the ethernet frames here, as we can set the station's MAC to the USB NCM device.
*/
void mac_spoof(mac_spoof_direction_t direction, uint8_t *buffer, uint16_t len, uint8_t own_mac[6])
{
}
esp_err_t wired_bridge_init(wired_rx_cb_t rx_cb, wired_free_cb_t free_cb)
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
tinyusb_net_config_t net_config = {
.on_recv_callback = rx_cb,
.free_tx_buffer = free_cb,
};
esp_read_mac(net_config.mac_addr, ESP_MAC_WIFI_STA);
esp_err_t ret = tinyusb_net_init(TINYUSB_USBDEV_0, &net_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "USB net init but not connect wifi");
return ret;
}
return ESP_OK;
}
esp_err_t wired_send(void *buffer, uint16_t len, void *buff_free_arg)
{
return tinyusb_net_send_sync(buffer, len, buff_free_arg, pdMS_TO_TICKS(100));
}
static void l2_free(void *h, void *buffer)
{
free(buffer);
}
static esp_err_t netif_transmit (void *h, void *buffer, size_t len)
{
if (wired_send(buffer, len, NULL) != ESP_OK) {
ESP_LOGE(TAG, "Failed to send buffer to USB!");
}
return ESP_OK;
}
static esp_err_t netif_recv_callback(void *buffer, uint16_t len, void *ctx)
{
if (s_netif) {
void *buf_copy = malloc(len);
if (!buf_copy) {
return ESP_ERR_NO_MEM;
}
memcpy(buf_copy, buffer, len);
return esp_netif_receive(s_netif, buf_copy, len, NULL);
}
return ESP_OK;
}
/**
* In this scenario of configuring WiFi, we setup USB-Ethernet to create a virtual network and run DHCP server,
* so it could assign an IP address to the PC
*
* ESP32 PC
* | lwip MAC=...01 | eth NIC MAC=...02
* | <DHCP server> usb | <-> [ USB-NCM device acting as eth-NIC ]
* | <HTTP server> |
* | (wifi-provisioning) |
*
* From the PC's NIC perspective the board acts as a separate network with it's own IP and MAC address,
* but the virtual ethernet NIC has also it's own IP and MAC address (configured via tinyusb_net_init()).
* That's why we need to create the virtual network with *different* MAC address.
* Here, we use two different OUI range MAC addresses.
*/
esp_err_t wired_netif_init(void)
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
const tinyusb_net_config_t net_config = {
// locally administrated address for the ncm device as it's going to be used internally
// for configuration only
.mac_addr = {0x02, 0x02, 0x11, 0x22, 0x33, 0x01},
.on_recv_callback = netif_recv_callback,
};
esp_err_t ret = tinyusb_net_init(TINYUSB_USBDEV_0, &net_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Cannot initialize USB Net device");
return ret;
}
// with OUI range MAC to create a virtual netif running http server
// this needs to be different to usb_interface_mac (==client)
uint8_t lwip_addr[6] = {0x02, 0x02, 0x11, 0x22, 0x33, 0x02};
// Definition of
// 1) Derive the base config (very similar to IDF's default WiFi AP with DHCP server)
esp_netif_inherent_config_t base_cfg = {
.flags = ESP_NETIF_DHCP_SERVER | ESP_NETIF_FLAG_AUTOUP, // Run DHCP server; set the netif "ip" immediately
.ip_info = &_g_esp_netif_soft_ap_ip, // Use the same IP ranges as IDF's soft AP
.if_key = "wired", // Set mame, key, priority
.if_desc = "usb ncm config device",
.route_prio = 10
};
// 2) Use static config for driver's config pointing only to static transmit and free functions
esp_netif_driver_ifconfig_t driver_cfg = {
.handle = (void *)1, // not using an instance, USB-NCM is a static singleton (must be != NULL)
.transmit = netif_transmit, // point to static Tx function
.driver_free_rx_buffer = l2_free // point to Free Rx buffer function
};
// 3) USB-NCM is an Ethernet netif from lwip perspective, we already have IO definitions for that:
struct esp_netif_netstack_config lwip_netif_config = {
.lwip = {
.init_fn = ethernetif_init,
.input_fn = ethernetif_input
}
};
// Config the esp-netif with:
// 1) inherent config (behavioural settings of an interface)
// 2) driver's config (connection to IO functions -- usb)
// 3) stack config (using lwip IO functions -- derive from eth)
esp_netif_config_t cfg = {
.base = &base_cfg,
.driver = &driver_cfg,
.stack = &lwip_netif_config
};
s_netif = esp_netif_new(&cfg);
if (s_netif == NULL) {
return ESP_FAIL;
}
esp_netif_set_mac(s_netif, lwip_addr);
// set the minimum lease time
uint32_t lease_opt = 1;
esp_netif_dhcps_option(s_netif, ESP_NETIF_OP_SET, IP_ADDRESS_LEASE_TIME, &lease_opt, sizeof(lease_opt));
// start the interface manually (as the driver has been started already)
esp_netif_action_start(s_netif, 0, 0, 0);
return ESP_OK;
}

View File

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
typedef esp_err_t (*wired_rx_cb_t)(void *buffer, uint16_t len, void *ctx);
typedef void (*wired_free_cb_t)(void *buffer, void *ctx);
typedef enum {
FROM_WIRED,
TO_WIRED
} mac_spoof_direction_t;
void mac_spoof(mac_spoof_direction_t direction, uint8_t *buffer, uint16_t len, uint8_t own_mac[6]);
esp_err_t wired_bridge_init(wired_rx_cb_t rx_cb, wired_free_cb_t free_cb);
esp_err_t wired_send(void *buffer, uint16_t len, void *buff_free_arg);
esp_err_t wired_netif_init(void);

View File

@ -0,0 +1 @@
CONFIG_EXAMPLE_WIRED_INTERFACE_IS_ETHERNET=y

View File

@ -0,0 +1,4 @@
# ESP32S2 has USB-OTG, let's prefer virtual Ethernet (USB-NCM device)
CONFIG_EXAMPLE_WIRED_INTERFACE_IS_USB=y
CONFIG_EXAMPLE_WIRED_INTERFACE_IS_ETHERNET=n
CONFIG_TINYUSB_NET_MODE_NCM=y

View File

@ -0,0 +1,11 @@
# ESP32S3 has USB-OTG, let's prefer virtual Ethernet (USB-NCM device)
CONFIG_EXAMPLE_WIRED_INTERFACE_IS_USB=y
CONFIG_EXAMPLE_WIRED_INTERFACE_IS_ETHERNET=n
# TinyUSB needs to be initialized and run from one core
# that's why we pin the task to CPU0 and init tusb in the task
# on dual core devices (ESP32S3)
CONFIG_TINYUSB_TASK_AFFINITY_CPU0=y
CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK=y
CONFIG_TINYUSB_NET_MODE_NCM=y

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS dns_server.c
INCLUDE_DIRS include
PRIV_REQUIRES esp_netif)

View File

@ -1,23 +1,22 @@
/* Captive Portal Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <sys/param.h>
#include <inttypes.h>
#include "esp_log.h"
#include "esp_system.h"
#include "esp_check.h"
#include "esp_netif.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "dns_server.h"
#define DNS_PORT (53)
#define DNS_MAX_LEN (256)
@ -57,6 +56,14 @@ typedef struct __attribute__((__packed__))
uint32_t ip_addr;
} dns_answer_t;
// DNS server handle
struct dns_server_handle {
bool started;
TaskHandle_t task;
int num_of_entries;
dns_entry_pair_t entry[];
};
/*
Parse the name from the packet from the DNS name format to a regular .-seperated name
returns the pointer to the next part of the packet
@ -90,7 +97,7 @@ static char *parse_dns_name(char *raw_name, char *parsed_name, size_t parsed_nam
}
// Parses the DNS request and prepares a DNS response with the IP of the softAP
static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t dns_reply_max_len)
static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t dns_reply_max_len, dns_server_handle_t h)
{
if (req_len > dns_reply_max_len) {
return -1;
@ -126,8 +133,8 @@ static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t
char *cur_qd_ptr = dns_reply + sizeof(dns_header_t);
char name[128];
// Respond to all questions with the ESP32's IP address
for (int i = 0; i < qd_count; i++) {
// Respond to all questions based on configured rules
for (int qd_i = 0; qd_i < qd_count; qd_i++) {
char *name_end_ptr = parse_dns_name(cur_qd_ptr, name, sizeof(name));
if (name_end_ptr == NULL) {
ESP_LOGE(TAG, "Failed to parse DNS question: %s", cur_qd_ptr);
@ -141,6 +148,25 @@ static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t
ESP_LOGD(TAG, "Received type: %d | Class: %d | Question for: %s", qd_type, qd_class, name);
if (qd_type == QD_TYPE_A) {
esp_ip4_addr_t ip = { .addr = IPADDR_ANY };
// Check the configured rules to decide whether to answer this question or not
for (int i = 0; i < h->num_of_entries; ++i) {
// check if the name either corresponds to the entry, or if we should answer to all queries ("*")
if (strcmp(h->entry[i].name, "*") == 0 || strcmp(h->entry[i].name, name) == 0) {
if (h->entry[i].if_key) {
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey(h->entry[i].if_key), &ip_info);
ip.addr = ip_info.ip.addr;
break;
} else if (h->entry->ip.addr != IPADDR_ANY) {
ip.addr = h->entry[i].ip.addr;
break;
}
}
}
if (ip.addr == IPADDR_ANY) { // no rule applies, continue with another question
continue;
}
dns_answer_t *answer = (dns_answer_t *)cur_ans_ptr;
answer->ptr_offset = htons(0xC000 | (cur_qd_ptr - dns_reply));
@ -148,12 +174,10 @@ static int parse_dns_request(char *req, size_t req_len, char *dns_reply, size_t
answer->class = htons(qd_class);
answer->ttl = htonl(ANS_TTL_SEC);
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);
ESP_LOGD(TAG, "Answer with PTR offset: 0x%" PRIX16 " and IP 0x%" PRIX32, ntohs(answer->ptr_offset), ip_info.ip.addr);
ESP_LOGD(TAG, "Answer with PTR offset: 0x%" PRIX16 " and IP 0x%" PRIX32, ntohs(answer->ptr_offset), ip.addr);
answer->addr_len = htons(sizeof(ip_info.ip.addr));
answer->ip_addr = ip_info.ip.addr;
answer->addr_len = htons(sizeof(ip.addr));
answer->ip_addr = ip.addr;
}
}
return reply_len;
@ -169,8 +193,9 @@ void dns_server_task(void *pvParameters)
char addr_str[128];
int addr_family;
int ip_protocol;
dns_server_handle_t handle = pvParameters;
while (1) {
while (handle->started) {
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = htonl(INADDR_ANY);
@ -193,7 +218,7 @@ void dns_server_task(void *pvParameters)
}
ESP_LOGI(TAG, "Socket bound, port %d", DNS_PORT);
while (1) {
while (handle->started) {
ESP_LOGI(TAG, "Waiting for data");
struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6
socklen_t socklen = sizeof(source_addr);
@ -218,7 +243,7 @@ void dns_server_task(void *pvParameters)
rx_buffer[len] = 0;
char reply[DNS_MAX_LEN];
int reply_len = parse_dns_request(rx_buffer, len, reply, DNS_MAX_LEN);
int reply_len = parse_dns_request(rx_buffer, len, reply, DNS_MAX_LEN, handle);
ESP_LOGI(TAG, "Received %d bytes from %s | DNS reply with len: %d", len, addr_str, reply_len);
if (reply_len <= 0) {
@ -242,7 +267,24 @@ void dns_server_task(void *pvParameters)
vTaskDelete(NULL);
}
void start_dns_server(void)
dns_server_handle_t start_dns_server(dns_server_config_t *config)
{
xTaskCreate(dns_server_task, "dns_server", 4096, NULL, 5, NULL);
dns_server_handle_t handle = calloc(1, sizeof(struct dns_server_handle) + config->num_of_entries * sizeof(dns_entry_pair_t));
ESP_RETURN_ON_FALSE(handle, NULL, TAG, "Failed to allocate dns server handle");
handle->started = true;
handle->num_of_entries = config->num_of_entries;
memcpy(handle->entry, config->item, config->num_of_entries * sizeof(dns_entry_pair_t));
xTaskCreate(dns_server_task, "dns_server", 4096, handle, 5, &handle->task);
return handle;
}
void stop_dns_server(dns_server_handle_t handle)
{
if (handle) {
handle->started = false;
vTaskDelete(handle->task);
free(handle);
}
}

View File

@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#ifndef DNS_SERVER_MAX_ITEMS
#define DNS_SERVER_MAX_ITEMS 1
#endif
#define DNS_SERVER_CONFIG_SINGLE(queried_name, netif_key) { \
.num_of_entries = 1, \
.item = { { .name = queried_name, .if_key = netif_key } } \
}
/**
* @brief Definition of one DNS entry: NAME - IP (or the netif whose IP to answer)
*
* @note Please use string literals (or ensure they are valid during dns_server lifetime) as names, since
* we don't take copies of the config values `name` and `if_key`
*/
typedef struct dns_entry_pair {
const char* name; /**<! Exact match of the name field of the DNS query to answer */
const char* if_key; /**<! Use this network interface IP to answer, only if NULL, use the static IP below */
esp_ip4_addr_t ip; /**<! Constant IP address to answer this query, if "if_key==NULL" */
} dns_entry_pair_t;
/**
* @brief DNS server config struct defining the rules for answering DNS (A type) queries
*
* @note If you want to define more rules, you can set `DNS_SERVER_MAX_ITEMS` before including this header
* Example of using 2 entries with constant IP addresses
* \code{.c}
* #define DNS_SERVER_MAX_ITEMS 2
* #include "dns_server.h"
*
* dns_server_config_t config = {
* .num_of_entries = 2,
* .item = { {.name = "my-esp32.com", .ip = { .addr = ESP_IP4TOADDR( 192, 168, 4, 1) } } ,
* {.name = "my-utils.com", .ip = { .addr = ESP_IP4TOADDR( 192, 168, 4, 100) } } } };
* start_dns_server(&config);
* \endcode
*/
typedef struct dns_server_config {
int num_of_entries; /**<! Number of rules specified in the config struct */
dns_entry_pair_t item[DNS_SERVER_MAX_ITEMS]; /**<! Array of pairs */
} dns_server_config_t;
/**
* @brief DNS server handle
*/
typedef struct dns_server_handle *dns_server_handle_t;
/**
* @brief Set ups and starts a simple DNS server that will respond to all A queries (IPv4)
* based on configured rules, pairs of name and either IPv4 address or a netif ID (to respond by it's IPv4 add)
*
* @param config Configuration structure listing the pairs of (name, IP/netif-id)
* @return dns_server's handle on success, NULL on failure
*/
dns_server_handle_t start_dns_server(dns_server_config_t *config);
/**
* @brief Stops and destroys DNS server's task and structs
* @param handle DNS server's handle to destroy
*/
void stop_dns_server(dns_server_handle_t handle);
#ifdef __cplusplus
}
#endif

View File

@ -1,3 +1,2 @@
idf_component_register(SRCS "main.c" "dns_server.c"
INCLUDE_DIRS "include"
EMBED_FILES root.html)
idf_component_register(SRCS main.c
EMBED_FILES root.html)

View File

@ -1,26 +0,0 @@
/* Captive Portal Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Set ups and starts a simple DNS server that will respond to all queries
* with the soft AP's IP address
*
*/
void start_dns_server(void);
#ifdef __cplusplus
}
#endif

View File

@ -159,5 +159,6 @@ void app_main(void)
start_webserver();
// Start the DNS server that will redirect all queries to the softAP IP
start_dns_server();
dns_server_config_t config = DNS_SERVER_CONFIG_SINGLE("*" /* all A queries */, "WIFI_AP_DEF" /* softAP netif ID */);
start_dns_server(&config);
}

View File

@ -40,10 +40,11 @@ python esp_prov.py --transport < mode of provisioning : softap \ ble \ console >
* `console` - for debugging via console-based provisioning
* The client->device commands are printed to STDOUT and device->client messages are accepted via STDIN.
* This is to be used when the device is accepting provisioning commands on UART console.
* `httpd` - the script works the same as for `softap`. This could be used on any other network interface than WiFi soft AP, e.g. Ethernet or USB.
* `--service_name <name>` (Optional)
- When transport mode is `ble`, this specifies the BLE device name to which connection is to be established for provisioned. If not provided, BLE scanning is initiated and a list of nearby devices, as seen by the host, is displayed, of which the target device can be chosen.
- When transport mode is `softap`, this specifies the HTTP server hostname / IP which is running the provisioning service, on the SoftAP network of the device which is to be provisioned. This defaults to `192.168.4.1:80` if not specified
- When transport mode is `softap` or `httpd`, this specifies the HTTP server hostname / IP which is running the provisioning service, on the SoftAP network (or any other interface for `httpd` mode) of the device which is to be provisioned. This defaults to `192.168.4.1:80` if not specified
* `--ssid <AP SSID>` (Optional)
- For specifying the SSID of the Wi-Fi AP to which the device is to connect after provisioning.

View File

@ -51,7 +51,7 @@ def get_security(secver, username, password, pop='', verbose=False):
async def get_transport(sel_transport, service_name):
try:
tp = None
if (sel_transport == 'softap'):
if (sel_transport in ['softap', 'httpd']):
if service_name is None:
service_name = '192.168.4.1:80'
tp = transport.Transport_HTTP(service_name)
@ -188,8 +188,8 @@ async def scan_wifi_APs(sel_transport, tp, sec):
APs = []
group_channels = 0
readlen = 100
if sel_transport == 'softap':
# In case of softAP we must perform the scan on individual channels, one by one,
if sel_transport in ['softap', 'httpd']:
# In case of softAP/httpd we must perform the scan on individual channels, one by one,
# so that the Wi-Fi controller gets ample time to send out beacons (necessary to
# maintain connectivity with authenticated stations. As scanning one channel at a
# time will be slow, we can group more than one channels to be scanned in quick
@ -329,14 +329,14 @@ async def main():
parser.add_argument('--transport', required=True, dest='mode', type=str,
help=desc_format(
'Mode of transport over which provisioning is to be performed.',
'This should be one of "softap", "ble" or "console"'))
'This should be one of "softap", "ble", "console" (or "httpd" which is an alias of softap)'))
parser.add_argument('--service_name', dest='name', type=str,
help=desc_format(
'This specifies the name of the provisioning service to connect to, '
'depending upon the mode of transport :',
'\t- transport "ble" : The BLE Device Name',
'\t- transport "softap" : HTTP Server hostname or IP',
'\t- transport "ble" : The BLE Device Name',
'\t- transport "softap/httpd" : HTTP Server hostname or IP',
'\t (default "192.168.4.1:80")'))
parser.add_argument('--proto_ver', dest='version', type=str, default='',