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