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:
commit
8d9ca8ef68
@ -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.
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -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)))
|
||||
|
@ -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" {
|
||||
|
3
sys/net/gnrc/sock/tcp/Makefile
Normal file
3
sys/net/gnrc/sock/tcp/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
MODULE = gnrc_sock_tcp
|
||||
|
||||
include $(RIOTBASE)/Makefile.base
|
146
sys/net/gnrc/sock/tcp/gnrc_sock_tcp.c
Normal file
146
sys/net/gnrc/sock/tcp/gnrc_sock_tcp.c
Normal 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);
|
||||
}
|
59
tests/gnrc_sock_tcp/Makefile
Normal file
59
tests/gnrc_sock_tcp/Makefile
Normal 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
|
6
tests/gnrc_sock_tcp/Makefile.board.dep
Normal file
6
tests/gnrc_sock_tcp/Makefile.board.dep
Normal file
@ -0,0 +1,6 @@
|
||||
# Put board specific dependencies here
|
||||
ifeq (native,$(BOARD))
|
||||
USEMODULE += netdev_tap
|
||||
else
|
||||
USEMODULE += stdio_ethos
|
||||
endif
|
44
tests/gnrc_sock_tcp/Makefile.ci
Normal file
44
tests/gnrc_sock_tcp/Makefile.ci
Normal 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 \
|
||||
#
|
20
tests/gnrc_sock_tcp/README.md
Normal file
20
tests/gnrc_sock_tcp/README.md
Normal 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
222
tests/gnrc_sock_tcp/main.c
Normal 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;
|
||||
}
|
161
tests/gnrc_sock_tcp/tests-as-root/01-run.py
Executable file
161
tests/gnrc_sock_tcp/tests-as-root/01-run.py
Executable 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')
|
244
tests/gnrc_sock_tcp/tests-as-root/helpers.py
Normal file
244
tests/gnrc_sock_tcp/tests-as-root/helpers.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user