1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-18 12:52:44 +01:00

Merge pull request #16494 from brummer-simon/gnrc_sock_tcp-add_gnrc_sock_tcp

gnrc_sock_tcp: add gnrc sock tcp
This commit is contained in:
benpicco 2021-08-15 19:47:33 +02:00 committed by GitHub
commit 8d9ca8ef68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 930 additions and 3 deletions

View File

@ -26,9 +26,13 @@
#include "net/gnrc/pkt.h"
#include "net/gnrc/tcp/tcb.h"
#ifdef SOCK_HAS_IPV6
#include "net/sock.h"
#else
#ifdef MODULE_GNRC_IPV6
#include "net/gnrc/ipv6.h"
#endif
#endif
#ifdef __cplusplus
extern "C" {
@ -44,6 +48,11 @@ extern "C" {
#define GNRC_TCP_NO_TIMEOUT (UINT32_MAX)
#endif
#ifdef SOCK_HAS_IPV6
/* Re-use sock endpoint if sock is available and supporting IPv6. */
typedef struct _sock_tl_ep gnrc_tcp_ep_t;
#else
/**
* @brief Address information for a single TCP connection endpoint.
* @extends sock_tcp_ep_t
@ -59,6 +68,7 @@ typedef struct {
uint16_t netif; /**< Network interface ID */
uint16_t port; /**< Port number (in host byte order) */
} gnrc_tcp_ep_t;
#endif
/**
* @brief Initialize TCP connection endpoint.

View File

@ -41,7 +41,7 @@ extern "C" {
/**
* @brief Transmission control block of GNRC TCP.
*/
typedef struct _transmission_control_block {
typedef struct sock_tcp {
uint8_t address_family; /**< Address Family of local_addr / peer_addr */
#ifdef MODULE_GNRC_IPV6
uint8_t local_addr[sizeof(ipv6_addr_t)]; /**< Local IP address */
@ -76,13 +76,13 @@ typedef struct _transmission_control_block {
ringbuffer_t rcv_buf; /**< Receive buffer data structure */
mutex_t fsm_lock; /**< Mutex for FSM access synchronization */
mutex_t function_lock; /**< Mutex for function call synchronization */
struct _transmission_control_block *next; /**< Pointer next TCB */
struct sock_tcp *next; /**< Pointer next TCB */
} gnrc_tcp_tcb_t;
/**
* @brief Transmission control block queue.
*/
typedef struct _transmission_control_block_queue {
typedef struct sock_tcp_queue {
mutex_t lock; /**< Mutex for access synchronization */
gnrc_tcp_tcb_t *tcbs; /**< Pointer to TCB sequence */
size_t tcbs_len; /**< Number of TCBs behind member tcbs */

View File

@ -130,6 +130,9 @@ endif
ifneq (,$(filter gnrc_sock_udp,$(USEMODULE)))
DIRS += sock/udp
endif
ifneq (,$(filter gnrc_sock_tcp,$(USEMODULE)))
DIRS += sock/tcp
endif
ifneq (,$(filter gnrc_udp,$(USEMODULE)))
DIRS += transport_layer/udp
endif

View File

@ -86,6 +86,10 @@ ifneq (,$(filter gnrc_sock_udp,$(USEMODULE)))
USEMODULE += random # to generate random ports
endif
ifneq (,$(filter gnrc_sock_tcp,$(USEMODULE)))
USEMODULE += gnrc_tcp
endif
ifneq (,$(filter gnrc_sock,$(USEMODULE)))
USEMODULE += gnrc_netapi_mbox
USEMODULE += sock
@ -398,6 +402,9 @@ ifneq (,$(filter gnrc,$(USEMODULE)))
ifneq (,$(filter sock_udp, $(USEMODULE)))
USEMODULE += gnrc_sock_udp
endif
ifneq (,$(filter sock_tcp, $(USEMODULE)))
USEMODULE += gnrc_sock_tcp
endif
endif
ifneq (,$(filter gnrc_pktbuf, $(USEMODULE)))

View File

@ -28,12 +28,14 @@
#include "mbox.h"
#include "net/af.h"
#include "net/gnrc.h"
#include "net/gnrc/tcp.h"
#include "net/gnrc/netreg.h"
#ifdef SOCK_HAS_ASYNC
#include "net/sock/async/types.h"
#endif
#include "net/sock/ip.h"
#include "net/sock/udp.h"
#include "net/sock/tcp.h"
#ifdef __cplusplus
extern "C" {

View File

@ -0,0 +1,3 @@
MODULE = gnrc_sock_tcp
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,146 @@
/*
* Copyright (C) 2021 Simon Brummer <simon.brummer@posteo.de>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @{
*
* @file
* @brief GNRC implementation of @ref net_sock_tcp
*
* @author Simon Brummer <simon.brummer@posteo.de>
*/
#include <assert.h>
#include "net/gnrc/tcp.h"
#include "net/sock/tcp.h"
#include "sock_types.h"
int sock_tcp_connect(sock_tcp_t *sock, const sock_tcp_ep_t *remote,
uint16_t local_port, uint16_t flags)
{
/* Asserts defined by API. */
assert(sock != NULL);
assert(remote != NULL);
assert(remote->port != 0);
/* Asserts to protect GNRC_TCP. Flags are not supported. */
assert(flags == 0);
(void) flags;
/* Initialize given TCB and try to open a connection */
gnrc_tcp_tcb_init(sock);
/* Forward call to gnrc_tcp_open. Return codes are identical except those that are
* not generated by gnrc_tcp_open: -ENETUNREACH and -EPERM */
return gnrc_tcp_open(sock, remote, local_port);
}
int sock_tcp_listen(sock_tcp_queue_t *queue, const sock_tcp_ep_t *local,
sock_tcp_t *queue_array, unsigned queue_len, uint16_t flags)
{
/* Asserts defined by API. */
assert(queue != NULL);
assert(local != NULL);
assert(local->port != 0);
assert(queue_array != NULL);
assert(queue_len != 0);
/* Asserts to protect GNRC_TCP. Flags are not supported. */
assert(flags == 0);
(void) flags;
/* Initialize given TCB queue, all given tcbs and forward call */
gnrc_tcp_tcb_queue_init(queue);
for (unsigned i = 0; i < queue_len; ++i) {
gnrc_tcp_tcb_init(&queue_array[i]);
}
return gnrc_tcp_listen(queue, queue_array, queue_len, local);
}
void sock_tcp_disconnect(sock_tcp_t *sock)
{
/* Asserts defined by API. */
assert(sock != NULL);
gnrc_tcp_close(sock);
}
void sock_tcp_stop_listen(sock_tcp_queue_t *queue)
{
/* Asserts defined by API. */
assert(queue != NULL);
gnrc_tcp_stop_listen(queue);
}
int sock_tcp_get_local(sock_tcp_t *sock, sock_tcp_ep_t *ep)
{
/* Asserts defined by API. */
assert(sock != NULL);
assert(ep != NULL);
return gnrc_tcp_get_local(sock, ep);
}
int sock_tcp_get_remote(sock_tcp_t *sock, sock_tcp_ep_t *ep)
{
/* Asserts defined by API. */
assert(sock != NULL);
assert(ep != NULL);
return gnrc_tcp_get_remote(sock, ep);
}
int sock_tcp_queue_get_local(sock_tcp_queue_t *queue, sock_tcp_ep_t *ep)
{
/* Asserts defined by API. */
assert(queue != NULL);
assert(ep != NULL);
return gnrc_tcp_queue_get_local(queue, ep);
}
int sock_tcp_accept(sock_tcp_queue_t *queue, sock_tcp_t **sock, uint32_t timeout)
{
/* Asserts defined by API. */
assert(queue != NULL);
assert(sock != NULL);
/* Map SOCK_NO_TIMEOUT to GNRC_TCP_NO_TIMEOUT */
if (timeout == SOCK_NO_TIMEOUT) {
timeout = GNRC_TCP_NO_TIMEOUT;
}
/* Forward call to gnrc_tcp_accept.
* NOTE: Errorcodes -ECONNABORTED, -EPERM are not returned by
* gnrc_tcp_accept. All other error codes share the same semantics. */
return gnrc_tcp_accept(queue, sock, timeout);
}
ssize_t sock_tcp_read(sock_tcp_t *sock, void *data, size_t max_len, uint32_t timeout)
{
/* Asserts defined by API. */
assert(sock != NULL);
assert(data != NULL);
/* Map SOCK_NO_TIMEOUT to GNRC_TCP_NO_TIMEOUT */
if (timeout == SOCK_NO_TIMEOUT) {
timeout = GNRC_TCP_NO_TIMEOUT;
}
/* Forward call to gnrc_tcp_recv: All error codes share the same semantics */
return gnrc_tcp_recv(sock, data, max_len, timeout);
}
ssize_t sock_tcp_write(sock_tcp_t *sock, const void *data, size_t len)
{
/* Asserts defined by API. */
assert(sock != NULL);
assert(data != NULL);
/* Forward call to gnrc_tcp_send.
* NOTE: gnrc_tcp_send offers a timeout. By setting it to 0, the call blocks
* until at least some data was transmitted. */
return gnrc_tcp_send(sock, data, len, 0);
}

View File

@ -0,0 +1,59 @@
include ../Makefile.tests_common
# Basic Configuration
BOARD ?= native
TAP ?= tap0
# Shorten default TCP timeouts to speedup testing
MSL_MS ?= 1000
TIMEOUT_MS ?= 3000
# This test depends on tap device setup (only allowed by root)
# Suppress test execution to avoid CI errors
TEST_ON_CI_BLACKLIST += all
ifeq (native,$(BOARD))
TERMFLAGS ?= $(TAP)
else
ETHOS_BAUDRATE ?= 115200
CFLAGS += -DETHOS_BAUDRATE=$(ETHOS_BAUDRATE)
TERMDEPS += ethos
TERMPROG ?= sudo $(RIOTTOOLS)/ethos/ethos
TERMFLAGS ?= $(TAP) $(PORT) $(ETHOS_BAUDRATE)
endif
USEMODULE += auto_init_gnrc_netif
USEMODULE += gnrc_ipv6_default
USEMODULE += gnrc_sock_tcp
USEMODULE += gnrc_pktbuf_cmd
USEMODULE += gnrc_netif_single # Only one interface used and it makes
# shell commands easier
USEMODULE += shell
USEMODULE += shell_commands
USEMODULE += od
# Export used tap device to environment
export TAPDEV = $(TAP)
.PHONY: ethos
ethos:
$(Q)env -u CC -u CFLAGS $(MAKE) -C $(RIOTTOOLS)/ethos
include $(RIOTBASE)/Makefile.include
# Set CONFIG_GNRC_TCP_MSL via CFLAGS if not being set via Kconfig
ifndef CONFIG_GNRC_TCP_MSL_MS
CFLAGS += -DCONFIG_GNRC_TCP_MSL_MS=$(MSL_MS)
endif
# Set CONFIG_GNRC_TCP_CONNECTION_TIMEOUT_DURATION via CFLAGS if not being set
# via Kconfig
ifndef CONFIG_GNRC_TCP_CONNECTION_TIMEOUT_DURATION_MS
CFLAGS += -DCONFIG_GNRC_TCP_CONNECTION_TIMEOUT_DURATION_MS=$(TIMEOUT_MS)
endif
# Set the shell echo configuration via CFLAGS if not being controlled via Kconfig
ifndef CONFIG_KCONFIG_USEMODULE_SHELL
CFLAGS += -DCONFIG_SHELL_NO_ECHO
endif

View File

@ -0,0 +1,6 @@
# Put board specific dependencies here
ifeq (native,$(BOARD))
USEMODULE += netdev_tap
else
USEMODULE += stdio_ethos
endif

View File

@ -0,0 +1,44 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-leonardo \
arduino-mega2560 \
arduino-nano \
arduino-uno \
atmega1284p \
atmega328p \
atmega328p-xplained-mini \
atxmega-a1u-xpro \
atxmega-a3bu-xplained \
bluepill-stm32f030c8 \
derfmega128 \
hifive1 \
hifive1b \
i-nucleo-lrwan1 \
im880b \
mega-xplained \
microduino-corerf \
msb-430 \
msb-430h \
nucleo-f030r8 \
nucleo-f031k6 \
nucleo-f042k6 \
nucleo-f070rb \
nucleo-f072rb \
nucleo-f303k8 \
nucleo-f334r8 \
nucleo-l011k4 \
nucleo-l031k6 \
nucleo-l053r8 \
samd10-xmini \
saml10-xpro \
saml11-xpro \
slstk3400a \
stk3200 \
stm32f030f4-demo \
stm32f0discovery \
stm32l0538-disco \
telosb \
waspmote-pro \
z1 \
zigduino \
#

View File

@ -0,0 +1,20 @@
Test description
==========
The testsuite tests the GNRC TCP integration into the SOCK TCP interface.
The tests offer only basic verification of the SOCK TCP interface since GNRC TCP aims
follow the SOCK TCP interface as close as possible, detailed tests are under tests/gnrc_tcp
Setup
==========
The test requires a tap-device setup. This can be achieved by running 'dist/tools/tapsetup/tapsetup'
or by executing the following commands:
sudo ip tuntap add tap0 mode tap user ${USER}
sudo ip link set tap0 up
Usage
==========
make BOARD=<BOARD_NAME> all flash
sudo make BOARD=<BOARD_NAME> test-as-root
'sudo' is required due to ethos and raw socket usage.

222
tests/gnrc_sock_tcp/main.c Normal file
View File

@ -0,0 +1,222 @@
/*
* Copyright (C) 2021 Simon Brummer <simon.brummer@posteo.de>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
#include <stdio.h>
#include <string.h>
#include "shell.h"
#include "msg.h"
#include "net/sock/tcp.h"
#define MAIN_QUEUE_SIZE (8)
#define SOCK_TCP_QUEUE_SIZE (1)
#define BUFFER_SIZE (1024)
static msg_t main_msg_queue[MAIN_QUEUE_SIZE];
static sock_tcp_t socks[SOCK_TCP_QUEUE_SIZE];
static sock_tcp_t *sock = socks;
static sock_tcp_queue_t sock_queue;
static char buffer[BUFFER_SIZE];
void dump_args(int argc, char **argv)
{
printf("%s: ", argv[0]);
printf("argc=%d", argc);
for (int i = 0; i < argc; ++i) {
printf(", argv[%d] = %s", i, argv[i]);
}
printf("\n");
}
void print_result(const char *name, int err)
{
if (err) {
printf("%s: returns %s\n", name, strerror(-err));
} else {
printf("%s: returns %d\n", name, err);
}
}
int sock_tcp_connect_cmd(int argc, char **argv)
{
dump_args(argc, argv);
sock_tcp_ep_t ep = SOCK_IPV6_EP_ANY;
gnrc_tcp_ep_from_str((gnrc_tcp_ep_t *) &ep, argv[1]);
uint16_t local_port = atol(argv[2]);
uint16_t flags = 0;
int err = sock_tcp_connect(sock, &ep, local_port, flags);
print_result(argv[0], err);
return 0;
}
int sock_tcp_disconnect_cmd(int argc, char **argv)
{
dump_args(argc, argv);
sock_tcp_disconnect(sock);
printf("%s: returns\n", argv[0]);
return 0;
}
int sock_tcp_listen_cmd(int argc, char **argv)
{
dump_args(argc, argv);
sock_tcp_ep_t ep = SOCK_IPV6_EP_ANY;
gnrc_tcp_ep_from_str((gnrc_tcp_ep_t *) &ep, argv[1]);
uint16_t flags = 0;
int err = sock_tcp_listen(&sock_queue, &ep, socks, SOCK_TCP_QUEUE_SIZE, flags);
print_result(argv[0], err);
return 0;
}
int sock_tcp_stop_listen_cmd(int argc, char **argv)
{
dump_args(argc, argv);
sock_tcp_stop_listen(&sock_queue);
printf("%s: returns\n", argv[0]);
return 0;
}
int sock_tcp_accept_cmd(int argc, char **argv)
{
dump_args(argc, argv);
sock_tcp_t *tmp = NULL;
uint16_t timeout = atol(argv[1]);
int err = sock_tcp_accept(&sock_queue, &tmp, timeout);
print_result(argv[0], err);
if (tmp) {
sock = tmp;
}
return 0;
}
int sock_tcp_read_cmd(int argc, char **argv)
{
dump_args(argc, argv);
unsigned to_receive = atol(argv[1]);
int timeout = (argc > 2) ? atol(argv[1]) : 0;
unsigned rcvd = 0;
while (rcvd < to_receive) {
int ret = sock_tcp_read(sock, buffer + rcvd, to_receive - rcvd, timeout);
if (ret > 0) {
rcvd += ret;
} else {
print_result(argv[0], ret);
if (ret == -EAGAIN) {
continue;
} else {
return ret;
}
}
}
buffer[to_receive] = '\0';
printf("%s: received %u %s\n", argv[0], rcvd, buffer);
return 0;
}
int sock_tcp_write_cmd(int argc, char **argv)
{
dump_args(argc, argv);
unsigned payload_size = strlen(argv[1]);
char *payload = argv[1];
unsigned sent = 0;
while (sent < payload_size)
{
int ret = sock_tcp_write(sock, payload + sent, payload_size - sent);
if (ret >= 0) {
sent += ret;
} else {
print_result(argv[0], ret);
return ret;
}
}
printf("%s: sent %u\n", argv[0], sent);
return 0;
}
int sock_tcp_get_local_cmd(int argc, char **argv)
{
dump_args(argc, argv);
sock_tcp_ep_t ep = SOCK_IPV6_EP_ANY;
int err = sock_tcp_get_local(sock, &ep);
print_result(argv[0], err);
if (err == 0) {
printf("Endpoint: addr.ipv6=");
ipv6_addr_print((ipv6_addr_t *) ep.addr.ipv6);
printf(" netif=%u port=%u\n", ep.netif, ep.port);
}
return 0;
}
int sock_tcp_queue_get_local_cmd(int argc, char **argv)
{
dump_args(argc, argv);
sock_tcp_ep_t ep = SOCK_IPV6_EP_ANY;
int err = sock_tcp_queue_get_local(&sock_queue, &ep);
print_result(argv[0], err);
if (err == 0) {
printf("Endpoint: addr.ipv6=");
ipv6_addr_print((ipv6_addr_t *) ep.addr.ipv6);
printf(" netif=%u port=%u\n", ep.netif, ep.port);
}
return 0;
}
int sock_tcp_get_remote_cmd(int argc, char **argv)
{
dump_args(argc, argv);
sock_tcp_ep_t ep = SOCK_IPV6_EP_ANY;
int err = sock_tcp_get_remote(sock, &ep);
print_result(argv[0], err);
if (err == 0) {
printf("Endpoint: addr.ipv6=");
ipv6_addr_print((ipv6_addr_t *) ep.addr.ipv6);
printf(" netif=%u port=%u\n", ep.netif, ep.port);
}
return 0;
}
/* Exporting GNRC SOCK TCP Api to for shell usage */
static const shell_command_t shell_commands[] = {
{ "sock_tcp_connect", "connect", sock_tcp_connect_cmd },
{ "sock_tcp_disconnect", "disconnect", sock_tcp_disconnect_cmd },
{ "sock_tcp_listen", "listen", sock_tcp_listen_cmd },
{ "sock_tcp_stop_listen", "stop_listen", sock_tcp_stop_listen_cmd },
{ "sock_tcp_accept", "accept", sock_tcp_accept_cmd },
{ "sock_tcp_read", "read", sock_tcp_read_cmd },
{ "sock_tcp_write", "write", sock_tcp_write_cmd },
{ "sock_tcp_get_local", "get_local", sock_tcp_get_local_cmd },
{ "sock_tcp_queue_get_local", "queue_get_local", sock_tcp_queue_get_local_cmd },
{ "sock_tcp_get_remote", "get_remote", sock_tcp_get_remote_cmd },
{ NULL, NULL, NULL }
};
int main(void)
{
/* we need a message queue for the thread running the shell in order to
* receive potentially fast incoming networking packets */
msg_init_queue(main_msg_queue, MAIN_QUEUE_SIZE);
printf("RIOT GNRC_TCP test application\n");
/* start shell */
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
/* should be never reached */
return 0;
}

View File

@ -0,0 +1,161 @@
#!/usr/bin/env python3
# Copyright (C) 2021 Simon Brummer <simon.brummer@posteo.de>
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
import os
import sys
import random
from helpers import Runner, SockTcpServer, SockTcpClient, HostTcpServer, HostTcpClient, \
generate_port_number, sudo_guard
@Runner(timeout=5)
def test_connection_lifecycle_as_client(child):
""" Open/close a single connection as sock tcp client """
# Setup Host as server
with HostTcpServer(generate_port_number()) as host_srv:
# Setup Riot Node as client
local_port = 54321
with SockTcpClient(child, host_srv, local_port) as sock_cli:
# Accept, verify Endpoints and close connection
host_srv.accept()
# Verify connection endpoints from sock perspective
sock_cli.get_local()
child.expect_exact('Endpoint: addr.ipv6={} netif={} port={}'.format(
sock_cli.address, sock_cli.interface, sock_cli.local_port)
)
sock_cli.get_remote()
child.expect_exact('Endpoint: addr.ipv6={} netif=0 port={}'.format(
host_srv.address, host_srv.listen_port)
)
# Close Connection
host_srv.close()
@Runner(timeout=10)
def test_connection_lifecycle_as_server(child):
""" Open/close a single connection as sock tcp server """
# Setup Riot Node as server
with SockTcpServer(child, generate_port_number()) as sock_srv:
# Verify listen parameters
sock_srv.queue_get_local()
child.expect_exact('Endpoint: addr.ipv6=:: netif=0 port={}'.format(
sock_srv.listen_port)
)
# Setup Host as client
with HostTcpClient(sock_srv) as host_cli:
# Accept connection
sock_srv.accept(timeout_ms=1000)
# Verify connection endpoints from sock perspective
sock_srv.get_local()
child.expect_exact('Endpoint: addr.ipv6={} netif={} port={}'.format(
sock_srv.address, sock_srv.interface, sock_srv.listen_port)
)
sock_srv.get_remote()
child.expect_exact('Endpoint: addr.ipv6={}'.format(host_cli.address))
# Close connection
sock_srv.disconnect()
@Runner(timeout=5)
def test_send_data_from_riot_to_host(child):
""" Send Data from RIOT Node to Host system """
# Setup Host as server
with HostTcpServer(generate_port_number()) as host_srv:
# Setup Riot as client
with SockTcpClient(child, host_srv) as sock_cli:
# Accept and close connection
host_srv.accept()
# Send Data from RIOT to Host system and verify reception
data = '0123456789'
sock_cli.write(data)
host_srv.receive(data)
# Teardown connection
host_srv.close()
@Runner(timeout=5)
def test_send_data_from_host_to_riot(child):
""" Send Data from Host system to RIOT node """
# Setup Riot Node as server
with SockTcpServer(child, generate_port_number()) as sock_srv:
# Setup Host as client
with HostTcpClient(sock_srv) as host_cli:
# Accept and close connection
sock_srv.accept(timeout_ms=1000)
# Send Data from Host system to RIOT
data = '0123456789'
host_cli.send(data)
sock_srv.read(data, 1000)
sock_srv.disconnect()
@Runner(timeout=10)
def test_connection_listen_accept_cycle(child, iterations=10):
""" This test verifies sock_tcp in a typical server role by
accepting a connection, exchange data, teardown connection, handle the next one
"""
# Setup RIOT Node as server
with SockTcpServer(child, generate_port_number()) as sock_srv:
# Establish multiple connections iterativly
for i in range(iterations):
print('\n Running listen/accept iteration {}'.format(i), end='')
with HostTcpClient(sock_srv) as host_cli:
# Accept connection from host system
sock_srv.accept(timeout_ms=0)
# Send data from host to RIOT
data = '0123456789'
host_cli.send(data)
sock_srv.read(data, 1000)
# Send data from RIOT to host
sock_srv.write(data)
host_cli.receive(data)
# Randomize connection teardown: The connections
# can be closed from either Regardless the type of the connection teardown
# sock_srv must be able to accept the next connection
dice_throw = random.randint(0, 1)
if dice_throw == 0:
sock_srv.disconnect()
host_cli.close()
elif dice_throw == 1:
host_cli.close()
sock_srv.disconnect()
if __name__ == '__main__':
sudo_guard()
# Read and run all test functions.
script = sys.modules[__name__]
tests = [getattr(script, t) for t in script.__dict__
if type(getattr(script, t)).__name__ == 'function'
and t.startswith('test_')]
for test in tests:
res = test()
if (res != 0):
sys.exit(res)
print('\n' + os.path.basename(sys.argv[0]) + ': success\n')

View File

@ -0,0 +1,244 @@
#!/usr/bin/env python3
# Copyright (C) 2018 Simon Brummer <simon.brummer@posteo.de>
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
import sys
import os
import re
import socket
import random
import testrunner
class Runner:
def __init__(self, timeout, echo=False, skip=False):
self.timeout = timeout
self.echo = echo
self.skip = skip
def __call__(self, fn):
if self.skip:
print('\n- "{}": SKIPPED'.format(fn.__name__), end='')
return 0
res = -1
try:
res = testrunner.run(fn, self.timeout, self.echo)
finally:
if res == 0:
print('- "{}": SUCCESS'.format(fn.__name__), end='')
else:
print('- "{}": FAILED'.format(fn.__name__), end='')
return res
class _HostTcpNode:
def __init__(self):
self.opened = False
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.interface = self._get_interface()
self.address = self._get_ip_address(self.interface)
def send(self, payload_to_send):
self.sock.send(payload_to_send.encode('utf-8'))
def receive(self, sent_payload):
total_bytes = len(sent_payload)
assert self.sock.recv(total_bytes, socket.MSG_WAITALL).decode('utf-8') == sent_payload
def close(self):
self.sock.close()
self.opened = False
def _get_interface(self):
# Check if given tap device is part of a network bridge
# if so use bridged interface instead of given tap device
tap = os.environ["TAPDEV"]
result = os.popen('bridge link show dev {}'.format(tap))
bridge = re.search('master (.*) state', result.read())
return bridge.group(1).strip() if bridge else tap
def _get_ip_address(self, interface):
result = os.popen('ip addr show dev ' + interface + ' scope link')
return re.search('inet6 (.*)/64', result.read()).group(1).strip()
class HostTcpServer(_HostTcpNode):
def __init__(self, listen_port):
super().__init__()
self.listening = False
self.listen_sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
self.listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.listen_port = listen_port
def __enter__(self):
if not self.listening:
self.listen()
return self
def __exit__(self, _1, _2, _3):
if self.listening:
self.stop_listen()
def listen(self):
self.listen_sock.bind(('::', self.listen_port))
self.listen_sock.listen(1)
self.listening = True
def accept(self):
self.sock, _ = self.listen_sock.accept()
def stop_listen(self):
self.listen_sock.close()
self.listening = False
class HostTcpClient(_HostTcpNode):
def __init__(self, target):
super().__init__()
self.target_addr = str(target.address)
self.target_port = str(target.listen_port)
def __enter__(self):
if not self.opened:
self.open()
return self
def __exit__(self, _1, _2, _3):
if self.opened:
self.close()
def open(self):
addrinfo = socket.getaddrinfo(
self.target_addr + '%' + self.interface,
self.target_port,
type=socket.SOCK_STREAM
)
self.sock.connect(addrinfo[0][-1])
self.opened = True
class _SockTcpNode:
def __init__(self, child):
self.child = child
self.connected = False
self.interface = self._get_interface()
self.address = self._get_ip_address()
def write(self, payload):
self.child.sendline('sock_tcp_write {}'.format(str(payload)))
self.child.expect_exact('sock_tcp_write: sent {}'.format(len(payload)))
def read(self, payload, timeout_ms=0):
total_bytes = str(len(payload))
self.child.sendline('sock_tcp_read {} {}'.format(total_bytes, timeout_ms))
self.child.expect_exact('sock_tcp_read: received {} {}'.format(
total_bytes, payload)
)
def disconnect(self):
self.child.sendline('sock_tcp_disconnect')
self.child.expect_exact('sock_tcp_disconnect: returns')
self.connected = False
def get_local(self):
self.child.sendline('sock_tcp_get_local')
self.child.expect_exact('sock_tcp_get_local: returns 0')
def get_remote(self):
self.child.sendline('sock_tcp_get_remote')
self.child.expect_exact('sock_tcp_get_remote: returns 0')
def _get_interface(self):
self.child.sendline('ifconfig')
self.child.expect(r'Iface\s+(\d+)\s')
return self.child.match.group(1).strip()
def _get_ip_address(self):
self.child.sendline('ifconfig')
self.child.expect(r'(fe80:[0-9a-f:]+)\s')
return self.child.match.group(1).strip()
class SockTcpServer(_SockTcpNode):
def __init__(self, child, listen_port, listen_addr='::'):
super().__init__(child)
self.listening = False
self.listen_port = str(listen_port)
self.listen_addr = str(listen_addr)
def __enter__(self):
if not self.listening:
self.listen()
return self
def __exit__(self, _1, _2, _3):
if self.listening:
self.stop_listen()
def listen(self):
self.child.sendline('sock_tcp_listen [{}]:{}'.format(
self.listen_addr, self.listen_port)
)
self.child.expect_exact('sock_tcp_listen: returns 0')
self.listening = True
def accept(self, timeout_ms):
self.child.sendline('sock_tcp_accept {}'.format(str(timeout_ms)))
self.child.expect_exact('sock_tcp_accept: returns 0')
self.opened = True
def stop_listen(self):
self.child.sendline('sock_tcp_stop_listen')
self.child.expect_exact('sock_tcp_stop_listen: returns')
self.listening = False
def queue_get_local(self):
self.child.sendline('sock_tcp_queue_get_local')
self.child.expect_exact('sock_tcp_queue_get_local: returns 0')
class SockTcpClient(_SockTcpNode):
def __init__(self, child, target, local_port=0):
super().__init__(child)
self.target_addr = target.address + '%' + self.interface
self.target_port = str(target.listen_port)
self.local_port = local_port
def __enter__(self):
if not self.connected:
self.connect()
return self
def __exit__(self, _1, _2, _3):
if self.connected:
self.disconnect()
def connect(self):
self.child.sendline('sock_tcp_connect [{}]:{} {}'.format(
self.target_addr, self.target_port, self.local_port)
)
self.child.expect_exact('sock_tcp_connect: returns 0')
self.connected = True
def generate_port_number():
return random.randint(1024, 65535)
def sudo_guard(uses_scapy=False):
sudo_required = uses_scapy or (os.environ.get("BOARD", "") != "native")
if sudo_required and os.geteuid() != 0:
print("\x1b[1;31mThis test requires root privileges.\n"
"It uses `./dist/tools/ethos/start_networking.sh` as term" +
(" and it's constructing and sending Ethernet frames."
if uses_scapy else "") + "\x1b[0m\n",
file=sys.stderr)
sys.exit(1)