/* * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "freertos/semphr.h" #include "esp_netif.h" #include "esp_eth.h" #include "esp_event.h" #include "esp_log.h" #include "driver/gpio.h" #include "sdkconfig.h" #include "arpa/inet.h" // for ntohs, etc. #include "lwip/prot/ethernet.h" // Ethernet headers #include "unity.h" #include "test_utils.h" #include "esp_vfs_l2tap.h" #if CONFIG_ESP_NETIF_L2_TAP #define ETH_FILTER_LE 0x7A05 #define ETH_FILTER_BE 0x057A #define ETH_START_BIT BIT(0) #define ETH_STOP_BIT BIT(1) #define ETH_CONNECT_BIT BIT(2) #define ETH_GOT_IP_BIT BIT(3) #define DEFAULT_SEND_DELAY_MS 1000 static const char *TAG = "l2tap_test"; typedef struct { esp_netif_t *eth_netif; esp_eth_mac_t *mac; esp_eth_phy_t *phy; void *glue; esp_eth_handle_t eth_handle; } test_vfs_eth_network_t; typedef struct { test_vfs_eth_network_t *eth_network_hndls_p; int32_t eth_type; uint32_t send_delay_ms; } send_task_control_t; typedef struct { struct eth_hdr header; union { int cnt; char str[44]; }; } test_vfs_eth_tap_msg_t; /* ============================================================================= * Common Routines * ============================================================================= */ /** * @brief Event handler for Ethernet events * */ static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { EventGroupHandle_t eth_event_group = (EventGroupHandle_t)arg; 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]); xEventGroupSetBits(eth_event_group, ETH_CONNECT_BIT); break; case ETHERNET_EVENT_DISCONNECTED: ESP_LOGI(TAG, "Ethernet Link Down"); break; case ETHERNET_EVENT_START: xEventGroupSetBits(eth_event_group, ETH_START_BIT); ESP_LOGI(TAG, "Ethernet Started"); break; case ETHERNET_EVENT_STOP: xEventGroupSetBits(eth_event_group, ETH_STOP_BIT); ESP_LOGI(TAG, "Ethernet Stopped"); break; default: break; } } /** * @brief Event handler for IP_EVENT_ETH_GOT_IP * */ static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { EventGroupHandle_t eth_event_group = (EventGroupHandle_t)arg; ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; const esp_netif_ip_info_t *ip_info = &event->ip_info; ESP_LOGI(TAG, "Ethernet Got IP Address"); ESP_LOGI(TAG, "~~~~~~~~~~~"); ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip)); ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask)); ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw)); ESP_LOGI(TAG, "~~~~~~~~~~~"); xEventGroupSetBits(eth_event_group, ETH_GOT_IP_BIT); } /** * @brief Initializes Ethernet interface and starts driver (internal EMAC & IP101 PHY) * */ static void ethernet_init(test_vfs_eth_network_t *network_hndls) { EventBits_t bits = 0; EventGroupHandle_t eth_event_group = xEventGroupCreate(); TEST_ASSERT(eth_event_group != NULL); test_case_uses_tcpip(); TEST_ESP_OK(esp_event_loop_create_default()); // create TCP/IP netif esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH(); network_hndls->eth_netif = esp_netif_new(&netif_cfg); eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); network_hndls->mac = esp_eth_mac_new_esp32(&mac_config); eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); network_hndls->phy = esp_eth_phy_new_ip101(&phy_config); esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(network_hndls->mac, network_hndls->phy); network_hndls->eth_handle = NULL; // install Ethernet driver TEST_ESP_OK(esp_eth_driver_install(ð_config, &network_hndls->eth_handle)); // combine driver with netif network_hndls->glue = esp_eth_new_netif_glue(network_hndls->eth_handle); TEST_ESP_OK(esp_netif_attach(network_hndls->eth_netif, network_hndls->glue)); // register user defined event handers TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, eth_event_group)); TEST_ESP_OK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, eth_event_group)); // set PHY loopback mode bool loopback_en = true; esp_eth_ioctl(network_hndls->eth_handle, ETH_CMD_S_PHY_LOOPBACK, &loopback_en); TEST_ESP_OK(esp_eth_start(network_hndls->eth_handle)); // wait for Ethernet start bits = xEventGroupWaitBits(eth_event_group, ETH_START_BIT, true, true, pdMS_TO_TICKS(10000)); TEST_ASSERT((bits & ETH_START_BIT) == ETH_START_BIT); bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(10000)); TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT); } /** * @brief De-initializes Ethernet interface * */ static void ethernet_deinit(test_vfs_eth_network_t *network_hndls) { TEST_ESP_OK(esp_eth_stop(network_hndls->eth_handle)); TEST_ESP_OK(esp_eth_del_netif_glue(network_hndls->glue)); /* driver should be uninstalled within 2 seconds */ //TEST_ESP_OK(test_uninstall_driver(eth_handle, 2000)); esp_eth_driver_uninstall(network_hndls->eth_handle); TEST_ESP_OK(network_hndls->phy->del(network_hndls->phy)); TEST_ESP_OK(network_hndls->mac->del(network_hndls->mac)); TEST_ESP_OK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, got_ip_event_handler)); TEST_ESP_OK(esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler)); esp_netif_destroy(network_hndls->eth_netif); TEST_ESP_OK(esp_event_loop_delete_default()); } // Global test message send by "send_task" static test_vfs_eth_tap_msg_t s_test_msg = { .header = { .src.addr = {0}, .dest.addr = { 0x01, 0x00, 0x00, 0x00, 0xBE, 0xEF }, .type = ETH_FILTER_BE, }, .str = "This is ESP32 L2 TAP test msg" }; /** * @brief "Asynchronously" sends global test message (send can be delayed per actual needs) * */ static void send_task(void *task_param) { send_task_control_t *send_control = (send_task_control_t *)task_param; test_vfs_eth_network_t *eth_network_hndls = send_control->eth_network_hndls_p; esp_eth_ioctl(eth_network_hndls->eth_handle, ETH_CMD_G_MAC_ADDR, &s_test_msg.header.src.addr); if (send_control->eth_type >= 0 && send_control->eth_type <= 0xFFFF) { s_test_msg.header.type = htons(send_control->eth_type); } else { s_test_msg.header.type = ETH_FILTER_BE; } int eth_tap_fd_send = open("/dev/net/tap", O_NONBLOCK); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd_send); TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd_send, L2TAP_S_INTF_DEVICE, "ETH_DEF")); // delay message write vTaskDelay(pdMS_TO_TICKS(send_control->send_delay_ms)); int n = write(eth_tap_fd_send, &s_test_msg, sizeof(s_test_msg)); TEST_ASSERT_NOT_EQUAL(-1, n); TEST_ASSERT_EQUAL(0, close(eth_tap_fd_send)); vTaskDelete(NULL); } /* ============================================================================= * Tests * ============================================================================= */ /** * @brief Verifies vfs register/unregister functions * */ TEST_CASE("esp32 l2tap - vfs register", "[ethernet][test_env=UT_T2_Ethernet]") { int eth_tap_fd; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL)); ESP_LOGI(TAG, "Verify that L2 TAP VFS can be registered only once..."); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_vfs_l2tap_intf_register(NULL)); //ethernet_init(ð_network_hndls); eth_tap_fd = open("/dev/net/tap", O_NONBLOCK); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd); ESP_LOGI(TAG, "Verify that L2 TAP VFS cannot be unregistered prior closing FD's..."); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_vfs_l2tap_intf_unregister(NULL)); TEST_ASSERT_EQUAL(0, close(eth_tap_fd)); TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL)); ESP_LOGI(TAG, "Verify that L2 TAP VFS can be registered with different base path..."); l2tap_vfs_config_t config = { .base_path = "/dev/super_tap", }; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(&config)); eth_tap_fd = open("/dev/net/tap", O_NONBLOCK); TEST_ASSERT_EQUAL(-1, eth_tap_fd); eth_tap_fd = open("/dev/super_tap", O_NONBLOCK); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd); ESP_LOGI(TAG, "Verify that L2 TAP VFS cannot be unregistered with default base path..."); TEST_ASSERT_EQUAL(0, close(eth_tap_fd)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_vfs_l2tap_intf_unregister(NULL)); TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister("/dev/super_tap")); } /** * @brief Verifies open and close functions * */ typedef struct { int eth_tap_fd; SemaphoreHandle_t sem; bool on_select; } open_close_task_ctrl_t; static void open_read_task(void *task_param) { char in_buffer[300] = { 0 }; open_close_task_ctrl_t *task_control = (open_close_task_ctrl_t *)task_param; task_control->eth_tap_fd = open("/dev/net/tap", 0); TEST_ASSERT_NOT_EQUAL(-1, task_control->eth_tap_fd); // Set the Ethertype filter (frames with this type will be available through the eth_tap_fd) uint16_t eth_type_filter = ETH_FILTER_LE; TEST_ASSERT_NOT_EQUAL(-1, ioctl(task_control->eth_tap_fd, L2TAP_S_RCV_FILTER, ð_type_filter)); // Set Ethernet interface on which to get raw frames TEST_ASSERT_NOT_EQUAL(-1, ioctl(task_control->eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_DEF")); // Check the Ethernet interface was assigned char *if_key_str; TEST_ASSERT_NOT_EQUAL(-1, ioctl(task_control->eth_tap_fd, L2TAP_G_INTF_DEVICE, &if_key_str)); TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str); xSemaphoreGive(task_control->sem); if (task_control->on_select == true) { ESP_LOGI(TAG, "task1: going to block on select..."); struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; fd_set rfds; FD_ZERO(&rfds); FD_SET(task_control->eth_tap_fd, &rfds); // it is expected that blocking select is not unblocked by close and it timeouts (the fd number may be reused later // though and so select released but that's not tested here) TEST_ASSERT_EQUAL(0, select(task_control->eth_tap_fd + 1, &rfds, NULL, NULL, &tv)); ESP_LOGI(TAG, "task1: select timeout"); // get an error when try to use closed fd TEST_ASSERT_EQUAL(-1, read(task_control->eth_tap_fd, in_buffer, sizeof(in_buffer))); } else { ESP_LOGI(TAG, "task1: going to block on read..."); // it is expected that blocking read is unblocked by close TEST_ASSERT_EQUAL(-1, read(task_control->eth_tap_fd, in_buffer, sizeof(in_buffer))); ESP_LOGI(TAG, "task1: unblocked"); } xSemaphoreGive(task_control->sem); vTaskDelete(NULL); } static void close_task(void *task_param) { open_close_task_ctrl_t *task_control = (open_close_task_ctrl_t *)task_param; vTaskDelay(pdMS_TO_TICKS(500)); ESP_LOGI(TAG, "task2: closing..."); TEST_ASSERT_EQUAL(0, close(task_control->eth_tap_fd)); vTaskDelete(NULL); } TEST_CASE("esp32 l2tap - open/close", "[ethernet][test_env=UT_T2_Ethernet]") { test_vfs_eth_network_t eth_network_hndls; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL)); ethernet_init(ð_network_hndls); open_close_task_ctrl_t task_control; task_control.sem = xSemaphoreCreateBinary(); task_control.on_select = false; // ========================================================== // Close when blocking on read // ========================================================== ESP_LOGI(TAG, "Verify closing blocking read from higher priority task..."); xTaskCreate(open_read_task, "open_read_task", 4096, &task_control, 5, NULL); TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(1000))); xTaskCreate(close_task, "close_task", 4096, &task_control, 10, NULL); TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(1000))); ESP_LOGI(TAG, "Verify closing blocking read from lower priority task..."); xTaskCreate(open_read_task, "open_read_task", 4096, &task_control, 10, NULL); TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(1000))); // Close blocking read from lower priority task xTaskCreate(close_task, "close_task", 4096, &task_control, 5, NULL); TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(1000))); // ========================================================== // Close when blocking on select // ========================================================== task_control.on_select = true; ESP_LOGI(TAG, "Verify closing blocking select from higher priority task..."); xTaskCreate(open_read_task, "open_read_task", 4096, &task_control, 5, NULL); TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(2000))); xTaskCreate(close_task, "close_task", 4096, &task_control, 10, NULL); TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(2000))); ESP_LOGI(TAG, "Verify closing blocking select from lower priority task..."); xTaskCreate(open_read_task, "open_read_task", 4096, &task_control, 10, NULL); TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(2000))); // Close blocking read from lower priority task xTaskCreate(close_task, "close_task", 4096, &task_control, 5, NULL); TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_control.sem, pdMS_TO_TICKS(2000))); // ========================================================== // Limit of opened FD's // ========================================================== ESP_LOGI(TAG, "Verify limit of opened fds..."); int eth_tap_fds[CONFIG_ESP_NETIF_L2_TAP_MAX_FDS + 10]; int i; for (i = 0; i < CONFIG_ESP_NETIF_L2_TAP_MAX_FDS + 10; i++) { if ((eth_tap_fds[i] = open("/dev/net/tap", 0)) == -1) { break; } } TEST_ASSERT_EQUAL(CONFIG_ESP_NETIF_L2_TAP_MAX_FDS, i); while (--i >= 0) { TEST_ASSERT_EQUAL(0, close(eth_tap_fds[i])); } TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL)); vSemaphoreDelete(task_control.sem); ethernet_deinit(ð_network_hndls); } /* ============================================================================= */ /** * @brief Verifies that read does not block when fd is opened in non-blocking mode * */ TEST_CASE("esp32 l2tap - non blocking read", "[ethernet][test_env=UT_T2_Ethernet]") { test_vfs_eth_network_t eth_network_hndls; int eth_tap_fd; int n; char in_buffer[1500] = { 0 }; int loop_cnt = 0; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL)); ethernet_init(ð_network_hndls); eth_tap_fd = open("/dev/net/tap", O_NONBLOCK); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd); // Set the Ethertype filter (frames with this type will be available through the eth_tap_fd) uint16_t eth_type_filter = ETH_FILTER_LE; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_RCV_FILTER, ð_type_filter)); // Set Ethernet interface on which to get raw frames TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_DEF")); // Check the Ethernet interface was assigned char *if_key_str; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_INTF_DEVICE, &if_key_str)); TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str); // ========================================================== // Verify simple non-blocking read operations // ========================================================== ESP_LOGI(TAG, "Verify simple non-blocking read operations..."); send_task_control_t send_task_ctrl = { .eth_network_hndls_p = ð_network_hndls, .eth_type = -1, .send_delay_ms = DEFAULT_SEND_DELAY_MS, }; xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); // Verify the read does not block while (loop_cnt < 100) { if ((n = read(eth_tap_fd, in_buffer, sizeof(in_buffer))) > 0) { ESP_LOG_BUFFER_HEX(TAG, in_buffer, n); ESP_LOGI(TAG, "recv test string: %s", ((test_vfs_eth_tap_msg_t *)in_buffer)->str); TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n); break; } else { TEST_ASSERT_EQUAL(EAGAIN, errno); } vTaskDelay(pdMS_TO_TICKS(100)); loop_cnt++; } ESP_LOGI(TAG, "elapsed loop_cnts to read %d", loop_cnt); TEST_ASSERT_GREATER_THAN(0, loop_cnt); TEST_ASSERT_LESS_THAN(100, loop_cnt); TEST_ASSERT_EQUAL(sizeof(s_test_msg), n); // ========================================================== // Verify non-blocking successful read operations used along with select // ========================================================== ESP_LOGI(TAG, "Verify non-blocking successful read operations used along with select..."); memset(in_buffer, 0, sizeof(in_buffer)); // Wait up to x seconds struct timeval tv; tv.tv_sec = 4; tv.tv_usec = 0; send_task_ctrl.eth_network_hndls_p = ð_network_hndls; send_task_ctrl.send_delay_ms = DEFAULT_SEND_DELAY_MS; xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); fd_set rfds; FD_ZERO(&rfds); FD_SET(eth_tap_fd, &rfds); TEST_ASSERT_NOT_EQUAL(-1, select(eth_tap_fd + 1, &rfds, NULL, NULL, &tv)); TEST_ASSERT_GREATER_THAN(-1, FD_ISSET(eth_tap_fd, &rfds)); loop_cnt = 0; while (loop_cnt < 100) { if ((n = read(eth_tap_fd, in_buffer, sizeof(in_buffer))) > 0) { ESP_LOG_BUFFER_HEX(TAG, in_buffer, n); ESP_LOGI(TAG, "recv test string: %s", ((test_vfs_eth_tap_msg_t *)in_buffer)->str); TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n); break; } vTaskDelay(pdMS_TO_TICKS(100)); loop_cnt++; } ESP_LOGI(TAG, "elapsed loop_cnts to read %d", loop_cnt); TEST_ASSERT_EQUAL(0, loop_cnt); TEST_ASSERT_EQUAL(sizeof(s_test_msg), n); // ========================================================== // Verify non-blocking unsuccessful read operations used along with select // ========================================================== ESP_LOGI(TAG, "Verify non-blocking unsuccessful read operations used along with select..."); memset(in_buffer, 0, sizeof(in_buffer)); // Wait up to x seconds tv.tv_sec = 2; tv.tv_usec = 0; // no send_task => select will timeout FD_ZERO(&rfds); FD_SET(eth_tap_fd, &rfds); TEST_ASSERT_EQUAL(0, select(eth_tap_fd + 1, &rfds, NULL, NULL, &tv)); TEST_ASSERT_EQUAL(EAGAIN, errno); n = read(eth_tap_fd, in_buffer, sizeof(in_buffer)); TEST_ASSERT_EQUAL(EAGAIN, errno); TEST_ASSERT_EQUAL(-1, n); TEST_ASSERT_EQUAL(0, close(eth_tap_fd)); TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL)); ethernet_deinit(ð_network_hndls); } /* ============================================================================= */ /** * @brief Verifies that read blocks when fd opened in blocking mode * */ TEST_CASE("esp32 l2tap - blocking read", "[ethernet][test_env=UT_T2_Ethernet]") { test_vfs_eth_network_t eth_network_hndls; int eth_tap_fd; int n; char in_buffer[1500] = { 0 }; int loop_cnt = 0; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL)); ethernet_init(ð_network_hndls); eth_tap_fd = open("/dev/net/tap", 0); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd); // Set the Ethertype filter (frames with this type will be available through the eth_tap_fd) uint16_t eth_type_filter = ETH_FILTER_LE; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_RCV_FILTER, ð_type_filter)); // Set Ethernet interface on which to get raw frames TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_DEF")); // Check the Ethernet interface was assigned char *if_key_str; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_INTF_DEVICE, &if_key_str)); TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str); // ========================================================== // Verify simple blocking read operations // ========================================================== ESP_LOGI(TAG, "Verify simple blocking read operations..."); send_task_control_t send_task_ctrl = { .eth_network_hndls_p = ð_network_hndls, .eth_type = -1, .send_delay_ms = DEFAULT_SEND_DELAY_MS, }; xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); // Verify the read does block while (loop_cnt < 100) { if ((n = read(eth_tap_fd, in_buffer, sizeof(in_buffer))) > 0) { ESP_LOG_BUFFER_HEX(TAG, in_buffer, n); ESP_LOGI(TAG, "recv test string: %s", ((test_vfs_eth_tap_msg_t *)in_buffer)->str); TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n); break; } vTaskDelay(pdMS_TO_TICKS(100)); loop_cnt++; } ESP_LOGI(TAG, "elapsed loop_cnts to read %d", loop_cnt); TEST_ASSERT_EQUAL(0, loop_cnt); TEST_ASSERT_EQUAL(sizeof(s_test_msg), n); TEST_ASSERT_EQUAL(0, close(eth_tap_fd)); vTaskDelay(pdMS_TO_TICKS(50)); // just for sure to give some time to send task close fd TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL)); ethernet_deinit(ð_network_hndls); } /* ============================================================================= */ /** * @brief Verifies write * */ TEST_CASE("esp32 l2tap - write", "[ethernet][test_env=UT_T2_Ethernet]") { test_vfs_eth_network_t eth_network_hndls; int eth_tap_fd; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL)); ethernet_init(ð_network_hndls); eth_tap_fd = open("/dev/net/tap", 0); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd); // Set the Ethertype filter (frames with this type will be available through the eth_tap_fd) uint16_t eth_type_filter = ETH_FILTER_LE; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_RCV_FILTER, ð_type_filter)); // Set Ethernet interface on which to get raw frames TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_DEF")); // Check the Ethernet interface was assigned char *if_key_str; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_INTF_DEVICE, &if_key_str)); TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str); ESP_LOGI(TAG, "Verify the write is not successful when use different Ethernet type than the fd is configured to..."); test_vfs_eth_tap_msg_t test_msg = { .header = { .src.addr = {0}, .dest.addr = { 0x01, 0x00, 0x00, 0x00, 0xBE, 0xEF }, .type = 0, } }; // set different Ethernet type than the fd is configured to test_msg.header.type = htons(ETH_FILTER_LE + 10); TEST_ASSERT_EQUAL(-1, write(eth_tap_fd, &test_msg, sizeof(test_msg))); TEST_ASSERT_EQUAL(EBADMSG, errno); TEST_ASSERT_EQUAL(0, close(eth_tap_fd)); TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL)); ethernet_deinit(ð_network_hndls); } /* ============================================================================= */ /** * @brief Verifies that concrurent access to shared resource (Ethernet) is correctly handled * */ #define NUM_OF_FDS 2 #define NUM_OF_TASKS 2 typedef struct { uint16_t task_id; uint16_t eth_filter; SemaphoreHandle_t semaphore; } task_info_t; static void multi_fds_task (void *task_param) { task_info_t *task_info = (task_info_t *)task_param; uint16_t eth_filter = task_info->eth_filter; int eth_tap_fds[NUM_OF_FDS]; test_vfs_eth_tap_msg_t test_msg = { .header = { .src.addr = {0}, .dest.addr = { 0x01, 0x00, 0x00, 0x00, 0xBE, 0xEF }, .type = 0, } }; char in_buffer[sizeof(test_msg)] = { 0 }; for (int i = 0; i < sizeof(eth_tap_fds) / sizeof(int); i++) { eth_tap_fds[i] = open("/dev/net/tap", O_NONBLOCK); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fds[i]); uint16_t eth_type_filter = eth_filter + i; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fds[i], L2TAP_S_RCV_FILTER, ð_type_filter)); // Set Ethernet interface on which to get raw frames TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fds[i], L2TAP_S_INTF_DEVICE, "ETH_DEF")); // Check the Ethernet interface was assigned char *if_key_str; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fds[i], L2TAP_G_INTF_DEVICE, &if_key_str)); TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str); } int msg_cnt; for (msg_cnt = 0; msg_cnt < 5000; msg_cnt++) { for (int i = 0; i < sizeof(eth_tap_fds) / sizeof(int); i++) { test_msg.header.type = htons(eth_filter + i); test_msg.cnt = msg_cnt; TEST_ASSERT_NOT_EQUAL(-1, write(eth_tap_fds[i], &test_msg, sizeof(test_msg))); memset(in_buffer, 0, sizeof(in_buffer)); struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 300 * 1000; fd_set rfds; FD_ZERO(&rfds); FD_SET(eth_tap_fds[i], &rfds); if (select(eth_tap_fds[i] + 1, &rfds, NULL, NULL, &tv) > -1) { if (FD_ISSET(eth_tap_fds[i], &rfds)) { int n = read(eth_tap_fds[i], in_buffer, sizeof(in_buffer)); TEST_ASSERT_GREATER_THAN(0, n); TEST_ASSERT_EQUAL(msg_cnt, ((test_vfs_eth_tap_msg_t *)in_buffer)->cnt); } else { TEST_FAIL_MESSAGE("time out, frame was not successfully written (due to possible race condition)"); } } else { TEST_FAIL_MESSAGE("select error"); } } } for (int i = 0; i < sizeof(eth_tap_fds) / sizeof(int); i++) { TEST_ASSERT_EQUAL(0, close(eth_tap_fds[i])); } ESP_LOGI(TAG, "multi_fds_task %u done", task_info->task_id); xSemaphoreGive(task_info->semaphore); vTaskDelete(NULL); } TEST_CASE("esp32 l2tap - read/write multiple fd's used by multiple tasks", "[ethernet][test_env=UT_T2_Ethernet]") { test_vfs_eth_network_t eth_network_hndls; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL)); ethernet_init(ð_network_hndls); task_info_t task_info[NUM_OF_TASKS]; for (int i = 0; i < NUM_OF_TASKS; i++) { task_info[i].task_id = i; task_info[i].eth_filter = 0x750A + i * 100; task_info[i].semaphore = xSemaphoreCreateBinary(); } xTaskCreate(multi_fds_task, "multi_fds_task_1", 4096, &task_info[0], tskIDLE_PRIORITY + 2, NULL); xTaskCreate(multi_fds_task, "multi_fds_task_2", 4096, &task_info[1], tskIDLE_PRIORITY + 4, NULL); for (int i = 0; i < NUM_OF_TASKS; i++) { TEST_ASSERT_NOT_EQUAL(pdFALSE, xSemaphoreTake(task_info[i].semaphore, pdMS_TO_TICKS(20 * 1000))); vSemaphoreDelete(task_info[i].semaphore); } TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL)); ethernet_deinit(ð_network_hndls); } /* ============================================================================= */ /** * @brief Verifies proper functionality of ioctl RCV_FILTER option * */ TEST_CASE("esp32 l2tap - ioctl - RCV_FILTER", "[ethernet][test_env=UT_T2_Ethernet]") { test_vfs_eth_network_t eth_network_hndls; int eth_tap_fd; char in_buffer[1500] = { 0 }; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL)); ethernet_init(ð_network_hndls); eth_tap_fd = open("/dev/net/tap", 0); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd); // Set the Ethertype filter (frames with this type will be available through the eth_tap_fd) uint16_t eth_type_filter = ETH_FILTER_LE; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_RCV_FILTER, ð_type_filter)); uint16_t eth_type_filter_get = 0; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_RCV_FILTER, ð_type_filter_get)); TEST_ASSERT_EQUAL(eth_type_filter, eth_type_filter_get); // Set Ethernet interface on which to get raw frames TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_DEF")); // Check the Ethernet interface was assigned char *if_key_str; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_INTF_DEVICE, &if_key_str)); TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str); ESP_LOGI(TAG, "Confirm that we are able to receive message with default Ethertype..."); send_task_control_t send_task_ctrl = { .eth_network_hndls_p = ð_network_hndls, .eth_type = -1, .send_delay_ms = DEFAULT_SEND_DELAY_MS, }; xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); int n = read(eth_tap_fd, in_buffer, sizeof(in_buffer)); TEST_ASSERT_GREATER_THAN(0, n); TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n); // Set new value of Ethernet type filter eth_type_filter = ETH_FILTER_LE + 100; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_RCV_FILTER, ð_type_filter)); TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_RCV_FILTER, ð_type_filter_get)); TEST_ASSERT_EQUAL(eth_type_filter, eth_type_filter_get); // Send the test message with the old (default) value of the Ethernet filter xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); ESP_LOGI(TAG, "Confirm that the message was not received (select timeouts)..."); struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 1000 * 1000; fd_set rfds; FD_ZERO(&rfds); FD_SET(eth_tap_fd, &rfds); TEST_ASSERT_EQUAL(0, select(eth_tap_fd + 1, &rfds, NULL, NULL, &tv)); // Send the test message with the new value of the Ethernet filter send_task_ctrl.eth_type = eth_type_filter; xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); ESP_LOGI(TAG, "Verify that the message with new Etherbet type is received..."); n = read(eth_tap_fd, in_buffer, sizeof(in_buffer)); TEST_ASSERT_GREATER_THAN(0, n); TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n); // Open another raw ethernet fd and try to set the same filter as for the first one int eth_tap_fd_2 = open("/dev/net/tap", 0); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd_2); ESP_LOGI(TAG, "Verify that the setting the same Ethernet type to other fd was unsuccessful..."); TEST_ASSERT_EQUAL(-1, ioctl(eth_tap_fd_2, L2TAP_S_RCV_FILTER, ð_type_filter)); TEST_ASSERT_EQUAL(EINVAL, errno); TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd_2, L2TAP_G_RCV_FILTER, ð_type_filter_get)); TEST_ASSERT_EQUAL(0, eth_type_filter_get); TEST_ASSERT_EQUAL(0, close(eth_tap_fd_2)); TEST_ASSERT_EQUAL(0, close(eth_tap_fd)); vTaskDelay(pdMS_TO_TICKS(50)); // just for sure to give some time to send task close fd TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL)); ethernet_deinit(ð_network_hndls); } /* ============================================================================= */ /** * @brief Verifies proper functionality of ioctl INTF_DEVICE option * */ static esp_err_t dummy_rx_hook(esp_netif_t *esp_netif, void *buff, size_t *size) { return ESP_OK; } TEST_CASE("esp32 l2tap - ioctl - INTF_DEVICE", "[ethernet][test_env=UT_T2_Ethernet]") { test_vfs_eth_network_t eth_network_hndls; int eth_tap_fd; char in_buffer[1500] = { 0 }; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL)); ethernet_init(ð_network_hndls); eth_tap_fd = open("/dev/net/tap", 0); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd); // Set the Ethertype filter (frames with this type will be available through the eth_tap_fd) uint16_t eth_type_filter = ETH_FILTER_LE; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_RCV_FILTER, ð_type_filter)); uint16_t eth_type_filter_get = 0; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_RCV_FILTER, ð_type_filter_get)); TEST_ASSERT_EQUAL(eth_type_filter, eth_type_filter_get); // Set Ethernet interface on which to get raw frames TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_DEF")); // Check the Ethernet interface was assigned char *if_key_str; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_INTF_DEVICE, &if_key_str)); TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str); ESP_LOGI(TAG, "Confirm that we are able to receive message at default netif..."); send_task_control_t send_task_ctrl = { .eth_network_hndls_p = ð_network_hndls, .eth_type = -1, .send_delay_ms = DEFAULT_SEND_DELAY_MS, }; xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); int n = read(eth_tap_fd, in_buffer, sizeof(in_buffer)); TEST_ASSERT_GREATER_THAN(0, n); TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n); ESP_LOGI(TAG, "Try to set non-existing Ethernet interface..."); TEST_ASSERT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_NOT_DEF")); TEST_ASSERT_EQUAL(ENODEV, errno); ESP_LOGI(TAG, "Verify that previous setting is kept..."); TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_INTF_DEVICE, &if_key_str)); TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str); xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 10, NULL); // set higher priority, we need to be sure that "send" task closes FD prior main task n = read(eth_tap_fd, in_buffer, sizeof(in_buffer)); TEST_ASSERT_GREATER_THAN(0, n); TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n); TEST_ASSERT_EQUAL(0, close(eth_tap_fd)); ESP_LOGI(TAG, "Verify ioctl response when Ethernet interface has already attached recv hook..."); TEST_ASSERT_EQUAL(ESP_OK, esp_netif_recv_hook_attach(eth_network_hndls.eth_netif, &dummy_rx_hook)); eth_tap_fd = open("/dev/net/tap", 0); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd); // Try to set Ethernet interface with already attached recv hook TEST_ASSERT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_DEF")); TEST_ASSERT_EQUAL(ENOSPC, errno); TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_INTF_DEVICE, &if_key_str)); TEST_ASSERT_EQUAL(NULL, if_key_str); TEST_ASSERT_EQUAL(0, close(eth_tap_fd)); vTaskDelay(pdMS_TO_TICKS(50)); // just for sure to give some time to send task close fd TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL)); ethernet_deinit(ð_network_hndls); } /* ============================================================================= */ /** * @brief Verifies proper functionality of ioctl unknown option * */ TEST_CASE("esp32 l2tap - ioctl - unknown", "[ethernet][test_env=UT_T2_Ethernet]") { test_vfs_eth_network_t eth_network_hndls; int eth_tap_fd; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL)); ethernet_init(ð_network_hndls); eth_tap_fd = open("/dev/net/tap", 0); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd); // Try to execute unknown option uint32_t option_val = 0x0BAD; TEST_ASSERT_EQUAL(-1, ioctl(eth_tap_fd, 412, &option_val)); TEST_ASSERT_EQUAL(0, close(eth_tap_fd)); TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL)); ethernet_deinit(ð_network_hndls); } /* ============================================================================= */ /** * @brief Verifies proper functionality of fcntl * */ TEST_CASE("esp32 l2tap - fcntl", "[ethernet][test_env=UT_T2_Ethernet]") { test_vfs_eth_network_t eth_network_hndls; int eth_tap_fd; char in_buffer[1500] = { 0 }; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL)); ethernet_init(ð_network_hndls); eth_tap_fd = open("/dev/net/tap", 0); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd); // Set the Ethertype filter (frames with this type will be available through the eth_tap_fd) uint16_t eth_type_filter = ETH_FILTER_LE; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_RCV_FILTER, ð_type_filter)); uint16_t eth_type_filter_get = 0; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_RCV_FILTER, ð_type_filter_get)); TEST_ASSERT_EQUAL(eth_type_filter, eth_type_filter_get); // Set Ethernet interface on which to get raw frames TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_DEF")); // Check the Ethernet interface was assigned char *if_key_str; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_INTF_DEVICE, &if_key_str)); TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str); send_task_control_t send_task_ctrl = { .eth_network_hndls_p = ð_network_hndls, .eth_type = -1, .send_delay_ms = DEFAULT_SEND_DELAY_MS, }; // Confirm the read blocks by default xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); int n = read(eth_tap_fd, in_buffer, sizeof(in_buffer)); TEST_ASSERT_GREATER_THAN(0, n); TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n); // Set non-blocking int flags = fcntl(eth_tap_fd, F_GETFL); TEST_ASSERT_GREATER_THAN(-1, flags); flags |= O_NONBLOCK; TEST_ASSERT_EQUAL(0, fcntl(eth_tap_fd, F_SETFL, flags)); int flags_new = fcntl(eth_tap_fd, F_GETFL); TEST_ASSERT_GREATER_THAN(-1, flags_new); TEST_ASSERT_EQUAL(flags_new, flags); // Verify the read does not block int loop_cnt = 0; xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); while (loop_cnt < 100) { if ((n = read(eth_tap_fd, in_buffer, sizeof(in_buffer))) > 0) { TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n); break; } else { TEST_ASSERT_EQUAL(EAGAIN, errno); } vTaskDelay(pdMS_TO_TICKS(100)); loop_cnt++; } ESP_LOGI(TAG, "elapsed loop_cnts to read %d", loop_cnt); TEST_ASSERT_GREATER_THAN(0, loop_cnt); TEST_ASSERT_LESS_THAN(100, loop_cnt); TEST_ASSERT_EQUAL(sizeof(s_test_msg), n); // Set back to blocking flags = fcntl(eth_tap_fd, F_GETFL); TEST_ASSERT_GREATER_THAN(-1, flags); flags &= ~O_NONBLOCK; TEST_ASSERT_EQUAL(0, fcntl(eth_tap_fd, F_SETFL, flags)); flags_new = fcntl(eth_tap_fd, F_GETFL); TEST_ASSERT_GREATER_THAN(-1, flags_new); TEST_ASSERT_EQUAL(flags_new, flags); loop_cnt = 0; xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); while (loop_cnt < 100) { if ((n = read(eth_tap_fd, in_buffer, sizeof(in_buffer))) > 0) { TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n); break; } else { TEST_ASSERT_NOT_EQUAL(EAGAIN, errno); } vTaskDelay(pdMS_TO_TICKS(100)); loop_cnt++; } ESP_LOGI(TAG, "elapsed loop_cnts to read %d", loop_cnt); TEST_ASSERT_EQUAL(0, loop_cnt); // Try to use unsupported operation int new_fd = fcntl(eth_tap_fd, F_DUPFD, 0); TEST_ASSERT_EQUAL(-1, new_fd); TEST_ASSERT_EQUAL(ENOSYS, errno); TEST_ASSERT_EQUAL(0, close(eth_tap_fd)); vTaskDelay(pdMS_TO_TICKS(50)); // just for sure to give some time to send task close fd TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL)); ethernet_deinit(ð_network_hndls); } /* ============================================================================= */ /** * @brief Verifies option to modify outbound Ethernet frames * */ static char append_msg[] = "!append msg!"; static esp_err_t mod_eth_frame(esp_netif_t *esp_netif, void **buff, size_t *size) { uint8_t *mod_buff; mod_buff = malloc(*size + sizeof(append_msg)); memcpy(mod_buff, *buff, *size); memcpy(&mod_buff[*size], &append_msg, sizeof(append_msg)); *buff = mod_buff; *size += sizeof(append_msg); return ESP_OK; } static void mod_eth_frame_clear(esp_netif_t *esp_netif, void *buff, size_t size) { free(buff); } TEST_CASE("esp32 l2tap - netif tx hook", "[ethernet][test_env=UT_T2_Ethernet]") { test_vfs_eth_network_t eth_network_hndls; int eth_tap_fd; char in_buffer[1500] = { 0 }; TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_register(NULL)); ethernet_init(ð_network_hndls); eth_tap_fd = open("/dev/net/tap", 0); TEST_ASSERT_NOT_EQUAL(-1, eth_tap_fd); // Set the Ethertype filter (frames with this type will be available through the eth_tap_fd) uint16_t eth_type_filter = ETH_FILTER_LE; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_RCV_FILTER, ð_type_filter)); uint16_t eth_type_filter_get = 0; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_RCV_FILTER, ð_type_filter_get)); TEST_ASSERT_EQUAL(eth_type_filter, eth_type_filter_get); // Set Ethernet interface on which to get raw frames TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_S_INTF_DEVICE, "ETH_DEF")); // Check the Ethernet interface was assigned char *if_key_str; TEST_ASSERT_NOT_EQUAL(-1, ioctl(eth_tap_fd, L2TAP_G_INTF_DEVICE, &if_key_str)); TEST_ASSERT_EQUAL_STRING("ETH_DEF", if_key_str); send_task_control_t send_task_ctrl = { .eth_network_hndls_p = ð_network_hndls, .eth_type = -1, .send_delay_ms = 0, }; // Send normal test message without any registered transmit hook at first xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); int n = read(eth_tap_fd, in_buffer, sizeof(in_buffer)); TEST_ASSERT_GREATER_THAN(0, n); TEST_ASSERT_EQUAL(sizeof(s_test_msg), n); TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n); // Attach transmit hook function to modify outbound Ethernet frames TEST_ASSERT_EQUAL(ESP_OK, esp_netif_transmit_hook_attach(eth_network_hndls.eth_netif, &mod_eth_frame)); // Attach post transmit hook function to clear buffer created by mod_eth_frame TEST_ASSERT_EQUAL(ESP_OK, esp_netif_post_transmit_hook_attach(eth_network_hndls.eth_netif, &mod_eth_frame_clear)); // Verify the outbound traffic was modified as expected xTaskCreate(send_task, "raw_eth_send_task", 1024, &send_task_ctrl, tskIDLE_PRIORITY + 2, NULL); n = read(eth_tap_fd, in_buffer, sizeof(in_buffer)); TEST_ASSERT_GREATER_THAN(0, n); TEST_ASSERT_EQUAL(sizeof(s_test_msg) + sizeof(append_msg), n); TEST_ASSERT_EQUAL_UINT8_ARRAY(&s_test_msg, in_buffer, n - sizeof(append_msg)); TEST_ASSERT_EQUAL_UINT8_ARRAY(&append_msg, &in_buffer[sizeof(s_test_msg)], n - sizeof(s_test_msg)); TEST_ASSERT_EQUAL(ESP_OK, esp_netif_transmit_hook_detach(eth_network_hndls.eth_netif)); TEST_ASSERT_EQUAL(ESP_OK, esp_netif_post_transmit_hook_detach(eth_network_hndls.eth_netif)); TEST_ASSERT_EQUAL(0, close(eth_tap_fd)); vTaskDelay(pdMS_TO_TICKS(50)); // just for sure to give some time to send task close fd TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_l2tap_intf_unregister(NULL)); ethernet_deinit(ð_network_hndls); } #endif // CONFIG_ESP_NETIF_L2_TAP