Merge branch 'example/icmpv6_ping' into 'master'

examples: Add ICMPv6 Echo Request/Reply example.

See merge request espressif/esp-idf!18731
pull/9408/head
David Čermák 2022-07-19 05:04:02 +08:00
commit d1dc30fd70
7 zmienionych plików z 423 dodań i 0 usunięć

Wyświetl plik

@ -212,6 +212,12 @@ examples/protocols/sntp:
temporary: true
reason: the other targets are not tested yet
examples/protocols/sockets/icmpv6_ping:
enable:
- if: IDF_TARGET in ["esp32", "esp32c2", "esp32c3", "esp32s2", "esp32s3"]
temporary: true
reason: Example test has not been implemented yet
examples/protocols/sockets/non_blocking:
disable_test:
- if: IDF_TARGET != "esp32"

Wyświetl plik

@ -0,0 +1,8 @@
# 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.16)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(icmpv6_ping)

Wyświetl plik

@ -0,0 +1,80 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- |
# ICMPv6 Ping example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
The application first finds a network interface with an IPv6 address of **Global** or **Unique Local** type.
The application then creates a raw ICMPv6 socket and sends an ICMPv6 Echo Request to the destination ipv6 address, with the ipv6 address previously found over the interface as the source address. The application keeps waiting for an echo reply from the target destination.
## Hardware Required
This example can be run on any commonly available ESP32 development board.
## How to use example
### Build and execute for **ESP32** targets:
Set the target as:
```sh
idf.py set-target esp32
```
#### Configure the project:
Set the following parameters under "***Example Connection Configuration***" Options:
* Set `WiFi SSID` of your IPv6 enabled WiFi network.
Ensure the WiFi network supports SLAAC.
* Set `WiFi Password` of the selected Wifi network.
* Press '**`Esc`**' to go to the previous menu.
Set the following parameters under "***Example Configuration***" Options:
* Set the '**Destination IPV6 Address**' for the ICMPv6 ping example.
**Destination IPV6 Address** can also be set manually by modifying the source file `icmpv6_ping.c`.
* Press '**`s`**' to save and '**`q`**' to quit the menu.
#### Build and execute as follows:
```sh
idf.py build
idf.py flash monitor -p <COM PORT>
```
The output of the execution should be as follows:
```sh
I (7668) ICMPv6_PING: Interface: st1
I (7668) ICMPv6_PING: IPv6 address: fe80:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (7678) ICMPv6_PING: IPv6 address: 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx, type: ESP_IP6_ADDR_IS_GLOBAL
I (7688) ICMPv6_PING: Source address: 2001:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
I (7698) ICMPv6_PING: Destination address: 2001:xxxx::xxxx:xxxx:xxxx:xxxx
I (7708) ICMPv6_PING: Interface name: st1
I (7708) ICMPv6_PING: ICMPv6 msg payload:
I (7708) ICMPv6_PING: 0x3ffc6e84 80 00 00 00 00 00 01 2c 54 65 73 74 |.......,Test|
I (7718) ICMPv6_PING: Sent ICMPv6 msg: type: 128, code: 0, id: 0, seqno: 300
I (7828) ICMPv6_PING: ICMPv6 msg payload:
I (7828) ICMPv6_PING: 0x3ffc6e5c 60 00 00 00 00 20 3a ff 20 xx xx xx xx xx xx xx |`.... :. .......|
I (7838) ICMPv6_PING: 0x3ffc6e6c xx xx xx xx xx xx xx xx 20 01 xx xx xx xx xx xx |V.....!. .......|
I (7848) ICMPv6_PING: 0x3ffc6e7c xx xx xx xx xx xx xx xx 88 00 1f f0 e0 00 00 00 |Z.%...A.........|
I (7858) ICMPv6_PING: 0x3ffc6e8c 20 01 0d b8 | ...|
I (7868) ICMPv6_PING: Received ICMPv6 msg: type: 136, code: 0, id: 224, seqno: 0
I (7868) ICMPv6_PING: ICMPv6 msg payload:
I (7878) ICMPv6_PING: 0x3ffc6e5c 60 00 00 00 00 0c 3a 40 20 xx xx xx xx xx xx xx |`.... :. .......|
I (7838) ICMPv6_PING: 0x3ffc6e6c xx xx xx xx xx xx xx xx 20 01 xx xx xx xx xx xx |V.....!. .......|
I (7848) ICMPv6_PING: 0x3ffc6e7c xx xx xx xx xx xx xx xx 81 00 8a a9 00 00 01 2c |Z.%...A........,|
I (7908) ICMPv6_PING: 0x3ffc6e8c 54 65 73 74 |Test|
I (7918) ICMPv6_PING: Received ICMPv6 msg: type: 129, code: 0, id: 0, seqno: 300
```
## Troubleshooting
Set the destination IPv6 address to your PC and run the following command on a Linux terminal on your PC to see the request and reply.
```sh
sudo tcpdump -XX -i enp0s1 "icmp6 && ip6[40] == 128 || ip6[40] == 129" -vvv
```
## Note:
According to [RFC 2460 section 8.1](https://datatracker.ietf.org/doc/html/rfc2460#section-8.1), any transport or other upper-layer protocol that includes the
addresses from the IP header in its checksum computation must be modified for use over IPv6, to include the 128-bit IPv6 addresses instead of 32-bit IPv4 addresses.
ICMPv6 is one of such protocols, and the responsibility of checksum calculation is supposed to be part of the ICMPv6 header building process.
But LWIP does this for the user. LWIP expects the checksum field to be set to zero by the user, and it calculates and sets the ICMPv6 checksum value before transmitting the packet.

Wyświetl plik

@ -0,0 +1,3 @@
idf_component_register(SRCS "icmpv6_ping.c"
INCLUDE_DIRS ".")

Wyświetl plik

@ -0,0 +1,9 @@
menu "Example Configuration"
config EXAMPLE_DST_IPV6_ADDR
string "Destination IPV6 Address"
default "2001:DB8::56AF:97FF:FEB3:2195"
help
The example will connect to this IPV6 address.
endmenu

Wyświetl plik

@ -0,0 +1,312 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* BSD Socket API Example */
#include <netdb.h>
#include "esp_log.h"
#include "esp_event.h"
#include "esp_netif_ip_addr.h"
#include "nvs_flash.h"
#include "lwip/icmp6.h"
#include "lwip/ip6.h"
#include "protocol_examples_common.h"
#include "sdkconfig.h"
#define MSG_BUFF_SIZE (256)
static const char *TAG = "ICMPv6_PING";
/* types of ipv6 addresses to be displayed on ipv6 events */
static const char *s_ipv6_addr_types[] = {
"ESP_IP6_ADDR_IS_UNKNOWN",
"ESP_IP6_ADDR_IS_GLOBAL",
"ESP_IP6_ADDR_IS_LINK_LOCAL",
"ESP_IP6_ADDR_IS_SITE_LOCAL",
"ESP_IP6_ADDR_IS_UNIQUE_LOCAL",
"ESP_IP6_ADDR_IS_IPV4_MAPPED_IPV6"
};
/**
* @brief Resolves an IPv6 address in string format or an url.
*
* @param[in] addr_str_i IPv6 address in string format.
* @param[out] addr_o IPv6 address in sockaddr_in6 format.
*
* @return length of the IPv6 address.
*/
static socklen_t resolve_v6addr(char *addr_str_i, struct sockaddr_in6 *addr_o)
{
struct addrinfo hints, *res;
socklen_t addrlen;
// Fill out hints for getaddrinfo() for Source address.
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = hints.ai_flags | AI_CANONNAME;
// Resolve source using getaddrinfo().
if (0 != getaddrinfo(addr_str_i, NULL, &hints, &res))
{
ESP_LOGE(TAG, "getaddrinfo(): Could not resolve address, got error: %d\n", errno);
return 0;
}
memset(addr_o, 0, sizeof(struct sockaddr_in6));
memcpy(addr_o, res->ai_addr, res->ai_addrlen);
addrlen = res->ai_addrlen;
freeaddrinfo(res);
return addrlen;
}
/**
* @brief Send a ICMPv6 ping request to the destination address over the given interface and wait for a response.
*
* @param[in] src_addr_str Source Global/Unique local IPv6 address of the interface.
* @param[in] dst_addr_str Global IPv6 address of the Destination.
* @param[in] interface Name of the interface.
*/
static void send_ping(char *src_addr_str, char *dst_addr_str, char *interface)
{
int len, datalen;
int sd;
struct sockaddr_in6 src, dst;
struct icmp6_echo_hdr *icmphdr;
socklen_t srclen;
uint8_t psdhdr[MSG_BUFF_SIZE];
uint8_t data[10] = "Test"; // ICMPv6 Payload
uint8_t *inpack;
struct msghdr msghdr;
struct cmsghdr *cmsghdr;
int cmsglen, hoplimit;
struct iovec iov[2];
struct ifreq ifr;
// Reset pseudo header buffer
memset(psdhdr, 0, MSG_BUFF_SIZE);
// Resolve source address.
if(0 == (srclen = resolve_v6addr(src_addr_str, &src)))
{
ESP_LOGE(TAG, "resolve_v6addr(): Source address error\n");
return;
}
// Resolve destination address.
if(0 == resolve_v6addr(dst_addr_str, &dst))
{
ESP_LOGE(TAG, "resolve_v6addr(): Destination address error\n");
return;
}
// Populate icmphdr portion of buffer outpack.
icmphdr = (struct icmp6_echo_hdr *)(psdhdr + IP6_HLEN);
icmphdr->type = ICMP6_TYPE_EREQ;
icmphdr->code = 0;
icmphdr->chksum = 0;
icmphdr->id = htons(0);
icmphdr->seqno = htons(300);
// ICMP data
datalen = 4;
// The payload is assigned to the data buffer during initialization.
memcpy(psdhdr + IP6_HLEN + ICMP6_HLEN, data, datalen * sizeof(uint8_t));
// Compose the msghdr structure.
memset(&msghdr, 0, sizeof(msghdr));
msghdr.msg_name = &dst; // pointer to socket address structure
msghdr.msg_namelen = sizeof(dst); // size of socket address structure
memset(&iov, 0, sizeof(iov));
iov[0].iov_base = (uint8_t *)icmphdr;
iov[0].iov_len = ICMP6_HLEN + datalen;
msghdr.msg_iov = iov; // scatter/gather array
msghdr.msg_iovlen = 1; // number of elements in scatter/gather array
// Initialize msghdr and control data to total length of the message to be sent.
// Allocate some memory for our cmsghdr data.
cmsglen = CMSG_SPACE(sizeof(int));
msghdr.msg_control = (uint8_t *)malloc(cmsglen * sizeof(uint8_t));
msghdr.msg_controllen = cmsglen;
// Change hop limit to 255.
hoplimit = 255;
cmsghdr = CMSG_FIRSTHDR(&msghdr);
cmsghdr->cmsg_level = IPPROTO_IPV6;
cmsghdr->cmsg_type = 20; // We want to change hop limit
cmsghdr->cmsg_len = CMSG_LEN(sizeof(int));
*((int *)CMSG_DATA(cmsghdr)) = hoplimit;
// Request a socket descriptor sd.
if ((sd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0)
{
ESP_LOGE(TAG, "Failed to get socket descriptor.: %d\n", errno);
return;
}
// Bind the socket descriptor to the source address.
if (bind(sd, (struct sockaddr *)&src, srclen) != 0)
{
ESP_LOGE(TAG, "Failed to bind the socket descriptor to the source address.: %d\n", errno);
return;
}
// Bind socket to interface index.
strcpy(ifr.ifr_name, interface);
if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0)
{
ESP_LOGE(TAG, "setsockopt() failed to bind to interface: %d\n", errno);
return;
}
// Send packet.
int ret = 0;
if ((ret = sendmsg(sd, &msghdr, 0)) < 0)
{
ESP_LOGE(TAG, "sendmsg() failed: %d\n", errno);
return;
}
free(msghdr.msg_control);
ESP_LOGI(TAG, "ICMPv6 msg payload:");
ESP_LOG_BUFFER_HEXDUMP(TAG, &(psdhdr[IP6_HLEN]), ICMP6_HLEN + datalen, ESP_LOG_INFO);
ESP_LOGI(TAG, "Sent ICMPv6 msg: type: %d, code: %d, id: %d, seqno: %d\n",
icmphdr->type,
icmphdr->code,
icmphdr->id,
ntohs(icmphdr->seqno));
// Prepare msghdr for recvmsg().
memset(psdhdr, 0, MSG_BUFF_SIZE);
inpack = psdhdr;
memset(&msghdr, 0, sizeof(msghdr));
memset(&iov, 0, sizeof(iov));
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
iov[0].iov_base = (uint8_t *)inpack;
iov[0].iov_len = 65535;
msghdr.msg_iov = iov;
msghdr.msg_iovlen = 1;
msghdr.msg_control = (uint8_t *)malloc(128 * sizeof(uint8_t));
msghdr.msg_controllen = 128 * sizeof(uint8_t);
// Listen for incoming message from socket sd.
// Keep at it until we get a Echo Reply.
icmphdr = (struct icmp6_echo_hdr *)(inpack + IP6_HLEN);
while (ICMP6_TYPE_EREP != icmphdr->type)
{
if ((len = recvmsg(sd, &msghdr, 0)) < 0)
{
ESP_LOGE(TAG, "recvmsg() failed: %d\n", errno);
return;
}
ESP_LOGI(TAG, "ICMPv6 msg payload:");
ESP_LOG_BUFFER_HEXDUMP(TAG, inpack, IP6_HLEN + ICMP6_HLEN + datalen, ESP_LOG_INFO);
ESP_LOGI(TAG, "Received ICMPv6 msg: type: %d, code: %d, id: %d, seqno: %d\n",
icmphdr->type,
icmphdr->code,
icmphdr->id,
ntohs(icmphdr->seqno));
}
free(msghdr.msg_control);
}
/**
* @brief Goes over each interface and searches for one Global/Unique local IPv6 address.
* Returns the interface name and IPv6 address of the interface in case of success.
*
* @param[out] interface Name of the interface.
* @param[out] src_addr_str Global/Unique local IPv6 address of the interface
* @return
* >0 : Successfully found an interface with Global/Unique local IPv6 address.
* -1 : Unable to to find a valid interface with Global/Unique local IPv6 address.
*/
bool get_src_iface(char *interface, char *src_addr_str)
{
esp_netif_t *netif = NULL;
int ip6_addrs_count = 0;
esp_ip6_addr_t ip6[LWIP_IPV6_NUM_ADDRESSES];
esp_err_t ret = ESP_FAIL;
// Get interface details and own global ipv6 address
for (int i = 0; i < esp_netif_get_nr_of_ifs(); ++i)
{
netif = esp_netif_next(netif);
ret = esp_netif_get_netif_impl_name(netif, interface);
if ((ESP_FAIL == ret) || (NULL == netif))
{
ESP_LOGE(TAG, "No interface available");
return false;
}
ESP_LOGI(TAG, "Interface: %s", interface);
ip6_addrs_count = esp_netif_get_all_ip6(netif, ip6);
for (int j = 0; j < ip6_addrs_count; ++j)
{
esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&(ip6[j]));
ESP_LOGI(TAG, "IPv6 address: " IPV6STR ", type: %s", IPV62STR(ip6[j]), s_ipv6_addr_types[ipv6_type]);
if ((ESP_IP6_ADDR_IS_GLOBAL == ipv6_type) ||
(ESP_IP6_ADDR_IS_UNIQUE_LOCAL == ipv6_type))
{
// Break as we have the source address
sprintf(src_addr_str, IPV6STR, IPV62STR(ip6[j]));
return true;
}
}
}
return false;
}
static void ping6_test_task(void *pvParameters)
{
char src_addr_str[50];
char interface[10];
char dst_addr_str[] = CONFIG_EXAMPLE_DST_IPV6_ADDR;
if (true == get_src_iface(interface, src_addr_str))
{
ESP_LOGI(TAG, "Source address: %s", src_addr_str);
ESP_LOGI(TAG, "Destination address: %s", dst_addr_str);
ESP_LOGI(TAG, "Interface name: %s", interface);
// send one single ping
send_ping(src_addr_str, dst_addr_str, interface);
}
vTaskDelete(NULL);
}
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
xTaskCreate(ping6_test_task, "iface_test", 4096, NULL, 5, NULL);
}

Wyświetl plik

@ -0,0 +1,5 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_EXAMPLE_CONNECT_IPV6_PREF_GLOBAL=y
CONFIG_LWIP_IPV6_AUTOCONFIG=y