diff --git a/components/esp_netif/include/esp_netif_ip_addr.h b/components/esp_netif/include/esp_netif_ip_addr.h index e4b126272e..e673b399bf 100644 --- a/components/esp_netif/include/esp_netif_ip_addr.h +++ b/components/esp_netif/include/esp_netif_ip_addr.h @@ -99,6 +99,24 @@ typedef struct _ip_addr { } u_addr; uint8_t type; } esp_ip_addr_t; + +typedef enum { + 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 +} esp_ip6_addr_type_t; + +/** + * @brief Get the IPv6 address type + * + * @param[in] ip6_addr IPv6 type + * + * @return IPv6 type in form of enum esp_ip6_addr_type_t + */ +esp_ip6_addr_type_t esp_netif_ip6_get_addr_type(esp_ip6_addr_t* ip6_addr); #ifdef __cplusplus } diff --git a/components/esp_netif/include/esp_netif_types.h b/components/esp_netif/include/esp_netif_types.h index 242ce53c36..b55d1650f7 100644 --- a/components/esp_netif/include/esp_netif_types.h +++ b/components/esp_netif/include/esp_netif_types.h @@ -120,6 +120,7 @@ typedef struct { int if_index; /*!< Interface index for which the event is received (left for legacy compilation) */ esp_netif_t *esp_netif; /*!< Pointer to corresponding esp-netif object */ esp_netif_ip6_info_t ip6_info; /*!< IPv6 address of the interface */ + int ip_index; /*!< IPv6 address index */ } ip_event_got_ip6_t; /** Event structure for IP_EVENT_AP_STAIPASSIGNED event */ diff --git a/components/esp_netif/lwip/esp_netif_lwip.c b/components/esp_netif/lwip/esp_netif_lwip.c index b746a9664d..83492e6d70 100644 --- a/components/esp_netif/lwip/esp_netif_lwip.c +++ b/components/esp_netif/lwip/esp_netif_lwip.c @@ -1370,7 +1370,26 @@ esp_err_t esp_netif_get_dns_info(esp_netif_t *esp_netif, esp_netif_dns_type_t ty return esp_netif_lwip_ipc_call(esp_netif_get_dns_info_api, esp_netif, (void *)&dns_param); } -static void esp_netif_nd6_cb(struct netif *p_netif, uint8_t ip_idex) +esp_ip6_addr_type_t esp_netif_ip6_get_addr_type(esp_ip6_addr_t* ip6_addr) +{ + ip6_addr_t* lwip_ip6_info = (ip6_addr_t*)ip6_addr; + + if (ip6_addr_isglobal(lwip_ip6_info)) { + return ESP_IP6_ADDR_IS_GLOBAL; + } else if (ip6_addr_islinklocal(lwip_ip6_info)) { + return ESP_IP6_ADDR_IS_LINK_LOCAL; + } else if (ip6_addr_issitelocal(lwip_ip6_info)) { + return ESP_IP6_ADDR_IS_SITE_LOCAL; + } else if (ip6_addr_isuniquelocal(lwip_ip6_info)) { + return ESP_IP6_ADDR_IS_UNIQUE_LOCAL; + } else if (ip6_addr_isipv4mappedipv6(lwip_ip6_info)) { + return ESP_IP6_ADDR_IS_IPV4_MAPPED_IPV6; + } + return ESP_IP6_ADDR_IS_UNKNOWN; + +} + +static void esp_netif_nd6_cb(struct netif *p_netif, uint8_t ip_index) { ESP_LOGD(TAG, "%s lwip-netif:%p", __func__, p_netif); if (!p_netif) { @@ -1381,9 +1400,9 @@ static void esp_netif_nd6_cb(struct netif *p_netif, uint8_t ip_idex) esp_netif_ip6_info_t ip6_info; ip6_addr_t lwip_ip6_info; //notify event - ip_event_got_ip6_t evt = { .esp_netif = p_netif->state, .if_index = -1 }; + ip_event_got_ip6_t evt = { .esp_netif = p_netif->state, .if_index = -1, .ip_index = ip_index }; - ip6_addr_set(&lwip_ip6_info, ip_2_ip6(&p_netif->ip6_addr[ip_idex])); + ip6_addr_set(&lwip_ip6_info, ip_2_ip6(&p_netif->ip6_addr[ip_index])); #if LWIP_IPV6_SCOPES memcpy(&ip6_info.ip, &lwip_ip6_info, sizeof(esp_ip6_addr_t)); #else diff --git a/examples/common_components/protocol_examples_common/CMakeLists.txt b/examples/common_components/protocol_examples_common/CMakeLists.txt index 7c4ebe34d6..5c19957ba6 100644 --- a/examples/common_components/protocol_examples_common/CMakeLists.txt +++ b/examples/common_components/protocol_examples_common/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "connect.c" "stdin_out.c" +idf_component_register(SRCS "connect.c" "stdin_out.c" "addr_from_stdin.c" INCLUDE_DIRS "include" PRIV_REQUIRES esp_netif ) diff --git a/examples/common_components/protocol_examples_common/Kconfig.projbuild b/examples/common_components/protocol_examples_common/Kconfig.projbuild index 6965c46a02..f47b8a6af5 100644 --- a/examples/common_components/protocol_examples_common/Kconfig.projbuild +++ b/examples/common_components/protocol_examples_common/Kconfig.projbuild @@ -175,9 +175,44 @@ menu "Example Connection Configuration" endif config EXAMPLE_CONNECT_IPV6 - bool "Obtain IPv6 link-local address" + bool "Obtain IPv6 address" default y help - By default, examples will wait until IPv4 and IPv6 addresses are obtained. + By default, examples will wait until IPv4 and IPv6 local link addresses are obtained. Disable this option if the network does not support IPv6. + Choose the preferred IPv6 address type if the connection code should wait until other than + the local link address gets assigned. + + if EXAMPLE_CONNECT_IPV6 + choice EXAMPLE_CONNECT_PREFERRED_IPV6 + prompt "Preferred IPv6 Type" + default EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK + help + Select which kind of IPv6 address the connect logic waits for. + + config EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK + bool "Local Link Address" + help + Blocks until Local link address assigned. + + config EXAMPLE_CONNECT_IPV6_PREF_GLOBAL + bool "Global Address" + help + Blocks until Global address assigned. + + config EXAMPLE_CONNECT_IPV6_PREF_SITE_LOCAL + bool "Site Local Address" + help + Blocks until Site link address assigned. + + config EXAMPLE_CONNECT_IPV6_PREF_UNIQUE_LOCAL + bool "Unique Local Link Address" + help + Blocks until Unique local address assigned. + + endchoice + + endif + + endmenu diff --git a/examples/common_components/protocol_examples_common/addr_from_stdin.c b/examples/common_components/protocol_examples_common/addr_from_stdin.c new file mode 100644 index 0000000000..460fe9a2ec --- /dev/null +++ b/examples/common_components/protocol_examples_common/addr_from_stdin.c @@ -0,0 +1,65 @@ +#include +#include "esp_system.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "protocol_examples_common.h" + +#include "lwip/sockets.h" +#include +#include + +#define HOST_IP_SIZE 128 + +esp_err_t get_addr_from_stdin(int port, int sock_type, int *ip_protocol, int *addr_family, struct sockaddr_in6 *dest_addr) +{ + char host_ip[HOST_IP_SIZE]; + int len; + static bool already_init = false; + + // this function could be called multiple times -> make sure UART init runs only once + if (!already_init) { + example_configure_stdin_stdout(); + already_init = true; + } + + // ignore empty or LF only string (could receive from DUT class) + do { + fgets(host_ip, HOST_IP_SIZE, stdin); + len = strlen(host_ip); + } while (len<=1 && host_ip[0] == '\n'); + host_ip[len - 1] = '\0'; + + struct addrinfo hints, *addr_list, *cur; + memset( &hints, 0, sizeof( hints ) ); + + // run getaddrinfo() to decide on the IP protocol + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = sock_type; + hints.ai_protocol = IPPROTO_TCP; + if( getaddrinfo( host_ip, NULL, &hints, &addr_list ) != 0 ) { + return ESP_FAIL; + } + for( cur = addr_list; cur != NULL; cur = cur->ai_next ) { + memcpy(dest_addr, cur->ai_addr, sizeof(*dest_addr)); + if (cur->ai_family == AF_INET) { + *ip_protocol = IPPROTO_IP; + *addr_family = AF_INET; + // add port number and return on first IPv4 match + ((struct sockaddr_in*)dest_addr)->sin_port = htons(port); + freeaddrinfo( addr_list ); + return ESP_OK; + + } else if (cur->ai_family == AF_INET6) { + *ip_protocol = IPPROTO_IPV6; + *addr_family = AF_INET6; + // add port and interface number and return on first IPv6 match + dest_addr->sin6_port = htons(port); + dest_addr->sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE); + freeaddrinfo( addr_list ); + return ESP_OK; + } + } + // no match found + freeaddrinfo( addr_list ); + return ESP_FAIL; +} \ No newline at end of file diff --git a/examples/common_components/protocol_examples_common/connect.c b/examples/common_components/protocol_examples_common/connect.c index 9713edda2c..e845837cb6 100644 --- a/examples/common_components/protocol_examples_common/connect.c +++ b/examples/common_components/protocol_examples_common/connect.c @@ -30,6 +30,17 @@ #ifdef CONFIG_EXAMPLE_CONNECT_IPV6 #define CONNECTED_BITS (GOT_IPV4_BIT | GOT_IPV6_BIT) + +#if defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK) +#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_LINK_LOCAL +#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_GLOBAL) +#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_GLOBAL +#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_SITE_LOCAL) +#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_SITE_LOCAL +#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_UNIQUE_LOCAL) +#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_UNIQUE_LOCAL +#endif // if-elif CONFIG_EXAMPLE_CONNECT_IPV6_PREF_... + #else #define CONNECTED_BITS (GOT_IPV4_BIT) #endif @@ -41,6 +52,16 @@ static esp_netif_t *s_example_esp_netif = NULL; #ifdef CONFIG_EXAMPLE_CONNECT_IPV6 static esp_ip6_addr_t s_ipv6_addr; + +/* 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" + }; #endif static const char *TAG = "example_connect"; @@ -70,9 +91,12 @@ static void on_got_ipv6(void *arg, esp_event_base_t event_base, ESP_LOGD(TAG, "Got IPv6 from another netif: ignored"); return; } - ESP_LOGI(TAG, "Got IPv6 event!"); - memcpy(&s_ipv6_addr, &event->ip6_info.ip, sizeof(s_ipv6_addr)); - xEventGroupSetBits(s_connect_event_group, GOT_IPV6_BIT); + esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&event->ip6_info.ip); + ESP_LOGI(TAG, "Got IPv6 address: " IPV6STR ", type: %s", IPV62STR(event->ip6_info.ip), s_ipv6_addr_types[ipv6_type]); + if (ipv6_type == EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE) { + memcpy(&s_ipv6_addr, &event->ip6_info.ip, sizeof(s_ipv6_addr)); + xEventGroupSetBits(s_connect_event_group, GOT_IPV6_BIT); + } } #endif // CONFIG_EXAMPLE_CONNECT_IPV6 diff --git a/examples/common_components/protocol_examples_common/include/addr_from_stdin.h b/examples/common_components/protocol_examples_common/include/addr_from_stdin.h new file mode 100644 index 0000000000..ab5043aed6 --- /dev/null +++ b/examples/common_components/protocol_examples_common/include/addr_from_stdin.h @@ -0,0 +1,44 @@ +/* Common utilities for socket address input interface: + The API get_addr_from_stdin() is mainly used by socket client examples which read IP address from stdin (if configured). + This option is typically used in the CI, but could be enabled in the project configuration. + In that case this component is used to receive a string that is evaluated and processed to output + socket structures to open a connectio + 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 + +#include "lwip/sys.h" +#include +#include + +/** + * @brief Read and evaluate IP address from stdin + * + * This API reads stdin and parses the input address using getaddrinfo() + * to fill in struct sockaddr_in6 (for both IPv4 and IPv6) used to open + * a socket. IP protocol is guessed from the IP address string. + * + * @param[in] port port number of expected connection + * @param[in] sock_type expected protocol: SOCK_STREAM or SOCK_DGRAM + * @param[out] ip_protocol resultant IP protocol: IPPROTO_IP or IPPROTO_IP6 + * @param[out] addr_family resultant address family: AF_INET or AF_INET6 + * @param[out] dest_addr sockaddr_in6 structure (for both IPv4 and IPv6) + * @return ESP_OK on success, ESP_FAIL otherwise + */ +esp_err_t get_addr_from_stdin(int port, int sock_type, + int *ip_protocol, + int *addr_family, + struct sockaddr_in6 *dest_addr); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/examples/protocols/sockets/README.md b/examples/protocols/sockets/README.md index 9d52415321..00f3f91755 100644 --- a/examples/protocols/sockets/README.md +++ b/examples/protocols/sockets/README.md @@ -63,15 +63,36 @@ nc -l 192.168.0.167 -p 3333 ``` ### Python scripts -Each script contains port number, IP version (IPv4 or IPv6) and IP address (only clients) that has to be altered to match the values used by the application. Example: +Each script in the application directory could be used to exercise the socket communication. +Command line arguments such as IP version (IPv4 or IPv6) and IP address and payload data (only clients) shall be supplied. +In addition to that, port number and interface id are hardcoded in the scripts and might need to be altered to match the values used by the application. Example: ``` -PORT = 3333; -IP_VERSION = 'IPv4' -IPV4 = '192.168.0.167' -IPV6 = 'FE80::32AE:A4FF:FE80:5288' +PORT = 3333 +INTERFACE = 'en0' ``` +### Note about IPv6 addresses + +Examples are configured to obtain multiple IPv6 addresses. The actual behavior may differ depending on the local network, typically the ESP gets assigned these two addresses + +* Local Link address + +* Unique Local address + +The value and type of the IPv6 address is displayed in the terminal, for example: + +Please make sure that when using the Local Link address, an interface id is included in the configuration: + +* In the embedded code +``` + dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(esp_netif_instance); +``` +* On the host + + - Interface name suffix is present when passing the address as a string, for example `fe80::260a:XXX:XXX:XXX%en0` + - The interface id is present when passing the endpoint as tupple, for example `socket.connect(('fd00::260a:XXXX:XXXX:XXXX', 3333, 0, 3))` + ## Hardware Required This example can be run on any commonly available ESP32 development board. diff --git a/examples/protocols/sockets/scripts/tcpclient.py b/examples/protocols/sockets/scripts/tcpclient.py deleted file mode 100644 index 84332e9382..0000000000 --- a/examples/protocols/sockets/scripts/tcpclient.py +++ /dev/null @@ -1,52 +0,0 @@ -# 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. - -# -*- coding: utf-8 -*- - -from builtins import input -import socket -import sys - -# ----------- Config ---------- -PORT = 3333 -IP_VERSION = 'IPv4' -IPV4 = '192.168.0.167' -IPV6 = 'FE80::32AE:A4FF:FE80:5288' -# ------------------------------- - -if IP_VERSION == 'IPv4': - family_addr = socket.AF_INET - host = IPV4 -elif IP_VERSION == 'IPv6': - family_addr = socket.AF_INET6 - host = IPV6 -else: - print('IP_VERSION must be IPv4 or IPv6') - sys.exit(1) - -try: - sock = socket.socket(family_addr, socket.SOCK_STREAM) -except socket.error as msg: - print('Could not create socket: ' + str(msg[0]) + ': ' + msg[1]) - sys.exit(1) - -try: - sock.connect((host, PORT)) -except socket.error as msg: - print('Could not open socket: ', msg) - sock.close() - sys.exit(1) - -while True: - msg = input('Enter message to send: ') - assert isinstance(msg, str) - msg = msg.encode() - sock.sendall(msg) - data = sock.recv(1024) - if not data: - break - print('Reply: ' + data.decode()) -sock.close() diff --git a/examples/protocols/sockets/scripts/tcpserver.py b/examples/protocols/sockets/scripts/tcpserver.py deleted file mode 100644 index a5b17c1d1b..0000000000 --- a/examples/protocols/sockets/scripts/tcpserver.py +++ /dev/null @@ -1,54 +0,0 @@ -# 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. - -# -*- coding: utf-8 -*- - -import socket -import sys - -# ----------- Config ---------- -IP_VERSION = 'IPv4' -PORT = 3333 -# ------------------------------- - -if IP_VERSION == 'IPv4': - family_addr = socket.AF_INET -elif IP_VERSION == 'IPv6': - family_addr = socket.AF_INET6 -else: - print('IP_VERSION must be IPv4 or IPv6') - sys.exit(1) - - -try: - sock = socket.socket(family_addr, socket.SOCK_STREAM) -except socket.error as msg: - print('Error: ' + str(msg[0]) + ': ' + msg[1]) - sys.exit(1) - -print('Socket created') - -try: - sock.bind(('', PORT)) - print('Socket binded') - sock.listen(1) - print('Socket listening') - conn, addr = sock.accept() - print('Connected by', addr) -except socket.error as msg: - print('Error: ' + str(msg[0]) + ': ' + msg[1]) - sock.close() - sys.exit(1) - -while True: - data = conn.recv(128) - if not data: - break - data = data.decode() - print('Received data: ' + data) - reply = 'OK: ' + data - conn.send(reply.encode()) -conn.close() diff --git a/examples/protocols/sockets/scripts/udpclient.py b/examples/protocols/sockets/scripts/udpclient.py deleted file mode 100644 index 342cadbfe8..0000000000 --- a/examples/protocols/sockets/scripts/udpclient.py +++ /dev/null @@ -1,47 +0,0 @@ -# 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. - -# -*- coding: utf-8 -*- - -from builtins import input -import socket -import sys - -# ----------- Config ---------- -PORT = 3333 -IP_VERSION = 'IPv4' -IPV4 = '192.168.0.167' -IPV6 = 'FE80::32AE:A4FF:FE80:5288' -# ------------------------------- - -if IP_VERSION == 'IPv4': - host = IPV4 - family_addr = socket.AF_INET -elif IP_VERSION == 'IPv6': - host = IPV6 - family_addr = socket.AF_INET6 -else: - print('IP_VERSION must be IPv4 or IPv6') - sys.exit(1) - - -try: - sock = socket.socket(family_addr, socket.SOCK_DGRAM) -except socket.error: - print('Failed to create socket') - sys.exit() - -while True: - msg = input('Enter message to send : ') - try: - sock.sendto(msg.encode(), (host, PORT)) - reply, addr = sock.recvfrom(128) - if not reply: - break - print('Reply[' + addr[0] + ':' + str(addr[1]) + '] - ' + str(reply)) - except socket.error as msg: - print('Error Code : ' + str(msg[0]) + ' Message: ' + msg[1]) - sys.exit() diff --git a/examples/protocols/sockets/scripts/udpserver.py b/examples/protocols/sockets/scripts/udpserver.py deleted file mode 100644 index 7c3676d9f6..0000000000 --- a/examples/protocols/sockets/scripts/udpserver.py +++ /dev/null @@ -1,52 +0,0 @@ -# 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. - -# -*- coding: utf-8 -*- - -import socket -import sys - -# ----------- Config ---------- -IP_VERSION = 'IPv4' -PORT = 3333 -# ------------------------------- - -if IP_VERSION == 'IPv4': - family_addr = socket.AF_INET -elif IP_VERSION == 'IPv6': - family_addr = socket.AF_INET6 -else: - print('IP_VERSION must be IPv4 or IPv6') - sys.exit(1) - - -try: - sock = socket.socket(family_addr, socket.SOCK_DGRAM) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -except socket.error as msg: - print('Failed to create socket. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]) - sys.exit() - -try: - sock.bind(('', PORT)) -except socket.error as msg: - print('Bind failed. Error: ' + str(msg[0]) + ': ' + msg[1]) - sys.exit() - -while True: - try: - print('Waiting for data...') - data, addr = sock.recvfrom(1024) - if not data: - break - data = data.decode() - print('Reply[' + addr[0] + ':' + str(addr[1]) + '] - ' + data) - reply = 'OK ' + data - sock.sendto(reply.encode(), addr) - except socket.error as msg: - print('Error Code : ' + str(msg[0]) + ' Message ' + msg[1]) - -sock.close() diff --git a/examples/protocols/sockets/tcp_client/README.md b/examples/protocols/sockets/tcp_client/README.md index af3348ed39..4c05a7080f 100644 --- a/examples/protocols/sockets/tcp_client/README.md +++ b/examples/protocols/sockets/tcp_client/README.md @@ -17,16 +17,17 @@ In addition to those tools, simple Python scripts can be found under sockets/scr ### TCP server using netcat ``` -nc -l 192.168.0.167 -p 3333 +nc -l 192.168.0.167 3333 ``` ### Python scripts -Script tcpserver.py contains configuration for port number and IP version (IPv4 or IPv6) that has to be altered to match the values used by the application. Example: +Script example_test.py could be used as a counter part to the tcp-client project, ip protocol name (IPv4 or IPv6) shall be stated as argument. Example: ``` -IP_VERSION = 'IPv4' -PORT = 3333; +python example_test.py IPv4 ``` +Note that this script is used in automated tests, as well, so the IDF test framework packages need to be imported; +please add `$IDF_PATH/tools/ci/python_packages` to `PYTHONPATH`. ## Hardware Required diff --git a/examples/protocols/sockets/tcp_client/example_test.py b/examples/protocols/sockets/tcp_client/example_test.py new file mode 100644 index 0000000000..941d4f3941 --- /dev/null +++ b/examples/protocols/sockets/tcp_client/example_test.py @@ -0,0 +1,125 @@ +# 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. + +# -*- coding: utf-8 -*- + +from __future__ import print_function +from __future__ import unicode_literals +from builtins import input +import os +import re +import sys +import netifaces +import socket +from threading import Thread, Event +import ttfw_idf + + +# ----------- Config ---------- +PORT = 3333 +INTERFACE = 'eth0' +# ------------------------------- + + +def get_my_ip(type): + for i in netifaces.ifaddresses(INTERFACE)[type]: + return i['addr'].replace("%{}".format(INTERFACE), "") + + +class TcpServer: + + def __init__(self, port, family_addr, persist=False): + self.port = port + self.socket = socket.socket(family_addr, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.settimeout(10.0) + self.shutdown = Event() + self.persist = persist + self.family_addr = family_addr + + def __enter__(self): + try: + self.socket.bind(('', self.port)) + except socket.error as e: + print("Bind failed:{}".format(e)) + raise + self.socket.listen(1) + + self.server_thread = Thread(target=self.run_server) + self.server_thread.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + if self.persist: + sock = socket.socket(self.family_addr, socket.SOCK_STREAM) + sock.connect(('localhost', self.port)) + sock.sendall(b'Stop', ) + sock.close() + self.shutdown.set() + self.shutdown.set() + self.server_thread.join() + self.socket.close() + + def run_server(self): + while not self.shutdown.is_set(): + try: + conn, address = self.socket.accept() # accept new connection + print("Connection from: {}".format(address)) + conn.setblocking(1) + data = conn.recv(1024) + if not data: + return + data = data.decode() + print('Received data: ' + data) + reply = 'OK: ' + data + conn.send(reply.encode()) + conn.close() + except socket.error as e: + print("Running server failed:{}".format(e)) + raise + if not self.persist: + break + + +@ttfw_idf.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_socket(env, extra_data): + """ + steps: + 1. join AP + 2. have the board connect to the server + 3. send and receive data + """ + dut1 = env.get_dut("tcp_client", "examples/protocols/sockets/tcp_client", dut_class=ttfw_idf.ESP32DUT) + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "tcp_client.bin") + bin_size = os.path.getsize(binary_file) + ttfw_idf.log_performance("tcp_client_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.check_performance("tcp_client_bin_size", bin_size // 1024, dut1.TARGET) + + # start test + dut1.start_app() + + data = dut1.expect(re.compile(r" IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"), timeout=30) + print("Connected with IPv4: {}".format(data[0])) + + # test IPv4 + with TcpServer(PORT, socket.AF_INET): + dut1.write(get_my_ip(netifaces.AF_INET)) + dut1.expect(re.compile(r"OK: Message from ESP32")) + # test IPv6 + with TcpServer(PORT, socket.AF_INET6): + dut1.write(get_my_ip(netifaces.AF_INET6)) + dut1.expect(re.compile(r"OK: Message from ESP32")) + + +if __name__ == '__main__': + if sys.argv[1:] and sys.argv[1].startswith("IPv"): # if additional arguments provided: + # Usage: example_test.py + family_addr = socket.AF_INET6 if sys.argv[1] == "IPv6" else socket.AF_INET + with TcpServer(PORT, family_addr, persist=True) as s: + print(input("Press Enter stop the server...")) + else: + test_examples_protocol_socket() diff --git a/examples/protocols/sockets/tcp_client/main/Kconfig.projbuild b/examples/protocols/sockets/tcp_client/main/Kconfig.projbuild index beabfa6f48..a1f5cea4a3 100644 --- a/examples/protocols/sockets/tcp_client/main/Kconfig.projbuild +++ b/examples/protocols/sockets/tcp_client/main/Kconfig.projbuild @@ -2,6 +2,7 @@ menu "Example Configuration" choice EXAMPLE_IP_MODE prompt "IP Version" + depends on EXAMPLE_SOCKET_IP_INPUT_STRING help Example can use either IPV4 or IPV6. @@ -34,4 +35,17 @@ menu "Example Configuration" help The remote port to which the client example will connect to. + choice EXAMPLE_SOCKET_IP_INPUT + prompt "Socket example source" + default EXAMPLE_SOCKET_IP_INPUT_STRING + help + Selects the input source of the IP used in the example. + + config EXAMPLE_SOCKET_IP_INPUT_STRING + bool "From string" + + config EXAMPLE_SOCKET_IP_INPUT_STDIN + bool "From stdin" + endchoice + endmenu diff --git a/examples/protocols/sockets/tcp_client/main/tcp_client.c b/examples/protocols/sockets/tcp_client/main/tcp_client.c index 2d2e515a0e..7425ba9603 100644 --- a/examples/protocols/sockets/tcp_client/main/tcp_client.c +++ b/examples/protocols/sockets/tcp_client/main/tcp_client.c @@ -18,17 +18,17 @@ #include "nvs_flash.h" #include "esp_netif.h" #include "protocol_examples_common.h" - +#include "addr_from_stdin.h" #include "lwip/err.h" #include "lwip/sockets.h" -#include "lwip/sys.h" -#include -#ifdef CONFIG_EXAMPLE_IPV4 +#if defined(CONFIG_EXAMPLE_IPV4) #define HOST_IP_ADDR CONFIG_EXAMPLE_IPV4_ADDR -#else +#elif defined(CONFIG_EXAMPLE_IPV6) #define HOST_IP_ADDR CONFIG_EXAMPLE_IPV6_ADDR +#else +#define HOST_IP_ADDR "" #endif #define PORT CONFIG_EXAMPLE_PORT @@ -39,38 +39,38 @@ static const char *payload = "Message from ESP32 "; static void tcp_client_task(void *pvParameters) { char rx_buffer[128]; - char addr_str[128]; - int addr_family; - int ip_protocol; + char host_ip[] = HOST_IP_ADDR; + int addr_family = 0; + int ip_protocol = 0; while (1) { - -#ifdef CONFIG_EXAMPLE_IPV4 +#if defined(CONFIG_EXAMPLE_IPV4) struct sockaddr_in dest_addr; - dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR); + dest_addr.sin_addr.s_addr = inet_addr(host_ip); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(PORT); addr_family = AF_INET; ip_protocol = IPPROTO_IP; - inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1); -#else // IPV6 - struct sockaddr_in6 dest_addr; - inet6_aton(HOST_IP_ADDR, &dest_addr.sin6_addr); +#elif defined(CONFIG_EXAMPLE_IPV6) + struct sockaddr_in6 dest_addr = { 0 }; + inet6_aton(host_ip, &dest_addr.sin6_addr); dest_addr.sin6_family = AF_INET6; dest_addr.sin6_port = htons(PORT); + dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE); addr_family = AF_INET6; ip_protocol = IPPROTO_IPV6; - inet6_ntoa_r(dest_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); +#elif defined(CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN) + struct sockaddr_in6 dest_addr = { 0 }; + ESP_ERROR_CHECK(get_addr_from_stdin(PORT, SOCK_STREAM, &ip_protocol, &addr_family, &dest_addr)); #endif - int sock = socket(addr_family, SOCK_STREAM, ip_protocol); if (sock < 0) { ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); break; } - ESP_LOGI(TAG, "Socket created, connecting to %s:%d", HOST_IP_ADDR, PORT); + ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, PORT); - int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); + int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6)); if (err != 0) { ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno); break; @@ -93,7 +93,7 @@ static void tcp_client_task(void *pvParameters) // Data received else { rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string - ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str); + ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip); ESP_LOGI(TAG, "%s", rx_buffer); } diff --git a/examples/protocols/sockets/tcp_client/sdkconfig.ci b/examples/protocols/sockets/tcp_client/sdkconfig.ci new file mode 100644 index 0000000000..62ac57ea21 --- /dev/null +++ b/examples/protocols/sockets/tcp_client/sdkconfig.ci @@ -0,0 +1 @@ +CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN=y \ No newline at end of file diff --git a/examples/protocols/sockets/tcp_server/README.md b/examples/protocols/sockets/tcp_server/README.md index ca72ff01bc..94201cf296 100644 --- a/examples/protocols/sockets/tcp_server/README.md +++ b/examples/protocols/sockets/tcp_server/README.md @@ -21,14 +21,14 @@ nc 192.168.0.167 3333 ``` ### Python scripts -Script tcpclient.py contains configuration for port number, IP version (IPv4 or IPv6) and IP address that has to be altered to match the values used by the application. Example: +Script example_test.py could be used as a counter part to the tcp-server application, +IP address and the message to be send to the server shall be stated as arguments. Example: ``` -PORT = 3333; -IP_VERSION = 'IPv4' -IPV4 = '192.168.0.167' -IPV6 = 'FE80::32AE:A4FF:FE80:5288' +python example_test.py 192.168.0.167 Message ``` +Note that this script is used in automated tests, as well, so the IDF test framework packages need to be imported; +please add `$IDF_PATH/tools/ci/python_packages` to `PYTHONPATH`. ## Hardware Required diff --git a/examples/protocols/sockets/tcp_server/example_test.py b/examples/protocols/sockets/tcp_server/example_test.py new file mode 100644 index 0000000000..db6cece26e --- /dev/null +++ b/examples/protocols/sockets/tcp_server/example_test.py @@ -0,0 +1,88 @@ +# 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. + +# -*- coding: utf-8 -*- + +from __future__ import print_function +from __future__ import unicode_literals +import os +import sys +import re +import socket +import ttfw_idf + + +# ----------- Config ---------- +PORT = 3333 +INTERFACE = 'eth0' +# ------------------------------- + + +def tcp_client(address, payload): + for res in socket.getaddrinfo(address, PORT, socket.AF_UNSPEC, + socket.SOCK_STREAM, 0, socket.AI_PASSIVE): + family_addr, socktype, proto, canonname, addr = res + try: + sock = socket.socket(family_addr, socket.SOCK_STREAM) + except socket.error as msg: + print('Could not create socket: ' + str(msg[0]) + ': ' + msg[1]) + raise + try: + sock.connect(addr) + except socket.error as msg: + print('Could not open socket: ', msg) + sock.close() + raise + sock.sendall(payload) + data = sock.recv(1024) + if not data: + return + print('Reply : ' + data.decode()) + sock.close() + return data + + +@ttfw_idf.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_socket(env, extra_data): + MESSAGE = "Data to ESP" + """ + steps: + 1. join AP + 2. have the board connect to the server + 3. send and receive data + """ + dut1 = env.get_dut("tcp_client", "examples/protocols/sockets/tcp_server", dut_class=ttfw_idf.ESP32DUT) + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "tcp_server.bin") + bin_size = os.path.getsize(binary_file) + ttfw_idf.log_performance("tcp_server_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.check_performance("tcp_server_bin_size", bin_size // 1024, dut1.TARGET) + + # start test + dut1.start_app() + + ipv4 = dut1.expect(re.compile(r" IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"), timeout=30)[0] + ipv6 = dut1.expect(re.compile(r" IPv6 address: ([0-9A-Fa-f\:]+)"), timeout=30)[0] + print("Connected with IPv4={} and IPv6={}".format(ipv4, ipv6)) + + # test IPv4 + received = tcp_client(ipv4, MESSAGE) + if not received == MESSAGE: + raise + dut1.expect(MESSAGE) + # test IPv6 + received = tcp_client("{}%{}".format(ipv6, INTERFACE), MESSAGE) + if not received == MESSAGE: + raise + dut1.expect(MESSAGE) + + +if __name__ == '__main__': + if sys.argv[2:]: # if two arguments provided: + # Usage: example_test.py + tcp_client(sys.argv[1], sys.argv[2]) + else: # otherwise run standard example test as in the CI + test_examples_protocol_socket() diff --git a/examples/protocols/sockets/tcp_server/main/Kconfig.projbuild b/examples/protocols/sockets/tcp_server/main/Kconfig.projbuild index 822c019be7..65f38224c6 100644 --- a/examples/protocols/sockets/tcp_server/main/Kconfig.projbuild +++ b/examples/protocols/sockets/tcp_server/main/Kconfig.projbuild @@ -1,18 +1,13 @@ menu "Example Configuration" - choice EXAMPLE_IP_MODE - prompt "IP Version" - help - Example can use either IPV4 or IPV6. + config EXAMPLE_IPV4 + bool "IPV4" + default y - config EXAMPLE_IPV4 - bool "IPV4" - - config EXAMPLE_IPV6 - bool "IPV6" - select EXAMPLE_CONNECT_IPV6 - - endchoice + config EXAMPLE_IPV6 + bool "IPV6" + default n + select EXAMPLE_CONNECT_IPV6 config EXAMPLE_PORT int "Port" diff --git a/examples/protocols/sockets/tcp_server/main/tcp_server.c b/examples/protocols/sockets/tcp_server/main/tcp_server.c index 7b5a1d1ae2..18284fd3ed 100644 --- a/examples/protocols/sockets/tcp_server/main/tcp_server.c +++ b/examples/protocols/sockets/tcp_server/main/tcp_server.c @@ -60,27 +60,22 @@ static void do_retransmit(const int sock) static void tcp_server_task(void *pvParameters) { char addr_str[128]; - int addr_family; - int ip_protocol; - - -#ifdef CONFIG_EXAMPLE_IPV4 - struct sockaddr_in dest_addr; - dest_addr.sin_addr.s_addr = htonl(INADDR_ANY); - dest_addr.sin_family = AF_INET; - dest_addr.sin_port = htons(PORT); - addr_family = AF_INET; - ip_protocol = IPPROTO_IP; - inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1); -#else // IPV6 + int addr_family = (int)pvParameters; + int ip_protocol = 0; struct sockaddr_in6 dest_addr; - bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un)); - dest_addr.sin6_family = AF_INET6; - dest_addr.sin6_port = htons(PORT); - addr_family = AF_INET6; - ip_protocol = IPPROTO_IPV6; - inet6_ntoa_r(dest_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); -#endif + + if (addr_family == AF_INET) { + struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr; + dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY); + dest_addr_ip4->sin_family = AF_INET; + dest_addr_ip4->sin_port = htons(PORT); + ip_protocol = IPPROTO_IP; + } else if (addr_family == AF_INET6) { + bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un)); + dest_addr.sin6_family = AF_INET6; + dest_addr.sin6_port = htons(PORT); + ip_protocol = IPPROTO_IPV6; + } int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol); if (listen_sock < 0) { @@ -88,11 +83,20 @@ static void tcp_server_task(void *pvParameters) vTaskDelete(NULL); return; } +#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6) + // Note that by default IPV6 binds to both protocols, it is must be disabled + // if both protocols used at the same time (used in CI) + int opt = 1; + setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); +#endif + ESP_LOGI(TAG, "Socket created"); int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); if (err != 0) { ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); + ESP_LOGE(TAG, "IPPROTO: %d", addr_family); goto CLEAN_UP; } ESP_LOGI(TAG, "Socket bound, port %d", PORT); @@ -146,5 +150,10 @@ void app_main(void) */ ESP_ERROR_CHECK(example_connect()); - xTaskCreate(tcp_server_task, "tcp_server", 4096, NULL, 5, NULL); +#ifdef CONFIG_EXAMPLE_IPV4 + xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)AF_INET, 5, NULL); +#endif +#ifdef CONFIG_EXAMPLE_IPV6 + xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)AF_INET6, 5, NULL); +#endif } diff --git a/examples/protocols/sockets/tcp_server/sdkconfig.ci b/examples/protocols/sockets/tcp_server/sdkconfig.ci new file mode 100644 index 0000000000..55acf406fb --- /dev/null +++ b/examples/protocols/sockets/tcp_server/sdkconfig.ci @@ -0,0 +1,2 @@ +CONFIG_EXAMPLE_IPV4=y +CONFIG_EXAMPLE_IPV6=y \ No newline at end of file diff --git a/examples/protocols/sockets/udp_client/README.md b/examples/protocols/sockets/udp_client/README.md index 85bdaa8304..4604a8d444 100644 --- a/examples/protocols/sockets/udp_client/README.md +++ b/examples/protocols/sockets/udp_client/README.md @@ -27,16 +27,18 @@ echo "Hello from PC" | nc -w1 -u 192.168.0.167 3333 ### UDP server using netcat ``` -nc -u -l 192.168.0.167 -p 3333 +nc -u -l 192.168.0.167 3333 ``` ### Python scripts -Script udpserver.py contains configuration for port number and IP version (IPv4 or IPv6) that has to be altered to match the values used by the application. Example: +Script example_test.py could be used as a counter part to the udp-client application, ip protocol name (IPv4 or IPv6) shall be stated as argument. Example: ``` -IP_VERSION = 'IPv4' -PORT = 3333; +python example_test.py IPv4 ``` +Note that this script is used in automated tests, as well, so the IDF test framework packages need to be imported; +please add `$IDF_PATH/tools/ci/python_packages` to `PYTHONPATH`. + ## Hardware Required diff --git a/examples/protocols/sockets/udp_client/example_test.py b/examples/protocols/sockets/udp_client/example_test.py new file mode 100644 index 0000000000..755dd993b3 --- /dev/null +++ b/examples/protocols/sockets/udp_client/example_test.py @@ -0,0 +1,118 @@ +# 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. + +# -*- coding: utf-8 -*- + +from __future__ import print_function +from __future__ import unicode_literals +from builtins import input +import os +import re +import netifaces +import socket +from threading import Thread, Event +import ttfw_idf +import sys + + +# ----------- Config ---------- +PORT = 3333 +INTERFACE = 'eth0' +# ------------------------------- + + +def get_my_ip(type): + for i in netifaces.ifaddresses(INTERFACE)[type]: + return i['addr'].replace("%{}".format(INTERFACE), "") + + +class UdpServer: + + def __init__(self, port, family_addr, persist=False): + self.port = port + self.family_addr = family_addr + self.socket = socket.socket(family_addr, socket.SOCK_DGRAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.settimeout(30.0) + self.shutdown = Event() + self.persist = persist + + def __enter__(self): + try: + self.socket.bind(('', self.port)) + except socket.error as e: + print("Bind failed:{}".format(e)) + raise + + self.server_thread = Thread(target=self.run_server) + self.server_thread.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + if self.persist: + sock = socket.socket(self.family_addr, socket.SOCK_DGRAM) + sock.sendto(b'Stop', ('localhost', self.port)) + sock.close() + self.shutdown.set() + self.server_thread.join() + self.socket.close() + + def run_server(self): + while not self.shutdown.is_set(): + try: + data, addr = self.socket.recvfrom(1024) + if not data: + return + data = data.decode() + print('Reply[' + addr[0] + ':' + str(addr[1]) + '] - ' + data) + reply = 'OK: ' + data + self.socket.sendto(reply.encode(), addr) + except socket.error as e: + print("Running server failed:{}".format(e)) + raise + if not self.persist: + break + + +@ttfw_idf.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_socket(env, extra_data): + """ + steps: + 1. join AP + 2. have the board connect to the server + 3. send and receive data + """ + dut1 = env.get_dut("udp_client", "examples/protocols/sockets/udp_client", dut_class=ttfw_idf.ESP32DUT) + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "udp_client.bin") + bin_size = os.path.getsize(binary_file) + ttfw_idf.log_performance("udp_client_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.check_performance("udp_client_bin_size", bin_size // 1024, dut1.TARGET) + + # start test + dut1.start_app() + + data = dut1.expect(re.compile(r" IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"), timeout=30) + print("Connected with IPv4: {}".format(data[0])) + + # test IPv4 + with UdpServer(PORT, socket.AF_INET): + dut1.write(get_my_ip(netifaces.AF_INET)) + dut1.expect(re.compile(r"OK: Message from ESP32")) + # test IPv6 + with UdpServer(PORT, socket.AF_INET6): + dut1.write(get_my_ip(netifaces.AF_INET6)) + dut1.expect(re.compile(r"OK: Message from ESP32")) + + +if __name__ == '__main__': + if sys.argv[1:] and sys.argv[1].startswith("IPv"): # if additional arguments provided: + # Usage: example_test.py + family_addr = socket.AF_INET6 if sys.argv[1] == "IPv6" else socket.AF_INET + with UdpServer(PORT, family_addr, persist=True) as s: + print(input("Press Enter stop the server...")) + else: + test_examples_protocol_socket() diff --git a/examples/protocols/sockets/udp_client/main/Kconfig.projbuild b/examples/protocols/sockets/udp_client/main/Kconfig.projbuild index d9b77d1857..e643498f0a 100644 --- a/examples/protocols/sockets/udp_client/main/Kconfig.projbuild +++ b/examples/protocols/sockets/udp_client/main/Kconfig.projbuild @@ -2,6 +2,7 @@ menu "Example Configuration" choice EXAMPLE_IP_MODE prompt "IP Version" + depends on EXAMPLE_SOCKET_IP_INPUT_STRING help Example can use either IPV4 or IPV6. @@ -35,4 +36,17 @@ menu "Example Configuration" help The remote port to which the client example will send data. + choice EXAMPLE_SOCKET_IP_INPUT + prompt "Socket example source" + default EXAMPLE_SOCKET_IP_INPUT_STRING + help + Selects the input source of the IP used in the example. + + config EXAMPLE_SOCKET_IP_INPUT_STRING + bool "From string" + + config EXAMPLE_SOCKET_IP_INPUT_STDIN + bool "From stdin" + endchoice + endmenu diff --git a/examples/protocols/sockets/udp_client/main/udp_client.c b/examples/protocols/sockets/udp_client/main/udp_client.c index 402d272740..5f153bc10f 100644 --- a/examples/protocols/sockets/udp_client/main/udp_client.c +++ b/examples/protocols/sockets/udp_client/main/udp_client.c @@ -23,12 +23,14 @@ #include "lwip/sockets.h" #include "lwip/sys.h" #include +#include "addr_from_stdin.h" - -#ifdef CONFIG_EXAMPLE_IPV4 +#if defined(CONFIG_EXAMPLE_IPV4) #define HOST_IP_ADDR CONFIG_EXAMPLE_IPV4_ADDR -#else +#elif defined(CONFIG_EXAMPLE_IPV6) #define HOST_IP_ADDR CONFIG_EXAMPLE_IPV6_ADDR +#else +#define HOST_IP_ADDR "" #endif #define PORT CONFIG_EXAMPLE_PORT @@ -40,13 +42,13 @@ static const char *payload = "Message from ESP32 "; static void udp_client_task(void *pvParameters) { char rx_buffer[128]; - char addr_str[128]; - int addr_family; - int ip_protocol; + char host_ip[] = HOST_IP_ADDR; + int addr_family = 0; + int ip_protocol = 0; while (1) { -#ifdef CONFIG_EXAMPLE_IPV4 +#if defined(CONFIG_EXAMPLE_IPV4) struct sockaddr_in dest_addr; dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR); dest_addr.sin_family = AF_INET; @@ -54,14 +56,18 @@ static void udp_client_task(void *pvParameters) addr_family = AF_INET; ip_protocol = IPPROTO_IP; inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1); -#else // IPV6 - struct sockaddr_in6 dest_addr; +#elif defined(CONFIG_EXAMPLE_IPV6) + struct sockaddr_in6 dest_addr = { 0 }; inet6_aton(HOST_IP_ADDR, &dest_addr.sin6_addr); dest_addr.sin6_family = AF_INET6; dest_addr.sin6_port = htons(PORT); + dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE); addr_family = AF_INET6; ip_protocol = IPPROTO_IPV6; inet6_ntoa_r(dest_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); +#elif defined(CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN) + struct sockaddr_in6 dest_addr = { 0 }; + ESP_ERROR_CHECK(get_addr_from_stdin(PORT, SOCK_DGRAM, &ip_protocol, &addr_family, &dest_addr)); #endif int sock = socket(addr_family, SOCK_DGRAM, ip_protocol); @@ -92,8 +98,12 @@ static void udp_client_task(void *pvParameters) // Data received else { rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string - ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str); + ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip); ESP_LOGI(TAG, "%s", rx_buffer); + if (strncmp(rx_buffer, "OK: ", 4) == 0) { + ESP_LOGI(TAG, "Received expected message, reconnecting"); + break; + } } vTaskDelay(2000 / portTICK_PERIOD_MS); diff --git a/examples/protocols/sockets/udp_client/sdkconfig.ci b/examples/protocols/sockets/udp_client/sdkconfig.ci new file mode 100644 index 0000000000..62ac57ea21 --- /dev/null +++ b/examples/protocols/sockets/udp_client/sdkconfig.ci @@ -0,0 +1 @@ +CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN=y \ No newline at end of file diff --git a/examples/protocols/sockets/udp_server/README.md b/examples/protocols/sockets/udp_server/README.md index bbfbd60327..2c805381bc 100644 --- a/examples/protocols/sockets/udp_server/README.md +++ b/examples/protocols/sockets/udp_server/README.md @@ -31,14 +31,14 @@ nc -u 192.168.0.167 3333 ``` ### Python scripts -Script udpclient.py contains configuration for port number, IP version (IPv4 or IPv6) and IP address that has to be altered to match the values used by the application. Example: +Script example_test.py could be used as a counter part to the udp-server application, +IP address and the message to be send to the server shall be stated as arguments. Example: ``` -PORT = 3333; -IP_VERSION = 'IPv4' -IPV4 = '192.168.0.167' -IPV6 = 'FE80::32AE:A4FF:FE80:5288' +python example_test.py 192.168.0.167 Message ``` +Note that this script is used in automated tests, as well, so the IDF test framework packages need to be imported; +please add `$IDF_PATH/tools/ci/python_packages` to `PYTHONPATH`. ## Hardware Required diff --git a/examples/protocols/sockets/udp_server/example_test.py b/examples/protocols/sockets/udp_server/example_test.py new file mode 100644 index 0000000000..d78358ef94 --- /dev/null +++ b/examples/protocols/sockets/udp_server/example_test.py @@ -0,0 +1,86 @@ +# 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. + +# -*- coding: utf-8 -*- + +from __future__ import print_function +from __future__ import unicode_literals +import os +import sys +import re +import socket +import ttfw_idf + + +# ----------- Config ---------- +PORT = 3333 +INTERFACE = 'eth0' +# ------------------------------- + + +def udp_client(address, payload): + for res in socket.getaddrinfo(address, PORT, socket.AF_UNSPEC, + socket.SOCK_DGRAM, 0, socket.AI_PASSIVE): + family_addr, socktype, proto, canonname, addr = res + try: + sock = socket.socket(family_addr, socket.SOCK_DGRAM) + except socket.error as msg: + print('Could not create socket: ' + str(msg[0]) + ': ' + msg[1]) + raise + try: + sock.sendto(payload, addr) + reply, addr = sock.recvfrom(128) + if not reply: + return + print('Reply[' + addr[0] + ':' + str(addr[1]) + '] - ' + str(reply)) + except socket.error as msg: + print('Error Code : ' + str(msg[0]) + ' Message: ' + msg[1]) + sock.close() + raise + return reply + + +@ttfw_idf.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_socket(env, extra_data): + MESSAGE = "Data to ESP" + """ + steps: + 1. join AP + 2. have the board connect to the server + 3. send and receive data + """ + dut1 = env.get_dut("udp_server", "examples/protocols/sockets/udp_server", dut_class=ttfw_idf.ESP32DUT) + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "udp_server.bin") + bin_size = os.path.getsize(binary_file) + ttfw_idf.log_performance("udp_server_bin_size", "{}KB".format(bin_size // 1024)) + ttfw_idf.check_performance("udp_server_bin_size", bin_size // 1024, dut1.TARGET) + + # start test + dut1.start_app() + + ipv4 = dut1.expect(re.compile(r" IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"), timeout=30)[0] + ipv6 = dut1.expect(re.compile(r" IPv6 address: ([0-9A-Fa-f\:]+)"), timeout=30)[0] + print("Connected with IPv4={} and IPv6={}".format(ipv4, ipv6)) + + # test IPv4 + received = udp_client(ipv4, MESSAGE) + if not received == MESSAGE: + raise + dut1.expect(MESSAGE) + # test IPv6 + received = udp_client("{}%{}".format(ipv6, INTERFACE), MESSAGE) + if not received == MESSAGE: + raise + dut1.expect(MESSAGE) + + +if __name__ == '__main__': + if sys.argv[2:]: # if two arguments provided: + # Usage: example_test.py + udp_client(sys.argv[1], sys.argv[2]) + else: # otherwise run standard example test as in the CI + test_examples_protocol_socket() diff --git a/examples/protocols/sockets/udp_server/main/Kconfig.projbuild b/examples/protocols/sockets/udp_server/main/Kconfig.projbuild index 822c019be7..65f38224c6 100644 --- a/examples/protocols/sockets/udp_server/main/Kconfig.projbuild +++ b/examples/protocols/sockets/udp_server/main/Kconfig.projbuild @@ -1,18 +1,13 @@ menu "Example Configuration" - choice EXAMPLE_IP_MODE - prompt "IP Version" - help - Example can use either IPV4 or IPV6. + config EXAMPLE_IPV4 + bool "IPV4" + default y - config EXAMPLE_IPV4 - bool "IPV4" - - config EXAMPLE_IPV6 - bool "IPV6" - select EXAMPLE_CONNECT_IPV6 - - endchoice + config EXAMPLE_IPV6 + bool "IPV6" + default n + select EXAMPLE_CONNECT_IPV6 config EXAMPLE_PORT int "Port" diff --git a/examples/protocols/sockets/udp_server/main/udp_server.c b/examples/protocols/sockets/udp_server/main/udp_server.c index 5936c7671a..86a283d402 100644 --- a/examples/protocols/sockets/udp_server/main/udp_server.c +++ b/examples/protocols/sockets/udp_server/main/udp_server.c @@ -31,28 +31,24 @@ static void udp_server_task(void *pvParameters) { char rx_buffer[128]; char addr_str[128]; - int addr_family; - int ip_protocol; + int addr_family = (int)pvParameters; + int ip_protocol = 0; + struct sockaddr_in6 dest_addr; while (1) { -#ifdef CONFIG_EXAMPLE_IPV4 - struct sockaddr_in dest_addr; - dest_addr.sin_addr.s_addr = htonl(INADDR_ANY); - dest_addr.sin_family = AF_INET; - dest_addr.sin_port = htons(PORT); - addr_family = AF_INET; - ip_protocol = IPPROTO_IP; - inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1); -#else // IPV6 - struct sockaddr_in6 dest_addr; - bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un)); - dest_addr.sin6_family = AF_INET6; - dest_addr.sin6_port = htons(PORT); - addr_family = AF_INET6; - ip_protocol = IPPROTO_IPV6; - inet6_ntoa_r(dest_addr.sin6_addr, addr_str, sizeof(addr_str) - 1); -#endif + if (addr_family == AF_INET) { + struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr; + dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY); + dest_addr_ip4->sin_family = AF_INET; + dest_addr_ip4->sin_port = htons(PORT); + ip_protocol = IPPROTO_IP; + } else if (addr_family == AF_INET6) { + bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un)); + dest_addr.sin6_family = AF_INET6; + dest_addr.sin6_port = htons(PORT); + ip_protocol = IPPROTO_IPV6; + } int sock = socket(addr_family, SOCK_DGRAM, ip_protocol); if (sock < 0) { @@ -61,6 +57,16 @@ static void udp_server_task(void *pvParameters) } ESP_LOGI(TAG, "Socket created"); +#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6) + if (addr_family == AF_INET6) { + // Note that by default IPV6 binds to both protocols, it is must be disabled + // if both protocols used at the same time (used in CI) + int opt = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); + } +#endif + int err = bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); if (err < 0) { ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); @@ -121,5 +127,11 @@ void app_main(void) */ ESP_ERROR_CHECK(example_connect()); - xTaskCreate(udp_server_task, "udp_server", 4096, NULL, 5, NULL); +#ifdef CONFIG_EXAMPLE_IPV4 + xTaskCreate(udp_server_task, "udp_server", 4096, (void*)AF_INET, 5, NULL); +#endif +#ifdef CONFIG_EXAMPLE_IPV6 + xTaskCreate(udp_server_task, "udp_server", 4096, (void*)AF_INET6, 5, NULL); +#endif + } diff --git a/examples/protocols/sockets/udp_server/sdkconfig.ci b/examples/protocols/sockets/udp_server/sdkconfig.ci new file mode 100644 index 0000000000..55acf406fb --- /dev/null +++ b/examples/protocols/sockets/udp_server/sdkconfig.ci @@ -0,0 +1,2 @@ +CONFIG_EXAMPLE_IPV4=y +CONFIG_EXAMPLE_IPV6=y \ No newline at end of file diff --git a/tools/ci/check_examples_rom_header.sh b/tools/ci/check_examples_rom_header.sh index 131723e0db..cf2cad9229 100755 --- a/tools/ci/check_examples_rom_header.sh +++ b/tools/ci/check_examples_rom_header.sh @@ -3,7 +3,7 @@ # Examples shouldn't include rom headers directly output=$(find ${IDF_PATH}/examples -name "*.[chS]" -o -name "*.cpp" -not -path "**/build/**") -files=$(grep ".*include.*rom.*h" ${output} | cut -d ":" -f 1) +files=$(egrep ".*include.*\.*h" ${output} | cut -d ":" -f 1) found_rom=0 for file in ${files} do