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

@ -10,6 +10,10 @@
#include "esp_event.h"
#include "esp_eth.h"
#include "esp_log.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "ping/ping_sock.h"
static const char *TAG = "esp_eth_test";
@ -17,11 +21,14 @@ static const char *TAG = "esp_eth_test";
#define ETH_STOP_BIT BIT(1)
#define ETH_CONNECT_BIT BIT(2)
#define ETH_GOT_IP_BIT BIT(3)
#define ETH_PING_END_BIT BIT(4)
#define ETH_START_TIMEOUT_MS (10000)
#define ETH_CONNECT_TIMEOUT_MS (40000)
#define ETH_STOP_TIMEOUT_MS (10000)
#define ETH_GET_IP_TIMEOUT_MS (60000)
#define ETH_PING_DURATION_MS (5000)
#define ETH_PING_END_TIMEOUT_MS (ETH_PING_DURATION_MS * 2)
/** Event handler for Ethernet events */
static void eth_event_handler(void *arg, esp_event_base_t event_base,
@ -66,6 +73,46 @@ static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
xEventGroupSetBits(eth_event_group, ETH_GOT_IP_BIT);
}
static void test_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 test_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 test_on_ping_end(esp_ping_handle_t hdl, void *args)
{
EventGroupHandle_t eth_event_group = (EventGroupHandle_t)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("%d packets transmitted, %d received, time %dms\n", transmitted, received, total_time_ms);
if (transmitted == received) {
xEventGroupSetBits(eth_event_group, ETH_PING_END_BIT);
}
}
TEST_CASE("esp32 ethernet io test", "[ethernet][test_env=UT_T2_Ethernet]")
{
TEST_ESP_OK(esp_event_loop_create_default());
@ -163,6 +210,83 @@ TEST_CASE("esp32 ethernet dhcp test", "[ethernet][test_env=UT_T2_Ethernet]")
vEventGroupDelete(eth_event_group);
}
TEST_CASE("esp32 ethernet icmp test", "[ethernet][test_env=UT_T2_Ethernet]")
{
EventBits_t bits = 0;
EventGroupHandle_t eth_event_group = xEventGroupCreate();
TEST_ASSERT(eth_event_group != NULL);
test_case_uses_tcpip();
TEST_ESP_OK(esp_event_loop_create_default());
TEST_ESP_OK(tcpip_adapter_set_default_eth_handlers());
TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, eth_event_group));
TEST_ESP_OK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, eth_event_group));
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
esp_eth_handle_t eth_handle = NULL;
TEST_ESP_OK(esp_eth_driver_install(&eth_config, &eth_handle));
/* wait for IP lease */
bits = xEventGroupWaitBits(eth_event_group, ETH_GOT_IP_BIT, true, true, pdMS_TO_TICKS(ETH_GET_IP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_GOT_IP_BIT) == ETH_GOT_IP_BIT);
// 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 URL to IP */
TEST_ASSERT(getaddrinfo("www.baidu.com", NULL, &hint, &res) == 0);
struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
freeaddrinfo(res);
esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG();
ping_config.target_addr = target_addr;
ping_config.count = 0; // ping in infinite mode
/* set callback functions */
esp_ping_callbacks_t cbs;
cbs.on_ping_success = test_on_ping_success;
cbs.on_ping_timeout = test_on_ping_timeout;
cbs.on_ping_end = test_on_ping_end;
cbs.cb_args = eth_event_group;
esp_ping_handle_t ping;
TEST_ESP_OK(esp_ping_new_session(&ping_config, &cbs, &ping));
/* start ping */
TEST_ESP_OK(esp_ping_start(ping));
/* ping for a while */
vTaskDelay(pdMS_TO_TICKS(ETH_PING_DURATION_MS));
/* stop ping */
TEST_ESP_OK(esp_ping_stop(ping));
/* wait for end of ping */
bits = xEventGroupWaitBits(eth_event_group, ETH_PING_END_BIT, true, true, pdMS_TO_TICKS(ETH_PING_END_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_PING_END_BIT) == ETH_PING_END_BIT);
/* restart ping */
TEST_ESP_OK(esp_ping_start(ping));
vTaskDelay(pdMS_TO_TICKS(ETH_PING_DURATION_MS));
TEST_ESP_OK(esp_ping_stop(ping));
bits = xEventGroupWaitBits(eth_event_group, ETH_PING_END_BIT, true, true, pdMS_TO_TICKS(ETH_PING_END_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_PING_END_BIT) == ETH_PING_END_BIT);
/* de-initialize ping process */
TEST_ESP_OK(esp_ping_delete_session(ping));
TEST_ESP_OK(esp_eth_driver_uninstall(eth_handle));
/* wait for connection stop */
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(ETH_STOP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
// "check link timer callback" might owned the reference of phy object, make sure it has release it
vTaskDelay(pdMS_TO_TICKS(2000));
TEST_ESP_OK(phy->del(phy));
TEST_ESP_OK(mac->del(mac));
TEST_ESP_OK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, got_ip_event_handler));
TEST_ESP_OK(tcpip_adapter_clear_default_eth_handlers());
TEST_ESP_OK(esp_event_loop_delete_default());
vEventGroupDelete(eth_event_group);
}
#if CONFIG_ETH_USE_SPI_ETHERNET
TEST_CASE("dm9051 io test", "[ethernet][ignore]")
{

View file

@ -10,6 +10,7 @@ set(srcs
"apps/dhcpserver/dhcpserver.c"
"apps/ping/esp_ping.c"
"apps/ping/ping.c"
"apps/ping/ping_sock.c"
"apps/sntp/sntp.c"
"lwip/src/api/api_lib.c"
"lwip/src/api/api_msg.c"

View file

@ -0,0 +1,371 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lwip/opt.h"
#include "lwip/init.h"
#include "lwip/mem.h"
#include "lwip/icmp.h"
#include "lwip/netif.h"
#include "lwip/sys.h"
#include "lwip/timeouts.h"
#include "lwip/inet.h"
#include "lwip/inet_chksum.h"
#include "lwip/ip.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "esp_log.h"
#include "ping/ping_sock.h"
const static char *TAG = "ping_sock";
#define PING_CHECK(a, str, goto_tag, ret_value, ...) \
do \
{ \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
ret = ret_value; \
goto goto_tag; \
} \
} while (0)
#define PING_TIME_DIFF_MS(_end, _start) ((uint32_t)(((_end).tv_sec - (_start).tv_sec) * 1000 + \
((_end).tv_usec - (_start).tv_usec) / 1000))
#define PING_CHECK_START_TIMEOUT_MS (1000)
#define PING_FLAGS_INIT (1 << 0)
#define PING_FLAGS_START (1 << 1)
typedef struct {
int sock;
struct sockaddr_storage target_addr;
TaskHandle_t ping_task_hdl;
struct icmp_echo_hdr *packet_hdr;
ip_addr_t recv_addr;
uint32_t recv_len;
uint32_t icmp_pkt_size;
uint32_t count;
uint32_t transmitted;
uint32_t received;
uint32_t interval_ms;
uint32_t elapsed_time_ms;
uint32_t total_time_ms;
uint8_t ttl;
uint32_t flags;
void (*on_ping_success)(esp_ping_handle_t hdl, void *args);
void (*on_ping_timeout)(esp_ping_handle_t hdl, void *args);
void (*on_ping_end)(esp_ping_handle_t hdl, void *args);
void *cb_args;
} esp_ping_t;
static esp_err_t esp_ping_send(esp_ping_t *ep)
{
esp_err_t ret = ESP_OK;
ep->packet_hdr->seqno++;
/* generate checksum since "seqno" has changed */
ep->packet_hdr->chksum = 0;
ep->packet_hdr->chksum = inet_chksum(ep->packet_hdr, ep->icmp_pkt_size);
int sent = sendto(ep->sock, ep->packet_hdr, ep->icmp_pkt_size, 0,
(struct sockaddr *)&ep->target_addr, sizeof(ep->target_addr));
if (sent != ep->icmp_pkt_size) {
int opt_val;
socklen_t opt_len = sizeof(opt_val);
getsockopt(ep->sock, SOL_SOCKET, SO_ERROR, &opt_val, &opt_len);
ESP_LOGE(TAG, "send error=%d", opt_val);
ret = ESP_FAIL;
} else {
ep->transmitted++;
}
return ret;
}
static int esp_ping_receive(esp_ping_t *ep)
{
char buf[64]; // 64 bytes are enough to cover IP header and ICMP header
int len = 0;
struct sockaddr_storage from;
int fromlen = sizeof(from);
while ((len = recvfrom(ep->sock, buf, sizeof(buf), 0, (struct sockaddr *)&from, (socklen_t *)&fromlen)) > 0) {
if (len >= (int)(sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr))) {
ep->recv_len = (uint32_t)len;
if (from.ss_family == AF_INET) {
// IPv4
struct sockaddr_in *from4 = (struct sockaddr_in *)&from;
inet_addr_to_ip4addr(ip_2_ip4(&ep->recv_addr), &from4->sin_addr);
IP_SET_TYPE_VAL(ep->recv_addr, IPADDR_TYPE_V4);
} else {
// IPv6
struct sockaddr_in6 *from6 = (struct sockaddr_in6 *)&from;
inet6_addr_to_ip6addr(ip_2_ip6(&ep->recv_addr), &from6->sin6_addr);
IP_SET_TYPE_VAL(ep->recv_addr, IPADDR_TYPE_V6);
}
// Currently we only process IPv4
if (IP_IS_V4_VAL(ep->recv_addr)) {
struct ip_hdr *iphdr = (struct ip_hdr *)buf;
struct icmp_echo_hdr *iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4));
if ((iecho->id == ep->packet_hdr->id) && (iecho->seqno == ep->packet_hdr->seqno)) {
ep->received++;
ep->ttl = iphdr->_ttl;
return len;
}
}
}
fromlen = sizeof(from);
}
// if timeout, len will be -1
return len;
}
static void esp_ping_thread(void *args)
{
esp_ping_t *ep = (esp_ping_t *)(args);
TickType_t last_wake;
struct timeval start_time, end_time;
int recv_ret;
while (1) {
/* wait for ping start signal */
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(PING_CHECK_START_TIMEOUT_MS))) {
/* initialize runtime statistics */
ep->packet_hdr->seqno = 0;
ep->transmitted = 0;
ep->received = 0;
ep->total_time_ms = 0;
last_wake = xTaskGetTickCount();
while ((ep->flags & PING_FLAGS_START) && ((ep->count == 0) || (ep->packet_hdr->seqno < ep->count))) {
esp_ping_send(ep);
gettimeofday(&start_time, NULL);
recv_ret = esp_ping_receive(ep);
gettimeofday(&end_time, NULL);
ep->elapsed_time_ms = PING_TIME_DIFF_MS(end_time, start_time);
ep->total_time_ms += ep->elapsed_time_ms;
if (recv_ret >= 0) {
if (ep->on_ping_success) {
ep->on_ping_success((esp_ping_handle_t)ep, ep->cb_args);
}
} else {
if (ep->on_ping_timeout) {
ep->on_ping_timeout((esp_ping_handle_t)ep, ep->cb_args);
}
}
vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(ep->interval_ms)); // to get a more accurate delay
}
/* batch of ping operations finished */
if (ep->on_ping_end) {
ep->on_ping_end((esp_ping_handle_t)ep, ep->cb_args);
}
} else {
// check if ping has been de-initialized
if (!(ep->flags & PING_FLAGS_INIT)) {
break;
}
}
}
/* before exit task, free all resources */
if (ep->packet_hdr) {
free(ep->packet_hdr);
}
if (ep->sock > 0) {
close(ep->sock);
}
free(ep);
vTaskDelete(NULL);
}
esp_err_t esp_ping_new_session(const esp_ping_config_t *config, const esp_ping_callbacks_t *cbs, esp_ping_handle_t *hdl_out)
{
esp_err_t ret = ESP_OK;
esp_ping_t *ep = NULL;
PING_CHECK(config, "ping config can't be null", err, ESP_ERR_INVALID_ARG);
PING_CHECK(hdl_out, "ping handle can't be null", err, ESP_ERR_INVALID_ARG);
ep = mem_calloc(1, sizeof(esp_ping_t));
PING_CHECK(ep, "no memory for esp_ping object", err, ESP_ERR_NO_MEM);
/* set INIT flag, so that ping task won't exit (must set before create ping task) */
ep->flags |= PING_FLAGS_INIT;
/* create ping thread */
BaseType_t xReturned = xTaskCreate(esp_ping_thread, "ping", config->task_stack_size, ep,
config->task_prio, &ep->ping_task_hdl);
PING_CHECK(xReturned == pdTRUE, "create ping task failed", err, ESP_ERR_NO_MEM);
/* callback functions */
if (cbs) {
ep->cb_args = cbs->cb_args;
ep->on_ping_end = cbs->on_ping_end;
ep->on_ping_timeout = cbs->on_ping_timeout;
ep->on_ping_success = cbs->on_ping_success;
}
/* set parameters for ping */
ep->recv_addr = config->target_addr;
ep->count = config->count;
ep->interval_ms = config->interval_ms;
ep->icmp_pkt_size = sizeof(struct icmp_echo_hdr) + config->data_size;
ep->packet_hdr = mem_calloc(1, ep->icmp_pkt_size);
PING_CHECK(ep->packet_hdr, "no memory for echo packet", err, ESP_ERR_NO_MEM);
/* set ICMP type and code field */
ep->packet_hdr->type = ICMP_ECHO;
ep->packet_hdr->code = 0;
/* ping id should be unique, treat task handle as ping ID */
ep->packet_hdr->id = ((uint32_t)ep->ping_task_hdl) & 0xFFFF;
/* fill the additional data buffer with some data */
char *d = (char *)(ep->packet_hdr) + sizeof(struct icmp_echo_hdr);
for (uint32_t i = 0; i < config->data_size; i++) {
d[i] = 'A' + i;
}
/* create socket */
if (IP_IS_V4(&config->target_addr) || ip6_addr_isipv4mappedipv6(ip_2_ip6(&config->target_addr))) {
ep->sock = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP);
} else {
ep->sock = socket(AF_INET6, SOCK_RAW, IP6_NEXTH_ICMP6);
}
PING_CHECK(ep->sock > 0, "create socket failed: %d", err, ESP_FAIL, ep->sock);
struct timeval timeout;
timeout.tv_sec = config->timeout_ms / 1000;
timeout.tv_usec = (config->timeout_ms % 1000) * 1000;
/* set receive timeout */
setsockopt(ep->sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
/* set tos */
setsockopt(ep->sock, IPPROTO_IP, IP_TOS, &config->tos, sizeof(config->tos));
/* set socket address */
if (IP_IS_V4(&config->target_addr)) {
struct sockaddr_in *to4 = (struct sockaddr_in *)&ep->target_addr;
to4->sin_family = AF_INET;
inet_addr_from_ip4addr(&to4->sin_addr, ip_2_ip4(&config->target_addr));
}
if (IP_IS_V6(&config->target_addr)) {
struct sockaddr_in6 *to6 = (struct sockaddr_in6 *)&ep->target_addr;
to6->sin6_family = AF_INET6;
inet6_addr_from_ip6addr(&to6->sin6_addr, ip_2_ip6(&config->target_addr));
}
/* return ping handle to user */
*hdl_out = (esp_ping_handle_t)ep;
return ESP_OK;
err:
if (ep) {
if (ep->sock > 0) {
close(ep->sock);
}
if (ep->packet_hdr) {
free(ep->packet_hdr);
}
if (ep->ping_task_hdl) {
vTaskDelete(ep->ping_task_hdl);
}
free(ep);
}
return ret;
}
esp_err_t esp_ping_delete_session(esp_ping_handle_t hdl)
{
esp_err_t ret = ESP_OK;
esp_ping_t *ep = (esp_ping_t *)hdl;
PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG);
/* reset init flags, then ping task will exit */
ep->flags &= ~PING_FLAGS_INIT;
return ESP_OK;
err:
return ret;
}
esp_err_t esp_ping_start(esp_ping_handle_t hdl)
{
esp_err_t ret = ESP_OK;
esp_ping_t *ep = (esp_ping_t *)hdl;
PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG);
ep->flags |= PING_FLAGS_START;
xTaskNotifyGive(ep->ping_task_hdl);
return ESP_OK;
err:
return ret;
}
esp_err_t esp_ping_stop(esp_ping_handle_t hdl)
{
esp_err_t ret = ESP_OK;
esp_ping_t *ep = (esp_ping_t *)hdl;
PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG);
ep->flags &= ~PING_FLAGS_START;
return ESP_OK;
err:
return ret;
}
esp_err_t esp_ping_get_profile(esp_ping_handle_t hdl, esp_ping_profile_t profile, void *data, uint32_t size)
{
esp_err_t ret = ESP_OK;
esp_ping_t *ep = (esp_ping_t *)hdl;
const void *from = NULL;
uint32_t copy_size = 0;
PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG);
PING_CHECK(data, "profile data can't be null", err, ESP_ERR_INVALID_ARG);
switch (profile) {
case ESP_PING_PROF_SEQNO:
from = &ep->packet_hdr->seqno;
copy_size = sizeof(ep->packet_hdr->seqno);
break;
case ESP_PING_PROF_TTL:
from = &ep->ttl;
copy_size = sizeof(ep->ttl);
break;
case ESP_PING_PROF_REQUEST:
from = &ep->transmitted;
copy_size = sizeof(ep->transmitted);
break;
case ESP_PING_PROF_REPLY:
from = &ep->received;
copy_size = sizeof(ep->received);
break;
case ESP_PING_PROF_IPADDR:
from = &ep->recv_addr;
copy_size = sizeof(ep->recv_addr);
break;
case ESP_PING_PROF_SIZE:
from = &ep->recv_len;
copy_size = sizeof(ep->recv_len);
break;
case ESP_PING_PROF_TIMEGAP:
from = &ep->elapsed_time_ms;
copy_size = sizeof(ep->elapsed_time_ms);
break;
case ESP_PING_PROF_DURATION:
from = &ep->total_time_ms;
copy_size = sizeof(ep->total_time_ms);
break;
default:
PING_CHECK(false, "unknow profile: %d", err, ESP_ERR_INVALID_ARG, profile);
break;
}
PING_CHECK(size >= copy_size, "unmatched data size for profile %d", err, ESP_ERR_INVALID_SIZE, profile);
memcpy(data, from, copy_size);
return ESP_OK;
err:
return ret;
}

View file

@ -0,0 +1,169 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "esp_err.h"
#include "lwip/ip_addr.h"
/**
* @brief Type of "ping" session handle
*
*/
typedef void *esp_ping_handle_t;
/**
* @brief Type of "ping" callback functions
*
*/
typedef struct {
/**
* @brief arguments for callback functions
*
*/
void *cb_args;
/**
* @brief Invoked by internal ping thread when received ICMP echo reply packet
*
*/
void (*on_ping_success)(esp_ping_handle_t hdl, void *args);
/**
* @brief Invoked by internal ping thread when receive ICMP echo reply packet timeout
*
*/
void (*on_ping_timeout)(esp_ping_handle_t hdl, void *args);
/**
* @brief Invoked by internal ping thread when a ping session is finished
*
*/
void (*on_ping_end)(esp_ping_handle_t hdl, void *args);
} esp_ping_callbacks_t;
/**
* @brief Type of "ping" configuration
*
*/
typedef struct {
uint32_t count; /*!< A "ping" session contains count procedures */
uint32_t interval_ms; /*!< Milliseconds between each ping procedure */
uint32_t timeout_ms; /*!< Timeout value (in milliseconds) of each ping procedure */
uint32_t data_size; /*!< Size of the data next to ICMP packet header */
uint8_t tos; /*!< Type of Service, a field specified in the IP header */
ip_addr_t target_addr; /*!< Target IP address, either IPv4 or IPv6 */
uint32_t task_stack_size; /*!< Stack size of internal ping task */
uint32_t task_prio; /*!< Priority of internal ping task */
} esp_ping_config_t;
/**
* @brief Default ping configuration
*
*/
#define ESP_PING_DEFAULT_CONFIG() \
{ \
.count = 5, \
.interval_ms = 1000, \
.timeout_ms = 1000, \
.data_size = 56, \
.tos = 0, \
.target_addr = ip_addr_any_type, \
.task_stack_size = 2048, \
.task_prio = 2, \
}
#define ESP_PING_COUNT_INFINITE (0) /*!< Set ping count to zero will ping target infinitely */
/**
* @brief Profile of ping session
*
*/
typedef enum {
ESP_PING_PROF_SEQNO, /*!< Sequence number of a ping procedure */
ESP_PING_PROF_TTL, /*!< Time to live of a ping procedure */
ESP_PING_PROF_REQUEST, /*!< Number of request packets sent out */
ESP_PING_PROF_REPLY, /*!< Number of reply packets received */
ESP_PING_PROF_IPADDR, /*!< IP address of replied target */
ESP_PING_PROF_SIZE, /*!< Size of received packet */
ESP_PING_PROF_TIMEGAP, /*!< Elapsed time between request and reply packet */
ESP_PING_PROF_DURATION /*!< Elapsed time of the whole ping session */
} esp_ping_profile_t;
/**
* @brief Create a ping session
*
* @param config ping configuration
* @param cbs a bunch of callback functions invoked by internal ping task
* @param hdl_out handle of ping session
* @return
* - ESP_ERR_INVALID_ARG: invalid parameters (e.g. configuration is null, etc)
* - ESP_ERR_NO_MEM: out of memory
* - ESP_FAIL: other internal error (e.g. socket error)
* - ESP_OK: create ping session successfully, user can take the ping handle to do follow-on jobs
*/
esp_err_t esp_ping_new_session(const esp_ping_config_t *config, const esp_ping_callbacks_t *cbs, esp_ping_handle_t *hdl_out);
/**
* @brief Delete a ping session
*
* @param hdl handle of ping session
* @return
* - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc)
* - ESP_OK: delete ping session successfully
*/
esp_err_t esp_ping_delete_session(esp_ping_handle_t hdl);
/**
* @brief Start the ping session
*
* @param hdl handle of ping session
* @return
* - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc)
* - ESP_OK: start ping session successfully
*/
esp_err_t esp_ping_start(esp_ping_handle_t hdl);
/**
* @brief Stop the ping session
*
* @param hdl handle of ping session
* @return
* - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc)
* - ESP_OK: stop ping session successfully
*/
esp_err_t esp_ping_stop(esp_ping_handle_t hdl);
/**
* @brief Get runtime profile of ping session
*
* @param hdl handle of ping session
* @param profile type of profile
* @param data profile data
* @param size profile data size
* @return
* - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc)
* - ESP_ERR_INVALID_SIZE: the actual profile data size doesn't match the "size" parameter
* - ESP_OK: get profile successfully
*/
esp_err_t esp_ping_get_profile(esp_ping_handle_t hdl, esp_ping_profile_t profile, void *data, uint32_t size);
#ifdef __cplusplus
}
#endif

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);
}
}