icmp: add example and unitest

This commit is contained in:
suda-morris
2019-10-13 19:01:33 +08:00
committed by bot
parent fa32a4bd93
commit dc6acf0033
12 changed files with 1122 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
# 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.5)
# (Not part of the boilerplate)
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(icmp-echo)

View File

@@ -0,0 +1,11 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := icmp-echo
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,100 @@
# ICMP Echo-Reply (Ping) example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
Ping is a useful network utility used to test if a remote host is reachable on the IP network. It measures the round-trip time for messages sent from the source host to a destination target that are echoed back to the source.
Ping operates by sending Internet Control Message Protocol (ICMP) echo request packets to the target host and waiting for an ICMP echo reply.
This example implements a simple ping command line util based on the [console component](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/console.html).
**Notes:** Currently this example only supports IPv4.
## How to use example
### Hardware Required
This example should be able to run on any commonly available ESP32 development board.
### Configure the project
```
idf.py menuconfig
```
In the `Example Connection Configuration` menu:
* Choose the network interface (Wi-Fi or Ethernet) used by this example under `Connect using`.
* If Wi-Fi interface is selected, you also have to set:
* Wi-Fi SSID and Wi-Fi password that your board will connect to.
* If Ethernet interface is selected, you also have to set:
* Select Ethernet type under `Ethernet Type`, for example, `Internal EMAC` or `SPI Ethernet Module`.
* Select Ethernet PHY chip model under `Ethernet PHY Device`, for example, `IP101`.
* You might also have to set other Ethernet driver specific parameters under `Component Config > Ethernet`, for example, EMAC Clock mode, GPIO used by SMI, and etc.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
* Run `help` to get manual of all supported commands.
```bash
esp32> help
help
Print the list of registered commands
ping [-W <t>] [-i <t>] [-s <n>] [-c <n>] [-Q <n>] <host>
send ICMP ECHO_REQUEST to network hosts
-W, --timeout=<t> Time to wait for a response, in seconds
-i, --interval=<t> Wait interval seconds between sending each packet
-s, --size=<n> Specify the number of data bytes to be sent
-c, --count=<n> Stop after sending count packets
-Q, --tos=<n> Set Type of Service related bits in IP datagrams
<host> Host address
```
* Run `ping` command to test reachable of remote server.
```bash
esp32> ping www.espressif.com
64 bytes from 119.9.92.99 icmp_seq=1 ttl=51 time=36 ms
64 bytes from 119.9.92.99 icmp_seq=2 ttl=51 time=34 ms
64 bytes from 119.9.92.99 icmp_seq=3 ttl=51 time=37 ms
64 bytes from 119.9.92.99 icmp_seq=4 ttl=51 time=36 ms
64 bytes from 119.9.92.99 icmp_seq=5 ttl=51 time=33 ms
--- 119.9.92.99 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 176ms
```
* Run `ping` with a wrong domain name
```bash
esp32> ping www.hello-world.io
ping: unknown host www.hello-world.io
Command returned non-zero error code: 0x1 (ERROR)
```
* Run `ping` with an unreachable server
```bash
esp32> ping www.zoom.us
From 69.171.230.18 icmp_seq=1 timeout
From 69.171.230.18 icmp_seq=2 timeout
From 69.171.230.18 icmp_seq=3 timeout
From 69.171.230.18 icmp_seq=4 timeout
From 69.171.230.18 icmp_seq=5 timeout
--- 69.171.230.18 ping statistics ---
5 packets transmitted, 0 received, 100% packet loss, time 4996ms
```

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "echo_example_main.c" "cmd_ping.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,161 @@
/* Ping command
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "cmd_ping.h"
#include "argtable3/argtable3.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "esp_console.h"
#include "ping/ping_sock.h"
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("%d bytes from %s icmp_seq=%d ttl=%d time=%d 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=%d timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno);
}
static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args)
{
ip_addr_t target_addr;
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_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));
uint32_t loss = (uint32_t)((1 - ((float)received) / transmitted) * 100);
if (IP_IS_V4(&target_addr)) {
printf("\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr)));
} else {
printf("\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr)));
}
printf("%d packets transmitted, %d received, %d%% packet loss, time %dms\n",
transmitted, received, loss, total_time_ms);
// delete the ping sessions, so that we clean up all resources and can create a new ping session
// we don't have to call delete function in the callback, instead we can call delete function from other tasks
esp_ping_delete_session(hdl);
}
static struct {
struct arg_dbl *timeout;
struct arg_dbl *interval;
struct arg_int *data_size;
struct arg_int *count;
struct arg_int *tos;
struct arg_str *host;
struct arg_end *end;
} ping_args;
static int do_ping_cmd(int argc, char **argv)
{
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
int nerrors = arg_parse(argc, argv, (void **)&ping_args);
if (nerrors != 0) {
arg_print_errors(stderr, ping_args.end, argv[0]);
return 1;
}
if (ping_args.timeout->count > 0) {
config.timeout_ms = (uint32_t)(ping_args.timeout->dval[0] * 1000);
}
if (ping_args.interval->count > 0) {
config.interval_ms = (uint32_t)(ping_args.interval->dval[0] * 1000);
}
if (ping_args.data_size->count > 0) {
config.data_size = (uint32_t)(ping_args.data_size->ival[0]);
}
if (ping_args.count->count > 0) {
config.count = (uint32_t)(ping_args.count->ival[0]);
}
if (ping_args.tos->count > 0) {
config.tos = (uint32_t)(ping_args.tos->ival[0]);
}
// parse IP address
ip_addr_t target_addr;
struct addrinfo hint;
struct addrinfo *res = NULL;
memset(&hint, 0, sizeof(hint));
memset(&target_addr, 0, sizeof(target_addr));
/* convert domain name to IP address */
if (getaddrinfo(ping_args.host->sval[0], NULL, &hint, &res) != 0) {
printf("ping: unknown host %s\n", ping_args.host->sval[0]);
return 1;
}
if (res->ai_family == AF_INET) {
struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
} else {
struct in6_addr addr6 = ((struct sockaddr_in6 *) (res->ai_addr))->sin6_addr;
inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
}
freeaddrinfo(res);
config.target_addr = target_addr;
/* set callback functions */
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(void)
{
ping_args.timeout = arg_dbl0("W", "timeout", "<t>", "Time to wait for a response, in seconds");
ping_args.interval = arg_dbl0("i", "interval", "<t>", "Wait interval seconds between sending each packet");
ping_args.data_size = arg_int0("s", "size", "<n>", "Specify the number of data bytes to be sent");
ping_args.count = arg_int0("c", "count", "<n>", "Stop after sending count packets");
ping_args.tos = arg_int0("Q", "tos", "<n>", "Set Type of Service related bits in IP datagrams");
ping_args.host = arg_str1(NULL, NULL, "<host>", "Host address");
ping_args.end = arg_end(1);
const esp_console_cmd_t ping_cmd = {
.command = "ping",
.help = "send ICMP ECHO_REQUEST to network hosts",
.hint = NULL,
.func = &do_ping_cmd,
.argtable = &ping_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&ping_cmd));
}

View File

@@ -0,0 +1,24 @@
/* Ping command
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Register Ping command
*
*/
void register_ping(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,145 @@
/* ICMP echo example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_console.h"
#include "driver/uart.h"
#include "esp_vfs_dev.h"
#include "esp_event.h"
#include "linenoise/linenoise.h"
#include "argtable3/argtable3.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "cmd_ping.h"
static void initialize_console(void)
{
/* Disable buffering on stdin */
setvbuf(stdin, NULL, _IONBF, 0);
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
/* Move the caret to the beginning of the next line on '\n' */
esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
/* Configure UART. Note that REF_TICK is used so that the baud rate remains
* correct while APB frequency is changing in light sleep mode.
*/
const uart_config_t uart_config = {
.baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.use_ref_tick = true
};
ESP_ERROR_CHECK(uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config));
/* Install UART driver for interrupt-driven reads and writes */
ESP_ERROR_CHECK(uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0));
/* Tell VFS to use UART driver */
esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
/* Initialize the console */
esp_console_config_t console_config = {
.max_cmdline_args = 16,
.max_cmdline_length = 256,
#if CONFIG_LOG_COLORS
.hint_color = atoi(LOG_COLOR_CYAN)
#endif
};
ESP_ERROR_CHECK(esp_console_init(&console_config));
/* Configure linenoise line completion library */
/* Enable multiline editing. If not set, long commands will scroll within
* single line.
*/
linenoiseSetMultiLine(1);
/* Tell linenoise where to get command completions and hints */
linenoiseSetCompletionCallback(&esp_console_get_completion);
linenoiseSetHintsCallback((linenoiseHintsCallback *) &esp_console_get_hint);
/* Set command history size */
linenoiseHistorySetMaxLen(100);
}
void app_main(void)
{
initialize_console();
ESP_ERROR_CHECK(nvs_flash_init());
tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* wait for active network connection */
ESP_ERROR_CHECK(example_connect());
/* Register commands */
esp_console_register_help_command();
register_ping();
/* Prompt to be printed before each line.
* This can be customized, made dynamic, etc.
*/
const char *prompt = LOG_COLOR_I "esp32> " LOG_RESET_COLOR;
printf("\n"
"Type 'help' to get the list of commands.\n"
"Use UP/DOWN arrows to navigate through command history.\n"
"Press TAB when typing command name to auto-complete.\n");
/* Figure out if the terminal supports escape sequences */
int probe_status = linenoiseProbe();
if (probe_status) { /* zero indicates success */
printf("\n"
"Your terminal application does not support escape sequences.\n"
"Line editing and history features are disabled.\n"
"On Windows, try using Putty instead.\n");
linenoiseSetDumbMode(1);
#if CONFIG_LOG_COLORS
/* Since the terminal doesn't support escape sequences,
* don't use color codes in the prompt.
*/
prompt = "esp32> ";
#endif //CONFIG_LOG_COLORS
}
/* Main loop */
while (true) {
/* Get a line using linenoise.
* The line is returned when ENTER is pressed.
*/
char *line = linenoise(prompt);
if (line == NULL) { /* Ignore empty lines */
continue;
}
/* Add the command to the history */
linenoiseHistoryAdd(line);
/* Try to run the command */
int ret;
esp_err_t err = esp_console_run(line, &ret);
if (err == ESP_ERR_NOT_FOUND) {
printf("Unrecognized command\n");
} else if (err == ESP_ERR_INVALID_ARG) {
// command was empty
} else if (err == ESP_OK && ret != ESP_OK) {
printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret));
} else if (err != ESP_OK) {
printf("Internal error: %s\n", esp_err_to_name(err));
}
/* linenoise allocates line buffer on the heap, so need to free it */
linenoiseFree(line);
}
}