From 976e98d6ff243ffbaf4083f26968a9d88ebd8a39 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 8 Sep 2023 11:36:35 +0200 Subject: [PATCH] feat(modem): Added iperf test for PPP netifs --- .github/workflows/modem__build-host-tests.yml | 2 +- .../test/target_iperf/CMakeLists.txt | 10 + .../esp_modem/test/target_iperf/README.md | 48 +++ .../test/target_iperf/main/CMakeLists.txt | 3 + .../test/target_iperf/main/NetworkDCE.cpp | 96 ++++++ .../test/target_iperf/main/cmd_pppclient.c | 289 ++++++++++++++++++ .../test/target_iperf/main/network_dce.h | 26 ++ .../test/target_iperf/main/pppd_iperf_main.c | 43 +++ .../test/target_iperf/pytest_ppp_iperf.py | 112 +++++++ .../test/target_iperf/sdkconfig.defaults | 3 + 10 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 components/esp_modem/test/target_iperf/CMakeLists.txt create mode 100644 components/esp_modem/test/target_iperf/README.md create mode 100644 components/esp_modem/test/target_iperf/main/CMakeLists.txt create mode 100644 components/esp_modem/test/target_iperf/main/NetworkDCE.cpp create mode 100644 components/esp_modem/test/target_iperf/main/cmd_pppclient.c create mode 100644 components/esp_modem/test/target_iperf/main/network_dce.h create mode 100644 components/esp_modem/test/target_iperf/main/pppd_iperf_main.c create mode 100644 components/esp_modem/test/target_iperf/pytest_ppp_iperf.py create mode 100644 components/esp_modem/test/target_iperf/sdkconfig.defaults diff --git a/.github/workflows/modem__build-host-tests.yml b/.github/workflows/modem__build-host-tests.yml index 7300921441..21204c6532 100644 --- a/.github/workflows/modem__build-host-tests.yml +++ b/.github/workflows/modem__build-host-tests.yml @@ -52,7 +52,7 @@ jobs: strategy: matrix: idf_ver: ["release-v5.0", "release-v5.1", "latest"] - test: ["target", "target_ota"] + test: ["target", "target_ota", "target_iperf"] runs-on: ubuntu-20.04 container: espressif/idf:${{ matrix.idf_ver }} diff --git a/components/esp_modem/test/target_iperf/CMakeLists.txt b/components/esp_modem/test/target_iperf/CMakeLists.txt new file mode 100644 index 0000000000..ab61aaaa6a --- /dev/null +++ b/components/esp_modem/test/target_iperf/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following five 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) + +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components + $ENV{IDF_PATH}/examples/common_components/iperf + "../..") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(pppd_test) diff --git a/components/esp_modem/test/target_iperf/README.md b/components/esp_modem/test/target_iperf/README.md new file mode 100644 index 0000000000..fd60017eaa --- /dev/null +++ b/components/esp_modem/test/target_iperf/README.md @@ -0,0 +1,48 @@ +# Target test for measuring TCP/UDP network performance + +## Overview + +The aim of this test is to run `iperf` application to measure network throughput of the esp_modem library. + +### Configure PPP server + +This test uses a network DCE device, which only needs a PPP server. To run the PPP server, use this command: +``` +sudo pppd /dev/ttyUSB1 115200 192.168.11.1:192.168.11.2 ms-dns 8.8.8.8 modem local noauth debug nocrtscts nodetach +ipv6 +``` + +### Running using pytest + +For checking the performance, you only need to execute the pytest, which will measure the network throughput automatically and report the resultant values. For running the pytest, you need to: +* install IDF pytest packages by running: `./install.sh --enable-pytest` +* add IDF internal packages to python path: `export PYTHONPATH=${DIF_PATH}/tools/ci/python_packages/` +* run the pytest as **superuser** + +It's useful to note that when running the test multiple times, you can use `pytest --skip-autoflash y` so the pytest wouldn't have to always reprogram the DUT. + +### Performance summary + +Here's an example of the resultant summary logged by the pytest +``` +2023-11-29 18:28:25 INFO [Performance][tcp_tx_throughput]: 0.75 Mbps +2023-11-29 18:28:25 INFO [Performance][tcp_rx_throughput]: 0.70 Mbps +2023-11-29 18:28:25 INFO [Performance][udp_tx_throughput]: 0.73 Mbps +2023-11-29 18:28:25 INFO [Performance][udp_rx_throughput]: 0.70 Mbps +``` + +### Running the iperf manually + +Execute `idf.py flash monitor` in one terminal and after connecting to the PPP server (after getting an IP address), you can use standard `iperf` commands. +In another terminal, you need to execute the iperf counterpart. +For example running for checking UDP performance, and running server on ESP32, please run: +* iperf -u -s -i 3 (in ESP32 terminal) +* iperf -u -c SERVER_IP -t 60 -i 3 (on the host side) + +Note that command `pppd info` will print actual IP addresses: +``` +iperf> pppd info +ppp: + IP: 192.168.11.2 + MASK: 255.255.255.255 + GW: 192.168.11.3 +``` diff --git a/components/esp_modem/test/target_iperf/main/CMakeLists.txt b/components/esp_modem/test/target_iperf/main/CMakeLists.txt new file mode 100644 index 0000000000..0219da876e --- /dev/null +++ b/components/esp_modem/test/target_iperf/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "NetworkDCE.cpp" + "cmd_pppclient.c" + "pppd_iperf_main.c") diff --git a/components/esp_modem/test/target_iperf/main/NetworkDCE.cpp b/components/esp_modem/test/target_iperf/main/NetworkDCE.cpp new file mode 100644 index 0000000000..470ce7f13d --- /dev/null +++ b/components/esp_modem/test/target_iperf/main/NetworkDCE.cpp @@ -0,0 +1,96 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "cxx_include/esp_modem_dte.hpp" +#include "esp_modem_config.h" +#include "cxx_include/esp_modem_api.hpp" +#include "cxx_include/esp_modem_dce_factory.hpp" +#include +#include + +using namespace esp_modem; +using namespace esp_modem::dce_factory; + +class NetModule; +typedef DCE_T NetDCE; + +/** + * @brief Custom factory which can build and create a DCE using a custom module + */ +class NetDCE_Factory: public Factory { +public: + template + static DCE_T *create(const config *cfg, Args &&... args) + { + return build_generic_DCE(cfg, std::forward(args)...); + } +}; + +/** + * @brief This is a null-module, doesn't define any AT commands, just passes everything to pppd + */ +class NetModule: public ModuleIf { +public: + explicit NetModule(std::shared_ptr dte, const esp_modem_dce_config *cfg): + dte(std::move(dte)) {} + + bool setup_data_mode() override + { + return true; + } + + bool set_mode(modem_mode mode) override + { + return true; + } + + static esp_err_t init(esp_netif_t *netif) + { + // configure + esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG(); + dte_config.uart_config.baud_rate = 921600; // check also 460800 + esp_modem_dce_config dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(""); + + // create DTE and minimal network DCE + auto uart_dte = create_uart_dte(&dte_config); + dce = NetDCE_Factory::create(&dce_config, uart_dte, netif); + return dce == nullptr ? ESP_FAIL : ESP_OK; + } + + static void deinit() + { + delete dce; + } + static void start() + { + dce->set_data(); + } + static void stop() + { + dce->exit_data(); + } + +private: + static NetDCE *dce; + std::shared_ptr dte; +}; + +NetDCE *NetModule::dce = nullptr; + +extern "C" esp_err_t modem_init_network(esp_netif_t *netif) +{ + return NetModule::init(netif); +} + +extern "C" esp_err_t modem_start_network() +{ + NetModule::start(); + return ESP_OK; +} + +extern "C" void modem_stop_network() +{ + NetModule::stop(); +} diff --git a/components/esp_modem/test/target_iperf/main/cmd_pppclient.c b/components/esp_modem/test/target_iperf/main/cmd_pppclient.c new file mode 100644 index 0000000000..0bb6a89699 --- /dev/null +++ b/components/esp_modem/test/target_iperf/main/cmd_pppclient.c @@ -0,0 +1,289 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "sys/socket.h" // for INADDR_ANY +#include "esp_netif.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_netif_ppp.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" + +#include "esp_console.h" +#include "esp_event.h" +#include "esp_bit_defs.h" +#include "argtable3/argtable3.h" +#include "iperf.h" +#include "sdkconfig.h" +#include "network_dce.h" + +static const char *TAG = "pppd_test"; +static EventGroupHandle_t event_group = NULL; +static esp_netif_t *s_ppp_netif; +static const int GOTIP_BIT = BIT0; + +static void on_modem_event(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + if (event_base == IP_EVENT) { + ESP_LOGD(TAG, "IP event! %" PRIu32, event_id); + if (event_id == IP_EVENT_PPP_GOT_IP) { + esp_netif_dns_info_t dns_info; + + + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + esp_netif_t *netif = event->esp_netif; + + ESP_LOGI(TAG, "Modem Connect to PPP Server"); + ESP_LOGI(TAG, "~~~~~~~~~~~~~~"); + ESP_LOGI(TAG, "IP : " IPSTR, IP2STR(&event->ip_info.ip)); + ESP_LOGI(TAG, "Netmask : " IPSTR, IP2STR(&event->ip_info.netmask)); + ESP_LOGI(TAG, "Gateway : " IPSTR, IP2STR(&event->ip_info.gw)); + esp_netif_get_dns_info(netif, ESP_NETIF_DNS_MAIN, &dns_info); + ESP_LOGI(TAG, "Name Server1: " IPSTR, IP2STR(&dns_info.ip.u_addr.ip4)); + esp_netif_get_dns_info(netif, ESP_NETIF_DNS_BACKUP, &dns_info); + ESP_LOGI(TAG, "Name Server2: " IPSTR, IP2STR(&dns_info.ip.u_addr.ip4)); + ESP_LOGI(TAG, "~~~~~~~~~~~~~~"); + + ESP_LOGI(TAG, "GOT ip event!!!"); + xEventGroupSetBits(event_group, GOTIP_BIT); + } else if (event_id == IP_EVENT_PPP_LOST_IP) { + ESP_LOGI(TAG, "Modem Disconnect from PPP Server"); + } else if (event_id == IP_EVENT_GOT_IP6) { + ESP_LOGI(TAG, "GOT IPv6 event!"); + ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data; + ESP_LOGI(TAG, "Got IPv6 address " IPV6STR, IPV62STR(event->ip6_info.ip)); + } + } +} + +static void on_ppp_changed(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + ESP_LOGI(TAG, "PPP state changed event %" PRId32, event_id); + if (event_id == NETIF_PPP_ERRORUSER) { + /* User interrupted event from esp-netif */ + esp_netif_t *netif = (esp_netif_t *)event_data; + ESP_LOGI(TAG, "User interrupted event from netif:%p", netif); + xEventGroupSetBits(event_group, 2); + } +} + +/* "pppd" command */ +static struct { + struct arg_str *control; + struct arg_end *end; +} pppd_control_args; + +static int pppd_cmd_control(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&pppd_control_args); + if (nerrors != 0) { + arg_print_errors(stderr, pppd_control_args.end, argv[0]); + return 1; + } + + if (!strncmp(pppd_control_args.control->sval[0], "info", 4)) { + esp_netif_ip_info_t ip; + printf("%s:\n", esp_netif_get_desc(s_ppp_netif)); + esp_netif_get_ip_info(s_ppp_netif, &ip); + printf(" IP: " IPSTR "\r\n", IP2STR(&ip.ip)); + printf(" MASK: " IPSTR "\r\n", IP2STR(&ip.netmask)); + printf(" GW: " IPSTR "\r\n", IP2STR(&ip.gw)); + } + return 0; +} + +/* "iperf" command */ + +static struct { + struct arg_str *ip; + struct arg_lit *server; + struct arg_lit *udp; + struct arg_lit *version; + struct arg_int *port; + struct arg_int *length; + struct arg_int *interval; + struct arg_int *time; + struct arg_int *bw_limit; + struct arg_lit *abort; + struct arg_end *end; +} iperf_args; + +static int ppp_cmd_iperf(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&iperf_args); + iperf_cfg_t cfg; + + if (nerrors != 0) { + arg_print_errors(stderr, iperf_args.end, argv[0]); + return 0; + } + + memset(&cfg, 0, sizeof(cfg)); + + // ethernet iperf only support IPV4 address + cfg.type = IPERF_IP_TYPE_IPV4; + + /* iperf -a */ + if (iperf_args.abort->count != 0) { + iperf_stop(); + return 0; + } + + if (((iperf_args.ip->count == 0) && (iperf_args.server->count == 0)) || + ((iperf_args.ip->count != 0) && (iperf_args.server->count != 0))) { + ESP_LOGE(__func__, "Wrong mode! ESP32 should run in client or server mode"); + return 0; + } + + /* iperf -s */ + if (iperf_args.ip->count == 0) { + cfg.flag |= IPERF_FLAG_SERVER; + } + /* iperf -c SERVER_ADDRESS */ + else { + cfg.destination_ip4 = esp_ip4addr_aton(iperf_args.ip->sval[0]); + cfg.flag |= IPERF_FLAG_CLIENT; + } + + if (iperf_args.length->count == 0) { + cfg.len_send_buf = 0; + } else { + cfg.len_send_buf = iperf_args.length->ival[0]; + } + + /* wait for ip, could blocked here */ + xEventGroupWaitBits(event_group, GOTIP_BIT, pdFALSE, pdTRUE, portMAX_DELAY); + + cfg.source_ip4 = INADDR_ANY; + + /* iperf -u */ + if (iperf_args.udp->count == 0) { + cfg.flag |= IPERF_FLAG_TCP; + } else { + cfg.flag |= IPERF_FLAG_UDP; + } + + /* iperf -p */ + if (iperf_args.port->count == 0) { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + if (cfg.flag & IPERF_FLAG_SERVER) { + cfg.sport = iperf_args.port->ival[0]; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = iperf_args.port->ival[0]; + } + } + + /* iperf -i */ + if (iperf_args.interval->count == 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } else { + cfg.interval = iperf_args.interval->ival[0]; + if (cfg.interval <= 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } + } + + /* iperf -t */ + if (iperf_args.time->count == 0) { + cfg.time = IPERF_DEFAULT_TIME; + } else { + cfg.time = iperf_args.time->ival[0]; + if (cfg.time <= cfg.interval) { + cfg.time = cfg.interval; + } + } + + /* iperf -b */ + if (iperf_args.bw_limit->count == 0) { + cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT; + } else { + cfg.bw_lim = iperf_args.bw_limit->ival[0]; + if (cfg.bw_lim <= 0) { + cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT; + } + } + + printf("mode=%s-%s sip=" IPSTR ":%" PRIu16 ", dip=%" PRIu32 ".%" PRIu32 ".%" PRIu32 ".%" PRIu32 ":%" PRIu16 ", interval=%" PRIu32 ", time=%" PRIu32 "\r\n", + cfg.flag & IPERF_FLAG_TCP ? "tcp" : "udp", + cfg.flag & IPERF_FLAG_SERVER ? "server" : "client", + (uint16_t) cfg.source_ip4 & 0xFF, + (uint16_t) (cfg.source_ip4 >> 8) & 0xFF, + (uint16_t) (cfg.source_ip4 >> 16) & 0xFF, + (uint16_t) (cfg.source_ip4 >> 24) & 0xFF, + cfg.sport, + cfg.destination_ip4 & 0xFF, (cfg.destination_ip4 >> 8) & 0xFF, + (cfg.destination_ip4 >> 16) & 0xFF, (cfg.destination_ip4 >> 24) & 0xFF, cfg.dport, + cfg.interval, cfg.time); + + iperf_start(&cfg); + return 0; +} + +void register_pppd(void) +{ + event_group = xEventGroupCreate(); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(esp_netif_init()); + + esp_netif_config_t ppp_netif_config = ESP_NETIF_DEFAULT_PPP(); + s_ppp_netif = esp_netif_new(&ppp_netif_config); + assert(s_ppp_netif); + esp_netif_ppp_config_t ppp_config = { true, true }; + esp_netif_ppp_set_params(s_ppp_netif, &ppp_config); + + ESP_ERROR_CHECK(modem_init_network(s_ppp_netif)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_modem_event, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_ppp_changed, NULL)); + + modem_start_network(); + + pppd_control_args.control = arg_str1(NULL, NULL, "", "Get info of pppd"); + pppd_control_args.end = arg_end(1); + const esp_console_cmd_t cmd = { + .command = "pppd", + .help = "PPP interface IO control", + .hint = NULL, + .func = pppd_cmd_control, + .argtable = &pppd_control_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); + + iperf_args.ip = arg_str0("c", "client", "", + "run in client mode, connecting to "); + 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.version = arg_lit0("V", "ipv6_domain", "use IPV6 address rather than IPV4"); + iperf_args.port = arg_int0("p", "port", "", + "server port to listen on/connect to"); + iperf_args.length = arg_int0("l", "len", "", "set read/write buffer size"); + iperf_args.interval = arg_int0("i", "interval", "", + "seconds between periodic bandwidth reports"); + iperf_args.time = arg_int0("t", "time", "