1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #16459 from brummer-simon/gnrc_tcp-rewrite_passive_open

gnrc_tcp: rewrite passive open
This commit is contained in:
Francisco 2021-07-08 12:08:52 +02:00 committed by GitHub
commit 79ee4fd489
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 923 additions and 323 deletions

View File

@ -17,13 +17,17 @@
#include "net/ipv6/addr.h"
#include "net/gnrc/pkt.h"
#define TCB_QUEUE_SIZE 1
#define ACCEPT_TIMEOUT_MS 60000
static uint32_t demux = GNRC_NETREG_DEMUX_CTX_ALL;
static gnrc_nettype_t ntype = GNRC_NETTYPE_TCP;
static gnrc_tcp_tcb_queue_t queue = GNRC_TCP_TCB_QUEUE_INIT;
static gnrc_tcp_tcb_t tcbs[TCB_QUEUE_SIZE];
static void *tcploop(void *arg)
{
mutex_t *tcpmtx = arg;
gnrc_tcp_tcb_t tcb;
gnrc_tcp_ep_t ep;
if (gnrc_tcp_ep_from_str(&ep, "[" SERVER_ADDR "]")) {
@ -31,16 +35,26 @@ static void *tcploop(void *arg)
}
ep.port = SERVER_PORT;
for (;;) {
gnrc_tcp_tcb_init(&tcb);
mutex_unlock(tcpmtx);
for (unsigned i = 0; i < TCB_QUEUE_SIZE; ++i) {
gnrc_tcp_tcb_init(&tcbs[i]);
}
mutex_unlock(tcpmtx);
int ret = gnrc_tcp_open_passive(&tcb, &ep);
if (!ret) {
errx(EXIT_FAILURE, "gnrc_tcp_open_passive failed: %d\n", ret);
int ret = gnrc_tcp_listen(&queue, tcbs, ARRAY_SIZE(tcbs), &ep);
if (ret) {
errx(EXIT_FAILURE, "gnrc_tcp_listen failed: %d\n", ret);
}
for (;;) {
gnrc_tcp_tcb_t *tcp = NULL;
ret = gnrc_tcp_accept(&queue, &tcp, ACCEPT_TIMEOUT_MS);
if (ret) {
errx(EXIT_FAILURE, "gnrc_tcp_accept failed: %d\n", ret);
}
}
/* Never reached but, for clean programming sake */
gnrc_tcp_stop_listen(&queue);
return NULL;
}

View File

@ -102,57 +102,78 @@ int gnrc_tcp_init(void);
void gnrc_tcp_tcb_init(gnrc_tcp_tcb_t *tcb);
/**
* @brief Opens a connection actively.
* @brief Opens a connection.
*
* @pre gnrc_tcp_tcb_init() must have been successfully called.
* @pre @p tcb must not be NULL
* @pre @p remote must not be NULL.
* @pre @p remote->port must not be 0.
*
* @note Blocks until a connection has been established or an error occurred.
* @note Blocks until a connection was established or an error occurred.
*
* @param[in,out] tcb TCB holding the connection information.
* @param[in] remote Remote endpoint of the host to connect to.
* @param[in,out] tcb TCB for this connection.
* @param[in] remote Remote endpoint to connect to.
* @param[in] local_port If zero or PORT_UNSPEC, the connections source port
* is randomly chosen. If local_port is non-zero
* the local_port is used as source port.
* is randomly selected. If local_port is non-zero
* it is used as source port.
*
* @return 0 on success.
* @return -EAFNOSUPPORT if @p address_family is not supported.
* @return -EINVAL if @p address_family is not the same the address_family use by the TCB.
* @return -EAFNOSUPPORT if @p remote address_family is not supported.
* @return -EINVAL if @p remote and @p tcb address_family do not match
* or @p target_addr is invalid.
* @return -EISCONN if TCB is already in use.
* @return -ENOMEM if the receive buffer for the TCB could not be allocated.
* @return -EADDRINUSE if @p local_port is already used by another connection.
* @return -ETIMEDOUT if the connection could not be opened.
* @return -ECONNREFUSED if the connection was reset by the peer.
* @return -EISCONN if @p tcb is already connected.
* @return -ENOMEM if there are no receive buffer left to use for @p tcb.
* @return -EADDRINUSE if @p local_port is already in use.
* @return -ETIMEDOUT if the connection attempt timed out.
* @return -ECONNREFUSED if the connection attempt was reset by the peer.
*/
int gnrc_tcp_open_active(gnrc_tcp_tcb_t *tcb, const gnrc_tcp_ep_t *remote,
uint16_t local_port);
int gnrc_tcp_open(gnrc_tcp_tcb_t *tcb, const gnrc_tcp_ep_t *remote, uint16_t local_port);
/**
* @brief Opens a connection passively, by waiting for an incoming request.
* @brief Configures a sequence of TCBs to wait for incoming connections.
*
* @pre gnrc_tcp_tcb_init() must have been successfully called.
* @pre @p tcb must not be NULL.
* @pre @p local must not be NULL.
* @pre port in @p local must not be zero.
* @pre All TCBs behind @p tcbs must have been initialized via gnrc_tcp_tcb_init().
* @pre @p queue must not be NULL.
* @pre @p tcbs must not be NULL.
* @pre @p tcbs_len must be greater 0.
* @pre @p local len must be NULL.
* @pre @p local->port must not be 0.
*
* @note Blocks until a connection has been established (incoming connection request
* to @p local_port) or an error occurred.
* @param[in,out] queue Listening queue for incoming connections.
* @param[in] tcbs TCBs associated with @p queue.
* @param[in] tcbs_len Number of TCBs behind @p tcbs.
* @param[in] local Endpoint specifying address and port to listen on.
*
* @param[in,out] tcb TCB holding the connection information.
* @param[in] local Endpoint specifying the port and address used to wait for
* incoming connections.
*
* @return 0 on success.
* @return -EAFNOSUPPORT if local_addr != NULL and @p address_family is not supported.
* @return -EINVAL if @p address_family is not the same the address_family used in TCB.
* or the address in @p local is invalid.
* @return -EISCONN if TCB is already in use.
* @return -ENOMEM if the receive buffer for the TCB could not be allocated.
* Hint: Increase "CONFIG_GNRC_TCP_RCV_BUFFERS".
* @returns 0 on success.
* @return -EAFNOSUPPORT given address family in @p local is not supported.
* @return -EINVAL address_family in @p tcbs and @p local do not match.
* @return -EISCONN a TCB in @p tcbs is already connected.
* @return -ENOMEM all available receive buffers are in use.
* Increase GNRC_TCP_RCV_BUFFERS.
*/
int gnrc_tcp_open_passive(gnrc_tcp_tcb_t *tcb, const gnrc_tcp_ep_t *local);
int gnrc_tcp_listen(gnrc_tcp_tcb_queue_t *queue, gnrc_tcp_tcb_t *tcbs, size_t tcbs_len,
const gnrc_tcp_ep_t *local);
/**
* @brief Accept TCP connection from listening queue.
*
* @pre @p queue must not be NULL
* @pre @p tcb must not be NULL
*
* @note Function blocks if user_timeout_duration_us is not zero.
*
* @param[in] queue Listening queue to accept connection from.
* @param[out] tcb Pointer to TCB associated with a established connection.
* @param[in] user_timeout_duration_ms User specified timeout in milliseconds.
*
* @return 0 on success.
* @return -ENOMEM if all connection in @p queue were already accepted.
* @return -EAGAIN if @p user_timeout_duration_ms was 0 and no connection is ready to accept.
* @return -ETIMEDOUT if @p user_timeout_duration_ms was not 0 and no connection
* could be established.
*/
int gnrc_tcp_accept(gnrc_tcp_tcb_queue_t *queue, gnrc_tcp_tcb_t **tcb,
uint32_t user_timeout_duration_ms);
/**
* @brief Transmit data to connected peer.
@ -230,6 +251,18 @@ void gnrc_tcp_close(gnrc_tcp_tcb_t *tcb);
*/
void gnrc_tcp_abort(gnrc_tcp_tcb_t *tcb);
/**
* @brief Close connections and stop listening on TCB queue
*
* @pre @p queue must not be NULL
*
* @note: Blocks until all currently opened connections maintained
* by @p queue were closed.
*
* @param[in,out] queue TCB queue to stop listening
*/
void gnrc_tcp_stop_listen(gnrc_tcp_tcb_queue_t *queue);
/**
* @brief Calculate and set checksum in TCP header.
*

View File

@ -68,6 +68,7 @@ typedef struct _transmission_control_block {
int32_t rto; /**< Retransmission timeout duration */
uint8_t retries; /**< Number of retransmissions */
evtimer_msg_event_t event_retransmit; /**< Retransmission event */
evtimer_msg_event_t event_timeout; /**< Timeout event */
evtimer_mbox_event_t event_misc; /**< General purpose event */
gnrc_pktsnip_t *pkt_retransmit; /**< Pointer to packet in "retransmit queue" */
mbox_t *mbox; /**< TCB mbox for synchronization */
@ -78,6 +79,20 @@ typedef struct _transmission_control_block {
struct _transmission_control_block *next; /**< Pointer next TCB */
} gnrc_tcp_tcb_t;
/**
* @brief Transmission control block queue.
*/
typedef struct _transmission_control_block_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 */
} gnrc_tcp_tcb_queue_t;
/**
* @brief Static initializer for type gnrc_tcp_tcb_queue_t
*/
#define GNRC_TCP_TCB_QUEUE_INIT { MUTEX_INIT, NULL, 0 }
#ifdef __cplusplus
}
#endif

View File

@ -32,8 +32,8 @@ extern "C" {
* @brief TCP offset value boundaries.
* @{
*/
#define TCP_HDR_OFFSET_MIN (0x05)
#define TCP_HDR_OFFSET_MAX (0x0F)
#define TCP_HDR_OFFSET_MIN (0x05) /**< Header offset minimum value */
#define TCP_HDR_OFFSET_MAX (0x0F) /**< Header offset maximum value */
/** @} */
/**
@ -49,7 +49,7 @@ extern "C" {
* @brief TCP option "length"-field values.
* @{
*/
#define TCP_OPTION_LENGTH_MIN (2U) /**< Minimum amount of bytes needed for an option with a length field */
#define TCP_OPTION_LENGTH_MIN (2U) /**< Minimum option field size in bytes */
#define TCP_OPTION_LENGTH_MSS (0x04) /**< MSS Option Size always 4 */
/** @} */

View File

@ -74,144 +74,43 @@ static void _unsched_mbox(evtimer_mbox_event_t *event)
TCP_DEBUG_LEAVE;
}
/**
* @brief Establishes a new TCP connection
*
* @param[in,out] tcb TCB holding the connection information.
* @param[in] target_addr Target address to connect to, if this is a active connection.
* @param[in] target_port Target port to connect to, if this is a active connection.
* @param[in] local_addr Local address to bind on, if this is a passive connection.
* @param[in] local_port Local port to bind on, if this is a passive connection.
* @param[in] passive Flag to indicate if this is a active or passive open.
*
* @returns Zero on success.
* -EISCONN if TCB is already connected.
* -ENOMEM if the receive buffer for the TCB could not be allocated.
* -EADDRINUSE if @p local_port is already in use.
* -ETIMEDOUT if the connection opening timed out.
* -ECONNREFUSED if the connection was reset by the peer.
*/
static int _gnrc_tcp_open(gnrc_tcp_tcb_t *tcb, const gnrc_tcp_ep_t *remote,
const uint8_t *local_addr, uint16_t local_port, int passive)
static void _close(gnrc_tcp_tcb_t *tcb)
{
TCP_DEBUG_ENTER;
msg_t msg;
msg_t msg_queue[TCP_MSG_QUEUE_SIZE];
mbox_t mbox = MBOX_INIT(msg_queue, TCP_MSG_QUEUE_SIZE);
int ret = 0;
_gnrc_tcp_fsm_state_t state = 0;
/* Lock the TCB for this function call */
mutex_lock(&(tcb->function_lock));
/* TCB is already connected: Return -EISCONN */
/* Return if connection is closed */
state = _gnrc_tcp_fsm_get_state(tcb);
if (state != FSM_STATE_CLOSED) {
mutex_unlock(&(tcb->function_lock));
TCP_DEBUG_ERROR("-EISCONN: TCB already connected.");
if (state == FSM_STATE_CLOSED) {
TCP_DEBUG_LEAVE;
return -EISCONN;
return;
}
/* Setup messaging */
_gnrc_tcp_fsm_set_mbox(tcb, &mbox);
/* Setup passive connection */
if (passive) {
/* Mark connection as passive opend */
tcb->status |= STATUS_PASSIVE;
#ifdef MODULE_GNRC_IPV6
/* If local address is specified: Copy it into TCB */
if (local_addr && tcb->address_family == AF_INET6) {
/* Store given address in TCB */
if (memcpy(tcb->local_addr, local_addr, sizeof(tcb->local_addr)) == NULL) {
TCP_DEBUG_ERROR("-EINVAL: Invalid peer address.");
TCP_DEBUG_LEAVE;
return -EINVAL;
}
/* Setup connection timeout */
_sched_connection_timeout(&tcb->event_misc, &mbox);
if (ipv6_addr_is_unspecified((ipv6_addr_t *) tcb->local_addr)) {
tcb->status |= STATUS_ALLOW_ANY_ADDR;
}
}
#else
/* Suppress Compiler Warnings */
(void) remote;
(void) local_addr;
#endif
/* Set port number to listen on */
tcb->local_port = local_port;
}
/* Setup active connection */
else {
assert(remote != NULL);
/* Start connection teardown sequence */
_gnrc_tcp_fsm(tcb, FSM_EVENT_CALL_CLOSE, NULL, NULL, 0);
/* Parse target address and port number into TCB */
#ifdef MODULE_GNRC_IPV6
if (tcb->address_family == AF_INET6) {
/* Store Address information in TCB */
if (memcpy(tcb->peer_addr, remote->addr.ipv6, sizeof(tcb->peer_addr)) == NULL) {
TCP_DEBUG_ERROR("-EINVAL: Invalid peer address.");
TCP_DEBUG_LEAVE;
return -EINVAL;
}
tcb->ll_iface = remote->netif;
}
#endif
/* Assign port numbers, verification happens in fsm */
tcb->local_port = local_port;
tcb->peer_port = remote->port;
/* Setup connection timeout */
_sched_connection_timeout(&tcb->event_misc, &mbox);
}
/* Call FSM with event: CALL_OPEN */
ret = _gnrc_tcp_fsm(tcb, FSM_EVENT_CALL_OPEN, NULL, NULL, 0);
if (ret == -ENOMEM) {
TCP_DEBUG_ERROR("-ENOMEM: All receive buffers are in use.");
}
else if (ret == -EADDRINUSE) {
TCP_DEBUG_ERROR("-EADDRINUSE: local_port is already in use.");
}
/* Wait until a connection was established or closed */
/* Loop until the connection has been closed */
state = _gnrc_tcp_fsm_get_state(tcb);
while (ret >= 0 && state != FSM_STATE_CLOSED && state != FSM_STATE_ESTABLISHED &&
state != FSM_STATE_CLOSE_WAIT) {
while ((state != FSM_STATE_CLOSED) && (state != FSM_STATE_LISTEN)) {
mbox_get(&mbox, &msg);
switch (msg.type) {
case MSG_TYPE_NOTIFY_USER:
TCP_DEBUG_INFO("Received MSG_TYPE_NOTIFY_USER.");
/* Setup a timeout to be able to revert back to LISTEN state, in case the
* send SYN+ACK we received upon entering SYN_RCVD is never acknowledged
* by the peer. */
state = _gnrc_tcp_fsm_get_state(tcb);
if ((state == FSM_STATE_SYN_RCVD) && (tcb->status & STATUS_PASSIVE)) {
_unsched_mbox(&tcb->event_misc);
_sched_connection_timeout(&tcb->event_misc, &mbox);
}
break;
case MSG_TYPE_CONNECTION_TIMEOUT:
TCP_DEBUG_INFO("Received MSG_TYPE_CONNECTION_TIMEOUT.");
_gnrc_tcp_fsm(tcb, FSM_EVENT_TIMEOUT_CONNECTION, NULL, NULL, 0);
break;
/* The connection establishment attempt timed out:
* 1) Active connections return -ETIMEOUT.
* 2) Passive connections stop the ongoing retransmissions and repeat the
* open call to wait for the next connection attempt. */
if (tcb->status & STATUS_PASSIVE) {
_gnrc_tcp_fsm(tcb, FSM_EVENT_CLEAR_RETRANSMIT, NULL, NULL, 0);
_gnrc_tcp_fsm(tcb, FSM_EVENT_CALL_OPEN, NULL, NULL, 0);
}
else {
_gnrc_tcp_fsm(tcb, FSM_EVENT_TIMEOUT_CONNECTION, NULL, NULL, 0);
TCP_DEBUG_ERROR("-ETIMEDOUT: Connection timed out.");
ret = -ETIMEDOUT;
}
case MSG_TYPE_NOTIFY_USER:
TCP_DEBUG_INFO("Received MSG_TYPE_NOTIFY_USER.");
break;
default:
@ -223,13 +122,17 @@ static int _gnrc_tcp_open(gnrc_tcp_tcb_t *tcb, const gnrc_tcp_ep_t *remote,
/* Cleanup */
_gnrc_tcp_fsm_set_mbox(tcb, NULL);
_unsched_mbox(&tcb->event_misc);
if (state == FSM_STATE_CLOSED && ret == 0) {
TCP_DEBUG_ERROR("-ECONNREFUSED: Connection refused by peer.");
ret = -ECONNREFUSED;
}
mutex_unlock(&(tcb->function_lock));
TCP_DEBUG_LEAVE;
return ret;
}
static void _abort(gnrc_tcp_tcb_t *tcb)
{
TCP_DEBUG_ENTER;
if (_gnrc_tcp_fsm_get_state(tcb) != FSM_STATE_CLOSED) {
_gnrc_tcp_fsm(tcb, FSM_EVENT_CALL_ABORT, NULL, NULL, 0);
}
TCP_DEBUG_LEAVE;
}
/* External GNRC TCP API */
@ -421,14 +324,16 @@ void gnrc_tcp_tcb_init(gnrc_tcp_tcb_t *tcb)
TCP_DEBUG_LEAVE;
}
int gnrc_tcp_open_active(gnrc_tcp_tcb_t *tcb, const gnrc_tcp_ep_t *remote, uint16_t local_port)
int gnrc_tcp_open(gnrc_tcp_tcb_t *tcb, const gnrc_tcp_ep_t *remote, uint16_t local_port)
{
/* Sanity checking */
TCP_DEBUG_ENTER;
assert(tcb != NULL);
assert(remote != NULL);
assert(remote->port != PORT_UNSPEC);
/* Check if given AF-Family in remote is supported */
/* Verify remote ep */
#ifdef MODULE_GNRC_IPV6
if (remote->family != AF_INET6) {
TCP_DEBUG_ERROR("-EAFNOSUPPORT: remote AF-Family not supported.");
@ -441,50 +346,298 @@ int gnrc_tcp_open_active(gnrc_tcp_tcb_t *tcb, const gnrc_tcp_ep_t *remote, uint1
return -EAFNOSUPPORT;
#endif
/* Protect TCB against usage in other TCP functions */
mutex_lock(&(tcb->function_lock));
/* Check if AF-Family for target address matches internally used AF-Family */
if (remote->family != tcb->address_family) {
mutex_unlock(&(tcb->function_lock));
TCP_DEBUG_ERROR("-EINVAL: local and remote AF-Family don't match.");
TCP_DEBUG_LEAVE;
return -EINVAL;
}
/* Proceed with connection opening */
int res = _gnrc_tcp_open(tcb, remote, NULL, local_port, 0);
/* TCB is already connected: Return -EISCONN */
_gnrc_tcp_fsm_state_t state = _gnrc_tcp_fsm_get_state(tcb);
if (state != FSM_STATE_CLOSED) {
mutex_unlock(&(tcb->function_lock));
TCP_DEBUG_ERROR("-EISCONN: TCB already connected.");
TCP_DEBUG_LEAVE;
return -EISCONN;
}
/* Setup messaging */
msg_t msg;
msg_t msg_queue[TCP_MSG_QUEUE_SIZE];
mbox_t mbox = MBOX_INIT(msg_queue, TCP_MSG_QUEUE_SIZE);
_gnrc_tcp_fsm_set_mbox(tcb, &mbox);
/* Parse target address and port number into TCB */
#ifdef MODULE_GNRC_IPV6
if (tcb->address_family == AF_INET6) {
/* Store Address information in TCB */
if (memcpy(tcb->peer_addr, remote->addr.ipv6, sizeof(tcb->peer_addr)) == NULL) {
TCP_DEBUG_ERROR("-EINVAL: Invalid peer address.");
TCP_DEBUG_LEAVE;
return -EINVAL;
}
tcb->ll_iface = remote->netif;
}
#endif
/* Assign port numbers, verification happens during connection establishment. */
tcb->local_port = local_port;
tcb->peer_port = remote->port;
/* Setup connection timeout */
_sched_connection_timeout(&tcb->event_misc, &mbox);
/* Call FSM with event: CALL_OPEN */
int ret = _gnrc_tcp_fsm(tcb, FSM_EVENT_CALL_OPEN, NULL, NULL, 0);
if (ret == -ENOMEM) {
TCP_DEBUG_ERROR("-ENOMEM: All receive buffers are in use.");
}
else if (ret == -EADDRINUSE) {
TCP_DEBUG_ERROR("-EADDRINUSE: local_port is already in use.");
}
/* Wait until a connection was established or closed */
state = _gnrc_tcp_fsm_get_state(tcb);
while (ret >= 0 && state != FSM_STATE_CLOSED && state != FSM_STATE_ESTABLISHED &&
state != FSM_STATE_CLOSE_WAIT) {
mbox_get(&mbox, &msg);
switch (msg.type) {
case MSG_TYPE_NOTIFY_USER:
TCP_DEBUG_INFO("Received MSG_TYPE_NOTIFY_USER.");
break;
case MSG_TYPE_CONNECTION_TIMEOUT:
TCP_DEBUG_INFO("Received MSG_TYPE_CONNECTION_TIMEOUT.");
_gnrc_tcp_fsm(tcb, FSM_EVENT_TIMEOUT_CONNECTION, NULL, NULL, 0);
TCP_DEBUG_ERROR("-ETIMEDOUT: Connection timed out.");
ret = -ETIMEDOUT;
break;
default:
TCP_DEBUG_ERROR("Received unexpected message.");
}
state = _gnrc_tcp_fsm_get_state(tcb);
}
/* Cleanup */
_unsched_mbox(&tcb->event_misc);
_gnrc_tcp_fsm_set_mbox(tcb, NULL);
state = _gnrc_tcp_fsm_get_state(tcb);
if (state == FSM_STATE_CLOSED && ret == 0) {
TCP_DEBUG_ERROR("-ECONNREFUSED: Connection was refused by peer.");
ret = -ECONNREFUSED;
}
mutex_unlock(&(tcb->function_lock));
TCP_DEBUG_LEAVE;
return res;
return ret;
}
int gnrc_tcp_open_passive(gnrc_tcp_tcb_t *tcb, const gnrc_tcp_ep_t *local)
int gnrc_tcp_listen(gnrc_tcp_tcb_queue_t *queue, gnrc_tcp_tcb_t *tcbs, size_t tcbs_len,
const gnrc_tcp_ep_t *local)
{
TCP_DEBUG_ENTER;
assert(tcb != NULL);
/* Sanity checks */
assert(queue != NULL);
assert(tcbs != NULL);
assert(tcbs_len > 0);
assert(local != NULL);
assert(local->port != PORT_UNSPEC);
/* Check if given AF-Family in local is supported */
/* Verfiy given endpoint */
#ifdef MODULE_GNRC_IPV6
if (local->family != AF_INET6) {
TCP_DEBUG_ERROR("-EAFNOSUPPORT: AF-Family not supported.");
TCP_DEBUG_LEAVE;
return -EAFNOSUPPORT;
}
/* Check if AF-Family matches internally used AF-Family */
if (local->family != tcb->address_family) {
TCP_DEBUG_ERROR("-EINVAL: AF-Family doesn't match.");
TCP_DEBUG_LEAVE;
return -EINVAL;
}
/* Proceed with connection opening */
int res = _gnrc_tcp_open(tcb, NULL, local->addr.ipv6, local->port, 1);
TCP_DEBUG_LEAVE;
return res;
#else
TCP_DEBUG_ERROR("-EAFNOSUPPORT: AF-Family not supported.");
TCP_DEBUG_LEAVE;
return -EAFNOSUPPORT;
#endif
/* Protect TCP Data structures against usage in other TCP function*/
mutex_lock(&queue->lock);
for (size_t i = 0; i < tcbs_len; ++i) {
mutex_lock(&(tcbs[i].function_lock));
}
/* Setup and verify each TCB */
int ret = 0;
for (size_t i = 0; i < tcbs_len; ++i) {
gnrc_tcp_tcb_t *tcb = &(tcbs[i]);
/* Verify current TCB */
if (tcb->address_family != local->family) {
TCP_DEBUG_ERROR("-EINVAL: local and remote AF-Family don't match.");
ret = -EINVAL;
}
else if (_gnrc_tcp_fsm_get_state(tcb) != FSM_STATE_CLOSED) {
TCP_DEBUG_ERROR("-EISCONN: tcb is already connected.");
ret = -EISCONN;
}
/* Setup TCB for incoming connections attempts */
if (!ret)
{
#ifdef MODULE_GNRC_IPV6
if (tcb->address_family == AF_INET6) {
memcpy(tcb->local_addr, local->addr.ipv6, sizeof(tcb->local_addr));
if (ipv6_addr_is_unspecified((ipv6_addr_t *) tcb->local_addr)) {
tcb->status |= STATUS_ALLOW_ANY_ADDR;
}
}
#endif
tcb->local_port = local->port;
tcb->status |= STATUS_LISTENING;
/* Open connection */
ret = _gnrc_tcp_fsm(tcb, FSM_EVENT_CALL_OPEN, NULL, NULL, 0);
}
/* If anything goes wrong, discard all potentially opened connections. */
if (ret) {
for (size_t j = 0; j <= i; ++j) {
tcb->status &= ~(STATUS_LISTENING);
_abort(tcb);
}
break;
}
}
/* If everything went well: setup queue and unlock all TCBs */
if (!ret) {
queue->tcbs = tcbs;
queue->tcbs_len = tcbs_len;
}
for (size_t i = 0; i < tcbs_len; ++i) {
mutex_unlock(&(tcbs[i].function_lock));
}
mutex_unlock(&queue->lock);
TCP_DEBUG_LEAVE;
return ret;
}
int gnrc_tcp_accept(gnrc_tcp_tcb_queue_t *queue, gnrc_tcp_tcb_t **tcb,
uint32_t user_timeout_duration_ms)
{
TCP_DEBUG_ENTER;
assert(queue != NULL);
assert(tcb != NULL);
int ret = 0;
int avail_tcbs = 0;
msg_t msg;
msg_t msg_queue[TCP_MSG_QUEUE_SIZE];
mbox_t mbox = MBOX_INIT(msg_queue, TCP_MSG_QUEUE_SIZE);
evtimer_mbox_event_t event_user_timeout;
gnrc_tcp_tcb_t *tmp = NULL;
_gnrc_tcp_fsm_state_t state = 0;
/* Search for non-accepted established connections */
*tcb = NULL;
mutex_lock(&queue->lock);
for (size_t i = 0; i < queue->tcbs_len; ++i) {
tmp = &(queue->tcbs[i]);
if (tmp->status & STATUS_ACCEPTED) {
continue;
}
state = _gnrc_tcp_fsm_get_state(tmp);
if (state == FSM_STATE_ESTABLISHED || state == FSM_STATE_CLOSE_WAIT) {
tmp->status |= STATUS_ACCEPTED;
*tcb = tmp;
break;
}
++avail_tcbs;
}
/* Return if a connection was found, accept was called as non-blocking or all
* TCBs were already accepted.
*/
if ((*tcb) || (user_timeout_duration_ms == 0) || (avail_tcbs == 0)) {
if (*tcb) {
TCP_DEBUG_INFO("Accepting connection.");
ret = 0;
}
else if (avail_tcbs == 0) {
TCP_DEBUG_ERROR("-ENOMEM: All TCBs are currently accepted.");
ret = -ENOMEM;
}
else if (user_timeout_duration_ms == 0) {
TCP_DEBUG_ERROR("-EAGAIN: Would block. Try again.");
ret = -EAGAIN;
}
mutex_unlock(&queue->lock);
TCP_DEBUG_LEAVE;
return ret;
}
/* Setup TCBs for message exchange with the FSM */
for (size_t i = 0; i < queue->tcbs_len; ++i) {
tmp = &(queue->tcbs[i]);
/* Setup only not accepted TCBS */
if (!(tmp->status & STATUS_ACCEPTED)) {
mutex_lock(&(tmp->function_lock));
tmp->status |= STATUS_LOCKED;
_gnrc_tcp_fsm_set_mbox(tmp, &mbox);
}
}
/* Setup User specified Timeout */
_sched_mbox(&event_user_timeout, user_timeout_duration_ms,
MSG_TYPE_USER_SPEC_TIMEOUT, &mbox);
/* Wait until a connection was established */
while (ret >= 0 && *tcb == NULL) {
mbox_get(&mbox, &msg);
switch (msg.type) {
case MSG_TYPE_NOTIFY_USER:
TCP_DEBUG_INFO("Received MSG_TYPE_NOTIFY_USER.");
tmp = (gnrc_tcp_tcb_t *) msg.content.ptr;
state = _gnrc_tcp_fsm_get_state(tmp);
if (state == FSM_STATE_ESTABLISHED || state == FSM_STATE_CLOSE_WAIT) {
tmp->status |= STATUS_ACCEPTED;
*tcb = tmp;
}
break;
case MSG_TYPE_USER_SPEC_TIMEOUT:
TCP_DEBUG_INFO("Received MSG_TYPE_USER_SPEC_TIMEOUT.");
TCP_DEBUG_ERROR("-ETIMEDOUT: User specified timeout expired.");
ret = -ETIMEDOUT;
break;
default:
TCP_DEBUG_ERROR("Received unexpected message.");
}
}
/* Cleanup */
_unsched_mbox(&event_user_timeout);
for (size_t i = 0; i < queue->tcbs_len; ++i) {
tmp = &(queue->tcbs[i]);
if (tmp->status & STATUS_LOCKED) {
tmp->status &= ~(STATUS_LOCKED);
_gnrc_tcp_fsm_set_mbox(tmp, NULL);
mutex_unlock(&(tmp->function_lock));
}
}
mutex_unlock(&queue->lock);
TCP_DEBUG_LEAVE;
return ret;
}
ssize_t gnrc_tcp_send(gnrc_tcp_tcb_t *tcb, const void *data, const size_t len,
@ -736,55 +889,10 @@ void gnrc_tcp_close(gnrc_tcp_tcb_t *tcb)
TCP_DEBUG_ENTER;
assert(tcb != NULL);
msg_t msg;
msg_t msg_queue[TCP_MSG_QUEUE_SIZE];
mbox_t mbox = MBOX_INIT(msg_queue, TCP_MSG_QUEUE_SIZE);
_gnrc_tcp_fsm_state_t state = 0;
/* Lock the TCB for this function call */
mutex_lock(&(tcb->function_lock));
/* Return if connection is closed */
state = _gnrc_tcp_fsm_get_state(tcb);
if (state == FSM_STATE_CLOSED) {
mutex_unlock(&(tcb->function_lock));
TCP_DEBUG_LEAVE;
return;
}
/* Setup messaging */
_gnrc_tcp_fsm_set_mbox(tcb, &mbox);
/* Setup connection timeout */
_sched_connection_timeout(&tcb->event_misc, &mbox);
/* Start connection teardown sequence */
_gnrc_tcp_fsm(tcb, FSM_EVENT_CALL_CLOSE, NULL, NULL, 0);
/* Loop until the connection has been closed */
state = _gnrc_tcp_fsm_get_state(tcb);
while (state != FSM_STATE_CLOSED) {
mbox_get(&mbox, &msg);
switch (msg.type) {
case MSG_TYPE_CONNECTION_TIMEOUT:
TCP_DEBUG_INFO("Received MSG_TYPE_CONNECTION_TIMEOUT.");
_gnrc_tcp_fsm(tcb, FSM_EVENT_TIMEOUT_CONNECTION, NULL, NULL, 0);
break;
case MSG_TYPE_NOTIFY_USER:
TCP_DEBUG_INFO("Received MSG_TYPE_NOTIFY_USER.");
break;
default:
TCP_DEBUG_ERROR("Received unexpected message.");
}
state = _gnrc_tcp_fsm_get_state(tcb);
}
/* Cleanup */
_gnrc_tcp_fsm_set_mbox(tcb, NULL);
_unsched_mbox(&tcb->event_misc);
_close(tcb);
mutex_unlock(&(tcb->function_lock));
TCP_DEBUG_LEAVE;
}
@ -793,13 +901,35 @@ void gnrc_tcp_abort(gnrc_tcp_tcb_t *tcb)
TCP_DEBUG_ENTER;
assert(tcb != NULL);
/* Lock the TCB for this function call */
mutex_lock(&(tcb->function_lock));
if (_gnrc_tcp_fsm_get_state(tcb) != FSM_STATE_CLOSED) {
/* Call FSM ABORT event */
_gnrc_tcp_fsm(tcb, FSM_EVENT_CALL_ABORT, NULL, NULL, 0);
}
_abort(tcb);
mutex_unlock(&(tcb->function_lock));
TCP_DEBUG_LEAVE;
}
void gnrc_tcp_stop_listen(gnrc_tcp_tcb_queue_t *queue)
{
TCP_DEBUG_ENTER;
assert(queue != NULL);
/* Close all connections associated with the given queue */
mutex_lock(&(queue->lock));
for (size_t i = 0; i < queue->tcbs_len; ++i) {
gnrc_tcp_tcb_t *tcb = &(queue->tcbs[i]);
mutex_lock(&(tcb->function_lock));
/* Clear LISTENING status causing re-opening on close */
tcb->status &= ~(STATUS_LISTENING);
_close(tcb);
mutex_unlock(&(tcb->function_lock));
}
/* Cleanup */
queue->tcbs = NULL;
queue->tcbs_len = 0;
mutex_unlock(&(queue->lock));
TCP_DEBUG_LEAVE;
}

View File

@ -330,6 +330,16 @@ static void *_eventloop(__attribute__((unused)) void *arg)
FSM_EVENT_TIMEOUT_TIMEWAIT, NULL, NULL, 0);
break;
/* A connection opening attempt from a TCB in listening mode failed.
* Clear retransmission and re-open for next attempt */
case MSG_TYPE_CONNECTION_TIMEOUT:
TCP_DEBUG_INFO("Received MSG_TYPE_CONNECTION_TIMEOUT.");
_gnrc_tcp_fsm((gnrc_tcp_tcb_t *)msg.content.ptr,
FSM_EVENT_CLEAR_RETRANSMIT, NULL, NULL, 0);
_gnrc_tcp_fsm((gnrc_tcp_tcb_t *)msg.content.ptr,
FSM_EVENT_CALL_OPEN, NULL, NULL, 0);
break;
default:
TCP_DEBUG_ERROR("Received unexpected message.");
}

View File

@ -111,8 +111,8 @@ static int _clear_retransmit(gnrc_tcp_tcb_t *tcb)
static int _restart_timewait_timer(gnrc_tcp_tcb_t *tcb)
{
TCP_DEBUG_ENTER;
_gnrc_tcp_eventloop_unsched(&tcb->event_retransmit);
_gnrc_tcp_eventloop_sched(&tcb->event_retransmit, 2 * CONFIG_GNRC_TCP_MSL_MS,
_gnrc_tcp_eventloop_unsched(&tcb->event_timeout);
_gnrc_tcp_eventloop_sched(&tcb->event_timeout, 2 * CONFIG_GNRC_TCP_MSL_MS,
MSG_TYPE_TIMEWAIT, tcb);
TCP_DEBUG_LEAVE;
return 0;
@ -139,17 +139,32 @@ static int _transition_to(gnrc_tcp_tcb_t *tcb, _gnrc_tcp_fsm_state_t state)
/* Clear retransmit queue */
_clear_retransmit(tcb);
/* Remove connection from active connections */
mutex_lock(&list->lock);
LL_DELETE(list->head, tcb);
mutex_unlock(&list->lock);
/* Close connection if not listenng */
if (!(tcb->status & STATUS_LISTENING))
{
/* Remove connection from active connections */
mutex_lock(&list->lock);
LL_DELETE(list->head, tcb);
mutex_unlock(&list->lock);
/* Free potentially allocated receive buffer */
_gnrc_tcp_rcvbuf_release_buffer(tcb);
/* Free potentially allocated receive buffer */
_gnrc_tcp_rcvbuf_release_buffer(tcb);
TCP_DEBUG_INFO("Connection closed");
}
/* Re-open connection as listenng */
else
{
TCP_DEBUG_INFO("Connection reopend");
state = FSM_STATE_LISTEN;
_transition_to(tcb, state);
}
tcb->status |= STATUS_NOTIFY_USER;
break;
case FSM_STATE_LISTEN:
/* Clear Accepted Status */
tcb->status &= ~(STATUS_ACCEPTED);
/* Clear address info */
#ifdef MODULE_GNRC_IPV6
if (tcb->address_family == AF_INET6) {
@ -196,8 +211,20 @@ static int _transition_to(gnrc_tcp_tcb_t *tcb, _gnrc_tcp_fsm_state_t state)
break;
case FSM_STATE_SYN_RCVD:
/* Setup timeout for listening TCBs */
if (tcb->status & STATUS_LISTENING) {
_gnrc_tcp_eventloop_sched(&tcb->event_timeout,
CONFIG_GNRC_TCP_CONNECTION_TIMEOUT_DURATION_MS,
MSG_TYPE_CONNECTION_TIMEOUT, tcb);
}
break;
case FSM_STATE_ESTABLISHED:
case FSM_STATE_CLOSE_WAIT:
/* Stop timeout for listening TCBs */
if (tcb->status & STATUS_LISTENING) {
_gnrc_tcp_eventloop_unsched(&tcb->event_timeout);
}
tcb->status |= STATUS_NOTIFY_USER;
break;
@ -236,7 +263,7 @@ static int _fsm_call_open(gnrc_tcp_tcb_t *tcb)
tcb->rcv_wnd = CONFIG_GNRC_TCP_DEFAULT_WINDOW;
if (tcb->status & STATUS_PASSIVE) {
if (tcb->status & STATUS_LISTENING) {
/* Passive open, T: CLOSED -> LISTEN */
_transition_to(tcb, FSM_STATE_LISTEN);
}
@ -629,7 +656,7 @@ static int _fsm_rcvd_pkt(gnrc_tcp_tcb_t *tcb, gnrc_pktsnip_t *in_pkt)
/* 2) Check RST: If RST is set ... */
if (ctl & MSK_RST) {
/* .. and state is SYN_RCVD and the connection is passive: SYN_RCVD -> LISTEN */
if (tcb->state == FSM_STATE_SYN_RCVD && (tcb->status & STATUS_PASSIVE)) {
if (tcb->state == FSM_STATE_SYN_RCVD && (tcb->status & STATUS_LISTENING)) {
_transition_to(tcb, FSM_STATE_LISTEN);
}
else {

View File

@ -42,9 +42,11 @@ extern "C" {
* @brief TCB status flags
* @{
*/
#define STATUS_PASSIVE (1 << 0)
#define STATUS_LISTENING (1 << 0)
#define STATUS_ALLOW_ANY_ADDR (1 << 1)
#define STATUS_NOTIFY_USER (1 << 2)
#define STATUS_ACCEPTED (1 << 3)
#define STATUS_LOCKED (1 << 4)
/** @} */
/**

View File

@ -30,6 +30,14 @@ in the tests directory.
7) 07-endpoint_construction.py
This test ensures the correctness of the endpoint construction.
8) 08-return_codes.py
This test tries to trigger all documented return codes from GNRC_TCPs functions.
9) 09-listen_accept_cycle.py
This test verifies that connection establishment via listen and accept can be repeated multiple
times.
Setup
==========
The test requires a tap-device setup. This can be achieved by running 'dist/tools/tapsetup/tapsetup'

View File

@ -15,10 +15,13 @@
#include "net/gnrc/tcp.h"
#define MAIN_QUEUE_SIZE (8)
#define TCB_QUEUE_SIZE (1)
#define BUFFER_SIZE (2049)
static msg_t main_msg_queue[MAIN_QUEUE_SIZE];
static gnrc_tcp_tcb_t tcb;
static gnrc_tcp_tcb_t tcbs[TCB_QUEUE_SIZE];
static gnrc_tcp_tcb_t *tcb = tcbs;
static gnrc_tcp_tcb_queue_t queue = GNRC_TCP_TCB_QUEUE_INIT;
static char buffer[BUFFER_SIZE];
void dump_args(int argc, char **argv)
@ -121,11 +124,17 @@ int gnrc_tcp_ep_from_str_cmd(int argc, char **argv)
int gnrc_tcp_tcb_init_cmd(int argc, char **argv)
{
dump_args(argc, argv);
gnrc_tcp_tcb_init(&tcb);
// Initialize all given TCBs
for (int i = 0; i < TCB_QUEUE_SIZE; ++i)
{
gnrc_tcp_tcb_init(&(tcbs[i]));
}
printf("%s: returns 0\n", argv[0]);
return 0;
}
int gnrc_tcp_open_active_cmd(int argc, char **argv)
int gnrc_tcp_open_cmd(int argc, char **argv)
{
dump_args(argc, argv);
@ -133,7 +142,7 @@ int gnrc_tcp_open_active_cmd(int argc, char **argv)
gnrc_tcp_ep_from_str(&remote, argv[1]);
uint16_t local_port = atol(argv[2]);
int err = gnrc_tcp_open_active(&tcb, &remote, local_port);
int err = gnrc_tcp_open(tcb, &remote, local_port);
switch (err) {
case -EAFNOSUPPORT:
printf("%s: returns -EAFNOSUPPORT\n", argv[0]);
@ -169,14 +178,14 @@ int gnrc_tcp_open_active_cmd(int argc, char **argv)
return err;
}
int gnrc_tcp_open_passive_cmd(int argc, char **argv)
int gnrc_tcp_listen_cmd(int argc, char **argv)
{
dump_args(argc, argv);
gnrc_tcp_ep_t local;
gnrc_tcp_ep_from_str(&local, argv[1]);
int err = gnrc_tcp_open_passive(&tcb, &local);
int err = gnrc_tcp_listen(&queue, tcbs, ARRAY_SIZE(tcbs), &local);
switch (err) {
case -EAFNOSUPPORT:
printf("%s: returns -EAFNOSUPPORT\n", argv[0]);
@ -200,6 +209,37 @@ int gnrc_tcp_open_passive_cmd(int argc, char **argv)
return err;
}
int gnrc_tcp_accept_cmd(int argc, char **argv)
{
dump_args(argc, argv);
gnrc_tcp_tcb_t *tmp = NULL;
int timeout = atol(argv[1]);
int err = gnrc_tcp_accept(&queue, &tmp, timeout);
switch (err) {
case -EAGAIN:
printf("%s: returns -EAGAIN\n", argv[0]);
break;
case -ENOMEM:
printf("%s: returns -ENOMEM\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) {
tcb = tmp;
}
return err;
}
int gnrc_tcp_send_cmd(int argc, char **argv)
{
dump_args(argc, argv);
@ -209,7 +249,7 @@ int gnrc_tcp_send_cmd(int argc, char **argv)
size_t sent = 0;
while (sent < to_send) {
int ret = gnrc_tcp_send(&tcb, buffer + sent, to_send - sent, timeout);
int ret = gnrc_tcp_send(tcb, buffer + sent, to_send - sent, timeout);
switch (ret) {
case -ENOTCONN:
printf("%s: returns -ENOTCONN\n", argv[0]);
@ -243,7 +283,7 @@ int gnrc_tcp_recv_cmd(int argc, char **argv)
size_t rcvd = 0;
while (rcvd < to_receive) {
int ret = gnrc_tcp_recv(&tcb, buffer + rcvd, to_receive - rcvd,
int ret = gnrc_tcp_recv(tcb, buffer + rcvd, to_receive - rcvd,
timeout);
switch (ret) {
case 0:
@ -280,14 +320,24 @@ int gnrc_tcp_recv_cmd(int argc, char **argv)
int gnrc_tcp_close_cmd(int argc, char **argv)
{
dump_args(argc, argv);
gnrc_tcp_close(&tcb);
gnrc_tcp_close(tcb);
printf("%s: returns\n", argv[0]);
return 0;
}
int gnrc_tcp_abort_cmd(int argc, char **argv)
{
dump_args(argc, argv);
gnrc_tcp_abort(&tcb);
gnrc_tcp_abort(tcb);
printf("%s: returns\n", argv[0]);
return 0;
}
int gnrc_tcp_stop_listen_cmd(int argc, char **argv)
{
dump_args(argc, argv);
gnrc_tcp_stop_listen(&queue);
printf("%s: returns\n", argv[0]);
return 0;
}
@ -295,11 +345,14 @@ int gnrc_tcp_abort_cmd(int argc, char **argv)
static const shell_command_t shell_commands[] = {
{ "gnrc_tcp_ep_from_str", "Build endpoint from string",
gnrc_tcp_ep_from_str_cmd },
{ "gnrc_tcp_tcb_init", "gnrc_tcp: init tcb", gnrc_tcp_tcb_init_cmd },
{ "gnrc_tcp_open_active", "gnrc_tcp: open active connection",
gnrc_tcp_open_active_cmd },
{ "gnrc_tcp_open_passive", "gnrc_tcp: open passive connection",
gnrc_tcp_open_passive_cmd },
{ "gnrc_tcp_tcb_init", "gnrc_tcp: init tcb",
gnrc_tcp_tcb_init_cmd },
{ "gnrc_tcp_open", "gnrc_tcp: open connection",
gnrc_tcp_open_cmd },
{ "gnrc_tcp_listen", "gnrc_tcp: listen for connection",
gnrc_tcp_listen_cmd },
{ "gnrc_tcp_accept", "gnrc_tcp: accept connection",
gnrc_tcp_accept_cmd },
{ "gnrc_tcp_send", "gnrc_tcp: send data to connected peer",
gnrc_tcp_send_cmd },
{ "gnrc_tcp_recv", "gnrc_tcp: recv data from connected peer",
@ -308,11 +361,16 @@ static const shell_command_t shell_commands[] = {
gnrc_tcp_close_cmd },
{ "gnrc_tcp_abort", "gnrc_tcp: close connection forcefully",
gnrc_tcp_abort_cmd },
{ "buffer_init", "init internal buffer", buffer_init_cmd },
{ "gnrc_tcp_stop_listen", "gnrc_tcp: stop listening",
gnrc_tcp_stop_listen_cmd },
{ "buffer_init", "init internal buffer",
buffer_init_cmd },
{ "buffer_get_max_size", "get max size of internal buffer",
buffer_get_max_size_cmd },
{ "buffer_write", "write data into internal buffer", buffer_write_cmd },
{ "buffer_read", "read data from internal buffer", buffer_read_cmd },
{ "buffer_write", "write data into internal buffer",
buffer_write_cmd },
{ "buffer_read", "read data from internal buffer",
buffer_read_cmd },
{ NULL, NULL, NULL }
};

View File

@ -32,8 +32,9 @@ def testfunc(child):
# Setup RIOT Node to connect to host systems TCP Server
child.sendline('gnrc_tcp_tcb_init')
child.sendline('gnrc_tcp_open_active [{}]:{} 0'.format(target_addr, str(port)))
child.expect_exact('gnrc_tcp_open_active: returns 0')
child.sendline('gnrc_tcp_open [{}]:{} 0'.format(target_addr, str(port)))
child.expect_exact('gnrc_tcp_open: returns 0')
# Close connection and verify that pktbuf is cleared
shutdown_event.set()

View File

@ -8,50 +8,35 @@
import os
import sys
import socket
import threading
from testrunner import run
from shared_func import generate_port_number, get_host_tap_device, get_riot_ll_addr, \
verify_pktbuf_empty, sudo_guard
from shared_func import Runner, RiotTcpServer, HostTcpClient, generate_port_number, \
sudo_guard
def tcp_client(addr, port, shutdown_event):
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
addr_info = socket.getaddrinfo(addr + '%' + get_host_tap_device(), port, type=socket.SOCK_STREAM)
sock.connect(addr_info[0][-1])
shutdown_event.wait()
sock.close()
def testfunc(child):
port = generate_port_number()
shutdown_event = threading.Event()
client_handle = threading.Thread(target=tcp_client, args=(get_riot_ll_addr(child), port, shutdown_event))
# Setup RIOT Node wait for incoming connections from host system
child.sendline('gnrc_tcp_tcb_init')
child.sendline('gnrc_tcp_open_passive [::]:{}'.format(str(port)))
client_handle.start()
child.expect_exact('gnrc_tcp_open_passive: returns 0')
# Close connection and verify that pktbuf is cleared
shutdown_event.set()
child.sendline('gnrc_tcp_close')
client_handle.join()
verify_pktbuf_empty(child)
print(os.path.basename(sys.argv[0]) + ': success')
@Runner(timeout=10)
def test_lifecycle_as_server(child):
""" Open/close a single connection as tcp server """
# Setup RIOT as server
with RiotTcpServer(child, generate_port_number()) as riot_srv:
# Setup Host as client
with HostTcpClient(riot_srv.addr, riot_srv.listen_port):
# Accept and close connection
riot_srv.accept(timeout_ms=1000)
riot_srv.close()
if __name__ == '__main__':
sudo_guard()
sys.exit(run(testfunc, timeout=7, echo=False, traceback=True))
# 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(os.path.basename(sys.argv[0]) + ': success\n')

View File

@ -40,8 +40,8 @@ def testfunc(child):
# Setup RIOT Node to connect to host systems TCP Server
child.sendline('gnrc_tcp_tcb_init')
child.sendline('gnrc_tcp_open_active [{}]:{} 0'.format(target_addr, str(port)))
child.expect_exact('gnrc_tcp_open_active: returns 0')
child.sendline('gnrc_tcp_open [{}]:{} 0'.format(target_addr, str(port)))
child.expect_exact('gnrc_tcp_open: returns 0')
# Send data from RIOT Node to Linux
write_data_to_internal_buffer(child, data)

View File

@ -40,8 +40,8 @@ def testfunc(child):
# Setup RIOT Node to connect to Hostsystems TCP Server
child.sendline('gnrc_tcp_tcb_init')
child.sendline('gnrc_tcp_open_active [{}]:{} 0'.format(target_addr, str(port)))
child.expect_exact('gnrc_tcp_open_active: returns 0')
child.sendline('gnrc_tcp_open [{}]:{} 0'.format(target_addr, str(port)))
child.expect_exact('gnrc_tcp_open: returns 0')
# Accept Data sent by the host system
child.sendline('gnrc_tcp_recv 1000000 ' + str(data_len))

View File

@ -33,12 +33,14 @@ def testfunc(func):
# Setup RIOT Node wait for incoming connections from host system
child.sendline('gnrc_tcp_tcb_init')
child.expect_exact('gnrc_tcp_tcb_init: argc=1, argv[0] = gnrc_tcp_tcb_init')
child.sendline('gnrc_tcp_open_passive [::]:{}'.format(port))
child.expect(r'gnrc_tcp_open_passive: argc=2, '
r'argv\[0\] = gnrc_tcp_open_passive, '
child.sendline('gnrc_tcp_listen [::]:{}'.format(port))
child.expect(r'gnrc_tcp_listen: argc=2, '
r'argv\[0\] = gnrc_tcp_listen, '
r'argv\[1\] = \[::\]:(\d+)\r\n')
assert int(child.match.group(1)) == port
child.sendline('gnrc_tcp_accept 15000')
try:
print("- {} ".format(func.__name__), end="")
if child.logfile == sys.stdout:
@ -53,6 +55,7 @@ def testfunc(func):
raise e
finally:
child.sendline('gnrc_tcp_close')
child.sendline('gnrc_tcp_stop_listen')
return runner
@ -65,7 +68,7 @@ def test_short_payload(child, src_if, src_ll, dst_if, dst_l2, dst_ll, dst_port):
addr_info = socket.getaddrinfo(dst_ll + '%' + src_if, dst_port,
type=socket.SOCK_STREAM)
sock.connect(addr_info[0][-1])
child.expect_exact('gnrc_tcp_open_passive: returns 0')
child.expect_exact('gnrc_tcp_accept: returns 0')
child.sendline('gnrc_tcp_recv 1000000 1')
child.expect_exact('gnrc_tcp_recv: argc=3, '
'argv[0] = gnrc_tcp_recv, '
@ -90,7 +93,7 @@ def test_short_header(child, src_if, src_ll, dst_if, dst_l2, dst_ll, dst_port):
addr_info = socket.getaddrinfo(dst_ll + '%' + src_if, dst_port,
type=socket.SOCK_STREAM)
sock.connect(addr_info[0][-1])
child.expect_exact('gnrc_tcp_open_passive: returns 0')
child.expect_exact('gnrc_tcp_accept: returns 0')
verify_pktbuf_empty(child)
@ -122,7 +125,7 @@ def test_send_ack_instead_of_syn(child, src_if, src_ll,
addr_info = socket.getaddrinfo(dst_ll + '%' + src_if, dst_port,
type=socket.SOCK_STREAM)
sock.connect(addr_info[0][-1])
child.expect_exact('gnrc_tcp_open_passive: returns 0')
child.expect_exact('gnrc_tcp_accept: returns 0')
verify_pktbuf_empty(child)
@ -140,7 +143,7 @@ def test_option_parsing_term(child, src_if, src_ll,
addr_info = socket.getaddrinfo(dst_ll + '%' + src_if, dst_port,
type=socket.SOCK_STREAM)
sock.connect(addr_info[0][-1])
child.expect_exact('gnrc_tcp_open_passive: returns 0')
child.expect_exact('gnrc_tcp_accept: returns 0')
verify_pktbuf_empty(child)

View File

@ -42,8 +42,8 @@ def testfunc(child):
# Setup RIOT Node to connect to Hostsystems TCP Server
child.sendline('gnrc_tcp_tcb_init')
child.sendline('gnrc_tcp_open_active [{}]:{} 0'.format(target_addr, str(port)))
child.expect_exact('gnrc_tcp_open_active: returns 0')
child.sendline('gnrc_tcp_open [{}]:{} 0'.format(target_addr, str(port)))
child.expect_exact('gnrc_tcp_open: returns 0')
# Initiate connection teardown from test host
shutdown_event.set()

View File

@ -132,14 +132,19 @@ def main(child):
if type(getattr(script, t)).__name__ == "function"
and t.startswith("test_")]
res = 0
for test in tests:
try:
test(child)
print('- {} SUCCESS'.format(test.__name__))
except Exception:
res = -1
print('- {} FAILED'.format(test.__name__))
return res
if __name__ == '__main__':
sudo_guard()

View File

@ -0,0 +1,69 @@
#!/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 sys
import os
from shared_func import Runner, RiotTcpServer, HostTcpClient, generate_port_number, \
sudo_guard
@Runner(timeout=0.5)
def test_gnrc_tcp_accept_returns_EAGAIN(child):
""" gnrc_tcp_accept must return with -EAGAIN
if no connection is ready and timeout is 0
"""
riot_srv = RiotTcpServer(child, generate_port_number())
riot_srv.listen()
child.sendline('gnrc_tcp_accept 0')
child.expect_exact('gnrc_tcp_accept: returns -EAGAIN')
@Runner(timeout=1.5)
def test_gnrc_tcp_accept_returns_ETIMEDOUT(child):
""" gnrc_tcp_accept must return with -ETIMEDOUT
if no connection is ready and timeout is not 0
"""
riot_srv = RiotTcpServer(child, generate_port_number())
riot_srv.listen()
child.sendline('gnrc_tcp_accept 1000')
child.expect_exact('gnrc_tcp_accept: returns -ETIMEDOUT')
@Runner(timeout=1)
def test_gnrc_tcp_accept_returns_ENOMEM(child):
""" gnrc_tcp_accept must return with -ENOMEM
if all TCBs already handle a connection
"""
with RiotTcpServer(child, generate_port_number()) as riot_srv:
# Establish connection to ensure that all TCBs are in use
with HostTcpClient(riot_srv.addr, riot_srv.listen_port):
riot_srv.accept(timeout_ms=0)
# Faulty accept should return immediately despite a huge timeout.
child.sendline('gnrc_tcp_accept 100000000')
child.expect_exact('gnrc_tcp_accept: returns -ENOMEM')
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(os.path.basename(sys.argv[0]) + ': success\n')

View File

@ -0,0 +1,78 @@
#!/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 sys
import os
import random
from shared_func import Runner, RiotTcpServer, HostTcpClient, generate_port_number, \
sudo_guard
@Runner(timeout=10)
def test_listen_accept_cycle(child, iterations=10):
""" This test verifies gnrc_tcp in a typical server role by
accepting a connection, exchange data, teardown connection, handle the next one
"""
# Setup RIOT Node as server
with RiotTcpServer(child, generate_port_number()) as riot_srv:
# Establish multiple connections iterativly
for i in range(iterations):
print(' Running iteration {}'.format(i))
with HostTcpClient(riot_srv.addr, riot_srv.listen_port) as host_cli:
# Accept connection from host system
riot_srv.accept(timeout_ms=0)
# Send data from host to RIOT
data = '0123456789'
host_cli.send(payload_to_send=data)
riot_srv.receive(timeout_ms=500, sent_payload=data)
# Send data from RIOT to host
riot_srv.send(timeout_ms=500, payload_to_send=data)
host_cli.receive(sent_payload=data)
# Randomize connection teardown: The connections
# can't be either closed or aborted from either
# side. Regardless type of the connection teardown
# riot_srv must be able to accept the next connection
# Note: python sockets don't offer abort...
dice_throw = random.randint(0, 3)
if dice_throw == 0:
riot_srv.close()
host_cli.close()
elif dice_throw == 1:
riot_srv.abort()
host_cli.close()
elif dice_throw == 2:
host_cli.close()
riot_srv.close()
elif dice_throw == 3:
host_cli.close()
riot_srv.abort()
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(os.path.basename(sys.argv[0]) + ': success\n')

View File

@ -10,6 +10,31 @@ 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('- Test "{}": SKIPPED'.format(fn.__name__))
return 0
res = -1
try:
res = testrunner.run(fn, self.timeout, self.echo)
finally:
if res == 0:
print('- Test "{}": SUCCESS'.format(fn.__name__))
else:
print('- Test "{}": FAILED'.format(fn.__name__))
return res
class TcpServer:
@ -37,6 +62,143 @@ class TcpServer:
return self.conn.recv(number_of_bytes, socket.MSG_WAITALL).decode('utf-8')
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 = get_host_tap_device()
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
class HostTcpClient(HostTcpNode):
def __init__(self, target_addr, target_port):
super().__init__()
self.target_addr = target_addr
self.target_port = target_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 RiotTcpNode:
def __init__(self, child):
self.child = child
self.opened = False
# Query local address of RIOT Node
self.addr = get_riot_ll_addr(self.child)
def tcb_init(self):
self.child.sendline('gnrc_tcp_tcb_init')
self.child.expect_exact('gnrc_tcp_tcb_init: returns')
def send(self, timeout_ms, payload_to_send):
total_bytes = len(payload_to_send)
# Verify that internal buffer can hold the given amount of data
assert setup_internal_buffer(self.child) >= total_bytes
# Write data to RIOT nodes internal buffer
write_data_to_internal_buffer(self.child, payload_to_send)
# Send buffer contents via tcp
self.child.sendline('gnrc_tcp_send {}'.format(str(timeout_ms)))
self.child.expect_exact('gnrc_tcp_send: sent {}'.format(str(total_bytes)))
# Verify that packet buffer is empty
verify_pktbuf_empty(self.child)
def receive(self, timeout_ms, sent_payload):
total_bytes = len(sent_payload)
# Verify that internal Buffer can hold the test data
assert setup_internal_buffer(self.child) >= total_bytes
# Receive Data
self.child.sendline('gnrc_tcp_recv {} {}'.format(timeout_ms, str(total_bytes)))
self.child.expect_exact('gnrc_tcp_recv: received ' + str(total_bytes), timeout=20)
# Readout internal buffer content of RIOT Note
assert read_data_from_internal_buffer(self.child, total_bytes) == sent_payload
# Verify that packet buffer is empty
verify_pktbuf_empty(self.child)
def close(self):
self.child.sendline('gnrc_tcp_close')
self.child.expect_exact('gnrc_tcp_close: returns')
verify_pktbuf_empty(self.child)
self.opened = False
def abort(self):
self.child.sendline('gnrc_tcp_abort')
self.child.expect_exact('gnrc_tcp_abort: returns')
verify_pktbuf_empty(self.child)
self.opened = False
class RiotTcpServer(RiotTcpNode):
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)
self.tcb_init()
def __enter__(self):
if not self.listening:
self.listen()
self.listening = True
return self
def __exit__(self, _1, _2, _3):
if self.listening:
self.stop_listen()
self.listening = False
def listen(self):
self.child.sendline('gnrc_tcp_listen {}:{}'.format(self.listen_addr, self.listen_port))
self.child.expect_exact('gnrc_tcp_listen: returns 0')
def accept(self, timeout_ms):
self.child.sendline('gnrc_tcp_accept {}'.format(str(timeout_ms)))
self.child.expect_exact('gnrc_tcp_accept: returns 0')
self.opened = True
def stop_listen(self):
self.child.sendline('gnrc_tcp_stop_listen')
self.child.expect_exact('gnrc_tcp_stop_listen: returns')
verify_pktbuf_empty(self.child)
def generate_port_number():
return random.randint(1024, 65535)