add iperf files
This commit is contained in:
parent
ad602f3a2f
commit
9d402a02de
|
|
@ -0,0 +1,5 @@
|
||||||
|
idf_component_register(
|
||||||
|
SRCS "iperf.c"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES lwip esp_event
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,340 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.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: %" PRIu64 " 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: %" PRIu64 " bytes in %" PRIu32 " 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: %" PRIu64 " bytes, %" PRIu32 " packets, %" PRIu32 " 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: %" PRIu64 " bytes, %" PRIu32 " packets in %" PRIu32 " 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
|
||||||
Loading…
Reference in New Issue