mirror of
https://github.com/espressif/esp-idf.git
synced 2024-09-19 14:26:01 -04:00
examples: added ESP-NETIF L2 TAP example
This commit is contained in:
parent
e4217ff868
commit
fcdb0306d0
@ -81,6 +81,16 @@ example_test_pytest_esp32_ethernet_ota:
|
||||
TARGET: ESP32
|
||||
ENV_MARKER: ethernet_ota
|
||||
|
||||
example_test_pytest_esp32_ethernet_ip101:
|
||||
extends:
|
||||
- .pytest_examples_dir_template
|
||||
- .rules:test:example_test-esp32
|
||||
needs:
|
||||
- build_pytest_examples_esp32
|
||||
variables:
|
||||
TARGET: ESP32
|
||||
ENV_MARKER: ip101
|
||||
|
||||
.pytest_components_dir_template:
|
||||
extends: .pytest_template
|
||||
variables:
|
||||
|
@ -182,6 +182,19 @@ All set configuration options have getter counterpart option to read the current
|
||||
| **ENOSPC** - NETIF L2 receive hook is already taken by other function when trying to assign Network Interface to the file descriptor.
|
||||
| **ENOSYS** - unsupported operation, passed configuration option does not exists.
|
||||
|
||||
fcntl()
|
||||
^^^^^^^
|
||||
``fcntl()`` is used to manipulate with properties of opened ESP-NETIF L2 TAP file descriptor.
|
||||
|
||||
The following commands manipulate the status flags associated with file descriptor:
|
||||
|
||||
* ``F_GETFD`` - the function returns the file descriptor flags, the third argument is ignored.
|
||||
* ``F_SETFD`` - sets the file descriptor flags to the value specified by the third argument. Zero is returned.
|
||||
|
||||
| On error, -1 is returned, and ``errno`` is set to indicate the error.
|
||||
| **EBADF** - not a valid file descriptor.
|
||||
| **ENOSYS** - unsupported command.
|
||||
|
||||
read()
|
||||
^^^^^^
|
||||
Opened and configured ESP-NETIF L2 TAP file descriptor can be accessed by ``read()`` to get inbound frames. The read operation can be either blocking or non-blocking based on actual state of ``O_NONBLOCK`` file status flag. When the file status flag is set blocking, the read operation waits until a frame is received and context is switched to other task. When the file status flag is set non-blocking, the read operation returns immediately. In such case, either a frame is returned if it was already queued or the function indicates the queue is empty. The number of queued frames associated with one file descriptor is limited by :ref:`CONFIG_ESP_NETIF_L2_TAP_RX_QUEUE_SIZE` Kconfig option. Once the number of queued frames reach configured threshold, the newly arriving frames are dropped until the queue has enough room to accept incoming traffic (Tail Drop queue management).
|
||||
@ -230,6 +243,8 @@ Please refer to the example section for basic initialization of default interfac
|
||||
|
||||
- Ethernet: :example_file:`ethernet/basic/main/ethernet_example_main.c`
|
||||
|
||||
- L2 TAP: :example_file:`protocols/l2tap/main/l2tap_main.c`
|
||||
|
||||
.. only:: CONFIG_ESP_WIFI_SOFTAP_SUPPORT
|
||||
|
||||
- WiFi Access Point: :example_file:`wifi/getting_started/softAP/main/softap_example_main.c`
|
||||
|
@ -479,6 +479,7 @@ static void eth_stop(void)
|
||||
ESP_ERROR_CHECK(esp_eth_stop(s_eth_handle));
|
||||
ESP_ERROR_CHECK(esp_eth_del_netif_glue(s_eth_glue));
|
||||
ESP_ERROR_CHECK(esp_eth_driver_uninstall(s_eth_handle));
|
||||
s_eth_handle = NULL;
|
||||
ESP_ERROR_CHECK(s_phy->del(s_phy));
|
||||
ESP_ERROR_CHECK(s_mac->del(s_mac));
|
||||
|
||||
@ -486,6 +487,11 @@ static void eth_stop(void)
|
||||
s_example_esp_netif = NULL;
|
||||
}
|
||||
|
||||
esp_eth_handle_t get_example_eth_handle(void)
|
||||
{
|
||||
return s_eth_handle;
|
||||
}
|
||||
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
esp_netif_t *get_example_netif(void)
|
||||
|
@ -78,6 +78,15 @@ esp_netif_t *get_example_netif(void);
|
||||
*/
|
||||
esp_netif_t *get_example_netif_from_desc(const char *desc);
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
/**
|
||||
* @brief Get the example Ethernet driver handle
|
||||
*
|
||||
* @return esp_eth_handle_t
|
||||
*/
|
||||
esp_eth_handle_t get_example_eth_handle(void);
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
9
examples/protocols/l2tap/CMakeLists.txt
Normal file
9
examples/protocols/l2tap/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(l2tap_example)
|
133
examples/protocols/l2tap/README.md
Normal file
133
examples/protocols/l2tap/README.md
Normal file
@ -0,0 +1,133 @@
|
||||
# ESP-NETIF L2 TAP Interface Example
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
## Overview
|
||||
|
||||
This example demonstrates basic usage of ESP-NETIF L2 TAP Interface. The ESP-NETIF L2 TAP interface is ESP-IDF mechanism utilized to access Data Link Layer (L2 per OSI/ISO) for frame reception and transmission from user application. Its typical usage in embedded world might be implementation of non-IP related protocols such as PTP, Wake on LAN and others.
|
||||
|
||||
---
|
||||
|
||||
**Note:** Only Ethernet (IEEE 802.3) is currently supported.
|
||||
|
||||
---
|
||||
|
||||
The ESP-NETIF L2 TAP interface is accessed using file descriptors of VFS which provides a file-like interfacing (using functions like ``open()``, ``read()``, ``write()``, etc.) and its usage is specifically demonstrated by the following:
|
||||
|
||||
1) Waiting for a frame using blocking ``read()`` and echoing it back to the originator.
|
||||
2) Waiting for a frame using ``select()`` with timeout and non-blocking ``read()``, and echoing it back to the originator.
|
||||
3) Periodical broadcast transmission of "Hello message" to show proper construction of the Ethernet frame to be transmitted.
|
||||
|
||||
The work flow of the example is then as follows:
|
||||
|
||||
1. Install Ethernet driver.
|
||||
2. Send DHCP requests and wait for a DHCP lease.
|
||||
3. If get IP address successfully, then you will be able to ping the device.
|
||||
4. Initialize ESP-NETIF L2 TAP interface, then you will be able to echo Ethernet frames with specific EthTypes (non-IP traffic).
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run this example, it is recommended to use official ESP32 Ethernet development board - [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html). This example should also work for 3rd party ESP32 board as long as it is integrated with a supported Ethernet PHY chip. Besides that, several third-party Ethernet modules which integrate MAC and PHY and provides common communication interface (e.g. SPI, USB, etc) is supported too. Consult "Example Connection Configuration" option in IDF project configuration tool for specific list of supported devices.
|
||||
|
||||
#### Pin Assignment
|
||||
|
||||
See common pin assignments for [Ethernet examples](../../ethernet/README.md#common-pin-assignments).
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
See common configurations for [Ethernet examples](../../ethernet/README.md#common-configurations).
|
||||
|
||||
### Build, Flash, and Run
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT build flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
### Send a message from Host PC
|
||||
|
||||
Once the board is flashed and runs, you can send a message to it via raw socket and expect echo of that message. You can utilize ``pytest_example_l2tap_echo.py``. The scripts takes two optional parameters as (order of parameters needs to be kept):
|
||||
|
||||
```bash
|
||||
python3 pytest_example_l2tap_echo.py [<dest_mac_address>] [<host_eth_interface>]
|
||||
```
|
||||
|
||||
* `dest_mac_address` - MAC address of the destination device (the ESP32 board with flashed example). ESP 32 board MAC address is printed by the example so you can find it in monitor output under "Ethernet HW Addr". Broadcast address is used when this parameter is omitted.
|
||||
|
||||
* `host_eth_interface` - name of host PC Ethernet device on which the frame is to be transmitted/received. When this parameter is omitted, the script tries to find Ethernet interface automatically. If you need, you can find names of installed Network interfaces by running either of bellow commands on Linux:
|
||||
|
||||
```bash
|
||||
$ ifconfig
|
||||
$ ip address
|
||||
$ ls /sys/class/net/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Note:** ``pytest_example_l2tap_echo.py`` needs to be run with appropriate rights (e.g. use ``sudo``) since it accesses Network interface at Link (L2) layer, which may be considered harmful in general and so not allowed for regular user accounts.
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
**Warning:** ``pytest_example_l2tap_echo.py`` can be currently used only on Linux.
|
||||
|
||||
---
|
||||
|
||||
## Example Output
|
||||
|
||||
Host PC output:
|
||||
|
||||
```bash
|
||||
$ sudo python3 pytest_example_l2tap_echo.py 08:3a:f2:31:20:f7 enx00e04c6801ac
|
||||
2022-03-31 09:21:19,699 Use enx00e04c6801ac for testing
|
||||
2022-03-31 09:21:19,737 Sent 52 bytes to 08:3a:f2:31:20:f7
|
||||
2022-03-31 09:21:19,737 Sent msg: "ESP32 test message with EthType 0x2220"
|
||||
2022-03-31 09:21:19,739 Received 60 bytes echoed from 08:3a:f2:31:20:f7
|
||||
2022-03-31 09:21:19,739 Echoed msg: "ESP32 test message with EthType 0x2220"
|
||||
2022-03-31 09:21:19,764 Use enx00e04c6801ac for testing
|
||||
2022-03-31 09:21:19,804 Sent 52 bytes to 08:3a:f2:31:20:f7
|
||||
2022-03-31 09:21:19,805 Sent msg: "ESP32 test message with EthType 0x2221"
|
||||
2022-03-31 09:21:19,806 Received 60 bytes echoed from 08:3a:f2:31:20:f7
|
||||
2022-03-31 09:21:19,806 Echoed msg: "ESP32 test message with EthType 0x2221"
|
||||
```
|
||||
|
||||
ESP32 L2 TAP Example output:
|
||||
|
||||
```bash
|
||||
I (5402) example_connect: Connected to example_connect: eth
|
||||
I (5402) example_connect: - IPv4 address: 192.168.20.103
|
||||
I (5412) example_connect: - IPv6 address: fe80:0000:0000:0000:0a3a:f2ff:fe31:20f7, type: ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
I (5422) l2tap_example: Ethernet HW Addr 08:3a:f2:31:20:f7
|
||||
I (5432) l2tap_example: /dev/net/tap fd 4 successfully opened
|
||||
I (5432) l2tap_example: /dev/net/tap fd 3 successfully opened
|
||||
I (5442) l2tap_example: L2 TAP fd 3 configured in blocking mode
|
||||
I (5442) l2tap_example: L2 TAP fd 4 configured in non-blocking mode
|
||||
I (5442) l2tap_example: L2 TAP fd 3 successfully bound to `ETH_DEF`
|
||||
I (5462) l2tap_example: L2 TAP fd 4 successfully bound to `ETH_DEF`
|
||||
I (5462) l2tap_example: L2 TAP fd 3 Ethernet type filter configured to 0x2220
|
||||
I (5472) l2tap_example: L2 TAP fd 4 Ethernet type filter configured to 0x2221
|
||||
I (5442) l2tap_example: /dev/net/tap fd 5 successfully opened
|
||||
I (5492) l2tap_example: L2 TAP fd 5 configured in blocking mode
|
||||
I (5492) l2tap_example: L2 TAP fd 5 successfully bound to `ETH_DEF`
|
||||
I (5502) l2tap_example: L2 TAP fd 5 Ethernet type filter configured to 0x2223
|
||||
I (12102) l2tap_example: fd 3 received 60 bytes from 00:e0:4c:68:01:ac
|
||||
I (12162) l2tap_example: fd 4 received 60 bytes from 00:e0:4c:68:01:ac
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
See common troubleshooting for examples using [Ethernet](../../ethernet/README.md#common-troubleshooting).
|
||||
|
||||
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)
|
2
examples/protocols/l2tap/main/CMakeLists.txt
Normal file
2
examples/protocols/l2tap/main/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "l2tap_main.c"
|
||||
INCLUDE_DIRS ".")
|
265
examples/protocols/l2tap/main/l2tap_main.c
Normal file
265
examples/protocols/l2tap/main/l2tap_main.c
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h> // read/write
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <errno.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_eth.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_vfs_l2tap.h"
|
||||
#include "lwip/prot/ethernet.h" // Ethernet header
|
||||
#include "arpa/inet.h" // ntohs, etc.
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#if !defined(CONFIG_EXAMPLE_CONNECT_ETHERNET)
|
||||
#error Ethernet interface is not configured to connect.
|
||||
#endif
|
||||
|
||||
#define ETH_INTERFACE "ETH_DEF"
|
||||
#define ETH_TYPE_FILTER_BLOCK 0x2220
|
||||
#define ETH_TYPE_FILTER_NOBLOCK 0x2221
|
||||
#define ETH_TYPE_FILTER_TX 0x2223
|
||||
|
||||
#define INVALID_FD -1
|
||||
|
||||
typedef struct {
|
||||
struct eth_hdr header;
|
||||
char payload[44];
|
||||
} test_vfs_eth_tap_msg_t;
|
||||
|
||||
static const char *TAG = "l2tap_example";
|
||||
|
||||
|
||||
/** Opens and configures L2 TAP file descriptor */
|
||||
static int init_l2tap_fd(int flags, uint16_t eth_type_filter)
|
||||
{
|
||||
int fd = open("/dev/net/tap", flags);
|
||||
if (fd < 0) {
|
||||
ESP_LOGE(TAG, "Unable to open L2 TAP interface: errno %d", errno);
|
||||
goto error;
|
||||
}
|
||||
ESP_LOGI(TAG, "/dev/net/tap fd %d successfully opened", fd);
|
||||
|
||||
// Check fd block status (just for demonstration purpose)
|
||||
flags = 0;
|
||||
flags = fcntl(fd, F_GETFL);
|
||||
if (flags == -1) {
|
||||
ESP_LOGE(TAG, "Unable to get L2 TAP fd %d status flag: errno %d", fd, errno);
|
||||
goto error;
|
||||
}
|
||||
if (flags & O_NONBLOCK) {
|
||||
ESP_LOGI(TAG, "L2 TAP fd %d configured in non-blocking mode", fd);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "L2 TAP fd %d configured in blocking mode", fd);
|
||||
}
|
||||
|
||||
// Configure Ethernet interface on which to get raw frames
|
||||
int ret;
|
||||
if ((ret = ioctl(fd, L2TAP_S_INTF_DEVICE, ETH_INTERFACE)) == -1) {
|
||||
ESP_LOGE(TAG, "Unable to bound L2 TAP fd %d with Ethernet device: errno %d", fd, errno);
|
||||
goto error;
|
||||
}
|
||||
ESP_LOGI(TAG, "L2 TAP fd %d successfully bound to `%s`", fd, ETH_INTERFACE);
|
||||
|
||||
// Configure Ethernet frames we want to filter out
|
||||
if ((ret = ioctl(fd, L2TAP_S_RCV_FILTER, ð_type_filter)) == -1) {
|
||||
ESP_LOGE(TAG, "Unable to configure fd %d Ethernet type receive filter: errno %d", fd, errno);
|
||||
goto error;
|
||||
}
|
||||
ESP_LOGI(TAG, "L2 TAP fd %d Ethernet type filter configured to 0x%x", fd, eth_type_filter);
|
||||
|
||||
return fd;
|
||||
error:
|
||||
if (fd != INVALID_FD) {
|
||||
close(fd);
|
||||
}
|
||||
return INVALID_FD;
|
||||
}
|
||||
|
||||
/** Creates "echo" message from received frame */
|
||||
static void create_echo_frame(test_vfs_eth_tap_msg_t *in_frame, test_vfs_eth_tap_msg_t *out_frame, int len)
|
||||
{
|
||||
// Set source address equal to our MAC address
|
||||
esp_eth_handle_t eth_hndl = get_example_eth_handle();
|
||||
uint8_t mac_addr[ETH_ADDR_LEN];
|
||||
esp_eth_ioctl(eth_hndl, ETH_CMD_G_MAC_ADDR, mac_addr);
|
||||
memcpy(out_frame->header.src.addr, mac_addr, ETH_ADDR_LEN);
|
||||
// Set destination address equal to source address from where the frame was received
|
||||
memcpy(out_frame->header.dest.addr, in_frame->header.src.addr, ETH_ADDR_LEN);
|
||||
// Set Ethernet type
|
||||
memcpy(&out_frame->header.type, &in_frame->header.type, sizeof(uint16_t));
|
||||
// Copy the payload
|
||||
memcpy(out_frame->payload, in_frame->payload, len - ETH_HEADER_LEN);
|
||||
}
|
||||
|
||||
/** Demonstrates usage of L2 TAP in blocking mode */
|
||||
static void echo_l2tap_task(void *pvParameters)
|
||||
{
|
||||
uint8_t rx_buffer[128];
|
||||
int eth_tap_fd;
|
||||
|
||||
// Open and configure L2 TAP File descriptor
|
||||
if ((eth_tap_fd = init_l2tap_fd(0, ETH_TYPE_FILTER_BLOCK)) == INVALID_FD) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
ssize_t len = read(eth_tap_fd, rx_buffer, sizeof(rx_buffer));
|
||||
if (len > 0) {
|
||||
test_vfs_eth_tap_msg_t *recv_msg = (test_vfs_eth_tap_msg_t *)rx_buffer;
|
||||
ESP_LOGI(TAG, "fd %d received %d bytes from %.2x:%.2x:%.2x:%.2x:%.2x:%.2x", eth_tap_fd,
|
||||
len, recv_msg->header.src.addr[0], recv_msg->header.src.addr[1], recv_msg->header.src.addr[2],
|
||||
recv_msg->header.src.addr[3], recv_msg->header.src.addr[4], recv_msg->header.src.addr[5]);
|
||||
// Construct echo frame
|
||||
test_vfs_eth_tap_msg_t echo_msg;
|
||||
create_echo_frame(recv_msg, &echo_msg, len);
|
||||
|
||||
// Send the echo message
|
||||
ssize_t ret = write(eth_tap_fd, &echo_msg, len);
|
||||
if (ret == -1) {
|
||||
ESP_LOGE(TAG, "L2 TAP fd %d write error: errno: %d\n", eth_tap_fd, errno);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "L2 TAP fd %d read error: errno %d", eth_tap_fd, errno);
|
||||
break;
|
||||
}
|
||||
}
|
||||
close(eth_tap_fd);
|
||||
error:
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/** Demonstrates usage of L2 TAP non-blocking mode with select */
|
||||
static void nonblock_l2tap_echo_task(void *pvParameters)
|
||||
{
|
||||
uint8_t rx_buffer[128];
|
||||
int eth_tap_fd;
|
||||
|
||||
// Open and configure L2 TAP File descriptor
|
||||
if ((eth_tap_fd = init_l2tap_fd(O_NONBLOCK, ETH_TYPE_FILTER_NOBLOCK)) == INVALID_FD) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 5;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(eth_tap_fd, &rfds);
|
||||
|
||||
int ret_sel = select(eth_tap_fd + 1, &rfds, NULL, NULL, &tv);
|
||||
if (ret_sel > 0) {
|
||||
ssize_t len = read(eth_tap_fd, rx_buffer, sizeof(rx_buffer));
|
||||
if (len > 0) {
|
||||
test_vfs_eth_tap_msg_t *recv_msg = (test_vfs_eth_tap_msg_t *)rx_buffer;
|
||||
ESP_LOGI(TAG, "fd %d received %d bytes from %.2x:%.2x:%.2x:%.2x:%.2x:%.2x", eth_tap_fd,
|
||||
len, recv_msg->header.src.addr[0], recv_msg->header.src.addr[1], recv_msg->header.src.addr[2],
|
||||
recv_msg->header.src.addr[3], recv_msg->header.src.addr[4], recv_msg->header.src.addr[5]);
|
||||
// Construct echo frame
|
||||
test_vfs_eth_tap_msg_t echo_msg;
|
||||
create_echo_frame(recv_msg, &echo_msg, len);
|
||||
|
||||
// Send the echo message
|
||||
ssize_t ret = write(eth_tap_fd, &echo_msg, len);
|
||||
if (ret == -1) {
|
||||
ESP_LOGE(TAG, "L2 TAP fd %d write error: errno: %d\n", eth_tap_fd, errno);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "L2 TAP fd %d read error: errno %d", eth_tap_fd, errno);
|
||||
break;
|
||||
}
|
||||
} else if (ret_sel == 0) {
|
||||
ESP_LOGD(TAG, "L2 TAP select timeout");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "L2 TAP select error: errno %d", errno);
|
||||
break;
|
||||
}
|
||||
}
|
||||
close(eth_tap_fd);
|
||||
error:
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/** Demonstrates of how to construct Ethernet frame for transmit via L2 TAP */
|
||||
static void hello_tx_l2tap_task(void *pvParameters)
|
||||
{
|
||||
uint16_t eth_type_filter = ETH_TYPE_FILTER_TX;
|
||||
int eth_tap_fd;
|
||||
|
||||
// Open and configure L2 TAP File descriptor
|
||||
if ((eth_tap_fd = init_l2tap_fd(0, eth_type_filter)) == INVALID_FD) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Construct "Hello" frame
|
||||
test_vfs_eth_tap_msg_t hello_msg = {
|
||||
.header = {
|
||||
.src.addr = {0},
|
||||
.dest.addr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // broadcast address
|
||||
.type = htons(eth_type_filter) // convert to big endian (network) byte order
|
||||
},
|
||||
.payload = "ESP32 hello to everybody!"
|
||||
};
|
||||
esp_eth_handle_t eth_hndl = get_example_eth_handle();
|
||||
esp_eth_ioctl(eth_hndl, ETH_CMD_G_MAC_ADDR, hello_msg.header.src.addr);
|
||||
|
||||
while (1) {
|
||||
// Send the Hello frame
|
||||
ssize_t ret = write(eth_tap_fd, &hello_msg, ETH_HEADER_LEN + strlen(hello_msg.payload));
|
||||
if (ret == -1) {
|
||||
ESP_LOGE(TAG, "L2 TAP fd %d write error: errno: %d\n", eth_tap_fd, errno);
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||||
}
|
||||
close(eth_tap_fd);
|
||||
error:
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialize L2 TAP VFS interface
|
||||
ESP_ERROR_CHECK(esp_vfs_l2tap_intf_register(NULL));
|
||||
|
||||
// Initialize NVS Flash
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
// Initialize TCP/IP network interface (should be called only once in application)
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
esp_eth_handle_t eth_hndl = get_example_eth_handle();
|
||||
uint8_t mac_addr[ETH_ADDR_LEN];
|
||||
esp_eth_ioctl(eth_hndl, 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]);
|
||||
|
||||
// Echo received message back to sender (blocks indefinitely)
|
||||
xTaskCreate(echo_l2tap_task, "echo", 4096, NULL, 6, NULL);
|
||||
// Echo received message back to sender (blocks with timeout)
|
||||
xTaskCreate(nonblock_l2tap_echo_task, "echo_no-block", 4096, NULL, 5, NULL);
|
||||
// Periodically broadcast "Hello message"
|
||||
xTaskCreate(hello_tx_l2tap_task, "hello_tx", 4096, NULL, 4, NULL);
|
||||
}
|
122
examples/protocols/l2tap/pytest_example_l2tap_echo.py
Normal file
122
examples/protocols/l2tap/pytest_example_l2tap_echo.py
Normal file
@ -0,0 +1,122 @@
|
||||
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
from typing import Iterator
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from scapy.all import Ether, raw
|
||||
|
||||
ETH_TYPE_1 = 0x2220
|
||||
ETH_TYPE_2 = 0x2221
|
||||
ETH_TYPE_3 = 0x2223
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def configure_eth_if(eth_type: int, target_if: str='') -> Iterator[socket.socket]:
|
||||
if target_if == '':
|
||||
# try to determine which interface to use
|
||||
netifs = os.listdir('/sys/class/net/')
|
||||
logging.info('detected interfaces: %s', str(netifs))
|
||||
for netif in netifs:
|
||||
if netif.find('eth') == 0 or netif.find('enx') == 0 or netif.find('enp') == 0 or netif.find('eno') == 0:
|
||||
target_if = netif
|
||||
break
|
||||
if target_if == '':
|
||||
raise Exception('no network interface found')
|
||||
logging.info('Use %s for testing', target_if)
|
||||
|
||||
so = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(eth_type))
|
||||
so.bind((target_if, 0))
|
||||
|
||||
try:
|
||||
yield so
|
||||
finally:
|
||||
so.close()
|
||||
|
||||
|
||||
def send_recv_eth_frame(payload_str: str, eth_type: int, dest_mac: str, eth_if: str='') -> str:
|
||||
with configure_eth_if(eth_type, eth_if) as so:
|
||||
so.settimeout(10)
|
||||
eth_frame = Ether(dst=dest_mac, src=so.getsockname()[4], type=eth_type) / raw(payload_str.encode())
|
||||
try:
|
||||
so.send(raw(eth_frame))
|
||||
logging.info('Sent %d bytes to %s', len(eth_frame), dest_mac)
|
||||
logging.info('Sent msg: "%s"', payload_str)
|
||||
|
||||
eth_frame_repl = Ether(so.recv(128))
|
||||
if eth_frame_repl.type == eth_type:
|
||||
logging.info('Received %d bytes echoed from %s', len(eth_frame_repl), eth_frame_repl.src)
|
||||
logging.info('Echoed msg: "%s"', eth_frame_repl.load.decode())
|
||||
except Exception as e:
|
||||
raise e
|
||||
# return echoed message and remove possible null characters which might have been appended since
|
||||
# minimal size of Ethernet frame to be transmitted physical layer is 60B (not including CRC)
|
||||
return str(eth_frame_repl.load.decode().rstrip('\x00'))
|
||||
|
||||
|
||||
def recv_eth_frame(eth_type: int, eth_if: str='') -> str:
|
||||
with configure_eth_if(eth_type, eth_if) as so:
|
||||
so.settimeout(10)
|
||||
try:
|
||||
eth_frame = Ether(so.recv(128))
|
||||
if eth_frame.type == eth_type:
|
||||
logging.info('Received %d bytes from %s', len(eth_frame), eth_frame.src)
|
||||
logging.info('Received msg: "%s"', eth_frame.load.decode())
|
||||
except Exception as e:
|
||||
raise e
|
||||
return str(eth_frame.load.decode().rstrip('\x00'))
|
||||
|
||||
|
||||
def actual_test(dut: Dut) -> None:
|
||||
# Get DUT's MAC address
|
||||
res = dut.expect(
|
||||
r'([\s\S]*)'
|
||||
r'Ethernet HW Addr ([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})'
|
||||
)
|
||||
dut_mac = res.group(2)
|
||||
|
||||
# Receive "ESP32 Hello frame"
|
||||
recv_eth_frame(ETH_TYPE_3)
|
||||
|
||||
# Sent a message and receive its echo
|
||||
message = 'ESP32 test message with EthType ' + hex(ETH_TYPE_1)
|
||||
echoed = send_recv_eth_frame(message, ETH_TYPE_1, dut_mac)
|
||||
if echoed == message:
|
||||
logging.info('PASS')
|
||||
else:
|
||||
raise Exception('Echoed message does not match!')
|
||||
message = 'ESP32 test message with EthType ' + hex(ETH_TYPE_2)
|
||||
echoed = send_recv_eth_frame(message, ETH_TYPE_2, dut_mac)
|
||||
if echoed == message:
|
||||
logging.info('PASS')
|
||||
else:
|
||||
raise Exception('Echoed message does not match!')
|
||||
|
||||
|
||||
@pytest.mark.esp32 # internally tested using ESP32 with IP101 but may support all targets with SPI Ethernet
|
||||
@pytest.mark.ip101
|
||||
@pytest.mark.flaky(reruns=3, reruns_delay=5)
|
||||
def test_esp_netif_l2tap_example(dut: Dut) -> None:
|
||||
actual_test(dut)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
|
||||
message_1 = 'ESP32 test message with EthType ' + hex(ETH_TYPE_1)
|
||||
message_2 = 'ESP32 test message with EthType ' + hex(ETH_TYPE_2)
|
||||
# Usage: pytest_example_l2tap_echo.py [<dest_mac_address>] [<host_eth_interface>]
|
||||
if sys.argv[2:]: # if two arguments provided:
|
||||
send_recv_eth_frame(message_1, ETH_TYPE_1, sys.argv[1], sys.argv[2])
|
||||
send_recv_eth_frame(message_2, ETH_TYPE_2, sys.argv[1], sys.argv[2])
|
||||
elif sys.argv[1:]: # if one argument provided:
|
||||
send_recv_eth_frame(message_1, ETH_TYPE_1, sys.argv[1])
|
||||
send_recv_eth_frame(message_2, ETH_TYPE_2, sys.argv[1])
|
||||
else: # if no argument provided:
|
||||
send_recv_eth_frame(message_1, ETH_TYPE_1, 'ff:ff:ff:ff:ff:ff')
|
||||
send_recv_eth_frame(message_2, ETH_TYPE_2, 'ff:ff:ff:ff:ff:ff')
|
6
examples/protocols/l2tap/sdkconfig.defaults
Normal file
6
examples/protocols/l2tap/sdkconfig.defaults
Normal file
@ -0,0 +1,6 @@
|
||||
# L2 TAP currently supports Ethernet only
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=n
|
||||
|
||||
# Enable L2 TAP
|
||||
CONFIG_ESP_NETIF_L2_TAP=y
|
Loading…
Reference in New Issue
Block a user