Merge branch 'bugfix/ipv6_examples' into 'master'

IPv6 related updates: esp-netif, common-connect, socket-examples

See merge request espressif/esp-idf!7500
This commit is contained in:
David Čermák
2020-03-24 19:32:33 +08:00
34 changed files with 830 additions and 333 deletions
+26 -5
View File
@@ -63,15 +63,36 @@ nc -l 192.168.0.167 -p 3333
```
### Python scripts
Each script contains port number, IP version (IPv4 or IPv6) and IP address (only clients) that has to be altered to match the values used by the application. Example:
Each script in the application directory could be used to exercise the socket communication.
Command line arguments such as IP version (IPv4 or IPv6) and IP address and payload data (only clients) shall be supplied.
In addition to that, port number and interface id are hardcoded in the scripts and might need to be altered to match the values used by the application. Example:
```
PORT = 3333;
IP_VERSION = 'IPv4'
IPV4 = '192.168.0.167'
IPV6 = 'FE80::32AE:A4FF:FE80:5288'
PORT = 3333
INTERFACE = 'en0'
```
### Note about IPv6 addresses
Examples are configured to obtain multiple IPv6 addresses. The actual behavior may differ depending on the local network, typically the ESP gets assigned these two addresses
* Local Link address
* Unique Local address
The value and type of the IPv6 address is displayed in the terminal, for example:
Please make sure that when using the Local Link address, an interface id is included in the configuration:
* In the embedded code
```
dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(esp_netif_instance);
```
* On the host
- Interface name suffix is present when passing the address as a string, for example `fe80::260a:XXX:XXX:XXX%en0`
- The interface id is present when passing the endpoint as tupple, for example `socket.connect(('fd00::260a:XXXX:XXXX:XXXX', 3333, 0, 3))`
## Hardware Required
This example can be run on any commonly available ESP32 development board.
@@ -1,52 +0,0 @@
# This example code is in the Public Domain (or CC0 licensed, at your option.)
# Unless required by applicable law or agreed to in writing, this
# software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied.
# -*- coding: utf-8 -*-
from builtins import input
import socket
import sys
# ----------- Config ----------
PORT = 3333
IP_VERSION = 'IPv4'
IPV4 = '192.168.0.167'
IPV6 = 'FE80::32AE:A4FF:FE80:5288'
# -------------------------------
if IP_VERSION == 'IPv4':
family_addr = socket.AF_INET
host = IPV4
elif IP_VERSION == 'IPv6':
family_addr = socket.AF_INET6
host = IPV6
else:
print('IP_VERSION must be IPv4 or IPv6')
sys.exit(1)
try:
sock = socket.socket(family_addr, socket.SOCK_STREAM)
except socket.error as msg:
print('Could not create socket: ' + str(msg[0]) + ': ' + msg[1])
sys.exit(1)
try:
sock.connect((host, PORT))
except socket.error as msg:
print('Could not open socket: ', msg)
sock.close()
sys.exit(1)
while True:
msg = input('Enter message to send: ')
assert isinstance(msg, str)
msg = msg.encode()
sock.sendall(msg)
data = sock.recv(1024)
if not data:
break
print('Reply: ' + data.decode())
sock.close()
@@ -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()
@@ -1,47 +0,0 @@
# This example code is in the Public Domain (or CC0 licensed, at your option.)
# Unless required by applicable law or agreed to in writing, this
# software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied.
# -*- coding: utf-8 -*-
from builtins import input
import socket
import sys
# ----------- Config ----------
PORT = 3333
IP_VERSION = 'IPv4'
IPV4 = '192.168.0.167'
IPV6 = 'FE80::32AE:A4FF:FE80:5288'
# -------------------------------
if IP_VERSION == 'IPv4':
host = IPV4
family_addr = socket.AF_INET
elif IP_VERSION == 'IPv6':
host = IPV6
family_addr = socket.AF_INET6
else:
print('IP_VERSION must be IPv4 or IPv6')
sys.exit(1)
try:
sock = socket.socket(family_addr, socket.SOCK_DGRAM)
except socket.error:
print('Failed to create socket')
sys.exit()
while True:
msg = input('Enter message to send : ')
try:
sock.sendto(msg.encode(), (host, PORT))
reply, addr = sock.recvfrom(128)
if not reply:
break
print('Reply[' + addr[0] + ':' + str(addr[1]) + '] - ' + str(reply))
except socket.error as msg:
print('Error Code : ' + str(msg[0]) + ' Message: ' + msg[1])
sys.exit()
@@ -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()
@@ -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
@@ -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()
@@ -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
@@ -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,38 +39,38 @@ static const char *payload = "Message from ESP32 ";
static void tcp_client_task(void *pvParameters)
{
char rx_buffer[128];
char addr_str[128];
int addr_family;
int ip_protocol;
char host_ip[] = HOST_IP_ADDR;
int addr_family = 0;
int ip_protocol = 0;
while (1) {
#ifdef CONFIG_EXAMPLE_IPV4
#if defined(CONFIG_EXAMPLE_IPV4)
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR);
dest_addr.sin_addr.s_addr = inet_addr(host_ip);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(PORT);
addr_family = AF_INET;
ip_protocol = IPPROTO_IP;
inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1);
#else // IPV6
struct sockaddr_in6 dest_addr;
inet6_aton(HOST_IP_ADDR, &dest_addr.sin6_addr);
#elif defined(CONFIG_EXAMPLE_IPV6)
struct sockaddr_in6 dest_addr = { 0 };
inet6_aton(host_ip, &dest_addr.sin6_addr);
dest_addr.sin6_family = AF_INET6;
dest_addr.sin6_port = htons(PORT);
dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE);
addr_family = AF_INET6;
ip_protocol = IPPROTO_IPV6;
inet6_ntoa_r(dest_addr.sin6_addr, addr_str, sizeof(addr_str) - 1);
#elif defined(CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN)
struct sockaddr_in6 dest_addr = { 0 };
ESP_ERROR_CHECK(get_addr_from_stdin(PORT, SOCK_STREAM, &ip_protocol, &addr_family, &dest_addr));
#endif
int sock = socket(addr_family, SOCK_STREAM, ip_protocol);
if (sock < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
break;
}
ESP_LOGI(TAG, "Socket created, connecting to %s:%d", HOST_IP_ADDR, PORT);
ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, PORT);
int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6));
if (err != 0) {
ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
break;
@@ -93,7 +93,7 @@ static void tcp_client_task(void *pvParameters)
// Data received
else {
rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str);
ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
ESP_LOGI(TAG, "%s", rx_buffer);
}
@@ -0,0 +1 @@
CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN=y
@@ -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
@@ -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()
@@ -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"
@@ -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
}
@@ -0,0 +1,2 @@
CONFIG_EXAMPLE_IPV4=y
CONFIG_EXAMPLE_IPV6=y
@@ -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
@@ -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()
@@ -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
@@ -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,14 +56,18 @@ static void udp_client_task(void *pvParameters)
addr_family = AF_INET;
ip_protocol = IPPROTO_IP;
inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1);
#else // IPV6
struct sockaddr_in6 dest_addr;
#elif defined(CONFIG_EXAMPLE_IPV6)
struct sockaddr_in6 dest_addr = { 0 };
inet6_aton(HOST_IP_ADDR, &dest_addr.sin6_addr);
dest_addr.sin6_family = AF_INET6;
dest_addr.sin6_port = htons(PORT);
dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE);
addr_family = AF_INET6;
ip_protocol = IPPROTO_IPV6;
inet6_ntoa_r(dest_addr.sin6_addr, addr_str, sizeof(addr_str) - 1);
#elif defined(CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN)
struct sockaddr_in6 dest_addr = { 0 };
ESP_ERROR_CHECK(get_addr_from_stdin(PORT, SOCK_DGRAM, &ip_protocol, &addr_family, &dest_addr));
#endif
int sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
@@ -92,8 +98,12 @@ static void udp_client_task(void *pvParameters)
// Data received
else {
rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str);
ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
ESP_LOGI(TAG, "%s", rx_buffer);
if (strncmp(rx_buffer, "OK: ", 4) == 0) {
ESP_LOGI(TAG, "Received expected message, reconnecting");
break;
}
}
vTaskDelay(2000 / portTICK_PERIOD_MS);
@@ -0,0 +1 @@
CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN=y
@@ -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
@@ -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()
@@ -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"
@@ -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
}
@@ -0,0 +1,2 @@
CONFIG_EXAMPLE_IPV4=y
CONFIG_EXAMPLE_IPV6=y