Initial commit: ESP32 iperf implementation
This commit is contained in:
commit
b3aa5ad76d
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Build artifacts
|
||||||
|
build/
|
||||||
|
sdkconfig
|
||||||
|
sdkconfig.old
|
||||||
|
*.pyc
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.bin
|
||||||
|
*.elf
|
||||||
|
*.map
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
dependencies/
|
||||||
|
managed_components/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
.Python
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# 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.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(esp32-iperf)
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
# ESP32 iperf
|
||||||
|
|
||||||
|
Network performance testing tool for ESP32 based on iperf2.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- TCP and UDP client/server modes
|
||||||
|
- Bandwidth measurement
|
||||||
|
- Packet loss detection (UDP)
|
||||||
|
- Console-based control interface
|
||||||
|
- WiFi station mode support
|
||||||
|
|
||||||
|
## Hardware Requirements
|
||||||
|
|
||||||
|
- ESP32 development board
|
||||||
|
- WiFi network
|
||||||
|
|
||||||
|
## Software Requirements
|
||||||
|
|
||||||
|
- ESP-IDF v5.0 or later
|
||||||
|
- iperf2 or iperf3 for testing with PC
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
1. Set up ESP-IDF environment:
|
||||||
|
```bash
|
||||||
|
. $HOME/Code/esp32/esp-idf/export.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Configure WiFi credentials:
|
||||||
|
```bash
|
||||||
|
idf.py menuconfig
|
||||||
|
```
|
||||||
|
Navigate to "ESP32 iperf Configuration" and set your SSID and password.
|
||||||
|
|
||||||
|
3. Build the project:
|
||||||
|
```bash
|
||||||
|
idf.py build
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Flash to ESP32:
|
||||||
|
```bash
|
||||||
|
idf.py -p /dev/ttyUSB0 flash monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### TCP Server Mode
|
||||||
|
On ESP32:
|
||||||
|
```
|
||||||
|
iperf> iperf -s
|
||||||
|
```
|
||||||
|
|
||||||
|
On PC:
|
||||||
|
```bash
|
||||||
|
iperf -c <ESP32_IP>
|
||||||
|
```
|
||||||
|
|
||||||
|
### TCP Client Mode
|
||||||
|
On PC:
|
||||||
|
```bash
|
||||||
|
iperf -s
|
||||||
|
```
|
||||||
|
|
||||||
|
On ESP32:
|
||||||
|
```
|
||||||
|
iperf> iperf -c <PC_IP>
|
||||||
|
```
|
||||||
|
|
||||||
|
### UDP Mode
|
||||||
|
Add `-u` flag for UDP testing:
|
||||||
|
```
|
||||||
|
iperf> iperf -s -u
|
||||||
|
iperf> iperf -c <IP> -u
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
- `-s` : Run as server
|
||||||
|
- `-c <ip>` : Run as client, connecting to server IP
|
||||||
|
- `-u` : Use UDP instead of TCP
|
||||||
|
- `-p <port>` : Port number (default: 5001)
|
||||||
|
- `-t <time>` : Test duration in seconds (default: 30)
|
||||||
|
- `-b <bw>` : Bandwidth limit for UDP in Mbps
|
||||||
|
- `-a` : Abort running test
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
```bash
|
||||||
|
# TCP client test for 10 seconds
|
||||||
|
iperf -c 192.168.1.100 -t 10
|
||||||
|
|
||||||
|
# UDP server on port 5002
|
||||||
|
iperf -s -u -p 5002
|
||||||
|
|
||||||
|
# Stop running test
|
||||||
|
iperf -a
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
esp32-iperf/
|
||||||
|
├── CMakeLists.txt
|
||||||
|
├── main/
|
||||||
|
│ ├── CMakeLists.txt
|
||||||
|
│ ├── Kconfig.projbuild
|
||||||
|
│ ├── main.c # WiFi initialization and console
|
||||||
|
│ ├── iperf.c # iperf implementation
|
||||||
|
│ └── iperf.h # iperf header
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Bob - Creator of iperf2
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Open source
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
idf_component_register(SRCS "main.c" "iperf.c"
|
||||||
|
INCLUDE_DIRS ".")
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
menu "ESP32 iperf Configuration"
|
||||||
|
|
||||||
|
config WIFI_SSID
|
||||||
|
string "WiFi SSID"
|
||||||
|
default "myssid"
|
||||||
|
help
|
||||||
|
SSID (network name) to connect to.
|
||||||
|
|
||||||
|
config WIFI_PASSWORD
|
||||||
|
string "WiFi Password"
|
||||||
|
default "mypassword"
|
||||||
|
help
|
||||||
|
WiFi password (WPA or WPA2).
|
||||||
|
|
||||||
|
config WIFI_MAXIMUM_RETRY
|
||||||
|
int "Maximum retry"
|
||||||
|
default 5
|
||||||
|
help
|
||||||
|
Set the Maximum retry to prevent station reconnecting to the AP unlimited when the AP doesn't exist.
|
||||||
|
|
||||||
|
endmenu
|
||||||
|
|
@ -0,0 +1,339 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/event_groups.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "iperf.h"
|
||||||
|
|
||||||
|
static const char *TAG = "iperf";
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
iperf_cfg_t cfg;
|
||||||
|
bool finish;
|
||||||
|
uint32_t total_len;
|
||||||
|
uint32_t buffer_len;
|
||||||
|
uint8_t *buffer;
|
||||||
|
uint32_t sockfd;
|
||||||
|
} iperf_ctrl_t;
|
||||||
|
|
||||||
|
static iperf_ctrl_t s_iperf_ctrl = {0};
|
||||||
|
static TaskHandle_t s_iperf_task_handle = NULL;
|
||||||
|
|
||||||
|
static void socket_send(int sockfd, const uint8_t *buffer, int len)
|
||||||
|
{
|
||||||
|
int actual_send = 0;
|
||||||
|
while (actual_send < len) {
|
||||||
|
int send_len = send(sockfd, buffer + actual_send, len - actual_send, 0);
|
||||||
|
if (send_len < 0) {
|
||||||
|
ESP_LOGE(TAG, "send failed: errno %d", errno);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
actual_send += send_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int socket_recv(int sockfd, uint8_t *buffer, int len, TickType_t timeout_ticks)
|
||||||
|
{
|
||||||
|
struct timeval timeout;
|
||||||
|
timeout.tv_sec = timeout_ticks / configTICK_RATE_HZ;
|
||||||
|
timeout.tv_usec = (timeout_ticks % configTICK_RATE_HZ) * (1000000 / configTICK_RATE_HZ);
|
||||||
|
|
||||||
|
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
|
||||||
|
ESP_LOGE(TAG, "setsockopt failed: errno %d", errno);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return recv(sockfd, buffer, len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t iperf_start_tcp_server(iperf_ctrl_t *ctrl)
|
||||||
|
{
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
int listen_sock;
|
||||||
|
int opt = 1;
|
||||||
|
|
||||||
|
listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
if (listen_sock < 0) {
|
||||||
|
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
|
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(ctrl->cfg.sport);
|
||||||
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
|
||||||
|
if (bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
||||||
|
ESP_LOGE(TAG, "Socket bind failed: errno %d", errno);
|
||||||
|
close(listen_sock);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(listen_sock, 5) != 0) {
|
||||||
|
ESP_LOGE(TAG, "Socket listen failed: errno %d", errno);
|
||||||
|
close(listen_sock);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "TCP server listening on port %d", ctrl->cfg.sport);
|
||||||
|
|
||||||
|
while (!ctrl->finish) {
|
||||||
|
struct sockaddr_in client_addr;
|
||||||
|
socklen_t addr_len = sizeof(client_addr);
|
||||||
|
|
||||||
|
int client_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &addr_len);
|
||||||
|
if (client_sock < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ESP_LOGE(TAG, "Accept failed: errno %d", errno);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Client connected from %s", inet_ntoa(client_addr.sin_addr));
|
||||||
|
|
||||||
|
uint64_t total_len = 0;
|
||||||
|
int recv_len;
|
||||||
|
|
||||||
|
while (!ctrl->finish) {
|
||||||
|
recv_len = socket_recv(client_sock, ctrl->buffer, ctrl->buffer_len,
|
||||||
|
pdMS_TO_TICKS(IPERF_SOCKET_RX_TIMEOUT * 1000));
|
||||||
|
if (recv_len <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
total_len += recv_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "TCP server received: %llu bytes", total_len);
|
||||||
|
close(client_sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(listen_sock);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t iperf_start_tcp_client(iperf_ctrl_t *ctrl)
|
||||||
|
{
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
int sockfd;
|
||||||
|
|
||||||
|
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
if (sockfd < 0) {
|
||||||
|
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(ctrl->cfg.dport);
|
||||||
|
addr.sin_addr.s_addr = htonl(ctrl->cfg.dip);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Connecting to TCP server...");
|
||||||
|
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
||||||
|
ESP_LOGE(TAG, "Socket connect failed: errno %d", errno);
|
||||||
|
close(sockfd);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Connected to TCP server");
|
||||||
|
|
||||||
|
uint64_t total_len = 0;
|
||||||
|
uint32_t start_time = xTaskGetTickCount();
|
||||||
|
uint32_t end_time = start_time + pdMS_TO_TICKS(ctrl->cfg.time * 1000);
|
||||||
|
|
||||||
|
while (!ctrl->finish && xTaskGetTickCount() < end_time) {
|
||||||
|
socket_send(sockfd, ctrl->buffer, ctrl->buffer_len);
|
||||||
|
total_len += ctrl->buffer_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t actual_time = (xTaskGetTickCount() - start_time) / configTICK_RATE_HZ;
|
||||||
|
float bandwidth = (float)total_len * 8 / actual_time / 1000000; // Mbps
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "TCP client sent: %llu bytes in %u seconds (%.2f Mbps)",
|
||||||
|
total_len, actual_time, bandwidth);
|
||||||
|
|
||||||
|
close(sockfd);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t iperf_start_udp_server(iperf_ctrl_t *ctrl)
|
||||||
|
{
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
int sockfd;
|
||||||
|
int opt = 1;
|
||||||
|
|
||||||
|
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
if (sockfd < 0) {
|
||||||
|
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
|
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(ctrl->cfg.sport);
|
||||||
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
|
||||||
|
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
||||||
|
ESP_LOGE(TAG, "Socket bind failed: errno %d", errno);
|
||||||
|
close(sockfd);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "UDP server listening on port %d", ctrl->cfg.sport);
|
||||||
|
|
||||||
|
uint64_t total_len = 0;
|
||||||
|
uint32_t packet_count = 0;
|
||||||
|
int32_t last_id = -1;
|
||||||
|
uint32_t lost_packets = 0;
|
||||||
|
|
||||||
|
while (!ctrl->finish) {
|
||||||
|
int recv_len = socket_recv(sockfd, ctrl->buffer, ctrl->buffer_len,
|
||||||
|
pdMS_TO_TICKS(IPERF_SOCKET_RX_TIMEOUT * 1000));
|
||||||
|
if (recv_len <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
total_len += recv_len;
|
||||||
|
packet_count++;
|
||||||
|
|
||||||
|
// Check for lost packets using UDP header
|
||||||
|
if (recv_len >= sizeof(udp_datagram)) {
|
||||||
|
udp_datagram *header = (udp_datagram *)ctrl->buffer;
|
||||||
|
int32_t current_id = ntohl(header->id);
|
||||||
|
|
||||||
|
if (last_id >= 0 && current_id > last_id + 1) {
|
||||||
|
lost_packets += (current_id - last_id - 1);
|
||||||
|
}
|
||||||
|
last_id = current_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float loss_rate = packet_count > 0 ? (float)lost_packets * 100 / packet_count : 0;
|
||||||
|
ESP_LOGI(TAG, "UDP server received: %llu bytes, %u packets, %u lost (%.2f%%)",
|
||||||
|
total_len, packet_count, lost_packets, loss_rate);
|
||||||
|
|
||||||
|
close(sockfd);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t iperf_start_udp_client(iperf_ctrl_t *ctrl)
|
||||||
|
{
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
int sockfd;
|
||||||
|
|
||||||
|
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
if (sockfd < 0) {
|
||||||
|
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(ctrl->cfg.dport);
|
||||||
|
addr.sin_addr.s_addr = htonl(ctrl->cfg.dip);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Starting UDP client");
|
||||||
|
|
||||||
|
uint64_t total_len = 0;
|
||||||
|
uint32_t packet_count = 0;
|
||||||
|
uint32_t start_time = xTaskGetTickCount();
|
||||||
|
uint32_t end_time = start_time + pdMS_TO_TICKS(ctrl->cfg.time * 1000);
|
||||||
|
|
||||||
|
while (!ctrl->finish && xTaskGetTickCount() < end_time) {
|
||||||
|
udp_datagram *header = (udp_datagram *)ctrl->buffer;
|
||||||
|
header->id = htonl(packet_count);
|
||||||
|
|
||||||
|
int send_len = sendto(sockfd, ctrl->buffer, ctrl->buffer_len, 0,
|
||||||
|
(struct sockaddr *)&addr, sizeof(addr));
|
||||||
|
if (send_len > 0) {
|
||||||
|
total_len += send_len;
|
||||||
|
packet_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bandwidth limiting
|
||||||
|
if (ctrl->cfg.bw_lim > 0) {
|
||||||
|
vTaskDelay(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t actual_time = (xTaskGetTickCount() - start_time) / configTICK_RATE_HZ;
|
||||||
|
float bandwidth = actual_time > 0 ? (float)total_len * 8 / actual_time / 1000000 : 0;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "UDP client sent: %llu bytes, %u packets in %u seconds (%.2f Mbps)",
|
||||||
|
total_len, packet_count, actual_time, bandwidth);
|
||||||
|
|
||||||
|
close(sockfd);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iperf_task(void *arg)
|
||||||
|
{
|
||||||
|
iperf_ctrl_t *ctrl = (iperf_ctrl_t *)arg;
|
||||||
|
|
||||||
|
if (ctrl->cfg.flag & IPERF_FLAG_TCP) {
|
||||||
|
if (ctrl->cfg.flag & IPERF_FLAG_SERVER) {
|
||||||
|
iperf_start_tcp_server(ctrl);
|
||||||
|
} else {
|
||||||
|
iperf_start_tcp_client(ctrl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ctrl->cfg.flag & IPERF_FLAG_SERVER) {
|
||||||
|
iperf_start_udp_server(ctrl);
|
||||||
|
} else {
|
||||||
|
iperf_start_udp_client(ctrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctrl->buffer) {
|
||||||
|
free(ctrl->buffer);
|
||||||
|
ctrl->buffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "iperf task finished");
|
||||||
|
s_iperf_task_handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void iperf_start(iperf_cfg_t *cfg)
|
||||||
|
{
|
||||||
|
if (s_iperf_task_handle != NULL) {
|
||||||
|
ESP_LOGW(TAG, "iperf is already running");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&s_iperf_ctrl.cfg, cfg, sizeof(iperf_cfg_t));
|
||||||
|
s_iperf_ctrl.finish = false;
|
||||||
|
|
||||||
|
// Allocate buffer
|
||||||
|
if (cfg->flag & IPERF_FLAG_TCP) {
|
||||||
|
s_iperf_ctrl.buffer_len = cfg->flag & IPERF_FLAG_SERVER ?
|
||||||
|
IPERF_TCP_RX_LEN : IPERF_TCP_TX_LEN;
|
||||||
|
} else {
|
||||||
|
s_iperf_ctrl.buffer_len = cfg->flag & IPERF_FLAG_SERVER ?
|
||||||
|
IPERF_UDP_RX_LEN : IPERF_UDP_TX_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_iperf_ctrl.buffer = (uint8_t *)malloc(s_iperf_ctrl.buffer_len);
|
||||||
|
if (!s_iperf_ctrl.buffer) {
|
||||||
|
ESP_LOGE(TAG, "Failed to allocate buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(s_iperf_ctrl.buffer, 0, s_iperf_ctrl.buffer_len);
|
||||||
|
|
||||||
|
xTaskCreate(iperf_task, "iperf", 4096, &s_iperf_ctrl, IPERF_TRAFFIC_TASK_PRIORITY,
|
||||||
|
&s_iperf_task_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void iperf_stop(void)
|
||||||
|
{
|
||||||
|
if (s_iperf_task_handle != NULL) {
|
||||||
|
s_iperf_ctrl.finish = true;
|
||||||
|
ESP_LOGI(TAG, "Stopping iperf...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
#ifndef IPERF_H
|
||||||
|
#define IPERF_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define IPERF_FLAG_CLIENT (1 << 0)
|
||||||
|
#define IPERF_FLAG_SERVER (1 << 1)
|
||||||
|
#define IPERF_FLAG_TCP (1 << 2)
|
||||||
|
#define IPERF_FLAG_UDP (1 << 3)
|
||||||
|
|
||||||
|
#define IPERF_DEFAULT_PORT 5001
|
||||||
|
#define IPERF_DEFAULT_INTERVAL 3
|
||||||
|
#define IPERF_DEFAULT_TIME 30
|
||||||
|
#define IPERF_TRAFFIC_TASK_PRIORITY 4
|
||||||
|
#define IPERF_REPORT_TASK_PRIORITY 5
|
||||||
|
|
||||||
|
#define IPERF_SOCKET_RX_TIMEOUT 10
|
||||||
|
#define IPERF_SOCKET_ACCEPT_TIMEOUT 5
|
||||||
|
|
||||||
|
#define IPERF_UDP_TX_LEN (1470)
|
||||||
|
#define IPERF_UDP_RX_LEN (16 << 10)
|
||||||
|
#define IPERF_TCP_TX_LEN (16 << 10)
|
||||||
|
#define IPERF_TCP_RX_LEN (16 << 10)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t flag;
|
||||||
|
uint8_t type;
|
||||||
|
uint16_t dip;
|
||||||
|
uint16_t dport;
|
||||||
|
uint16_t sport;
|
||||||
|
uint32_t interval;
|
||||||
|
uint32_t time;
|
||||||
|
uint16_t bw_lim;
|
||||||
|
uint32_t buffer_len;
|
||||||
|
} iperf_cfg_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t total_len;
|
||||||
|
uint32_t buffer_len;
|
||||||
|
uint32_t sockfd;
|
||||||
|
uint32_t actual_len;
|
||||||
|
uint32_t packet_count;
|
||||||
|
uint8_t *buffer;
|
||||||
|
uint32_t udp_lost_counter;
|
||||||
|
uint32_t udp_packet_counter;
|
||||||
|
} iperf_traffic_t;
|
||||||
|
|
||||||
|
// UDP header for iperf
|
||||||
|
typedef struct {
|
||||||
|
int32_t id;
|
||||||
|
uint32_t tv_sec;
|
||||||
|
uint32_t tv_usec;
|
||||||
|
} udp_datagram;
|
||||||
|
|
||||||
|
void iperf_start(iperf_cfg_t *cfg);
|
||||||
|
void iperf_stop(void);
|
||||||
|
|
||||||
|
#endif // IPERF_H
|
||||||
|
|
@ -0,0 +1,241 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/event_groups.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
#include "esp_console.h"
|
||||||
|
#include "argtable3/argtable3.h"
|
||||||
|
#include "esp_netif.h"
|
||||||
|
#include "lwip/inet.h"
|
||||||
|
#include "iperf.h"
|
||||||
|
|
||||||
|
#define WIFI_SSID CONFIG_WIFI_SSID
|
||||||
|
#define WIFI_PASS CONFIG_WIFI_PASSWORD
|
||||||
|
#define WIFI_MAXIMUM_RETRY CONFIG_WIFI_MAXIMUM_RETRY
|
||||||
|
|
||||||
|
static const char *TAG = "main";
|
||||||
|
|
||||||
|
static EventGroupHandle_t s_wifi_event_group;
|
||||||
|
#define WIFI_CONNECTED_BIT BIT0
|
||||||
|
#define WIFI_FAIL_BIT BIT1
|
||||||
|
|
||||||
|
static int s_retry_num = 0;
|
||||||
|
|
||||||
|
static void event_handler(void* arg, esp_event_base_t event_base,
|
||||||
|
int32_t event_id, void* event_data)
|
||||||
|
{
|
||||||
|
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
||||||
|
esp_wifi_connect();
|
||||||
|
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||||
|
if (s_retry_num < WIFI_MAXIMUM_RETRY) {
|
||||||
|
esp_wifi_connect();
|
||||||
|
s_retry_num++;
|
||||||
|
ESP_LOGI(TAG, "retry to connect to the AP");
|
||||||
|
} else {
|
||||||
|
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG,"connect to the AP fail");
|
||||||
|
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||||
|
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
|
||||||
|
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
|
||||||
|
s_retry_num = 0;
|
||||||
|
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void wifi_init_sta(void)
|
||||||
|
{
|
||||||
|
s_wifi_event_group = xEventGroupCreate();
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_netif_init());
|
||||||
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||||
|
esp_netif_create_default_wifi_sta();
|
||||||
|
|
||||||
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||||
|
|
||||||
|
esp_event_handler_instance_t instance_any_id;
|
||||||
|
esp_event_handler_instance_t instance_got_ip;
|
||||||
|
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
|
||||||
|
ESP_EVENT_ANY_ID,
|
||||||
|
&event_handler,
|
||||||
|
NULL,
|
||||||
|
&instance_any_id));
|
||||||
|
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
|
||||||
|
IP_EVENT_STA_GOT_IP,
|
||||||
|
&event_handler,
|
||||||
|
NULL,
|
||||||
|
&instance_got_ip));
|
||||||
|
|
||||||
|
wifi_config_t wifi_config = {
|
||||||
|
.sta = {
|
||||||
|
.ssid = WIFI_SSID,
|
||||||
|
.password = WIFI_PASS,
|
||||||
|
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
|
||||||
|
ESP_ERROR_CHECK(esp_wifi_start() );
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "wifi_init_sta finished.");
|
||||||
|
|
||||||
|
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
|
||||||
|
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
|
||||||
|
pdFALSE,
|
||||||
|
pdFALSE,
|
||||||
|
portMAX_DELAY);
|
||||||
|
|
||||||
|
if (bits & WIFI_CONNECTED_BIT) {
|
||||||
|
ESP_LOGI(TAG, "connected to ap SSID:%s", WIFI_SSID);
|
||||||
|
} else if (bits & WIFI_FAIL_BIT) {
|
||||||
|
ESP_LOGI(TAG, "Failed to connect to SSID:%s", WIFI_SSID);
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "UNEXPECTED EVENT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Console command structure for iperf */
|
||||||
|
static struct {
|
||||||
|
struct arg_str *ip;
|
||||||
|
struct arg_lit *server;
|
||||||
|
struct arg_lit *udp;
|
||||||
|
struct arg_lit *client;
|
||||||
|
struct arg_int *port;
|
||||||
|
struct arg_int *interval;
|
||||||
|
struct arg_int *time;
|
||||||
|
struct arg_int *bw;
|
||||||
|
struct arg_lit *abort;
|
||||||
|
struct arg_end *end;
|
||||||
|
} iperf_args;
|
||||||
|
|
||||||
|
static int cmd_iperf(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int nerrors = arg_parse(argc, argv, (void **)&iperf_args);
|
||||||
|
if (nerrors != 0) {
|
||||||
|
arg_print_errors(stderr, iperf_args.end, argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if needs to abort */
|
||||||
|
if (iperf_args.abort->count != 0) {
|
||||||
|
iperf_stop();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
iperf_cfg_t cfg;
|
||||||
|
memset(&cfg, 0, sizeof(cfg));
|
||||||
|
|
||||||
|
/* Set protocol type */
|
||||||
|
cfg.flag |= (iperf_args.udp->count == 0) ? IPERF_FLAG_TCP : IPERF_FLAG_UDP;
|
||||||
|
|
||||||
|
/* Set server/client mode */
|
||||||
|
if (iperf_args.server->count != 0) {
|
||||||
|
cfg.flag |= IPERF_FLAG_SERVER;
|
||||||
|
cfg.sport = IPERF_DEFAULT_PORT;
|
||||||
|
if (iperf_args.port->count != 0) {
|
||||||
|
cfg.sport = iperf_args.port->ival[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cfg.flag |= IPERF_FLAG_CLIENT;
|
||||||
|
cfg.dport = IPERF_DEFAULT_PORT;
|
||||||
|
if (iperf_args.port->count != 0) {
|
||||||
|
cfg.dport = iperf_args.port->ival[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iperf_args.ip->count == 0) {
|
||||||
|
ESP_LOGE(TAG, "Please input destination IP address");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
cfg.dip = ipaddr_addr(iperf_args.ip->sval[0]);
|
||||||
|
if (cfg.dip == IPADDR_NONE) {
|
||||||
|
ESP_LOGE(TAG, "Invalid IP address: %s", iperf_args.ip->sval[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set test time */
|
||||||
|
cfg.time = IPERF_DEFAULT_TIME;
|
||||||
|
if (iperf_args.time->count != 0) {
|
||||||
|
cfg.time = iperf_args.time->ival[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set bandwidth limit for UDP */
|
||||||
|
cfg.bw_lim = 0;
|
||||||
|
if (iperf_args.bw->count != 0) {
|
||||||
|
cfg.bw_lim = iperf_args.bw->ival[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "mode=%s-%s sip=%u.%u.%u.%u:%u, dip=%u.%u.%u.%u:%u, interval=%u, time=%u",
|
||||||
|
(cfg.flag & IPERF_FLAG_TCP) ? "tcp" : "udp",
|
||||||
|
(cfg.flag & IPERF_FLAG_SERVER) ? "server" : "client",
|
||||||
|
cfg.sip & 0xFF, (cfg.sip >> 8) & 0xFF, (cfg.sip >> 16) & 0xFF, (cfg.sip >> 24) & 0xFF, cfg.sport,
|
||||||
|
cfg.dip & 0xFF, (cfg.dip >> 8) & 0xFF, (cfg.dip >> 16) & 0xFF, (cfg.dip >> 24) & 0xFF, cfg.dport,
|
||||||
|
cfg.interval, cfg.time);
|
||||||
|
|
||||||
|
iperf_start(&cfg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register_iperf(void)
|
||||||
|
{
|
||||||
|
iperf_args.ip = arg_str0("c", "client", "<ip>", "run in client mode, connecting to <host>");
|
||||||
|
iperf_args.server = arg_lit0("s", "server", "run in server mode");
|
||||||
|
iperf_args.udp = arg_lit0("u", "udp", "use UDP rather than TCP");
|
||||||
|
iperf_args.port = arg_int0("p", "port", "<port>", "server port to listen on/connect to");
|
||||||
|
iperf_args.interval = arg_int0("i", "interval", "<interval>", "seconds between periodic bandwidth reports");
|
||||||
|
iperf_args.time = arg_int0("t", "time", "<time>", "time in seconds to transmit for (default 30 secs)");
|
||||||
|
iperf_args.bw = arg_int0("b", "bandwidth", "<bandwidth>", "bandwidth to send at in Mbits/sec");
|
||||||
|
iperf_args.abort = arg_lit0("a", "abort", "abort running iperf");
|
||||||
|
iperf_args.end = arg_end(1);
|
||||||
|
|
||||||
|
const esp_console_cmd_t iperf_cmd = {
|
||||||
|
.command = "iperf",
|
||||||
|
.help = "iperf command",
|
||||||
|
.hint = NULL,
|
||||||
|
.func = &cmd_iperf,
|
||||||
|
.argtable = &iperf_args
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_console_cmd_register(&iperf_cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initialize_console(void)
|
||||||
|
{
|
||||||
|
esp_console_repl_t *repl = NULL;
|
||||||
|
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
||||||
|
repl_config.prompt = "iperf>";
|
||||||
|
|
||||||
|
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
||||||
|
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
|
||||||
|
|
||||||
|
register_iperf();
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "ESP32 iperf starting...");
|
||||||
|
|
||||||
|
// Initialize NVS
|
||||||
|
esp_err_t ret = nvs_flash_init();
|
||||||
|
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||||
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||||
|
ret = nvs_flash_init();
|
||||||
|
}
|
||||||
|
ESP_ERROR_CHECK(ret);
|
||||||
|
|
||||||
|
// Initialize WiFi
|
||||||
|
wifi_init_sta();
|
||||||
|
|
||||||
|
// Initialize console
|
||||||
|
initialize_console();
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Ready. Type 'iperf --help' for usage");
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue