socket examples: add tests for server and client applications

This commit is contained in:
David Cermak
2020-03-17 20:30:33 +01:00
committed by bot
parent 63aa0d6e9c
commit 995ef85e85
33 changed files with 696 additions and 328 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "addr_from_stdin.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES protocol_examples_common)

View File

@@ -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.

View File

@@ -0,0 +1,65 @@
#include <string.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"
#include "lwip/sockets.h"
#include <lwip/netdb.h>
#include <arpa/inet.h>
#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;
}

View File

@@ -0,0 +1,5 @@
#
# Component Makefile
#
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := .

View File

@@ -0,0 +1,22 @@
#include "lwip/sys.h"
#include <lwip/netdb.h>
#include <arpa/inet.h>
/**
* @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);

View File

@@ -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)

View File

@@ -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

View File

@@ -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 <IPv4|IPv6>
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()

View File

@@ -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

View File

@@ -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 <lwip/netdb.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
@@ -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);
}

View File

@@ -0,0 +1 @@
CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN=y

View File

@@ -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

View File

@@ -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 <server_address> <message_to_send_to_server>
tcp_client(sys.argv[1], sys.argv[2])
else: # otherwise run standard example test as in the CI
test_examples_protocol_socket()

View File

@@ -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"

View File

@@ -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
}

View File

@@ -0,0 +1,2 @@
CONFIG_EXAMPLE_IPV4=y
CONFIG_EXAMPLE_IPV6=y

View File

@@ -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)

View File

@@ -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

View File

@@ -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 <IPv4|IPv6>
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()

View File

@@ -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

View File

@@ -23,12 +23,14 @@
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
#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);

View File

@@ -0,0 +1 @@
CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN=y

View File

@@ -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

View File

@@ -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 <server_address> <message_to_send_to_server>
udp_client(sys.argv[1], sys.argv[2])
else: # otherwise run standard example test as in the CI
test_examples_protocol_socket()

View File

@@ -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"

View File

@@ -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
}

View File

@@ -0,0 +1,2 @@
CONFIG_EXAMPLE_IPV4=y
CONFIG_EXAMPLE_IPV6=y

View File

@@ -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]"