Răsfoiți Sursa

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
David Cermak 2 ani în urmă
părinte
comite
7302801bda

+ 1 - 1
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)

+ 1 - 1
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
 

+ 1 - 1
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 "")

+ 6 - 2
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

+ 101 - 56
examples/network/sta_to_eth/main/ethernet_iface.c

@@ -4,6 +4,7 @@
  * SPDX-License-Identifier: Unlicense OR CC0-1.0
  */
 #include <string.h>
+#include "cc.h"
 #include "esp_log.h"
 #include "esp_netif.h"
 #include "esp_event.h"
@@ -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(&eth_handles, &eth_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;
 }

+ 1 - 2
examples/network/sta_to_eth/main/sta2wired_main.c → 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;

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

+ 6 - 0
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