From 94ded5fb2f2f64ddf43257f8c35da0fe35e8f657 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 4 Feb 2020 16:38:23 +0100 Subject: [PATCH 1/6] socket-examples: IPv6 related update for examples to set correct scoped id The scope id must be present when connecting to IPv6 Local Link address. --- examples/protocols/sockets/scripts/tcpclient.py | 14 ++++++++------ examples/protocols/sockets/scripts/udpclient.py | 12 +++++++----- .../protocols/sockets/tcp_client/main/tcp_client.c | 3 ++- .../protocols/sockets/udp_client/main/udp_client.c | 3 ++- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/examples/protocols/sockets/scripts/tcpclient.py b/examples/protocols/sockets/scripts/tcpclient.py index 84332e9382..a1c1056fcd 100644 --- a/examples/protocols/sockets/scripts/tcpclient.py +++ b/examples/protocols/sockets/scripts/tcpclient.py @@ -12,17 +12,19 @@ import sys # ----------- Config ---------- PORT = 3333 -IP_VERSION = 'IPv4' -IPV4 = '192.168.0.167' -IPV6 = 'FE80::32AE:A4FF:FE80:5288' +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 - host = IPV4 + addr = (IPV4, PORT) elif IP_VERSION == 'IPv6': family_addr = socket.AF_INET6 - host = IPV6 + 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) @@ -34,7 +36,7 @@ except socket.error as msg: sys.exit(1) try: - sock.connect((host, PORT)) + sock.connect((IPV6, PORT)) except socket.error as msg: print('Could not open socket: ', msg) sock.close() diff --git a/examples/protocols/sockets/scripts/udpclient.py b/examples/protocols/sockets/scripts/udpclient.py index 342cadbfe8..2c31c419c4 100644 --- a/examples/protocols/sockets/scripts/udpclient.py +++ b/examples/protocols/sockets/scripts/udpclient.py @@ -12,17 +12,19 @@ import sys # ----------- Config ---------- PORT = 3333 -IP_VERSION = 'IPv4' +IP_VERSION = 'IPv6' IPV4 = '192.168.0.167' -IPV6 = 'FE80::32AE:A4FF:FE80:5288' +IPV6 = 'fe80:0000:0000:0000:260a:c4ff:fe11:a1e0%wlp1s0' # ------------------------------- if IP_VERSION == 'IPv4': - host = IPV4 + addr = (IPV4, PORT) family_addr = socket.AF_INET elif IP_VERSION == 'IPv6': - host = 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) @@ -37,7 +39,7 @@ except socket.error: while True: msg = input('Enter message to send : ') try: - sock.sendto(msg.encode(), (host, PORT)) + sock.sendto(msg.encode(), addr) reply, addr = sock.recvfrom(128) if not reply: break diff --git a/examples/protocols/sockets/tcp_client/main/tcp_client.c b/examples/protocols/sockets/tcp_client/main/tcp_client.c index 2d2e515a0e..ba0a0527f7 100644 --- a/examples/protocols/sockets/tcp_client/main/tcp_client.c +++ b/examples/protocols/sockets/tcp_client/main/tcp_client.c @@ -54,10 +54,11 @@ static void tcp_client_task(void *pvParameters) ip_protocol = IPPROTO_IP; inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1); #else // IPV6 - struct sockaddr_in6 dest_addr; + 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); diff --git a/examples/protocols/sockets/udp_client/main/udp_client.c b/examples/protocols/sockets/udp_client/main/udp_client.c index 402d272740..babacd658c 100644 --- a/examples/protocols/sockets/udp_client/main/udp_client.c +++ b/examples/protocols/sockets/udp_client/main/udp_client.c @@ -55,10 +55,11 @@ static void udp_client_task(void *pvParameters) ip_protocol = IPPROTO_IP; inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1); #else // IPV6 - struct sockaddr_in6 dest_addr; + 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); From 56725fa6782743a5e774223a99aa9d3e4fa6ab02 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 6 Feb 2020 16:48:36 +0100 Subject: [PATCH 2/6] esp-netif: support for ipv6 addr types and indices --- .../esp_netif/include/esp_netif_ip_addr.h | 18 +++++++++++++ .../esp_netif/include/esp_netif_types.h | 1 + components/esp_netif/lwip/esp_netif_lwip.c | 25 ++++++++++++++++--- .../protocol_examples_common/connect.c | 2 ++ 4 files changed, 43 insertions(+), 3 deletions(-) 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/connect.c b/examples/common_components/protocol_examples_common/connect.c index 9713edda2c..de7e98208d 100644 --- a/examples/common_components/protocol_examples_common/connect.c +++ b/examples/common_components/protocol_examples_common/connect.c @@ -73,6 +73,8 @@ static void on_got_ipv6(void *arg, esp_event_base_t event_base, 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_LOGI(TAG, "IPv6 address: " IPV6STR ", index: %d, type: %d", IPV62STR(s_ipv6_addr), event->ip_index, esp_ip6_get_addr_type(&s_ipv6_addr)); + } #endif // CONFIG_EXAMPLE_CONNECT_IPV6 From 63aa0d6e9ce2406fba008b72a3fbc1954c83abf1 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 7 Feb 2020 09:19:55 +0100 Subject: [PATCH 3/6] common_connect: add support for getting multiple IPv6 addresses --- .../Kconfig.projbuild | 39 ++++++++++++++++++- .../protocol_examples_common/connect.c | 32 ++++++++++++--- examples/protocols/sockets/README.md | 20 ++++++++++ 3 files changed, 84 insertions(+), 7 deletions(-) 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/connect.c b/examples/common_components/protocol_examples_common/connect.c index de7e98208d..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,11 +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_LOGI(TAG, "IPv6 address: " IPV6STR ", index: %d, type: %d", IPV62STR(s_ipv6_addr), event->ip_index, esp_ip6_get_addr_type(&s_ipv6_addr)); - + 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/protocols/sockets/README.md b/examples/protocols/sockets/README.md index 9d52415321..8410d1edcc 100644 --- a/examples/protocols/sockets/README.md +++ b/examples/protocols/sockets/README.md @@ -71,6 +71,26 @@ IP_VERSION = 'IPv4' IPV4 = '192.168.0.167' IPV6 = 'FE80::32AE:A4FF:FE80:5288' ``` +### 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 From 995ef85e85a44eccdc3147a8cbaab0a1c7eeb320 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 17 Mar 2020 20:30:33 +0100 Subject: [PATCH 4/6] 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]" From a5a750ba4898f35672e060204d3a37c35b2e6e08 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 19 Mar 2020 11:45:02 +0100 Subject: [PATCH 5/6] examples: add socket stdin utils to common connect component --- .../protocol_examples_common/CMakeLists.txt | 2 +- .../addr_from_stdin.c | 0 .../include/addr_from_stdin.h | 24 ++++++++++++++++++- .../socket_addr_from_stdin/CMakeLists.txt | 3 --- .../sockets/socket_addr_from_stdin/README.md | 6 ----- .../socket_addr_from_stdin/component.mk | 5 ---- .../sockets/tcp_client/CMakeLists.txt | 3 +-- .../sockets/udp_client/CMakeLists.txt | 3 +-- tools/ci/check_examples_cmake_make.sh | 2 +- 9 files changed, 27 insertions(+), 21 deletions(-) rename examples/{protocols/sockets/socket_addr_from_stdin => common_components/protocol_examples_common}/addr_from_stdin.c (100%) rename examples/{protocols/sockets/socket_addr_from_stdin => common_components/protocol_examples_common}/include/addr_from_stdin.h (51%) delete mode 100644 examples/protocols/sockets/socket_addr_from_stdin/CMakeLists.txt delete mode 100644 examples/protocols/sockets/socket_addr_from_stdin/README.md delete mode 100644 examples/protocols/sockets/socket_addr_from_stdin/component.mk 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/protocols/sockets/socket_addr_from_stdin/addr_from_stdin.c b/examples/common_components/protocol_examples_common/addr_from_stdin.c similarity index 100% rename from examples/protocols/sockets/socket_addr_from_stdin/addr_from_stdin.c rename to examples/common_components/protocol_examples_common/addr_from_stdin.c diff --git a/examples/protocols/sockets/socket_addr_from_stdin/include/addr_from_stdin.h b/examples/common_components/protocol_examples_common/include/addr_from_stdin.h similarity index 51% rename from examples/protocols/sockets/socket_addr_from_stdin/include/addr_from_stdin.h rename to examples/common_components/protocol_examples_common/include/addr_from_stdin.h index 3d2e6182ea..ab5043aed6 100644 --- a/examples/protocols/sockets/socket_addr_from_stdin/include/addr_from_stdin.h +++ b/examples/common_components/protocol_examples_common/include/addr_from_stdin.h @@ -1,3 +1,21 @@ +/* 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 @@ -19,4 +37,8 @@ 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 + struct sockaddr_in6 *dest_addr); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/examples/protocols/sockets/socket_addr_from_stdin/CMakeLists.txt b/examples/protocols/sockets/socket_addr_from_stdin/CMakeLists.txt deleted file mode 100644 index 22e024f005..0000000000 --- a/examples/protocols/sockets/socket_addr_from_stdin/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 9a837b82bf..0000000000 --- a/examples/protocols/sockets/socket_addr_from_stdin/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# 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/component.mk b/examples/protocols/sockets/socket_addr_from_stdin/component.mk deleted file mode 100644 index 9b8ec90274..0000000000 --- a/examples/protocols/sockets/socket_addr_from_stdin/component.mk +++ /dev/null @@ -1,5 +0,0 @@ -# -# Component Makefile -# -COMPONENT_ADD_INCLUDEDIRS := include -COMPONENT_SRCDIRS := . \ 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 735796989f..aee70cad5e 100644 --- a/examples/protocols/sockets/tcp_client/CMakeLists.txt +++ b/examples/protocols/sockets/tcp_client/CMakeLists.txt @@ -4,8 +4,7 @@ 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 - $ENV{IDF_PATH}/examples/protocols/sockets/socket_addr_from_stdin) +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(tcp_client) diff --git a/examples/protocols/sockets/udp_client/CMakeLists.txt b/examples/protocols/sockets/udp_client/CMakeLists.txt index afe45d068d..2049b81939 100644 --- a/examples/protocols/sockets/udp_client/CMakeLists.txt +++ b/examples/protocols/sockets/udp_client/CMakeLists.txt @@ -4,8 +4,7 @@ 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 - $ENV{IDF_PATH}/examples/protocols/sockets/socket_addr_from_stdin) +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(udp_client) diff --git a/tools/ci/check_examples_cmake_make.sh b/tools/ci/check_examples_cmake_make.sh index 30b7483672..aa7d540589 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/" | grep -v "/socket_addr_from_stdin/" | 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/" | sort -n) MAKEFILES=$( find ${IDF_PATH}/examples/ -type f -name Makefile | grep -v "/build_system/cmake/" | sort -n) echo " [DONE]" From 39011c055a7666cb62b0eddc04a3e7ce0d197068 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 19 Mar 2020 11:47:37 +0100 Subject: [PATCH 6/6] ci: fix rom header checker to validate *rom* on word boundaries --- tools/ci/check_examples_rom_header.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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