From 6edd1be973c048239e250dc28b9137ccc371b6c9 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 30 May 2023 17:30:06 +0200 Subject: [PATCH 1/6] Examples/http-server: Make DNS server a simple configurable component --- .../components/dns_server/CMakeLists.txt | 3 + .../dns_server}/dns_server.c | 84 ++++++++++++++----- .../dns_server/include/dns_server.h | 78 +++++++++++++++++ .../captive_portal/main/CMakeLists.txt | 5 +- .../captive_portal/main/include/dns_server.h | 26 ------ .../http_server/captive_portal/main/main.c | 3 +- 6 files changed, 148 insertions(+), 51 deletions(-) create mode 100644 examples/protocols/http_server/captive_portal/components/dns_server/CMakeLists.txt rename examples/protocols/http_server/captive_portal/{main => components/dns_server}/dns_server.c (73%) create mode 100644 examples/protocols/http_server/captive_portal/components/dns_server/include/dns_server.h delete mode 100644 examples/protocols/http_server/captive_portal/main/include/dns_server.h diff --git a/examples/protocols/http_server/captive_portal/components/dns_server/CMakeLists.txt b/examples/protocols/http_server/captive_portal/components/dns_server/CMakeLists.txt new file mode 100644 index 0000000000..423058515c --- /dev/null +++ b/examples/protocols/http_server/captive_portal/components/dns_server/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS dns_server.c + INCLUDE_DIRS include + PRIV_REQUIRES esp_netif) diff --git a/examples/protocols/http_server/captive_portal/main/dns_server.c b/examples/protocols/http_server/captive_portal/components/dns_server/dns_server.c similarity index 73% rename from examples/protocols/http_server/captive_portal/main/dns_server.c rename to examples/protocols/http_server/captive_portal/components/dns_server/dns_server.c index fbe0b74436..13d4098e18 100644 --- a/examples/protocols/http_server/captive_portal/main/dns_server.c +++ b/examples/protocols/http_server/captive_portal/components/dns_server/dns_server.c @@ -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 #include #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); + } } diff --git a/examples/protocols/http_server/captive_portal/components/dns_server/include/dns_server.h b/examples/protocols/http_server/captive_portal/components/dns_server/include/dns_server.h new file mode 100644 index 0000000000..ae954e9e94 --- /dev/null +++ b/examples/protocols/http_server/captive_portal/components/dns_server/include/dns_server.h @@ -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; /** Date: Thu, 8 Jun 2023 07:29:22 +0200 Subject: [PATCH 2/6] Examples/network: Add wifi station to usb 1:1 forwarder (L2) --- .../wifi_eth_usb_bridge/CMakeLists.txt | 9 + .../network/wifi_eth_usb_bridge/README.md | 97 +++++ .../wifi_eth_usb_bridge/main/CMakeLists.txt | 9 + .../main/Kconfig.projbuild | 17 + .../main/idf_component.yml | 5 + .../wifi_eth_usb_bridge/main/manual_config.c | 113 ++++++ .../wifi_eth_usb_bridge/main/provisioning.c | 96 +++++ .../wifi_eth_usb_bridge/main/provisioning.h | 22 ++ .../main/scheme_generic_httpd.c | 91 +++++ .../wifi_eth_usb_bridge/main/tusb_ncm_main.c | 339 ++++++++++++++++++ .../wifi_eth_usb_bridge/sdkconfig.defaults | 6 + 11 files changed, 804 insertions(+) create mode 100644 examples/network/wifi_eth_usb_bridge/CMakeLists.txt create mode 100644 examples/network/wifi_eth_usb_bridge/README.md create mode 100644 examples/network/wifi_eth_usb_bridge/main/CMakeLists.txt create mode 100644 examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild create mode 100644 examples/network/wifi_eth_usb_bridge/main/idf_component.yml create mode 100644 examples/network/wifi_eth_usb_bridge/main/manual_config.c create mode 100644 examples/network/wifi_eth_usb_bridge/main/provisioning.c create mode 100644 examples/network/wifi_eth_usb_bridge/main/provisioning.h create mode 100644 examples/network/wifi_eth_usb_bridge/main/scheme_generic_httpd.c create mode 100644 examples/network/wifi_eth_usb_bridge/main/tusb_ncm_main.c create mode 100644 examples/network/wifi_eth_usb_bridge/sdkconfig.defaults diff --git a/examples/network/wifi_eth_usb_bridge/CMakeLists.txt b/examples/network/wifi_eth_usb_bridge/CMakeLists.txt new file mode 100644 index 0000000000..09ccbdecf3 --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(tusb_ncm) diff --git a/examples/network/wifi_eth_usb_bridge/README.md b/examples/network/wifi_eth_usb_bridge/README.md new file mode 100644 index 0000000000..aa884d4c91 --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/README.md @@ -0,0 +1,97 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | + +# TinyUSB Network Control Model Device Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +Network Control Model (NCM) is a sub-class of Communication Device Class (CDC) USB Device for Ethernet-over-USB applications. + +In this example, we implemented the ESP development board to transmit WiFi data to the Linux host via USB, so that the Linux host could access the Internet. + +As a USB stack, a TinyUSB component is used. + +## How to use example + +This example demonstrate usage of USB NCM device as USB-WiFi bridge. It also allows for reconfiguring WiFi settings using a virtual network in NCM device. 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. + +### Hardware Required + +Any ESP board that have USB-OTG supported. + +#### Pin Assignment + +_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below. + +See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments). + +### 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 softap ... +``` +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: + +``` +I (725) usb_net: Wi-Fi STA connected +I (735) usb_net: CONNECTED_BIT + +I (735) usb_net: connect success + +I (735) wifi:BcnInt:102400, DTIM:1 +I (745) usb_net: USB net initialization +I (745) 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 +I (925) usb_net: USB NCM initialization DONE +``` diff --git a/examples/network/wifi_eth_usb_bridge/main/CMakeLists.txt b/examples/network/wifi_eth_usb_bridge/main/CMakeLists.txt new file mode 100644 index 0000000000..d9293eaaea --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/CMakeLists.txt @@ -0,0 +1,9 @@ +if(CONFIG_EXAMPLE_WIFI_CONFIGURATION_MANUAL) + set(config_method manual_config.c) +else() + set(config_method provisioning.c scheme_generic_httpd.c) +endif() + +idf_component_register(SRCS tusb_ncm_main.c + ${config_method} + INCLUDE_DIRS "") diff --git a/examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild b/examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild new file mode 100644 index 0000000000..0d3677e2dc --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild @@ -0,0 +1,17 @@ +menu "Example Configuration" + + 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 + +endmenu diff --git a/examples/network/wifi_eth_usb_bridge/main/idf_component.yml b/examples/network/wifi_eth_usb_bridge/main/idf_component.yml new file mode 100644 index 0000000000..73302c2ced --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "^1.0.1" + idf: "^5.0" diff --git a/examples/network/wifi_eth_usb_bridge/main/manual_config.c b/examples/network/wifi_eth_usb_bridge/main/manual_config.c new file mode 100644 index 0000000000..f2bc863062 --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/manual_config.c @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#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[] = "


\n" + "SSID:

\n" + "Password:

\n" + " " + "
"; + 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)) { + const char wifi_configured[] = "

Connecting...

"; + ESP_LOGI(TAG, "WiFi settings accepted!"); + esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); + + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, wifi_configured, strlen(wifi_configured)); + 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 */, "usb" /* USB netif ID */); + start_dns_server(&config); + + s_flags = flags; + s_success_bit = success_bit; + return ESP_OK; +} diff --git a/examples/network/wifi_eth_usb_bridge/main/provisioning.c b/examples/network/wifi_eth_usb_bridge/main/provisioning.c new file mode 100644 index 0000000000..6c77bb1b2a --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/provisioning.c @@ -0,0 +1,96 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#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"; + +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 */, "usb" /* USB 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)); + + /* TODO: Add more security options to menuconfig + */ + ESP_ERROR_CHECK(wifi_prov_mgr_start_provisioning(WIFI_PROV_SECURITY_0, NULL, NULL, NULL)); + return ESP_OK; +} + +bool is_provisioned(void) +{ + bool provisioned = false; + ESP_ERROR_CHECK(wifi_prov_mgr_is_provisioned(&provisioned)); + return provisioned; +} diff --git a/examples/network/wifi_eth_usb_bridge/main/provisioning.h b/examples/network/wifi_eth_usb_bridge/main/provisioning.h new file mode 100644 index 0000000000..a17d98469b --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/provisioning.h @@ -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); diff --git a/examples/network/wifi_eth_usb_bridge/main/scheme_generic_httpd.c b/examples/network/wifi_eth_usb_bridge/main/scheme_generic_httpd.c new file mode 100644 index 0000000000..c0fbef202d --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/scheme_generic_httpd.c @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include "sdkconfig.h" +#include +#include +#include +#include +#include + +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 +}; diff --git a/examples/network/wifi_eth_usb_bridge/main/tusb_ncm_main.c b/examples/network/wifi_eth_usb_bridge/main/tusb_ncm_main.c new file mode 100644 index 0000000000..99b100a8eb --- /dev/null +++ b/examples/network/wifi_eth_usb_bridge/main/tusb_ncm_main.c @@ -0,0 +1,339 @@ +/* + * 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 +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" + +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "esp_private/wifi.h" +#include "nvs_flash.h" +#include "dhcpserver/dhcpserver_options.h" +#include "esp_mac.h" +#include "driver/gpio.h" + +#include "tinyusb.h" +#include "tinyusb_net.h" +#include "provisioning.h" + +static const char *TAG = "USB_NCM"; + +static EventGroupHandle_t s_event_flags; +static bool s_wifi_is_connected = false; + +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; + +static esp_netif_t *s_netif = NULL; + +/** + * WiFi -- USB bridge functionality + */ + +static esp_err_t usb_recv_callback(void *buffer, uint16_t len, void* ctx) +{ + if (s_wifi_is_connected) { + 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 pkt_wifi2usb(void *buffer, uint16_t len, void *eb) +{ + if (tinyusb_net_send_sync(buffer, len, eb, pdMS_TO_TICKS(100)) != 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, pkt_wifi2usb); + 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; +} + +static void on_usb_net_init(void *ctx) +{ + ESP_LOGE(TAG, "USB NET device has been initialized!"); +} + +static esp_err_t usb_ncm_wifi_bridge(void) +{ + 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 = usb_recv_callback, + .free_tx_buffer = wifi_buff_free, + .on_init_callback = on_usb_net_init + }; + + 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; +} + +/** + * USB internal network functionality + */ + +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; +} + +static esp_err_t netif_transmit (void *h, void *buffer, size_t len) +{ + if (tinyusb_net_send_sync(buffer, len, NULL, pdMS_TO_TICKS(100)) != ESP_OK) { + ESP_LOGE(TAG, "Failed to send buffer to USB!"); + } + return ESP_OK; +} + +static void l2_free(void *h, void* buffer) +{ + free(buffer); +} + +static esp_err_t usb_ncm_with_network(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}; + + + // 1) Derive the base config from the default AP (using DHCP server) + esp_netif_inherent_config_t base_cfg = ESP_NETIF_INHERENT_DEFAULT_WIFI_AP(); + base_cfg.if_key = "usb"; + base_cfg.if_desc = "usb ncm config device"; + // 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, // will be replaced by the driver pointer only tinyusb_net supports ti + .transmit = netif_transmit, + .driver_free_rx_buffer = l2_free + }; + + // 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, + // 3) use ethernet style of lwip netif settings + .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH + }; + + 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; +} + +/** + * GPIO button functionality + */ + +#define GPIO_INPUT_IO_0 0 +#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_IO_0) == 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< Date: Tue, 30 May 2023 21:59:51 +0200 Subject: [PATCH 3/6] provisioning: Add httpd transport (alias of softAP, netif agnostic) The script works the same way with httpd and softap mode, but it's a bit confusing to provision the device over Ethernet or USB and call the transport "softap". That's why we introduce an alias called httpd which uses the same idea (http server with service name), but that service runs on any interface (where the specified service is available) --- tools/esp_prov/README.md | 3 ++- tools/esp_prov/esp_prov.py | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tools/esp_prov/README.md b/tools/esp_prov/README.md index b617f2a7c5..2528472c20 100644 --- a/tools/esp_prov/README.md +++ b/tools/esp_prov/README.md @@ -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 ` (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 ` (Optional) - For specifying the SSID of the Wi-Fi AP to which the device is to connect after provisioning. diff --git a/tools/esp_prov/esp_prov.py b/tools/esp_prov/esp_prov.py index 567ac16719..5b1600f0a0 100644 --- a/tools/esp_prov/esp_prov.py +++ b/tools/esp_prov/esp_prov.py @@ -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='', From 6ac17b5020c52a3a431c4ebf4bf52125ecc17cd0 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 1 Jun 2023 22:44:11 +0200 Subject: [PATCH 4/6] Examples/network: Add Ethernet iface to sta-2-wired bridge * adds description about that it's not a bridge, but more like an 1:1 forwarder on L2 * add and describe mac spoofing for Ethernet interface * describe virtual networking for USB-NCM interface --- examples/network/.build-test-rules.yml | 3 + .../CMakeLists.txt | 5 +- .../README.md | 43 ++- .../main/CMakeLists.txt | 9 +- .../network/sta_to_eth/main/Kconfig.projbuild | 42 +++ .../network/sta_to_eth/main/ethernet_iface.c | 318 ++++++++++++++++++ .../network/sta_to_eth/main/idf_component.yml | 9 + .../main/manual_config.c | 13 +- .../main/provisioning.c | 2 +- .../main/provisioning.h | 0 .../main/scheme_generic_httpd.c | 0 .../main/sta2wired_main.c} | 172 ++-------- .../network/sta_to_eth/main/usb_ncm_iface.c | 173 ++++++++++ .../network/sta_to_eth/main/wired_iface.h | 24 ++ .../network/sta_to_eth/sdkconfig.defaults | 1 + .../sta_to_eth/sdkconfig.defaults.esp32s2 | 3 + .../sta_to_eth/sdkconfig.defaults.esp32s3 | 5 + .../main/Kconfig.projbuild | 17 - .../main/idf_component.yml | 5 - .../wifi_eth_usb_bridge/sdkconfig.defaults | 6 - 20 files changed, 642 insertions(+), 208 deletions(-) rename examples/network/{wifi_eth_usb_bridge => sta_to_eth}/CMakeLists.txt (58%) rename examples/network/{wifi_eth_usb_bridge => sta_to_eth}/README.md (71%) rename examples/network/{wifi_eth_usb_bridge => sta_to_eth}/main/CMakeLists.txt (52%) create mode 100644 examples/network/sta_to_eth/main/Kconfig.projbuild create mode 100644 examples/network/sta_to_eth/main/ethernet_iface.c create mode 100644 examples/network/sta_to_eth/main/idf_component.yml rename examples/network/{wifi_eth_usb_bridge => sta_to_eth}/main/manual_config.c (87%) rename examples/network/{wifi_eth_usb_bridge => sta_to_eth}/main/provisioning.c (98%) rename examples/network/{wifi_eth_usb_bridge => sta_to_eth}/main/provisioning.h (100%) rename examples/network/{wifi_eth_usb_bridge => sta_to_eth}/main/scheme_generic_httpd.c (100%) rename examples/network/{wifi_eth_usb_bridge/main/tusb_ncm_main.c => sta_to_eth/main/sta2wired_main.c} (55%) create mode 100644 examples/network/sta_to_eth/main/usb_ncm_iface.c create mode 100644 examples/network/sta_to_eth/main/wired_iface.h create mode 100644 examples/network/sta_to_eth/sdkconfig.defaults create mode 100644 examples/network/sta_to_eth/sdkconfig.defaults.esp32s2 create mode 100644 examples/network/sta_to_eth/sdkconfig.defaults.esp32s3 delete mode 100644 examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild delete mode 100644 examples/network/wifi_eth_usb_bridge/main/idf_component.yml delete mode 100644 examples/network/wifi_eth_usb_bridge/sdkconfig.defaults diff --git a/examples/network/.build-test-rules.yml b/examples/network/.build-test-rules.yml index 7bbb24e350..2d2e70f24d 100644 --- a/examples/network/.build-test-rules.yml +++ b/examples/network/.build-test-rules.yml @@ -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 diff --git a/examples/network/wifi_eth_usb_bridge/CMakeLists.txt b/examples/network/sta_to_eth/CMakeLists.txt similarity index 58% rename from examples/network/wifi_eth_usb_bridge/CMakeLists.txt rename to examples/network/sta_to_eth/CMakeLists.txt index 09ccbdecf3..7a48649f7a 100644 --- a/examples/network/wifi_eth_usb_bridge/CMakeLists.txt +++ b/examples/network/sta_to_eth/CMakeLists.txt @@ -3,7 +3,8 @@ 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) +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(tusb_ncm) +project(wifi_to_wired) diff --git a/examples/network/wifi_eth_usb_bridge/README.md b/examples/network/sta_to_eth/README.md similarity index 71% rename from examples/network/wifi_eth_usb_bridge/README.md rename to examples/network/sta_to_eth/README.md index aa884d4c91..2b66f72924 100644 --- a/examples/network/wifi_eth_usb_bridge/README.md +++ b/examples/network/sta_to_eth/README.md @@ -1,30 +1,26 @@ | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 | | ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -# TinyUSB Network Control Model Device Example +# WiFi station to "Wired" interface L2 forwarder (See the README.md file in the upper level 'examples' directory for more information about examples.) -Network Control Model (NCM) is a sub-class of Communication Device Class (CDC) USB Device for Ethernet-over-USB applications. +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) -In this example, we implemented the ESP development board to transmit WiFi data to the Linux host via USB, so that the Linux host could access the Internet. - -As a USB stack, a TinyUSB component is used. +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 demonstrate usage of USB NCM device as USB-WiFi bridge. It also allows for reconfiguring WiFi settings using a virtual network in NCM device. 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. +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 ESP board that have USB-OTG supported. - -#### Pin Assignment - -_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below. - -See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments). +Any board with either Ethernet of USB-OTG supported. ### Configure the project @@ -36,7 +32,7 @@ In the `Example Configuration` menu choose the provisioning method: 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 softap ... +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. @@ -58,15 +54,15 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui After the flashing you should see the output at idf monitor: +(note that this is the output of USB configuration) ``` -I (725) usb_net: Wi-Fi STA connected -I (735) usb_net: CONNECTED_BIT - -I (735) usb_net: connect success - -I (735) wifi:BcnInt:102400, DTIM:1 -I (745) usb_net: USB net initialization -I (745) tusb_desc: +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 │ ├───────────────────┬─────────────┤ @@ -93,5 +89,4 @@ I (745) tusb_desc: │bNumConfigurations │ 0x1 │ └───────────────────┴─────────────┘ I (915) TinyUSB: TinyUSB Driver installed -I (925) usb_net: USB NCM initialization DONE ``` diff --git a/examples/network/wifi_eth_usb_bridge/main/CMakeLists.txt b/examples/network/sta_to_eth/main/CMakeLists.txt similarity index 52% rename from examples/network/wifi_eth_usb_bridge/main/CMakeLists.txt rename to examples/network/sta_to_eth/main/CMakeLists.txt index d9293eaaea..4e7e324ff1 100644 --- a/examples/network/wifi_eth_usb_bridge/main/CMakeLists.txt +++ b/examples/network/sta_to_eth/main/CMakeLists.txt @@ -4,6 +4,13 @@ else() set(config_method provisioning.c scheme_generic_httpd.c) endif() -idf_component_register(SRCS tusb_ncm_main.c +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 sta2wired_main.c + ${wired_iface} ${config_method} INCLUDE_DIRS "") diff --git a/examples/network/sta_to_eth/main/Kconfig.projbuild b/examples/network/sta_to_eth/main/Kconfig.projbuild new file mode 100644 index 0000000000..bc71e4059f --- /dev/null +++ b/examples/network/sta_to_eth/main/Kconfig.projbuild @@ -0,0 +1,42 @@ +menu "Example Configuration" + + 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_WIRED_INTERFACE + prompt "Choose the Wired interface" + default EXAMPLE_WIRED_INTERFACE_IS_ETHERNET + help + Choose how the WiFi settings should be configured. + + 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 0 46 + 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 diff --git a/examples/network/sta_to_eth/main/ethernet_iface.c b/examples/network/sta_to_eth/main/ethernet_iface.c new file mode 100644 index 0000000000..bf81f8640e --- /dev/null +++ b/examples/network/sta_to_eth/main/ethernet_iface.c @@ -0,0 +1,318 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#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" + +/** + * 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 + +static const char *TAG = "example_wired_ethernet"; +static esp_netif_t *s_netif = NULL; +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; + + switch (event_id) { + case ETHERNET_EVENT_CONNECTED: + esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr); + ESP_LOGI(TAG, "Ethernet Link Up"); + 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"); + 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 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}; + +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 && 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 (memcmp(dhcp_magic, dhcp_type, sizeof(dhcp_type)) == 0) { + eth_nic_mac_found = true; + memcpy(eth_nic_mac, src_mac, 6); + } + } // DHCP + } // UDP/IP +#if !ETH_BRIDGE_PROMISCUOUS + // try to find AP HW address (look for DHCP offer packet) + } else if (!ap_mac_found && 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; + const uint8_t dhcp_type[] = DHCP_COOKIE_WITH_PKT_TYPE(DHCP_OFFER); + if (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 + } + + // 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(ð_handles, ð_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); + 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, ð_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 + * | eth | <-> [ eth-NIC ] + * | | + * | (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) + */ +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, buffer) != ESP_OK) { + ESP_LOGE(TAG, "Failed to send buffer to USB!"); + } + return ESP_OK; +} + +static esp_err_t netif_recv_callback(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t len, void *priv) +{ + 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; +} + +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(ð_handles, ð_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); + ESP_ERROR_CHECK(esp_eth_update_input_path(s_eth_handle, netif_recv_callback, NULL)); + + // 1) Derive the base config from the default AP (using DHCP server) + esp_netif_inherent_config_t base_cfg = ESP_NETIF_INHERENT_DEFAULT_WIFI_AP(); + base_cfg.if_key = "wired"; + base_cfg.if_desc = "ethernet config device"; + // 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, // will be replaced by the driver pointer only tinyusb_net supports ti + .transmit = netif_transmit, + .driver_free_rx_buffer = l2_free + }; + + // 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, + // 3) use ethernet style of lwip netif settings + .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH + }; + + s_netif = esp_netif_new(&cfg); + if (s_netif == NULL) { + return ESP_FAIL; + } + + uint8_t mac[6]; + ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_G_MAC_ADDR, &mac)); + esp_netif_set_mac(s_netif, mac); + + // 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)); + 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)); + + // start the interface manually (as the driver has been started already) + esp_netif_action_start(s_netif, 0, 0, 0); + return ESP_OK; +} diff --git a/examples/network/sta_to_eth/main/idf_component.yml b/examples/network/sta_to_eth/main/idf_component.yml new file mode 100644 index 0000000000..bff20c669a --- /dev/null +++ b/examples/network/sta_to_eth/main/idf_component.yml @@ -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" diff --git a/examples/network/wifi_eth_usb_bridge/main/manual_config.c b/examples/network/sta_to_eth/main/manual_config.c similarity index 87% rename from examples/network/wifi_eth_usb_bridge/main/manual_config.c rename to examples/network/sta_to_eth/main/manual_config.c index f2bc863062..06864f3097 100644 --- a/examples/network/wifi_eth_usb_bridge/main/manual_config.c +++ b/examples/network/sta_to_eth/main/manual_config.c @@ -58,8 +58,15 @@ static esp_err_t http_get_handler(httpd_req_t *req) if (strlen((char*)wifi_cfg.sta.ssid) > 0 && strlen((char*)wifi_cfg.sta.password)) { const char wifi_configured[] = "

Connecting...

"; - ESP_LOGI(TAG, "WiFi settings accepted!"); - esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); + 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) { + ESP_LOGI(TAG, "WiFi settings accepted!"); + } else { + ESP_LOGE(TAG, "Failed to set WiFi config to flash"); + } +// +// esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); httpd_resp_set_type(req, "text/html"); httpd_resp_send(req, wifi_configured, strlen(wifi_configured)); @@ -104,7 +111,7 @@ esp_err_t start_provisioning(EventGroupHandle_t *flags, int success_bit, int fai { 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 */, "usb" /* USB netif ID */); + dns_server_config_t config = DNS_SERVER_CONFIG_SINGLE("wifi.settings" /* name */, "wired" /* USB netif ID */); start_dns_server(&config); s_flags = flags; diff --git a/examples/network/wifi_eth_usb_bridge/main/provisioning.c b/examples/network/sta_to_eth/main/provisioning.c similarity index 98% rename from examples/network/wifi_eth_usb_bridge/main/provisioning.c rename to examples/network/sta_to_eth/main/provisioning.c index 6c77bb1b2a..e53ca899b4 100644 --- a/examples/network/wifi_eth_usb_bridge/main/provisioning.c +++ b/examples/network/sta_to_eth/main/provisioning.c @@ -66,7 +66,7 @@ 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 */, "usb" /* USB netif ID */); + 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; diff --git a/examples/network/wifi_eth_usb_bridge/main/provisioning.h b/examples/network/sta_to_eth/main/provisioning.h similarity index 100% rename from examples/network/wifi_eth_usb_bridge/main/provisioning.h rename to examples/network/sta_to_eth/main/provisioning.h diff --git a/examples/network/wifi_eth_usb_bridge/main/scheme_generic_httpd.c b/examples/network/sta_to_eth/main/scheme_generic_httpd.c similarity index 100% rename from examples/network/wifi_eth_usb_bridge/main/scheme_generic_httpd.c rename to examples/network/sta_to_eth/main/scheme_generic_httpd.c diff --git a/examples/network/wifi_eth_usb_bridge/main/tusb_ncm_main.c b/examples/network/sta_to_eth/main/sta2wired_main.c similarity index 55% rename from examples/network/wifi_eth_usb_bridge/main/tusb_ncm_main.c rename to examples/network/sta_to_eth/main/sta2wired_main.c index 99b100a8eb..2a264a958d 100644 --- a/examples/network/wifi_eth_usb_bridge/main/tusb_ncm_main.c +++ b/examples/network/sta_to_eth/main/sta2wired_main.c @@ -4,11 +4,7 @@ * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ -/* DESCRIPTION: - * This example contains code to make ESP32-S2/S3 as a USB network Device. - */ - -#include +#include #include #include "freertos/FreeRTOS.h" @@ -20,18 +16,15 @@ #include "esp_event.h" #include "esp_private/wifi.h" #include "nvs_flash.h" -#include "dhcpserver/dhcpserver_options.h" -#include "esp_mac.h" #include "driver/gpio.h" - -#include "tinyusb.h" -#include "tinyusb_net.h" #include "provisioning.h" +#include "wired_iface.h" -static const char *TAG = "USB_NCM"; +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; @@ -39,15 +32,13 @@ const int RECONFIGURE_BIT = BIT2; const int PROV_SUCCESS_BIT = BIT3; const int PROV_FAIL_BIT = BIT4; -static esp_netif_t *s_netif = NULL; - /** - * WiFi -- USB bridge functionality + * WiFi -- Wired packet path */ - -static esp_err_t usb_recv_callback(void *buffer, uint16_t len, void* ctx) +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!"); } @@ -60,9 +51,10 @@ static void wifi_buff_free(void* buffer, void* ctx) esp_wifi_internal_free_rx_buffer(buffer); } -static esp_err_t pkt_wifi2usb(void *buffer, uint16_t len, void *eb) +static esp_err_t wifi_recv_callback(void *buffer, uint16_t len, void *eb) { - if (tinyusb_net_send_sync(buffer, len, eb, pdMS_TO_TICKS(100)) != ESP_OK) { + 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!"); } @@ -77,11 +69,12 @@ static void event_handler(void *arg, esp_event_base_t event_base, 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, pkt_wifi2usb); + 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); @@ -109,137 +102,16 @@ static esp_err_t connect_wifi(void) return ESP_ERR_TIMEOUT; } -static void on_usb_net_init(void *ctx) -{ - ESP_LOGE(TAG, "USB NET device has been initialized!"); -} - -static esp_err_t usb_ncm_wifi_bridge(void) -{ - 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 = usb_recv_callback, - .free_tx_buffer = wifi_buff_free, - .on_init_callback = on_usb_net_init - }; - - 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; -} - -/** - * USB internal network functionality - */ - -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; -} - -static esp_err_t netif_transmit (void *h, void *buffer, size_t len) -{ - if (tinyusb_net_send_sync(buffer, len, NULL, pdMS_TO_TICKS(100)) != ESP_OK) { - ESP_LOGE(TAG, "Failed to send buffer to USB!"); - } - return ESP_OK; -} - -static void l2_free(void *h, void* buffer) -{ - free(buffer); -} - -static esp_err_t usb_ncm_with_network(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}; - - - // 1) Derive the base config from the default AP (using DHCP server) - esp_netif_inherent_config_t base_cfg = ESP_NETIF_INHERENT_DEFAULT_WIFI_AP(); - base_cfg.if_key = "usb"; - base_cfg.if_desc = "usb ncm config device"; - // 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, // will be replaced by the driver pointer only tinyusb_net supports ti - .transmit = netif_transmit, - .driver_free_rx_buffer = l2_free - }; - - // 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, - // 3) use ethernet style of lwip netif settings - .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH - }; - - 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; -} - /** * GPIO button functionality */ - -#define GPIO_INPUT_IO_0 0 +#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_IO_0) == 0) { + if (gpio_get_level(GPIO_INPUT) == 0) { last_pushed = esp_timer_get_time(); } else { uint64_t now = esp_timer_get_time(); @@ -257,19 +129,19 @@ static void IRAM_ATTR gpio_isr_handler(void* arg) static void gpio_init(void) { gpio_config_t io_conf = { .intr_type = GPIO_INTR_ANYEDGE, - .pin_bit_mask = (1ULL< +#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 "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 + * | usb | <-> [ USB-NCM device acting as eth-NIC ] + * | | + * | (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}; + + + // 1) Derive the base config from the default AP (using DHCP server) + esp_netif_inherent_config_t base_cfg = ESP_NETIF_INHERENT_DEFAULT_WIFI_AP(); + base_cfg.if_key = "wired"; + base_cfg.if_desc = "usb ncm config device"; + // 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, // will be replaced by the driver pointer only tinyusb_net supports ti + .transmit = netif_transmit, + .driver_free_rx_buffer = l2_free + }; + + // 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, + // 3) use ethernet style of lwip netif settings + .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH + }; + + 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; +} diff --git a/examples/network/sta_to_eth/main/wired_iface.h b/examples/network/sta_to_eth/main/wired_iface.h new file mode 100644 index 0000000000..d4c3716829 --- /dev/null +++ b/examples/network/sta_to_eth/main/wired_iface.h @@ -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); diff --git a/examples/network/sta_to_eth/sdkconfig.defaults b/examples/network/sta_to_eth/sdkconfig.defaults new file mode 100644 index 0000000000..6e41a8774f --- /dev/null +++ b/examples/network/sta_to_eth/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_EXAMPLE_WIRED_INTERFACE_IS_ETHERNET=y diff --git a/examples/network/sta_to_eth/sdkconfig.defaults.esp32s2 b/examples/network/sta_to_eth/sdkconfig.defaults.esp32s2 new file mode 100644 index 0000000000..a8e8359333 --- /dev/null +++ b/examples/network/sta_to_eth/sdkconfig.defaults.esp32s2 @@ -0,0 +1,3 @@ +CONFIG_EXAMPLE_WIRED_INTERFACE_IS_USB=y +CONFIG_EXAMPLE_WIRED_INTERFACE_IS_ETHERNET=n +CONFIG_TINYUSB_NET_MODE_NCM=y diff --git a/examples/network/sta_to_eth/sdkconfig.defaults.esp32s3 b/examples/network/sta_to_eth/sdkconfig.defaults.esp32s3 new file mode 100644 index 0000000000..ef3717be29 --- /dev/null +++ b/examples/network/sta_to_eth/sdkconfig.defaults.esp32s3 @@ -0,0 +1,5 @@ +CONFIG_EXAMPLE_WIRED_INTERFACE_IS_USB=y +CONFIG_EXAMPLE_WIRED_INTERFACE_IS_ETHERNET=n +CONFIG_TINYUSB_TASK_AFFINITY_CPU0=y +CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK=y +CONFIG_TINYUSB_NET_MODE_NCM=y diff --git a/examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild b/examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild deleted file mode 100644 index 0d3677e2dc..0000000000 --- a/examples/network/wifi_eth_usb_bridge/main/Kconfig.projbuild +++ /dev/null @@ -1,17 +0,0 @@ -menu "Example Configuration" - - 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 - -endmenu diff --git a/examples/network/wifi_eth_usb_bridge/main/idf_component.yml b/examples/network/wifi_eth_usb_bridge/main/idf_component.yml deleted file mode 100644 index 73302c2ced..0000000000 --- a/examples/network/wifi_eth_usb_bridge/main/idf_component.yml +++ /dev/null @@ -1,5 +0,0 @@ -## IDF Component Manager Manifest File -dependencies: - espressif/esp_tinyusb: - version: "^1.0.1" - idf: "^5.0" diff --git a/examples/network/wifi_eth_usb_bridge/sdkconfig.defaults b/examples/network/wifi_eth_usb_bridge/sdkconfig.defaults deleted file mode 100644 index 298e827b12..0000000000 --- a/examples/network/wifi_eth_usb_bridge/sdkconfig.defaults +++ /dev/null @@ -1,6 +0,0 @@ - -# -# USB Network Class (NCM) -# -CONFIG_TINYUSB_NCM_ENABLE=y -# end of USB Network Class (NCM) From 022ddb110648d6343ed2f17955f0aac260a693f1 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 5 Jun 2023 10:48:49 +0200 Subject: [PATCH 5/6] Examples/network: Add security options to provision sta-wired example --- .../network/sta_to_eth/main/Kconfig.projbuild | 43 +++++ .../network/sta_to_eth/main/ethernet_iface.c | 58 +++--- .../network/sta_to_eth/main/manual_config.c | 26 +-- .../network/sta_to_eth/main/provisioning.c | 178 ++++++++++++++---- .../sta_to_eth/main/scheme_generic_httpd.c | 22 +-- .../network/sta_to_eth/main/sta2wired_main.c | 20 +- .../network/sta_to_eth/main/usb_ncm_iface.c | 58 +++--- 7 files changed, 284 insertions(+), 121 deletions(-) diff --git a/examples/network/sta_to_eth/main/Kconfig.projbuild b/examples/network/sta_to_eth/main/Kconfig.projbuild index bc71e4059f..df49a96809 100644 --- a/examples/network/sta_to_eth/main/Kconfig.projbuild +++ b/examples/network/sta_to_eth/main/Kconfig.projbuild @@ -14,6 +14,49 @@ menu "Example Configuration" 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. + + 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 diff --git a/examples/network/sta_to_eth/main/ethernet_iface.c b/examples/network/sta_to_eth/main/ethernet_iface.c index bf81f8640e..9b46cf1eab 100644 --- a/examples/network/sta_to_eth/main/ethernet_iface.c +++ b/examples/network/sta_to_eth/main/ethernet_iface.c @@ -39,26 +39,26 @@ void eth_event_handler(void *arg, esp_event_base_t event_base, esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data; switch (event_id) { - case ETHERNET_EVENT_CONNECTED: - esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr); - ESP_LOGI(TAG, "Ethernet Link Up"); - 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"); - 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; + case ETHERNET_EVENT_CONNECTED: + esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr); + ESP_LOGI(TAG, "Ethernet Link Up"); + 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"); + 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; } } @@ -178,7 +178,7 @@ void mac_spoof(mac_spoof_direction_t direction, uint8_t *buffer, uint16_t len, u 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); + esp_err_t ret = s_rx_cb(buffer, len, buffer); free(buffer); return ret; } @@ -235,7 +235,7 @@ esp_err_t wired_send(void *buffer, uint16_t len, void *buff_free_arg) * 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) */ -static void l2_free(void *h, void* buffer) +static void l2_free(void *h, void *buffer) { free(buffer); } @@ -281,9 +281,9 @@ esp_err_t wired_netif_init(void) base_cfg.if_desc = "ethernet config device"; // 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, // will be replaced by the driver pointer only tinyusb_net supports ti - .transmit = netif_transmit, - .driver_free_rx_buffer = l2_free + .handle = (void *)1, // will be replaced by the driver pointer only tinyusb_net supports ti + .transmit = netif_transmit, + .driver_free_rx_buffer = l2_free }; // Config the esp-netif with: @@ -291,10 +291,10 @@ esp_err_t wired_netif_init(void) // 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, - // 3) use ethernet style of lwip netif settings - .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH + .base = &base_cfg, + .driver = &driver_cfg, + // 3) use ethernet style of lwip netif settings + .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH }; s_netif = esp_netif_new(&cfg); diff --git a/examples/network/sta_to_eth/main/manual_config.c b/examples/network/sta_to_eth/main/manual_config.c index 06864f3097..e95d041822 100644 --- a/examples/network/sta_to_eth/main/manual_config.c +++ b/examples/network/sta_to_eth/main/manual_config.c @@ -37,7 +37,7 @@ static esp_err_t http_get_handler(httpd_req_t *req) "Password:

\n" " " ""; - char* buf = NULL; + char *buf = NULL; size_t buf_len; buf_len = httpd_req_get_url_query_len(req) + 1; if (buf_len > 1) { @@ -49,27 +49,27 @@ static esp_err_t http_get_handler(httpd_req_t *req) 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)); + 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)); + 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)) { - const char wifi_configured[] = "

Connecting...

"; + 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) { + esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg) == ESP_OK) { + const char wifi_configured[] = "

Connecting...

"; ESP_LOGI(TAG, "WiFi settings accepted!"); + httpd_resp_send(req, wifi_configured, strlen(wifi_configured)); } else { + const char wifi_config_failed[] = "

Failed to configure WiFi settings

"; ESP_LOGE(TAG, "Failed to set WiFi config to flash"); + httpd_resp_send(req, wifi_config_failed, strlen(wifi_config_failed)); } -// -// esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); - httpd_resp_set_type(req, "text/html"); - httpd_resp_send(req, wifi_configured, strlen(wifi_configured)); free(buf); if (s_flags) { xEventGroupSetBits(*s_flags, s_success_bit); @@ -87,9 +87,9 @@ static esp_err_t http_get_handler(httpd_req_t *req) } static const httpd_uri_t root = { - .uri = "/", - .method = HTTP_GET, - .handler = http_get_handler, + .uri = "/", + .method = HTTP_GET, + .handler = http_get_handler, }; static void start_webserver(void) diff --git a/examples/network/sta_to_eth/main/provisioning.c b/examples/network/sta_to_eth/main/provisioning.c index e53ca899b4..2a08088323 100644 --- a/examples/network/sta_to_eth/main/provisioning.c +++ b/examples/network/sta_to_eth/main/provisioning.c @@ -13,6 +13,74 @@ 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; @@ -25,39 +93,39 @@ static void event_handler(void *arg, esp_event_base_t event_base, { 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; + 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; + 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; } } @@ -75,16 +143,56 @@ esp_err_t start_provisioning(EventGroupHandle_t *flags, int success_bit, int fai 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, + .scheme = wifi_prov_scheme_httpd, }; /* Initialize provisioning manager with the * configuration parameters set above */ ESP_ERROR_CHECK(wifi_prov_mgr_init(config)); - /* TODO: Add more security options to menuconfig + /* 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 */ - ESP_ERROR_CHECK(wifi_prov_mgr_start_provisioning(WIFI_PROV_SECURITY_0, NULL, NULL, NULL)); + +#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; } diff --git a/examples/network/sta_to_eth/main/scheme_generic_httpd.c b/examples/network/sta_to_eth/main/scheme_generic_httpd.c index c0fbef202d..e0bd85bbca 100644 --- a/examples/network/sta_to_eth/main/scheme_generic_httpd.c +++ b/examples/network/sta_to_eth/main/scheme_generic_httpd.c @@ -25,9 +25,9 @@ static esp_err_t prov_start(protocomm_t *pc, void *config) return ESP_ERR_INVALID_ARG; } protocomm_httpd_config_t default_config = { - .data = { - .config = PROTOCOMM_HTTPD_DEFAULT_CONFIG() - } + .data = { + .config = PROTOCOMM_HTTPD_DEFAULT_CONFIG() + } }; /* Start protocomm server on top of HTTP */ @@ -60,7 +60,7 @@ static esp_err_t prov_stop(protocomm_t *pc) */ static void *new_config(void) { - return (void*)1; + return (void *)1; } static void delete_config(void *config) @@ -81,11 +81,11 @@ static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, ui * @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 + .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 }; diff --git a/examples/network/sta_to_eth/main/sta2wired_main.c b/examples/network/sta_to_eth/main/sta2wired_main.c index 2a264a958d..64a94e9f39 100644 --- a/examples/network/sta_to_eth/main/sta2wired_main.c +++ b/examples/network/sta_to_eth/main/sta2wired_main.c @@ -35,7 +35,7 @@ const int PROV_FAIL_BIT = BIT4; /** * WiFi -- Wired packet path */ -static esp_err_t wired_recv_callback(void *buffer, uint16_t len, void* ctx) +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); @@ -46,7 +46,7 @@ static esp_err_t wired_recv_callback(void *buffer, uint16_t len, void* ctx) return ESP_OK; } -static void wifi_buff_free(void* buffer, void* ctx) +static void wifi_buff_free(void *buffer, void *ctx) { esp_wifi_internal_free_rx_buffer(buffer); } @@ -62,7 +62,7 @@ static esp_err_t wifi_recv_callback(void *buffer, uint16_t len, void *eb) } static void event_handler(void *arg, esp_event_base_t event_base, - int32_t event_id, void *event_data) + 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"); @@ -93,7 +93,7 @@ static esp_err_t connect_wifi(void) return ESP_FAIL; } esp_wifi_connect(); - EventBits_t status = xEventGroupWaitBits(s_event_flags, CONNECTED_BIT, 0, 1, 10000/portTICK_PERIOD_MS); + 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; @@ -108,7 +108,7 @@ static esp_err_t connect_wifi(void) #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 void IRAM_ATTR gpio_isr_handler(void *arg) { static int64_t last_pushed = -1; if (gpio_get_level(GPIO_INPUT) == 0) { @@ -129,9 +129,10 @@ static void IRAM_ATTR gpio_isr_handler(void* arg) static void gpio_init(void) { gpio_config_t io_conf = { .intr_type = GPIO_INTR_ANYEDGE, - .pin_bit_mask = (1ULL< Date: Thu, 8 Jun 2023 18:01:21 +0200 Subject: [PATCH 6/6] Examples/network: sta-2-wired to support HW address update in DHCP * Add support for runtime update of DHCP packets that contain HW addresses (some routers wouldn't assing IP if the MAC was spoofed only in Ethernet and ARP frames) * Simplify Ethernet initialization using default eth-netif glue --- examples/network/sta_to_eth/CMakeLists.txt | 2 +- examples/network/sta_to_eth/README.md | 2 +- .../network/sta_to_eth/main/CMakeLists.txt | 2 +- .../network/sta_to_eth/main/Kconfig.projbuild | 8 +- .../network/sta_to_eth/main/ethernet_iface.c | 157 +++++++++++------- .../{sta2wired_main.c => sta_to_eth_main.c} | 3 +- .../sta_to_eth/sdkconfig.defaults.esp32s2 | 1 + .../sta_to_eth/sdkconfig.defaults.esp32s3 | 6 + 8 files changed, 118 insertions(+), 63 deletions(-) rename examples/network/sta_to_eth/main/{sta2wired_main.c => sta_to_eth_main.c} (99%) diff --git a/examples/network/sta_to_eth/CMakeLists.txt b/examples/network/sta_to_eth/CMakeLists.txt index 7a48649f7a..11459c67f2 100644 --- a/examples/network/sta_to_eth/CMakeLists.txt +++ b/examples/network/sta_to_eth/CMakeLists.txt @@ -7,4 +7,4 @@ set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/http_server/captiv $ENV{IDF_PATH}/examples/ethernet/basic/components/ethernet_init) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(wifi_to_wired) +project(sta_to_eth) diff --git a/examples/network/sta_to_eth/README.md b/examples/network/sta_to_eth/README.md index 2b66f72924..9eb5ebc228 100644 --- a/examples/network/sta_to_eth/README.md +++ b/examples/network/sta_to_eth/README.md @@ -34,7 +34,7 @@ To provision the device using IDF provisioning tools (if `EXAMPLE_WIFI_CONFIGURA ```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. +Please refer to the provisioning documentation and `esp_prov` script [documentation](../../../tools/esp_prov/README.md) for more details. ### Build, Flash, and Run diff --git a/examples/network/sta_to_eth/main/CMakeLists.txt b/examples/network/sta_to_eth/main/CMakeLists.txt index 4e7e324ff1..58873dfd47 100644 --- a/examples/network/sta_to_eth/main/CMakeLists.txt +++ b/examples/network/sta_to_eth/main/CMakeLists.txt @@ -10,7 +10,7 @@ else() set(wired_iface usb_ncm_iface.c) endif() -idf_component_register(SRCS sta2wired_main.c +idf_component_register(SRCS sta_to_eth_main.c ${wired_iface} ${config_method} INCLUDE_DIRS "") diff --git a/examples/network/sta_to_eth/main/Kconfig.projbuild b/examples/network/sta_to_eth/main/Kconfig.projbuild index df49a96809..7b85896e38 100644 --- a/examples/network/sta_to_eth/main/Kconfig.projbuild +++ b/examples/network/sta_to_eth/main/Kconfig.projbuild @@ -1,5 +1,7 @@ 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 @@ -21,6 +23,8 @@ menu "Example Configuration" 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" @@ -61,7 +65,7 @@ menu "Example Configuration" prompt "Choose the Wired interface" default EXAMPLE_WIRED_INTERFACE_IS_ETHERNET help - Choose how the WiFi settings should be configured. + Choose the wired interface: Ethernet or USB config EXAMPLE_WIRED_INTERFACE_IS_ETHERNET bool @@ -74,7 +78,7 @@ menu "Example Configuration" config EXAMPLE_RECONFIGURE_BUTTON int "Button for switching to reconfigure mode" - range 0 46 + range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX default 2 if EXAMPLE_WIRED_INTERFACE_IS_ETHERNET default 0 help diff --git a/examples/network/sta_to_eth/main/ethernet_iface.c b/examples/network/sta_to_eth/main/ethernet_iface.c index 9b46cf1eab..87c99182d3 100644 --- a/examples/network/sta_to_eth/main/ethernet_iface.c +++ b/examples/network/sta_to_eth/main/ethernet_iface.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include +#include "cc.h" #include "esp_log.h" #include "esp_netif.h" #include "esp_event.h" @@ -11,6 +12,7 @@ #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 @@ -20,8 +22,13 @@ */ #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_netif_t *s_netif = NULL; static esp_eth_handle_t s_eth_handle = NULL; static bool s_ethernet_is_connected = false; static uint8_t s_eth_mac[6]; @@ -37,17 +44,20 @@ void eth_event_handler(void *arg, esp_event_base_t event_base, 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_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr); 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: @@ -90,12 +100,38 @@ void eth_event_handler(void *arg, esp_event_base_t event_base, #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) { @@ -112,7 +148,7 @@ void mac_spoof(mac_spoof_direction_t direction, uint8_t *buffer, uint16_t len, u 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 && direction == FROM_WIRED && eth_type[1] == 0x00) { // ETH IP4 + 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; @@ -120,29 +156,67 @@ void mac_spoof(mac_spoof_direction_t direction, uint8_t *buffer, uint16_t len, u 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 (memcmp(dhcp_magic, dhcp_type, sizeof(dhcp_type)) == 0) { + 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 +#if !ETH_BRIDGE_PROMISCUOUS || MODIFY_DHCP_MSGS // try to find AP HW address (look for DHCP offer packet) - } else if (!ap_mac_found && direction == TO_WIRED && eth_type[1] == 0x00) { // ETH IP4 + } 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 (memcmp(dhcp_magic, dhcp_type, sizeof(dhcp_type)) == 0) { + 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 +#endif // !ETH_BRIDGE_PROMISCUOUS || MODIFY_DHCP_MSGS } // swap addresses in ARP probes @@ -189,7 +263,7 @@ esp_err_t wired_bridge_init(wired_rx_cb_t rx_cb, wired_free_cb_t free_cb) esp_eth_handle_t *eth_handles; ESP_ERROR_CHECK(example_eth_init(ð_handles, ð_port_cnt)); - // Check or multiple ethernet interface + // 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."); } @@ -235,31 +309,6 @@ esp_err_t wired_send(void *buffer, uint16_t len, void *buff_free_arg) * 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) */ -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, buffer) != ESP_OK) { - ESP_LOGE(TAG, "Failed to send buffer to USB!"); - } - return ESP_OK; -} - -static esp_err_t netif_recv_callback(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t len, void *priv) -{ - 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; -} esp_err_t wired_netif_init(void) { @@ -273,46 +322,42 @@ esp_err_t wired_netif_init(void) } s_eth_handle = eth_handles[0]; free(eth_handles); - ESP_ERROR_CHECK(esp_eth_update_input_path(s_eth_handle, netif_recv_callback, NULL)); - // 1) Derive the base config from the default AP (using DHCP server) - esp_netif_inherent_config_t base_cfg = ESP_NETIF_INHERENT_DEFAULT_WIFI_AP(); - base_cfg.if_key = "wired"; - base_cfg.if_desc = "ethernet config device"; - // 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, // will be replaced by the driver pointer only tinyusb_net supports ti - .transmit = netif_transmit, - .driver_free_rx_buffer = l2_free + // 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 (connection to IO functions -- usb) - // 3) stack config (using lwip IO functions -- derive from eth) + // 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, - .driver = &driver_cfg, - // 3) use ethernet style of lwip netif settings .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH }; - s_netif = esp_netif_new(&cfg); - if (s_netif == NULL) { + 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(s_netif, mac); + esp_netif_set_mac(netif, mac); // 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)); - ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, NULL)); + 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)); - - // start the interface manually (as the driver has been started already) - esp_netif_action_start(s_netif, 0, 0, 0); return ESP_OK; } diff --git a/examples/network/sta_to_eth/main/sta2wired_main.c b/examples/network/sta_to_eth/main/sta_to_eth_main.c similarity index 99% rename from examples/network/sta_to_eth/main/sta2wired_main.c rename to examples/network/sta_to_eth/main/sta_to_eth_main.c index 64a94e9f39..4729d34382 100644 --- a/examples/network/sta_to_eth/main/sta2wired_main.c +++ b/examples/network/sta_to_eth/main/sta_to_eth_main.c @@ -12,6 +12,7 @@ #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" @@ -142,8 +143,6 @@ static void gpio_init(void) /** * Application */ -#include "esp_mac.h" - void app_main(void) { static __NOINIT_ATTR uint32_t s_reconfigure_requested; diff --git a/examples/network/sta_to_eth/sdkconfig.defaults.esp32s2 b/examples/network/sta_to_eth/sdkconfig.defaults.esp32s2 index a8e8359333..11a2e1d600 100644 --- a/examples/network/sta_to_eth/sdkconfig.defaults.esp32s2 +++ b/examples/network/sta_to_eth/sdkconfig.defaults.esp32s2 @@ -1,3 +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 diff --git a/examples/network/sta_to_eth/sdkconfig.defaults.esp32s3 b/examples/network/sta_to_eth/sdkconfig.defaults.esp32s3 index ef3717be29..e0c0938990 100644 --- a/examples/network/sta_to_eth/sdkconfig.defaults.esp32s3 +++ b/examples/network/sta_to_eth/sdkconfig.defaults.esp32s3 @@ -1,5 +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