| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- // 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;
- if (ep->packet_hdr->type == ICMP_ECHO) {
- ep->packet_hdr->chksum = inet_chksum(ep->packet_hdr, ep->icmp_pkt_size);
- }
- ssize_t sent = sendto(ep->sock, ep->packet_hdr, ep->icmp_pkt_size, 0,
- (struct sockaddr *)&ep->target_addr, sizeof(ep->target_addr));
- if (sent != (ssize_t)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);
- uint16_t data_head = 0;
- while ((len = recvfrom(ep->sock, buf, sizeof(buf), 0, (struct sockaddr *)&from, (socklen_t *)&fromlen)) > 0) {
- 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);
- data_head = (uint16_t)(sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr));
- }
- #if CONFIG_LWIP_IPV6
- 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);
- data_head = (uint16_t)(sizeof(struct ip6_hdr) + sizeof(struct icmp6_echo_hdr));
- }
- #endif
- if (len >= data_head) {
- if (IP_IS_V4_VAL(ep->recv_addr)) { // Currently we process IPv4
- 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;
- ep->recv_len = lwip_ntohs(IPH_LEN(iphdr)) - data_head; // The data portion of ICMP
- return len;
- }
- }
- #if CONFIG_LWIP_IPV6
- else if (IP_IS_V6_VAL(ep->recv_addr)) { // Currently we process IPv6
- struct ip6_hdr *iphdr = (struct ip6_hdr *)buf;
- struct icmp6_echo_hdr *iecho6 = (struct icmp6_echo_hdr *)(buf + sizeof(struct ip6_hdr)); // IPv6 head length is 40
- if ((iecho6->id == ep->packet_hdr->id) && (iecho6->seqno == ep->packet_hdr->seqno)) {
- ep->received++;
- ep->recv_len = IP6H_PLEN(iphdr) - sizeof(struct icmp6_echo_hdr); //The data portion of ICMPv6
- return len;
- }
- }
- #endif
- }
- 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);
- }
- }
- if (pdMS_TO_TICKS(ep->interval_ms)) {
- 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_FAIL;
- 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->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)
- #if CONFIG_LWIP_IPV6
- || ip6_addr_isipv4mappedipv6(ip_2_ip6(&config->target_addr))
- #endif
- ) {
- ep->sock = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP);
- }
- #if CONFIG_LWIP_IPV6
- else {
- ep->sock = socket(AF_INET6, SOCK_RAW, IP6_NEXTH_ICMP6);
- }
- #endif
- PING_CHECK(ep->sock > 0, "create socket failed: %d", err, ESP_FAIL, ep->sock);
- /* set if index */
- if(config->interface) {
- struct ifreq iface;
- if(netif_index_to_name(config->interface, iface.ifr_name) == NULL) {
- ESP_LOGE(TAG, "fail to find interface name with netif index %d", config->interface);
- goto err;
- }
- if(setsockopt(ep->sock, SOL_SOCKET, SO_BINDTODEVICE, &iface, sizeof(iface)) != 0) {
- ESP_LOGE(TAG, "fail to setsockopt SO_BINDTODEVICE");
- goto err;
- }
- }
- 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));
- ep->packet_hdr->type = ICMP_ECHO;
- }
- #if CONFIG_LWIP_IPV6
- 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));
- ep->packet_hdr->type = ICMP6_TYPE_EREQ;
- }
- #endif
- /* 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;
- }
|