/* * cmd_ping.c * * Copyright (c) 2025 Umber Networks & Robert McMahon * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "esp_log.h" #include "esp_console.h" #include "argtable3/argtable3.h" #include "ping/ping_sock.h" #include "lwip/inet.h" #include "lwip/netdb.h" #include "app_console.h" // ============================================================================ // COMMAND: ping (ICMP Echo) // ============================================================================ static struct { struct arg_str *host; struct arg_int *count; struct arg_int *interval; struct arg_end *end; } ping_args; static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args) { uint8_t ttl; uint16_t seqno; uint32_t elapsed_time, recv_len; ip_addr_t target_addr; esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); printf("%" PRIu32 " bytes from %s: icmp_seq=%u ttl=%u time=%" PRIu32 " ms\n", recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time); } static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args) { uint16_t seqno; ip_addr_t target_addr; esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); printf("From %s: icmp_seq=%u timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno); } static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args) { uint32_t transmitted; uint32_t received; uint32_t total_time_ms; esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); printf("\n--- ping statistics ---\n"); printf("%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %" PRIu32 "ms\n", transmitted, received, (transmitted - received) * 100 / transmitted, total_time_ms); esp_ping_delete_session(hdl); } static int cmd_ping(int argc, char **argv) { int nerrors = arg_parse(argc, argv, (void **)&ping_args); if (nerrors != 0) { arg_print_errors(stderr, ping_args.end, argv[0]); return 1; } esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG(); // Parse Args if (ping_args.count->count > 0) { config.count = ping_args.count->ival[0]; } if (ping_args.interval->count > 0) { config.interval_ms = ping_args.interval->ival[0] * 1000; } // Parse Target IP or Hostname ip_addr_t target_addr; struct addrinfo hint; struct addrinfo *res = NULL; memset(&hint, 0, sizeof(hint)); memset(&target_addr, 0, sizeof(target_addr)); // Check if simple IP string if (inet_aton(ping_args.host->sval[0], &target_addr.u_addr.ip4)) { target_addr.type = IPADDR_TYPE_V4; } else { // Resolve Hostname printf("Resolving %s...\n", ping_args.host->sval[0]); // Set hint to prefer IPv4 if desired, or leave 0 for ANY hint.ai_family = AF_INET; if (getaddrinfo(ping_args.host->sval[0], NULL, &hint, &res) != 0) { printf("ping: unknown host %s\n", ping_args.host->sval[0]); return 1; } // Convert struct sockaddr_in to ip_addr_t struct sockaddr_in *sa = (struct sockaddr_in *)res->ai_addr; inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &sa->sin_addr); target_addr.type = IPADDR_TYPE_V4; freeaddrinfo(res); } config.target_addr = target_addr; config.task_stack_size = 4096; // Ensure enough stack for callbacks esp_ping_callbacks_t cbs = { .on_ping_success = cmd_ping_on_ping_success, .on_ping_timeout = cmd_ping_on_ping_timeout, .on_ping_end = cmd_ping_on_ping_end, .cb_args = NULL }; esp_ping_handle_t ping; esp_ping_new_session(&config, &cbs, &ping); esp_ping_start(ping); return 0; } void register_ping_cmd(void) { ping_args.host = arg_str1(NULL, NULL, "", "Host address or name"); ping_args.count = arg_int0("c", "count", "", "Stop after replies"); ping_args.interval = arg_int0("i", "interval", "", "Wait interval"); ping_args.end = arg_end(1); const esp_console_cmd_t cmd = { .command = "ping", .help = "Send ICMP ECHO_REQUEST to network hosts", .hint = NULL, .func = &cmd_ping, .argtable = &ping_args }; ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); }