From 995ef85e85a44eccdc3147a8cbaab0a1c7eeb320 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 17 Mar 2020 20:30:33 +0100 Subject: [PATCH] socket examples: add tests for server and client applications --- examples/protocols/sockets/README.md | 11 +- .../protocols/sockets/scripts/tcpclient.py | 54 -------- .../protocols/sockets/scripts/tcpserver.py | 54 -------- .../protocols/sockets/scripts/udpclient.py | 49 ------- .../protocols/sockets/scripts/udpserver.py | 52 -------- .../socket_addr_from_stdin/CMakeLists.txt | 3 + .../sockets/socket_addr_from_stdin/README.md | 6 + .../socket_addr_from_stdin/addr_from_stdin.c | 65 +++++++++ .../socket_addr_from_stdin/component.mk | 5 + .../include/addr_from_stdin.h | 22 +++ .../sockets/tcp_client/CMakeLists.txt | 3 +- .../protocols/sockets/tcp_client/README.md | 9 +- .../sockets/tcp_client/example_test.py | 125 ++++++++++++++++++ .../sockets/tcp_client/main/Kconfig.projbuild | 14 ++ .../sockets/tcp_client/main/tcp_client.c | 37 +++--- .../protocols/sockets/tcp_client/sdkconfig.ci | 1 + .../protocols/sockets/tcp_server/README.md | 10 +- .../sockets/tcp_server/example_test.py | 88 ++++++++++++ .../sockets/tcp_server/main/Kconfig.projbuild | 19 +-- .../sockets/tcp_server/main/tcp_server.c | 51 ++++--- .../protocols/sockets/tcp_server/sdkconfig.ci | 2 + .../sockets/udp_client/CMakeLists.txt | 3 +- .../protocols/sockets/udp_client/README.md | 10 +- .../sockets/udp_client/example_test.py | 118 +++++++++++++++++ .../sockets/udp_client/main/Kconfig.projbuild | 14 ++ .../sockets/udp_client/main/udp_client.c | 27 ++-- .../protocols/sockets/udp_client/sdkconfig.ci | 1 + .../protocols/sockets/udp_server/README.md | 10 +- .../sockets/udp_server/example_test.py | 86 ++++++++++++ .../sockets/udp_server/main/Kconfig.projbuild | 19 +-- .../sockets/udp_server/main/udp_server.c | 52 +++++--- .../protocols/sockets/udp_server/sdkconfig.ci | 2 + tools/ci/check_examples_cmake_make.sh | 2 +- 33 files changed, 696 insertions(+), 328 deletions(-) delete mode 100644 examples/protocols/sockets/scripts/tcpclient.py delete mode 100644 examples/protocols/sockets/scripts/tcpserver.py delete mode 100644 examples/protocols/sockets/scripts/udpclient.py delete mode 100644 examples/protocols/sockets/scripts/udpserver.py create mode 100644 examples/protocols/sockets/socket_addr_from_stdin/CMakeLists.txt create mode 100644 examples/protocols/sockets/socket_addr_from_stdin/README.md create mode 100644 examples/protocols/sockets/socket_addr_from_stdin/addr_from_stdin.c create mode 100644 examples/protocols/sockets/socket_addr_from_stdin/component.mk create mode 100644 examples/protocols/sockets/socket_addr_from_stdin/include/addr_from_stdin.h create mode 100644 examples/protocols/sockets/tcp_client/example_test.py create mode 100644 examples/protocols/sockets/tcp_client/sdkconfig.ci create mode 100644 examples/protocols/sockets/tcp_server/example_test.py create mode 100644 examples/protocols/sockets/tcp_server/sdkconfig.ci create mode 100644 examples/protocols/sockets/udp_client/example_test.py create mode 100644 examples/protocols/sockets/udp_client/sdkconfig.ci create mode 100644 examples/protocols/sockets/udp_server/example_test.py create mode 100644 examples/protocols/sockets/udp_server/sdkconfig.ci diff --git a/examples/protocols/sockets/README.md b/examples/protocols/sockets/README.md index 8410d1edcc..00f3f91755 100644 --- a/examples/protocols/sockets/README.md +++ b/examples/protocols/sockets/README.md @@ -63,14 +63,15 @@ 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 diff --git a/examples/protocols/sockets/scripts/tcpclient.py b/examples/protocols/sockets/scripts/tcpclient.py deleted file mode 100644 index a1c1056fcd..0000000000 --- a/examples/protocols/sockets/scripts/tcpclient.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 -*- - -from builtins import input -import socket -import sys - -# ----------- Config ---------- -PORT = 3333 -IP_VERSION = 'IPv6' -IPV4 = '192.168.0.42' -IPV6 = 'fd00:0000:0000:0000:260a:c4ff:fe09:885c' -# ------------------------------- - -if IP_VERSION == 'IPv4': - family_addr = socket.AF_INET - addr = (IPV4, PORT) -elif IP_VERSION == 'IPv6': - family_addr = socket.AF_INET6 - for res in socket.getaddrinfo(IPV6, PORT, socket.AF_INET6, - socket.SOCK_STREAM, socket.SOL_TCP): - af, socktype, proto, canonname, addr = res -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((IPV6, 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 2c31c419c4..0000000000 --- a/examples/protocols/sockets/scripts/udpclient.py +++ /dev/null @@ -1,49 +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 = 'IPv6' -IPV4 = '192.168.0.167' -IPV6 = 'fe80:0000:0000:0000:260a:c4ff:fe11:a1e0%wlp1s0' -# ------------------------------- - -if IP_VERSION == 'IPv4': - addr = (IPV4, PORT) - family_addr = socket.AF_INET -elif IP_VERSION == 'IPv6': - family_addr = socket.AF_INET6 - for res in socket.getaddrinfo(IPV6, PORT, socket.AF_INET6, - socket.SOCK_DGRAM, socket.SOL_UDP): - af, socktype, proto, canonname, addr = res -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(), addr) - 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/socket_addr_from_stdin/CMakeLists.txt b/examples/protocols/sockets/socket_addr_from_stdin/CMakeLists.txt new file mode 100644 index 0000000000..22e024f005 --- /dev/null +++ b/examples/protocols/sockets/socket_addr_from_stdin/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "addr_from_stdin.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES protocol_examples_common) \ No newline at end of file diff --git a/examples/protocols/sockets/socket_addr_from_stdin/README.md b/examples/protocols/sockets/socket_addr_from_stdin/README.md new file mode 100644 index 0000000000..9a837b82bf --- /dev/null +++ b/examples/protocols/sockets/socket_addr_from_stdin/README.md @@ -0,0 +1,6 @@ +# Socket address input + +This directory contains a common component for 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 connection. diff --git a/examples/protocols/sockets/socket_addr_from_stdin/addr_from_stdin.c b/examples/protocols/sockets/socket_addr_from_stdin/addr_from_stdin.c new file mode 100644 index 0000000000..460fe9a2ec --- /dev/null +++ b/examples/protocols/sockets/socket_addr_from_stdin/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/protocols/sockets/socket_addr_from_stdin/component.mk b/examples/protocols/sockets/socket_addr_from_stdin/component.mk new file mode 100644 index 0000000000..9b8ec90274 --- /dev/null +++ b/examples/protocols/sockets/socket_addr_from_stdin/component.mk @@ -0,0 +1,5 @@ +# +# Component Makefile +# +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_SRCDIRS := . \ No newline at end of file diff --git a/examples/protocols/sockets/socket_addr_from_stdin/include/addr_from_stdin.h b/examples/protocols/sockets/socket_addr_from_stdin/include/addr_from_stdin.h new file mode 100644 index 0000000000..3d2e6182ea --- /dev/null +++ b/examples/protocols/sockets/socket_addr_from_stdin/include/addr_from_stdin.h @@ -0,0 +1,22 @@ +#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); \ No newline at end of file diff --git a/examples/protocols/sockets/tcp_client/CMakeLists.txt b/examples/protocols/sockets/tcp_client/CMakeLists.txt index aee70cad5e..735796989f 100644 --- a/examples/protocols/sockets/tcp_client/CMakeLists.txt +++ b/examples/protocols/sockets/tcp_client/CMakeLists.txt @@ -4,7 +4,8 @@ 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) +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common + $ENV{IDF_PATH}/examples/protocols/sockets/socket_addr_from_stdin) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(tcp_client) 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 ba0a0527f7..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,39 +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 +#elif defined(CONFIG_EXAMPLE_IPV6) struct sockaddr_in6 dest_addr = { 0 }; - inet6_aton(HOST_IP_ADDR, &dest_addr.sin6_addr); + 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; @@ -94,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/CMakeLists.txt b/examples/protocols/sockets/udp_client/CMakeLists.txt index 2049b81939..afe45d068d 100644 --- a/examples/protocols/sockets/udp_client/CMakeLists.txt +++ b/examples/protocols/sockets/udp_client/CMakeLists.txt @@ -4,7 +4,8 @@ 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) +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common + $ENV{IDF_PATH}/examples/protocols/sockets/socket_addr_from_stdin) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(udp_client) 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 babacd658c..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,7 +56,7 @@ 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 +#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; @@ -63,6 +65,9 @@ static void udp_client_task(void *pvParameters) 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); @@ -93,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_cmake_make.sh b/tools/ci/check_examples_cmake_make.sh index aa7d540589..30b7483672 100755 --- a/tools/ci/check_examples_cmake_make.sh +++ b/tools/ci/check_examples_cmake_make.sh @@ -5,7 +5,7 @@ echo "- Getting paths of CMakeLists and Makefiles" IFS= -CMAKELISTS=$( find ${IDF_PATH}/examples/ -type f -name CMakeLists.txt | grep -v "/components/" | grep -v "/common_components/" | grep -v "/cxx/experimental/experimental_cpp_component/" | grep -v "/main/" | grep -v "/build_system/cmake/" | grep -v "/mb_example_common/" | sort -n) +CMAKELISTS=$( find ${IDF_PATH}/examples/ -type f -name CMakeLists.txt | grep -v "/components/" | grep -v "/common_components/" | grep -v "/cxx/experimental/experimental_cpp_component/" | grep -v "/main/" | grep -v "/build_system/cmake/" | grep -v "/mb_example_common/" | grep -v "/socket_addr_from_stdin/" | sort -n) MAKEFILES=$( find ${IDF_PATH}/examples/ -type f -name Makefile | grep -v "/build_system/cmake/" | sort -n) echo " [DONE]"