diff --git a/components/iperf/CMakeLists.txt b/components/iperf/CMakeLists.txt new file mode 100644 index 0000000..00639ee --- /dev/null +++ b/components/iperf/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "iperf.c" + INCLUDE_DIRS "." + REQUIRES lwip esp_event +) diff --git a/components/iperf/iperf.c b/components/iperf/iperf.c new file mode 100644 index 0000000..dda0294 --- /dev/null +++ b/components/iperf/iperf.c @@ -0,0 +1,340 @@ +#include +#include +#include +#include +#include +#include +#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..."); + } +} diff --git a/components/iperf/iperf.h b/components/iperf/iperf.h new file mode 100644 index 0000000..589f37c --- /dev/null +++ b/components/iperf/iperf.h @@ -0,0 +1,59 @@ +#ifndef IPERF_H +#define IPERF_H + +#include +#include + +#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