diff --git a/fuzzing/gnrc_tcp/main.c b/fuzzing/gnrc_tcp/main.c index 278bda6b38..8dadff8f17 100644 --- a/fuzzing/gnrc_tcp/main.c +++ b/fuzzing/gnrc_tcp/main.c @@ -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; } diff --git a/sys/include/net/gnrc/tcp.h b/sys/include/net/gnrc/tcp.h index 85991758d8..bedef622b9 100644 --- a/sys/include/net/gnrc/tcp.h +++ b/sys/include/net/gnrc/tcp.h @@ -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. * diff --git a/sys/include/net/gnrc/tcp/tcb.h b/sys/include/net/gnrc/tcp/tcb.h index 0f8408d796..2a991df182 100644 --- a/sys/include/net/gnrc/tcp/tcb.h +++ b/sys/include/net/gnrc/tcp/tcb.h @@ -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 diff --git a/sys/include/net/tcp.h b/sys/include/net/tcp.h index f106302788..4aad1559ad 100644 --- a/sys/include/net/tcp.h +++ b/sys/include/net/tcp.h @@ -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 */ /** @} */ diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c index 016b44ce84..5fe06af5bb 100644 --- a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c +++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c @@ -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; } diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_eventloop.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_eventloop.c index d05a3ea400..d03cce3a4a 100644 --- a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_eventloop.c +++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_eventloop.c @@ -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."); } diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_fsm.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_fsm.c index 5d761bd1bc..b2f24a612d 100644 --- a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_fsm.c +++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_fsm.c @@ -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 { diff --git a/sys/net/gnrc/transport_layer/tcp/include/gnrc_tcp_common.h b/sys/net/gnrc/transport_layer/tcp/include/gnrc_tcp_common.h index 6067dadadb..f991998c8f 100644 --- a/sys/net/gnrc/transport_layer/tcp/include/gnrc_tcp_common.h +++ b/sys/net/gnrc/transport_layer/tcp/include/gnrc_tcp_common.h @@ -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) /** @} */ /** diff --git a/tests/gnrc_tcp/README.md b/tests/gnrc_tcp/README.md index 7ae4cc22ac..c306b88130 100644 --- a/tests/gnrc_tcp/README.md +++ b/tests/gnrc_tcp/README.md @@ -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' diff --git a/tests/gnrc_tcp/main.c b/tests/gnrc_tcp/main.c index cbcdfe377c..9b8abc0714 100644 --- a/tests/gnrc_tcp/main.c +++ b/tests/gnrc_tcp/main.c @@ -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 } }; diff --git a/tests/gnrc_tcp/tests-as-root/01-conn_lifecycle_as_client.py b/tests/gnrc_tcp/tests-as-root/01-conn_lifecycle_as_client.py index 7191d757d7..4dcae82038 100755 --- a/tests/gnrc_tcp/tests-as-root/01-conn_lifecycle_as_client.py +++ b/tests/gnrc_tcp/tests-as-root/01-conn_lifecycle_as_client.py @@ -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() diff --git a/tests/gnrc_tcp/tests-as-root/02-conn_lifecycle_as_server.py b/tests/gnrc_tcp/tests-as-root/02-conn_lifecycle_as_server.py index bf8a608638..a33c309608 100755 --- a/tests/gnrc_tcp/tests-as-root/02-conn_lifecycle_as_server.py +++ b/tests/gnrc_tcp/tests-as-root/02-conn_lifecycle_as_server.py @@ -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') diff --git a/tests/gnrc_tcp/tests-as-root/03-send_data.py b/tests/gnrc_tcp/tests-as-root/03-send_data.py index 1083bd2876..dda2fcd2cb 100755 --- a/tests/gnrc_tcp/tests-as-root/03-send_data.py +++ b/tests/gnrc_tcp/tests-as-root/03-send_data.py @@ -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) diff --git a/tests/gnrc_tcp/tests-as-root/04-receive_data.py b/tests/gnrc_tcp/tests-as-root/04-receive_data.py index 829f8be4f4..960806cf0c 100755 --- a/tests/gnrc_tcp/tests-as-root/04-receive_data.py +++ b/tests/gnrc_tcp/tests-as-root/04-receive_data.py @@ -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)) diff --git a/tests/gnrc_tcp/tests-as-root/05-garbage-pkts.py b/tests/gnrc_tcp/tests-as-root/05-garbage-pkts.py index 1cababd0b1..8c1e31bbcd 100755 --- a/tests/gnrc_tcp/tests-as-root/05-garbage-pkts.py +++ b/tests/gnrc_tcp/tests-as-root/05-garbage-pkts.py @@ -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) diff --git a/tests/gnrc_tcp/tests-as-root/06-receive_data_closed_conn.py b/tests/gnrc_tcp/tests-as-root/06-receive_data_closed_conn.py index 063a380add..0177bed1d6 100755 --- a/tests/gnrc_tcp/tests-as-root/06-receive_data_closed_conn.py +++ b/tests/gnrc_tcp/tests-as-root/06-receive_data_closed_conn.py @@ -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() diff --git a/tests/gnrc_tcp/tests-as-root/07-endpoint_construction.py b/tests/gnrc_tcp/tests-as-root/07-endpoint_construction.py index 7bb402c8ec..1c202af531 100755 --- a/tests/gnrc_tcp/tests-as-root/07-endpoint_construction.py +++ b/tests/gnrc_tcp/tests-as-root/07-endpoint_construction.py @@ -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() diff --git a/tests/gnrc_tcp/tests-as-root/08-return_codes.py b/tests/gnrc_tcp/tests-as-root/08-return_codes.py new file mode 100755 index 0000000000..a938c40efc --- /dev/null +++ b/tests/gnrc_tcp/tests-as-root/08-return_codes.py @@ -0,0 +1,69 @@ +#!/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 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') diff --git a/tests/gnrc_tcp/tests-as-root/09-listen_accept_cycle.py b/tests/gnrc_tcp/tests-as-root/09-listen_accept_cycle.py new file mode 100755 index 0000000000..443fdd7043 --- /dev/null +++ b/tests/gnrc_tcp/tests-as-root/09-listen_accept_cycle.py @@ -0,0 +1,78 @@ +#!/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 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') diff --git a/tests/gnrc_tcp/tests-as-root/shared_func.py b/tests/gnrc_tcp/tests-as-root/shared_func.py index f3a9d1d7d6..aa4184d265 100644 --- a/tests/gnrc_tcp/tests-as-root/shared_func.py +++ b/tests/gnrc_tcp/tests-as-root/shared_func.py @@ -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)