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
This commit is contained in:
David Cermak 2023-06-01 22:44:11 +02:00
parent 08c5e6e07c
commit 6ac17b5020
20 changed files with 642 additions and 208 deletions

View File

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

View File

@ -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)

View File

@ -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
```

View File

@ -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 "")

View File

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

View File

@ -0,0 +1,318 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "wired_iface.h"
#include "dhcpserver/dhcpserver_options.h"
#include "esp_mac.h"
#include "ethernet_init.h"
/**
* 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(&eth_handles, &eth_port_cnt));
// Check or multiple ethernet interface
if (1 < eth_port_cnt) {
ESP_LOGW(TAG, "Multiple Ethernet Interface detected: Only the first initialized interface is going to be used.");
}
s_eth_handle = eth_handles[0];
free(eth_handles);
ESP_ERROR_CHECK(esp_eth_update_input_path(s_eth_handle, wired_recv, NULL));
#if ETH_BRIDGE_PROMISCUOUS
bool eth_promiscuous = true;
ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_S_PROMISCUOUS, &eth_promiscuous));
#endif
ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_G_MAC_ADDR, &s_eth_mac));
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, NULL));
ESP_ERROR_CHECK(esp_eth_start(s_eth_handle));
s_rx_cb = rx_cb;
s_free_cb = free_cb;
return ESP_OK;
}
esp_err_t wired_send(void *buffer, uint16_t len, void *buff_free_arg)
{
if (s_ethernet_is_connected) {
if (esp_eth_transmit(s_eth_handle, buffer, len) != ESP_OK) {
ESP_LOGE(TAG, "Ethernet send packet failed");
return ESP_FAIL;
}
if (s_free_cb) {
s_free_cb(buff_free_arg, NULL);
}
return ESP_OK;
}
return ESP_ERR_INVALID_STATE;
}
/**
* In this scenario of configuring WiFi, we setup Ethernet to create a network and run DHCP server,
* so it could assign an IP address to the PC
*
* ESP32 PC
* | <DHCP server> eth | <-> [ eth-NIC ]
* | <HTTP server> |
* | (wifi-provisioning) |
*
* From the PC's NIC perspective the board acts as a separate network with it's own IP and MAC address
* (this network's MAC address is the native ESP32's Ethernet interface MAC)
*/
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(&eth_handles, &eth_port_cnt));
// Check or multiple ethernet interface
if (1 < eth_port_cnt) {
ESP_LOGW(TAG, "Multiple Ethernet Interface detected: Only the first initialized interface is going to be used.");
}
s_eth_handle = eth_handles[0];
free(eth_handles);
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;
}

View File

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

View File

@ -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[] = "<h1>Connecting...</h1>";
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;

View File

@ -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;

View File

@ -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 <stdio.h>
#include <string.h>
#include <esp_timer.h>
#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<<GPIO_INPUT_IO_0),
.pin_bit_mask = (1ULL<<GPIO_INPUT),
.mode = GPIO_MODE_INPUT,
.pull_up_en = 1 };
gpio_config(&io_conf);
gpio_install_isr_service(0);
//hook isr handler for specific gpio pin
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, NULL);
gpio_isr_handler_add(GPIO_INPUT, gpio_isr_handler, NULL);
}
/**
* Application
*/
#include "esp_mac.h"
void app_main(void)
{
@ -279,7 +151,7 @@ void app_main(void)
/* Check reset reason and decide if we should re-provision */
bool do_provision = false;
esp_reset_reason_t reason = esp_reset_reason();
ESP_LOGW(TAG, "After restart! %d", reason);
ESP_LOGD(TAG, "After restart! %d", reason);
if (reason != ESP_RST_SW) {
s_reconfigure_requested = 0;
} else if (s_reconfigure_requested == RECONFIGURE_REQUEST) {
@ -302,6 +174,7 @@ void app_main(void)
/* Init the re-provisioning button (long-press with initiate provisioning restart) */
gpio_init();
esp_read_mac(s_sta_mac, ESP_MAC_WIFI_STA);
/* Start the application in configuration mode (to perform provisioning)
* or in a bridge mode (already provisioned) */
@ -312,8 +185,8 @@ void app_main(void)
// needed to complete provisioning with getting a valid IP event
esp_netif_create_default_wifi_sta();
// starts usb ncm with virtual network used to configure/provision the example
usb_ncm_with_network();
// starts the wired interface with virtual network used to configure/provision the example
wired_netif_init();
start_provisioning(&s_event_flags, PROV_SUCCESS_BIT, PROV_FAIL_BIT);
} else {
ESP_LOGI(TAG, "Starting USB-WiFi bridge");
@ -321,7 +194,8 @@ void app_main(void)
// if we cannot connect to WiFi we just try to re-provision
xEventGroupSetBits(s_event_flags, RECONFIGURE_BIT);
} else {
usb_ncm_wifi_bridge();
// start the wired interface in the bridge mode
wired_bridge_init(wired_recv_callback, wifi_buff_free);
}
}

View File

@ -0,0 +1,173 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* DESCRIPTION:
* This example contains code to make ESP32-S2/S3 as a USB network Device.
*/
#include <stdio.h>
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "tinyusb.h"
#include "tinyusb_net.h"
#include "wired_iface.h"
#include "dhcpserver/dhcpserver_options.h"
#include "esp_mac.h"
static const char *TAG = "example_wired_tusb_ncm";
static esp_netif_t *s_netif = NULL;
/**
* In this scenario of WiFi station to Ethernet bridge mode, we have this configuration
*
* (ISP) router ESP32 PC
* [ AP ] <-> [ sta -- USB ] <-> [ USB-NCM device acting as eth-NIC ]
*
* From the PC's NIC perspective the L2 forwarding should be transparent and resemble this configuration:
*
* (ISP) router PC
* [ AP ] <----------> [ virtual wifi-NIC ]
*
* In order for the ESP32 to act as L2 bridge it needs to accept the frames for the NCM device,
* which we have fully under control, we can modify it's MAC address, as well as the WiFi station
* MAC address, which need to be the same so the AP would see one device (virtual eth-NIC).
* No need to modify the ethernet frames here, as we can set the station's MAC to the USB NCM device.
*/
void mac_spoof(mac_spoof_direction_t direction, uint8_t *buffer, uint16_t len, uint8_t own_mac[6])
{
}
esp_err_t wired_bridge_init(wired_rx_cb_t rx_cb, wired_free_cb_t free_cb)
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
tinyusb_net_config_t net_config = {
.on_recv_callback = rx_cb,
.free_tx_buffer = free_cb,
};
esp_read_mac(net_config.mac_addr, ESP_MAC_WIFI_STA);
esp_err_t ret = tinyusb_net_init(TINYUSB_USBDEV_0, &net_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "USB net init but not connect wifi");
return ret;
}
return ESP_OK;
}
esp_err_t wired_send(void *buffer, uint16_t len, void *buff_free_arg)
{
return tinyusb_net_send_sync(buffer, len, buff_free_arg, pdMS_TO_TICKS(100));
}
static void l2_free(void *h, void* buffer)
{
free(buffer);
}
static esp_err_t netif_transmit (void *h, void *buffer, size_t len)
{
if (wired_send(buffer, len, NULL) != ESP_OK) {
ESP_LOGE(TAG, "Failed to send buffer to USB!");
}
return ESP_OK;
}
static esp_err_t netif_recv_callback(void *buffer, uint16_t len, void* ctx)
{
if (s_netif) {
void *buf_copy = malloc(len);
if (!buf_copy) {
return ESP_ERR_NO_MEM;
}
memcpy(buf_copy, buffer, len);
return esp_netif_receive(s_netif, buf_copy, len, NULL);
}
return ESP_OK;
}
/**
* In this scenario of configuring WiFi, we setup USB-Ethernet to create a virtual network and run DHCP server,
* so it could assign an IP address to the PC
*
* ESP32 PC
* | lwip MAC=...01 | eth NIC MAC=...02
* | <DHCP server> usb | <-> [ USB-NCM device acting as eth-NIC ]
* | <HTTP server> |
* | (wifi-provisioning) |
*
* From the PC's NIC perspective the board acts as a separate network with it's own IP and MAC address,
* but the virtual ethernet NIC has also it's own IP and MAC address (configured via tinyusb_net_init()).
* That's why we need to create the virtual network with *different* MAC address.
* Here, we use two different OUI range MAC addresses.
*/
esp_err_t wired_netif_init(void)
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
const tinyusb_net_config_t net_config = {
// locally administrated address for the ncm device as it's going to be used internally
// for configuration only
.mac_addr = {0x02, 0x02, 0x11, 0x22, 0x33, 0x01},
.on_recv_callback = netif_recv_callback,
};
esp_err_t ret = tinyusb_net_init(TINYUSB_USBDEV_0, &net_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Cannot initialize USB Net device");
return ret;
}
// with OUI range MAC to create a virtual netif running http server
// this needs to be different to usb_interface_mac (==client)
uint8_t lwip_addr[6]= {0x02, 0x02, 0x11, 0x22, 0x33, 0x02};
// 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;
}

View File

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

View File

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

View File

@ -0,0 +1,3 @@
CONFIG_EXAMPLE_WIRED_INTERFACE_IS_USB=y
CONFIG_EXAMPLE_WIRED_INTERFACE_IS_ETHERNET=n
CONFIG_TINYUSB_NET_MODE_NCM=y

View File

@ -0,0 +1,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

View File

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

View File

@ -1,5 +0,0 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "^1.0.1"
idf: "^5.0"

View File

@ -1,6 +0,0 @@
#
# USB Network Class (NCM)
#
CONFIG_TINYUSB_NCM_ENABLE=y
# end of USB Network Class (NCM)