From 6fba0d970c6e9665f58c140b23c32ed77c603ae5 Mon Sep 17 00:00:00 2001 From: Simon Brummer Date: Sat, 22 May 2021 13:41:03 +0200 Subject: [PATCH] gnrc_sock_tcp: integrate gnrc_tcp --- sys/include/net/gnrc/tcp.h | 10 + sys/include/net/gnrc/tcp/tcb.h | 6 +- sys/net/gnrc/Makefile | 3 + sys/net/gnrc/Makefile.dep | 7 + sys/net/gnrc/sock/include/sock_types.h | 2 + sys/net/gnrc/sock/tcp/Makefile | 3 + sys/net/gnrc/sock/tcp/gnrc_sock_tcp.c | 146 +++++++++++ tests/gnrc_sock_tcp/Makefile | 59 +++++ tests/gnrc_sock_tcp/Makefile.board.dep | 6 + tests/gnrc_sock_tcp/Makefile.ci | 44 ++++ tests/gnrc_sock_tcp/README.md | 20 ++ tests/gnrc_sock_tcp/main.c | 222 +++++++++++++++++ tests/gnrc_sock_tcp/tests-as-root/01-run.py | 161 ++++++++++++ tests/gnrc_sock_tcp/tests-as-root/helpers.py | 244 +++++++++++++++++++ 14 files changed, 930 insertions(+), 3 deletions(-) create mode 100644 sys/net/gnrc/sock/tcp/Makefile create mode 100644 sys/net/gnrc/sock/tcp/gnrc_sock_tcp.c create mode 100644 tests/gnrc_sock_tcp/Makefile create mode 100644 tests/gnrc_sock_tcp/Makefile.board.dep create mode 100644 tests/gnrc_sock_tcp/Makefile.ci create mode 100644 tests/gnrc_sock_tcp/README.md create mode 100644 tests/gnrc_sock_tcp/main.c create mode 100755 tests/gnrc_sock_tcp/tests-as-root/01-run.py create mode 100644 tests/gnrc_sock_tcp/tests-as-root/helpers.py diff --git a/sys/include/net/gnrc/tcp.h b/sys/include/net/gnrc/tcp.h index db2bbb28cfa5..6dd2360e444f 100644 --- a/sys/include/net/gnrc/tcp.h +++ b/sys/include/net/gnrc/tcp.h @@ -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. diff --git a/sys/include/net/gnrc/tcp/tcb.h b/sys/include/net/gnrc/tcp/tcb.h index 2a991df18242..a83d333892ae 100644 --- a/sys/include/net/gnrc/tcp/tcb.h +++ b/sys/include/net/gnrc/tcp/tcb.h @@ -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 */ diff --git a/sys/net/gnrc/Makefile b/sys/net/gnrc/Makefile index 024233cbe55e..e079b12dc650 100644 --- a/sys/net/gnrc/Makefile +++ b/sys/net/gnrc/Makefile @@ -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 diff --git a/sys/net/gnrc/Makefile.dep b/sys/net/gnrc/Makefile.dep index 7d31be3afb07..c9e7f352adde 100644 --- a/sys/net/gnrc/Makefile.dep +++ b/sys/net/gnrc/Makefile.dep @@ -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))) diff --git a/sys/net/gnrc/sock/include/sock_types.h b/sys/net/gnrc/sock/include/sock_types.h index 53542d591138..6369914ec8ef 100644 --- a/sys/net/gnrc/sock/include/sock_types.h +++ b/sys/net/gnrc/sock/include/sock_types.h @@ -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" { diff --git a/sys/net/gnrc/sock/tcp/Makefile b/sys/net/gnrc/sock/tcp/Makefile new file mode 100644 index 000000000000..f32b7f153f16 --- /dev/null +++ b/sys/net/gnrc/sock/tcp/Makefile @@ -0,0 +1,3 @@ +MODULE = gnrc_sock_tcp + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/gnrc/sock/tcp/gnrc_sock_tcp.c b/sys/net/gnrc/sock/tcp/gnrc_sock_tcp.c new file mode 100644 index 000000000000..290113e78d2b --- /dev/null +++ b/sys/net/gnrc/sock/tcp/gnrc_sock_tcp.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2021 Simon Brummer + * + * 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 + */ + +#include + +#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); +} diff --git a/tests/gnrc_sock_tcp/Makefile b/tests/gnrc_sock_tcp/Makefile new file mode 100644 index 000000000000..198ec5b55cfd --- /dev/null +++ b/tests/gnrc_sock_tcp/Makefile @@ -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 diff --git a/tests/gnrc_sock_tcp/Makefile.board.dep b/tests/gnrc_sock_tcp/Makefile.board.dep new file mode 100644 index 000000000000..b595b8605ca1 --- /dev/null +++ b/tests/gnrc_sock_tcp/Makefile.board.dep @@ -0,0 +1,6 @@ +# Put board specific dependencies here +ifeq (native,$(BOARD)) + USEMODULE += netdev_tap +else + USEMODULE += stdio_ethos +endif diff --git a/tests/gnrc_sock_tcp/Makefile.ci b/tests/gnrc_sock_tcp/Makefile.ci new file mode 100644 index 000000000000..630b8e80a94b --- /dev/null +++ b/tests/gnrc_sock_tcp/Makefile.ci @@ -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 \ + # diff --git a/tests/gnrc_sock_tcp/README.md b/tests/gnrc_sock_tcp/README.md new file mode 100644 index 000000000000..7aa77c502320 --- /dev/null +++ b/tests/gnrc_sock_tcp/README.md @@ -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= all flash + sudo make BOARD= test-as-root + +'sudo' is required due to ethos and raw socket usage. diff --git a/tests/gnrc_sock_tcp/main.c b/tests/gnrc_sock_tcp/main.c new file mode 100644 index 000000000000..a24c24432480 --- /dev/null +++ b/tests/gnrc_sock_tcp/main.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2021 Simon Brummer + * + * 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 +#include + +#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; +} diff --git a/tests/gnrc_sock_tcp/tests-as-root/01-run.py b/tests/gnrc_sock_tcp/tests-as-root/01-run.py new file mode 100755 index 000000000000..d48c8aa52779 --- /dev/null +++ b/tests/gnrc_sock_tcp/tests-as-root/01-run.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2021 Simon Brummer +# +# 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') diff --git a/tests/gnrc_sock_tcp/tests-as-root/helpers.py b/tests/gnrc_sock_tcp/tests-as-root/helpers.py new file mode 100644 index 000000000000..effe250aae65 --- /dev/null +++ b/tests/gnrc_sock_tcp/tests-as-root/helpers.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2018 Simon Brummer +# +# 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)