From 2aa08a451c345c8cedc841b1170ad147314f21ab 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/net/gnrc/Makefile | 3 + sys/net/gnrc/Makefile.dep | 7 + sys/net/gnrc/sock/include/sock_types.h | 18 + sys/net/gnrc/sock/tcp/Makefile | 3 + sys/net/gnrc/sock/tcp/gnrc_sock_tcp.c | 216 ++++++++++++ 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 | 346 +++++++++++++++++++ tests/gnrc_sock_tcp/tests-as-root/01-run.py | 161 +++++++++ tests/gnrc_sock_tcp/tests-as-root/helpers.py | 245 +++++++++++++ 12 files changed, 1128 insertions(+) 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/net/gnrc/Makefile b/sys/net/gnrc/Makefile index 024233cbe55ea..e079b12dc650e 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 7d31be3afb075..c9e7f352addea 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 53542d591138a..86fc50047e65b 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" { @@ -136,6 +138,22 @@ struct sock_udp { uint16_t flags; /**< option flags */ }; +/** + * @brief TCP sock type + * @internal + */ +struct sock_tcp { + gnrc_tcp_tcb_t tcb; /**< tcb */ +}; + +/** + * @brief TCP queue sock type + * @internal + */ +struct sock_tcp_queue { + gnrc_tcp_tcb_queue_t tcb_queue; /**< tcb queue*/ +}; + #ifdef __cplusplus } #endif diff --git a/sys/net/gnrc/sock/tcp/Makefile b/sys/net/gnrc/sock/tcp/Makefile new file mode 100644 index 0000000000000..f32b7f153f16a --- /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 0000000000000..be67396850055 --- /dev/null +++ b/sys/net/gnrc/sock/tcp/gnrc_sock_tcp.c @@ -0,0 +1,216 @@ +/* + * 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); + assert(sizeof(sock_tcp_t) == sizeof(gnrc_tcp_tcb_t)); + assert(sizeof(sock_tcp_ep_t) == sizeof(gnrc_tcp_ep_t)); + + /* NOTE: GNRC_TCP and GNRC_SOCK_TCP types must have the same memory representation. */ + gnrc_tcp_tcb_t *tcb = (gnrc_tcp_tcb_t *) sock; + gnrc_tcp_ep_t *ep = (gnrc_tcp_ep_t *) remote; + + /* Initialize given TCB and try to open a connection */ + gnrc_tcp_tcb_init(tcb); + + /* 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(tcb, ep, 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); + assert(sizeof(sock_tcp_queue_t) == sizeof(gnrc_tcp_tcb_queue_t)); + assert(sizeof(sock_tcp_t) == sizeof(gnrc_tcp_tcb_t)); + assert(sizeof(sock_tcp_ep_t) == sizeof(gnrc_tcp_ep_t)); + + /* NOTE: GNRC_TCP and GNRC_SOCK_TCP types must have the same memory representation. */ + gnrc_tcp_tcb_queue_t *tcb_queue = (gnrc_tcp_tcb_queue_t *) queue; + gnrc_tcp_tcb_t *tcbs = (gnrc_tcp_tcb_t *) queue_array; + gnrc_tcp_ep_t *ep = (gnrc_tcp_ep_t *) local; + + /* Initialize given TCB queue, all given tcbs and forward call */ + gnrc_tcp_tcb_queue_init(tcb_queue); + for (unsigned i = 0; i < queue_len; ++i) { + gnrc_tcp_tcb_init(&tcbs[i]); + } + return gnrc_tcp_listen(tcb_queue, tcbs, queue_len, ep); +} + +void sock_tcp_disconnect(sock_tcp_t *sock) +{ + /* Asserts defined by API. */ + assert(sock != NULL); + + /* Asserts to protect GNRC_TCP. */ + assert(sizeof(sock_tcp_t) == sizeof(gnrc_tcp_tcb_t)); + + /* NOTE: GNRC_TCP and GNRC_SOCK_TCP types must have the same memory representation. */ + gnrc_tcp_tcb_t *tcb = (gnrc_tcp_tcb_t *) sock; + gnrc_tcp_close(tcb); +} + +void sock_tcp_stop_listen(sock_tcp_queue_t *queue) +{ + /* Asserts defined by API. */ + assert(queue != NULL); + + /* Asserts to protect GNRC_TCP. */ + assert(sizeof(sock_tcp_queue_t) == sizeof(gnrc_tcp_tcb_queue_t)); + + /* NOTE: GNRC_TCP and GNRC_SOCK_TCP types must have the same memory representation. */ + gnrc_tcp_tcb_queue_t *tcb_queue = (gnrc_tcp_tcb_queue_t *) queue; + gnrc_tcp_stop_listen(tcb_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); + + /* Asserts to protect GNRC_TCP. */ + assert(sizeof(sock_tcp_t) == sizeof(gnrc_tcp_tcb_t)); + assert(sizeof(sock_tcp_ep_t) == sizeof(gnrc_tcp_ep_t)); + + /* NOTE: GNRC_TCP and GNRC_SOCK_TCP types must have the same memory representation. */ + gnrc_tcp_tcb_t *tcb = (gnrc_tcp_tcb_t *) sock; + gnrc_tcp_ep_t *local = (gnrc_tcp_ep_t *) ep; + return gnrc_tcp_get_local(tcb, local); +} + +int sock_tcp_get_remote(sock_tcp_t *sock, sock_tcp_ep_t *ep) +{ + /* Asserts defined by API. */ + assert(sock != NULL); + assert(ep != NULL); + + /* Asserts to protect GNRC_TCP. */ + assert(sizeof(sock_tcp_t) == sizeof(gnrc_tcp_tcb_t)); + assert(sizeof(sock_tcp_ep_t) == sizeof(gnrc_tcp_ep_t)); + + /* NOTE: GNRC_TCP and GNRC_SOCK_TCP types must have the same memory representation. */ + gnrc_tcp_tcb_t *tcb = (gnrc_tcp_tcb_t *) sock; + gnrc_tcp_ep_t *remote = (gnrc_tcp_ep_t *) ep; + return gnrc_tcp_get_remote(tcb, remote); +} + +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); + + /* Asserts to protect GNRC_TCP. */ + assert(sizeof(sock_tcp_queue_t) == sizeof(gnrc_tcp_tcb_queue_t)); + assert(sizeof(sock_tcp_ep_t) == sizeof(gnrc_tcp_ep_t)); + + /* NOTE: GNRC_TCP and GNRC_SOCK_TCP types must have the same memory representation. */ + gnrc_tcp_tcb_queue_t *tcb_queue = (gnrc_tcp_tcb_queue_t *) queue; + gnrc_tcp_ep_t *local = (gnrc_tcp_ep_t *) ep; + return gnrc_tcp_queue_get_local(tcb_queue, local); +} + +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); + + /* Asserts to protect GNRC_TCP. */ + assert(sizeof(sock_tcp_queue_t) == sizeof(gnrc_tcp_tcb_queue_t)); + assert(sizeof(sock_tcp_t) == sizeof(gnrc_tcp_tcb_t)); + + /* NOTE: GNRC_TCP and GNRC_SOCK_TCP types must have the same memory representation. */ + gnrc_tcp_tcb_queue_t *tcb_queue = (gnrc_tcp_tcb_queue_t *) queue; + gnrc_tcp_tcb_t **tcb = (gnrc_tcp_tcb_t **) sock; + + /* Map SOCK_NO_TIMEOUT to 0. 0 is used in GNRC_TCP for no timeout */ + if (timeout == SOCK_NO_TIMEOUT) { + timeout = 0; + } + + /* 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(tcb_queue, tcb, 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); + assert(max_len > 0); + + /* Asserts to protect GNRC_TCP. */ + assert(sizeof(sock_tcp_t) == sizeof(gnrc_tcp_tcb_t)); + + /* NOTE: GNRC_TCP and GNRC_SOCK_TCP types must have the same memory representation. */ + gnrc_tcp_tcb_t *tcb = (gnrc_tcp_tcb_t *) sock; + + /* Map SOCK_NO_TIMEOUT to 0. 0 is used in GNRC_TCP for no timeout */ + if (timeout == SOCK_NO_TIMEOUT) { + timeout = 0; + } + + /* Forward call to gnrc_tcp_recv: All error codes share the same semantics */ + return gnrc_tcp_recv(tcb, 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); + assert(len > 0); + + /* Asserts to protect GNRC_TCP. */ + assert(sizeof(sock_tcp_t) == sizeof(gnrc_tcp_tcb_t)); + + /* NOTE: GNRC_TCP and GNRC_SOCK_TCP types must have the same memory representation. */ + gnrc_tcp_tcb_t *tcb = (gnrc_tcp_tcb_t *) sock; + + /* 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(tcb, data, len, 0); +} diff --git a/tests/gnrc_sock_tcp/Makefile b/tests/gnrc_sock_tcp/Makefile new file mode 100644 index 0000000000000..198ec5b55cfdc --- /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 0000000000000..b595b8605ca10 --- /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 0000000000000..630b8e80a94b4 --- /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 0000000000000..7aa77c502320f --- /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 0000000000000..a469dba5078b8 --- /dev/null +++ b/tests/gnrc_sock_tcp/main.c @@ -0,0 +1,346 @@ +/* + * 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"); +} + +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); + switch (err) { + case -EADDRINUSE: + printf("%s: returns -EADDRINUSE\n", argv[0]); + break; + + case -EAFNOSUPPORT: + printf("%s: returns -EAFNOSUPPORT\n", argv[0]); + break; + + case -ECONNREFUSED: + printf("%s: returns -ECONNREFUSED\n", argv[0]); + break; + + case -EINVAL: + printf("%s: returns -EINVAL\n", argv[0]); + break; + + case -ENETUNREACH: + printf("%s: returns -ENETUNREACH\n", argv[0]); + break; + + case -ENOMEM: + printf("%s: returns -ENOMEM\n", argv[0]); + break; + + case -EPERM: + printf("%s: returns -EPERM\n", argv[0]); + break; + + case -ETIMEDOUT: + printf("%s: returns -ETIMEDOUT\n", argv[0]); + break; + + default: + printf("%s: returns %d\n", 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); + switch (err) { + case -EADDRINUSE: + printf("%s: returns -EADDRINUSE\n", argv[0]); + break; + + case -EAFNOSUPPORT: + printf("%s: returns -EAFNOSUPPORT\n", argv[0]); + break; + + case -EINVAL: + printf("%s: returns -EINVAL\n", argv[0]); + break; + + case -ENOMEM: + printf("%s: returns -ENOMEM\n", argv[0]); + break; + + default: + printf("%s: returns %d\n", 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); + switch (err) { + case -EAGAIN: + printf("%s: returns -EAGAIN\n", argv[0]); + break; + + case -ECONNABORTED: + printf("%s: returns -ECONNABORTED\n", argv[0]); + break; + + case -EINVAL: + printf("%s: returns -EINVAL\n", argv[0]); + break; + + case -ENOMEM: + printf("%s: returns -ENOMEM\n", argv[0]); + break; + + case -EPERM: + printf("%s: returns -EPERM\n", argv[0]); + break; + + case -ETIMEDOUT: + printf("%s: returns -ETIMEDOUT\n", argv[0]); + break; + + default: + printf("%s: returns %d\n", argv[0], err); + } + + if (tmp) { + sock = tmp; + } + return 0; +} + +int sock_tcp_read_cmd(int argc, char **argv) +{ + dump_args(argc, argv); + + int timeout = atol(argv[1]); + unsigned to_receive = atol(argv[2]); + unsigned rcvd = 0; + + while (rcvd < to_receive) { + int ret = sock_tcp_read(sock, buffer + rcvd, to_receive - rcvd, timeout); + switch (ret) { + case 0: + printf("%s: returns 0\n", argv[0]); + return ret; + + case -EAGAIN: + printf("%s: returns -EAGAIN\n", argv[0]); + continue; + + case -ECONNABORTED: + printf("%s: returns -ECONNABORTED\n", argv[0]); + return ret; + + case -ECONNRESET: + printf("%s: returns -ECONNABORTED\n", argv[0]); + return ret; + + case -ENOTCONN: + printf("%s: returns -ENOTCONN\n", argv[0]); + return ret; + + case -ETIMEDOUT: + printf("%s: returns -ETIMEDOUT\n", argv[0]); + return ret; + } + rcvd += 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 = atol(argv[1]); + char *payload = argv[2]; + + unsigned sent = 0; + while (sent < payload_size) + { + int ret = sock_tcp_write(sock, payload + sent, payload_size - sent); + switch (ret) { + case -ECONNABORTED: + printf("%s: returns -ECONNABORTED\n", argv[0]); + return ret; + + case -ECONNRESET: + printf("%s: returns -ECONNABORTED\n", argv[0]); + return ret; + + case -ENOMEM: + printf("%s: returns -ENOMEM\n", argv[0]); + return ret; + + case -ENOTCONN: + printf("%s: returns -ENOTCONN\n", argv[0]); + return ret; + } + sent += 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); + switch (err) { + case 0: + printf("%s: returns 0\n", argv[0]); + printf("Endpoint: addr.ipv6="); + ipv6_addr_print((ipv6_addr_t *) ep.addr.ipv6); + printf(" netif=%u port=%u\n", ep.netif, ep.port); + break; + + case -EADDRNOTAVAIL: + printf("%s: returns -EADDRNOTAVAIL\n", argv[0]); + break; + + default: + printf("%s: returns %d\n", argv[0], err); + } + 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); + switch (err) { + case 0: + printf("%s: returns 0\n", argv[0]); + printf("Endpoint: addr.ipv6="); + ipv6_addr_print((ipv6_addr_t *) ep.addr.ipv6); + printf(" netif=%u port=%u\n", ep.netif, ep.port); + break; + + case -EADDRNOTAVAIL: + printf("%s: returns -EADDRNOTAVAIL\n", argv[0]); + break; + + default: + printf("%s: returns %d\n", argv[0], err); + } + 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); + switch (err) { + case 0: + printf("%s: returns 0\n", argv[0]); + printf("Endpoint: addr.ipv6="); + ipv6_addr_print((ipv6_addr_t *) ep.addr.ipv6); + printf(" netif=%u port=%u\n", ep.netif, ep.port); + break; + + case -EADDRNOTAVAIL: + printf("%s: returns -EADDRNOTAVAIL\n", argv[0]); + break; + + default: + printf("%s: returns %d\n", argv[0], err); + } + 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 0000000000000..36f4dcca68616 --- /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(1000, data) + + 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(1000, data) + + # 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 0000000000000..28c75cd67e9d0 --- /dev/null +++ b/tests/gnrc_sock_tcp/tests-as-root/helpers.py @@ -0,0 +1,245 @@ +#!/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): + total_bytes = str(len(payload)) + self.child.sendline('sock_tcp_write {} {}'.format(total_bytes, str(payload))) + self.child.expect_exact('sock_tcp_write: sent {}'.format(total_bytes)) + + def read(self, timeout_ms, payload): + total_bytes = str(len(payload)) + self.child.sendline('sock_tcp_read {} {}'.format(timeout_ms, total_bytes)) + 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)