diff --git a/Makefile.dep b/Makefile.dep index 1c292a44480937e23c39767cd05247e3c90ca845..5301dc4174c53ef7c2171181b555e5b85e58a618 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -335,6 +335,12 @@ ifneq (,$(filter gnrc_udp,$(USEMODULE))) USEMODULE += udp endif +ifneq (,$(filter gnrc_tcp,$(USEMODULE))) + USEMODULE += inet_csum + USEMODULE += random + USEMODULE += xtimer +endif + ifneq (,$(filter gnrc_nettest,$(USEMODULE))) USEMODULE += gnrc_netapi USEMODULE += gnrc_netreg diff --git a/examples/gnrc_tcp_cli/Makefile b/examples/gnrc_tcp_cli/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..06a4cfc01cb5bb01fa9f1a760596b0ab25398587 --- /dev/null +++ b/examples/gnrc_tcp_cli/Makefile @@ -0,0 +1,43 @@ +# name of your application +APPLICATION = gnrc_tcp_cli + +# If no BOARD is found in the environment, use this default: +BOARD ?= native +PORT ?= tap1 + +TCP_TARGET_ADDR ?= fe80::5c38:e9ff:fe76:6195 +TCP_TARGET_PORT ?= 80 +TCP_TEST_CYCLES ?= 10 + +# Mark Boards with insufficient memory +BOARD_INSUFFICIENT_MEMORY := airfy-beacon arduino-duemilanove arduino-mega2560\ + arduino-uno calliope-mini chronos microbit sb-430\ + sb-430h nrf51dongle nrf6310 nucleo-f030 nucleo-f042\ + nucleo32-f042 nucleo-f070 nucleo-f072 nucleo32-f303\ + nucleo-f334 pca10000 pca10005 stm32f0discovery\ + telosb weio wsn430-v1_3b wsn430-v1_4\ + yunjia-nrf51822 z1 msb-430 msb-430h + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# Target Address, Target Port and number of Test Cycles +CFLAGS += -DTARGET_ADDR=\"$(TCP_TARGET_ADDR)\" +CFLAGS += -DTARGET_PORT=$(TCP_TARGET_PORT) +CFLAGS += -DCYCLES=$(TCP_TEST_CYCLES) + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +#CFLAGS += -DDEVELHELP + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +# Modules to include +USEMODULE += gnrc_netdev_default +USEMODULE += auto_init_gnrc_netif +USEMODULE += gnrc_ipv6_default +USEMODULE += gnrc_tcp + +include $(RIOTBASE)/Makefile.include diff --git a/examples/gnrc_tcp_cli/main.c b/examples/gnrc_tcp_cli/main.c new file mode 100644 index 0000000000000000000000000000000000000000..5d1bbbe22df4536e2342855717ece25e4ef423ce --- /dev/null +++ b/examples/gnrc_tcp_cli/main.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2015 Simon Brummer + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +#include <stdio.h> +#include <errno.h> +#include "net/af.h" +#include "random.h" +#include "net/gnrc/ipv6.h" +#include "net/gnrc/tcp.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* Number of possible parallel connections */ +#ifndef CONNS +#define CONNS 1 +#endif + +/* Amount of data to transmit */ +#ifndef NBYTE +#define NBYTE (2048) +#endif + +/* Test Pattern used by Client Application */ +#ifndef TEST_PATERN_CLI +#define TEST_PATERN_CLI (0xF0) +#endif + +/* Test Pattern used by Server Application */ +#ifndef TEST_PATERN_SRV +#define TEST_PATERN_SRV (0xA7) +#endif + +uint8_t bufs[CONNS][NBYTE]; +uint8_t stacks[CONNS][THREAD_STACKSIZE_DEFAULT + THREAD_EXTRA_STACKSIZE_PRINTF]; + +void *cli_thread(void *arg); + +int main(void) +{ + printf("\nStarting Client Threads. TARGET_ADDR=%s, TARGET_PORT=%d, ", TARGET_ADDR, TARGET_PORT); + printf("CONNS=%d, NBYTE=%d, CYCLES=%d\n\n", CONNS, NBYTE, CYCLES ); + + /* Start Connection Handling Threads */ + for (int i = 0; i < CONNS; i += 1) { + thread_create((char *)stacks[i], sizeof(stacks[i]), THREAD_PRIORITY_MAIN, 0, cli_thread, + (void *)i, NULL); + } + + return 0; +} + +void *cli_thread(void *arg) +{ + /* Test Program variables */ + int tid = (int) arg; + uint32_t cycles = 0; + uint32_t cycles_ok = 0; + uint32_t failed_payload_verifications = 0; + + /* Transmission Control Block */ + gnrc_tcp_tcb_t tcb; + + /* Target Peer Address Information */ + ipv6_addr_t target_addr; + uint16_t target_port; + + /* Initialize Target Information */ + ipv6_addr_from_str(&target_addr, TARGET_ADDR); + target_port = TARGET_PORT; + + printf("Client running: TID=%d\n", tid); + while (cycles < CYCLES) { + /* Initialize tcb struct */ + gnrc_tcp_tcb_init(&tcb); + + /* Connect to Peer */ + int ret = gnrc_tcp_open_active(&tcb, AF_INET6, (uint8_t *) &target_addr, target_port, 0); + switch (ret) { + case 0: + DEBUG("TID=%d : gnrc_tcp_open_active() : 0 : ok\n", tid); + break; + + case -EISCONN: + printf("TID=%d : gnrc_tcp_open_active() : -EISCONN\n", tid); + return 0; + + case -EINVAL: + printf("TID=%d : gnrc_tcp_open_active() : -EINVAL\n", tid); + return 0; + + case -EAFNOSUPPORT: + printf("TID=%d : gnrc_tcp_open_active() : -EAFNOSUPPORT\n", tid); + return 0; + + case -EADDRINUSE: + printf("TID=%d : gnrc_tcp_open_active() : -EADDRINUSE\n", tid); + return 0; + + case -ECONNREFUSED: + printf("TID=%d : gnrc_tcp_open_active() : -ECONNREFUSED : retry after 10sec\n", + tid); + xtimer_sleep(10); + continue; + + case -ENOMEM: + printf("TID=%d : gnrc_tcp_open_active() : -ENOMEM\n", tid); + return 0; + + case -ETIMEDOUT: + printf("TID=%d : gnrc_tcp_open_active() : -ETIMEDOUT : retry after 10sec\n", + tid); + xtimer_sleep(10); + continue; + + default: + printf("TID=%d : gnrc_tcp_open_active() : %d\n", tid, ret); + return 0; + } + + /* Fill Buffer with a test pattern */ + for (size_t i=0; i < sizeof(bufs[tid]); ++i){ + bufs[tid][i] = TEST_PATERN_CLI; + } + + /* Send Data, stop if errors were found */ + for (size_t sent = 0; sent < sizeof(bufs[tid]) && ret >= 0; sent += ret) { + ret = gnrc_tcp_send(&tcb, bufs[tid] + sent, sizeof(bufs[tid]) - sent, 0); + switch (ret) { + case -ENOTCONN: + printf("TID=%d : gnrc_tcp_send() : -ENOTCONN\n", tid); + break; + + case -ECONNABORTED: + printf("TID=%d : gnrc_tcp_send() : -ECONNABORTED\n", tid); + break; + + case -ETIMEDOUT: + printf("TID=%d : gnrc_tcp_send() : -ETIMEDOUT\n", tid); + break; + + case -ECONNRESET: + printf("TID=%d : gnrc_tcp_send() : -ECONNRESET\n", tid); + break; + + default: + if (ret >= 0) { + DEBUG("TID=%d : gnrc_tcp_send() : %d Bytes sent\n", tid, ret); + } + else { + printf("TID=%d : gnrc_tcp_send() : %d\n", tid, ret); + return 0; + } + } + } + + /* Receive Data, stop if errors were found */ + for (size_t rcvd = 0; rcvd < sizeof(bufs[tid]) && ret >= 0; rcvd += ret) { + ret = gnrc_tcp_recv(&tcb, (void *)(bufs[tid] + rcvd), sizeof(bufs[tid]) - rcvd, + GNRC_TCP_CONNECTION_TIMEOUT_DURATION); + switch (ret) { + case -ENOTCONN: + printf("TID=%d : gnrc_tcp_rcvd() : -ENOTCONN\n", tid); + break; + + case -EAGAIN: + printf("TID=%d : gnrc_tcp_rcvd() : -EAGAIN : retry after 10sec\n", tid); + ret = 0; + xtimer_sleep(10); + break; + + case -ECONNABORTED: + printf("TID=%d : gnrc_tcp_rcvd() : -ECONNABORTED\n", tid); + break; + + case -ECONNRESET: + printf("TID=%d : gnrc_tcp_rcvd() : -ECONNRESET\n", tid); + break; + + case -ETIMEDOUT: + printf("TID=%d : gnrc_tcp_rcvd() : -ETIMEDOUT\n", tid); + break; + + default: + if (ret >= 0) { + DEBUG("TID=%d : gnrc_tcp_rcvd() : %d Bytes read.\n", tid, ret); + } + else { + printf("TID=%d : gnrc_tcp_rcvd() : %d\n", tid, ret); + return 0; + } + } + } + + /* If there was no error: Check received pattern */ + for (size_t i=0; i < sizeof(bufs[tid]); ++i) { + if (bufs[tid][i] != TEST_PATERN_SRV) { + printf("TID=%d : Payload verfication failed\n", tid); + failed_payload_verifications += 1; + break; + } + } + + /* Close Connection */ + gnrc_tcp_close(&tcb); + + /* Gather Data */ + cycles += 1; + if(ret >= 0) { + cycles_ok += 1; + } + printf("TID=%d : %"PRIi32" test cycles completed. %"PRIi32" ok, %"PRIi32" faulty", + tid, cycles, cycles_ok, cycles - cycles_ok); + printf(", %"PRIi32" failed payload verifications\n", failed_payload_verifications); + } + printf("client thread terminating: TID=%d\n", tid); + return 0; +} diff --git a/examples/gnrc_tcp_srv/Makefile b/examples/gnrc_tcp_srv/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..75edf187c0811d6e44102426701afbc567cab8be --- /dev/null +++ b/examples/gnrc_tcp_srv/Makefile @@ -0,0 +1,42 @@ +# name of your application +APPLICATION = gnrc_tcp_srv + +# If no BOARD is found in the environment, use this default: +BOARD ?= native +PORT ?= tap0 + +TCP_LOCAL_PORT ?= 80 + +# Mark Boards with insufficient memory +BOARD_INSUFFICIENT_MEMORY := airfy-beacon arduino-duemilanove arduino-mega2560\ + arduino-uno calliope-mini chronos microbit sb-430\ + sb-430h nrf51dongle nrf6310 nucleo-f030 nucleo-f042\ + nucleo32-f042 nucleo-f070 nucleo-f072 nucleo32-f303\ + nucleo-f334 pca10000 pca10005 stm32f0discovery\ + telosb weio wsn430-v1_3b wsn430-v1_4\ + yunjia-nrf51822 z1 msb-430 msb-430h + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# Specify local Port to open +CFLAGS += -DLOCAL_PORT=$(TCP_LOCAL_PORT) + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +#CFLAGS += -DDEVELHELP + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +# Modules to include +USEMODULE += gnrc_netdev_default +USEMODULE += auto_init_gnrc_netif +USEMODULE += gnrc_ipv6_default +USEMODULE += gnrc_tcp + +# include this for printing IP addresses +USEMODULE += shell_commands + +include $(RIOTBASE)/Makefile.include diff --git a/examples/gnrc_tcp_srv/main.c b/examples/gnrc_tcp_srv/main.c new file mode 100644 index 0000000000000000000000000000000000000000..9a9ed39e448c7d9c99fad2032331441e2cb6a441 --- /dev/null +++ b/examples/gnrc_tcp_srv/main.c @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2015 Simon Brummer + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +#include <stdio.h> +#include <errno.h> +#include "thread.h" +#include "net/af.h" +#include "net/gnrc/ipv6.h" +#include "net/gnrc/tcp.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* Number of possible parallel connections */ +#ifndef CONNS +#define CONNS 1 +#endif + +/* Amount of data to transmit */ +#ifndef NBYTE +#define NBYTE (2048) +#endif + +/* Test Pattern used by Client Application */ +#ifndef TEST_PATERN_CLI +#define TEST_PATERN_CLI (0xF0) +#endif + +/* Test Pattern used by Server Application */ +#ifndef TEST_PATERN_SRV +#define TEST_PATERN_SRV (0xA7) +#endif + +uint8_t bufs[CONNS][NBYTE]; +uint8_t stacks[CONNS][THREAD_STACKSIZE_DEFAULT + THREAD_EXTRA_STACKSIZE_PRINTF]; + +/* ifconfig shell command */ +extern int _netif_config(int argc, char **argv); + +/* Server Thread */ +void *srv_thread(void *arg); + +int main(void) +{ + + /* Print all configured addresses of the server */ + printf("\nStarting server: LOCAL_PORT=%d, CONNS=%d, NBYTE=%d\n\n", LOCAL_PORT, CONNS, NBYTE); + printf("Printing Servers Network Configuration:\n"); + _netif_config(0, NULL); + + /* Start Threads to handle each connection */ + for (int i = 0; i < CONNS; i += 1) { + thread_create((char *)stacks[i], sizeof(stacks[i]), THREAD_PRIORITY_MAIN, 0, srv_thread, + (void *)i, NULL); + } + return 0; +} + +void *srv_thread(void *arg) +{ + int tid = (int)arg; + uint32_t cycles = 0; + uint32_t cycles_ok = 0; + uint32_t failed_payload_verifications = 0; + + /* Transmission control block */ + gnrc_tcp_tcb_t tcb; + + /* Connection handling code */ + printf("Server running: TID=%d\n", tid); + while (1) { + /* Initialize tcb struct */ + gnrc_tcp_tcb_init(&tcb); + + /* Connect to Peer */ + int ret = gnrc_tcp_open_passive(&tcb, AF_INET6, NULL, LOCAL_PORT); + switch (ret) { + case 0: + DEBUG("TID=%d : gnrc_tcp_open_passive() : 0 : ok\n", tid); + break; + + case -EISCONN: + printf("TID=%d : gnrc_tcp_open_passive() : -EISCONN\n", tid); + return 0; + + case -EINVAL: + printf("TID=%d : gnrc_tcp_open_passive() : -EINVAL\n", tid); + return 0; + + case -EAFNOSUPPORT: + printf("TID=%d : gnrc_tcp_open_passive() : -EAFNOSUPPORT\n", tid); + return 0; + + case -ENOMEM: + printf("TID=%d : gnrc_tcp_open_passive() : -ENOMEM\n", tid); + return 0; + + default: + printf("TID=%d : gnrc_tcp_open_passive() : %d\n", tid, ret); + return 0; + } + + /* Receive Data, stop if errors were found */ + for (size_t rcvd = 0; rcvd < sizeof(bufs[tid]) && ret >= 0; rcvd += ret) { + ret = gnrc_tcp_recv(&tcb, (void *)(bufs[tid] + rcvd), sizeof(bufs[tid]) - rcvd, + GNRC_TCP_CONNECTION_TIMEOUT_DURATION); + switch (ret) { + case -ENOTCONN: + printf("TID=%d : gnrc_tcp_rcvd() : -ENOTCONN\n", tid); + break; + + case -EAGAIN: + printf("TID=%d : gnrc_tcp_rcvd() : -EAGAIN : retry after 10sec\n", tid); + ret = 0; + xtimer_sleep(10); + break; + + case -ECONNABORTED: + printf("TID=%d : gnrc_tcp_rcvd() : -ECONNABORTED\n", tid); + break; + + case -ECONNRESET: + printf("TID=%d : gnrc_tcp_rcvd() : -ECONNRESET\n", tid); + break; + + case -ETIMEDOUT: + printf("TID=%d : gnrc_tcp_rcvd() : -ETIMEDOUT\n", tid); + break; + + default: + if (ret >= 0) { + DEBUG("TID=%d : gnrc_tcp_rcvd() : %d Bytes read\n", tid, ret); + } + else { + printf("TID=%d : gnrc_tcp_rcvd() : %d\n", tid, ret); + return 0; + } + } + } + + /* Check received pattern */ + for (size_t i=0; i < sizeof(bufs[tid]); ++i) { + if (bufs[tid][i] != TEST_PATERN_CLI) { + printf("TID=%d : Payload verfication failed\n", tid); + failed_payload_verifications += 1; + break; + } + } + + /* Fill Buffer with a test pattern */ + for (size_t i=0; i < sizeof(bufs[tid]); ++i) { + bufs[tid][i] = TEST_PATERN_SRV; + } + + /* Send Data, stop if errors were found */ + for (size_t sent = 0; sent < sizeof(bufs[tid]) && ret >= 0; sent += ret) { + ret = gnrc_tcp_send(&tcb, bufs[tid] + sent, sizeof(bufs[tid]) - sent, 0); + switch (ret) { + case -ENOTCONN: + printf("TID=%d : gnrc_tcp_send() : -ENOTCONN\n", tid); + break; + + case -ECONNABORTED: + printf("TID=%d : gnrc_tcp_send() : -ECONNABORTED\n", tid); + break; + + case -ETIMEDOUT: + printf("TID=%d : gnrc_tcp_send() : -ETIMEDOUT\n", tid); + break; + + case -ECONNRESET: + printf("TID=%d : gnrc_tcp_send() : -ECONNRESET\n", tid); + break; + + default: + if (ret >= 0) { + DEBUG("TID=%d : gnrc_tcp_send() : %d Bytes sent.\n", tid, ret); + } + else { + printf("TID=%d : gnrc_tcp_send() : %d\n", tid, ret); + return 0; + } + } + } + + /* Close Connection */ + gnrc_tcp_close(&tcb); + + /* Gather Data */ + cycles += 1; + if(ret >= 0) { + cycles_ok += 1; + } + printf("TID=%d : %"PRIi32" test cycles completed. %"PRIi32" ok, %"PRIi32" faulty", + tid, cycles, cycles_ok, cycles - cycles_ok); + printf(", %"PRIi32" failed payload verifications\n", failed_payload_verifications); + } + return 0; +} diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index 5af0e8f00592db6e0e1d33274da05190b57207fb..e28438d6e9cee993159368354e7596dd6edb6c03 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -76,6 +76,10 @@ #include "net/gnrc/udp.h" #endif +#ifdef MODULE_GNRC_TCP +#include "net/gnrc/tcp.h" +#endif + #ifdef MODULE_LWIP #include "lwip.h" #endif @@ -152,6 +156,10 @@ void auto_init(void) DEBUG("Auto init UDP module.\n"); gnrc_udp_init(); #endif +#ifdef MODULE_GNRC_TCP + DEBUG("Auto init TCP module\n"); + gnrc_tcp_init(); +#endif #ifdef MODULE_DHT DEBUG("Auto init DHT devices.\n"); extern void dht_auto_init(void); diff --git a/sys/include/net/gnrc/tcp.h b/sys/include/net/gnrc/tcp.h new file mode 100644 index 0000000000000000000000000000000000000000..a7ccf0e6aa4d50e2279b2c2bb17e23e4235a9ad5 --- /dev/null +++ b/sys/include/net/gnrc/tcp.h @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @defgroup net_gnrc_tcp TCP + * @ingroup net_gnrc + * @brief RIOT's tcp implementation for the gnrc stack + * + * @{ + * + * @file + * @brief TCP interface definition + * + * @author Simon Brummer <simon.brummer@haw-hamburg.de> + */ + +#ifndef GNRC_TCP_H_ +#define GNRC_TCP_H_ + +#include "net/gnrc/netapi.h" +#include "net/gnrc/nettype.h" +#include "net/gnrc/tcp/hdr.h" +#include "net/gnrc/tcp/tcb.h" + +#ifdef MODULE_GNRC_IPV6 +#include "net/gnrc/ipv6.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Port unspecified. + * + * @note PORT 0 is reserved, according to rfc 1700(https://www.ietf.org/rfc/rfc1700.txt) + */ +#define GNRC_TCP_PORT_UNSPEC 0 + +/** + * @brief Head of conn linked list. + */ +extern gnrc_tcp_tcb_t *_list_gnrc_tcp_tcb_head; + +/** + * @brief Mutex to protect linked list. + */ +extern mutex_t _list_gnrc_tcp_tcb_lock; + +/** + * @brief Initialize and start TCP + * + * @return PID of TCP thread on success + * @return -1 if thread is already running. + * @return -EINVAL, if priority is greater than or equal SCHED_PRIO_LEVELS + * @return -EOVERFLOW, if there are too many threads running. + */ +int gnrc_tcp_init(void); + +/** + * @brief Initialize Transmission Control Block (tcb) + * @pre tcb must not be NULL. + * + * @param[in,out] tcb Transmission that should be initialized. + */ +void gnrc_tcp_tcb_init(gnrc_tcp_tcb_t* tcb); + + /** + * @brief Opens a connection actively. + * + * @pre gnrc_tcp_tcb_init() must have been successfully called. + * @pre tcb must not be NULL + * @pre target_addr must not be NULL. + * @pre target_port must not be 0. + * + * @note Blocks until a connection has been established or an error occured. + * + * @param[in,out] tcb This connections Transmission control block. + * @param[in] address_family Address Family of @p target_addr. + * @param[in] target_addr Pointer to target address. + * @param[in] target_port Targets port number. + * @param[in] local_port If zero or GNRC_TCP_PORT_UNSPEC, the connections + * source port is randomly choosen. If local_port is non-zero + * the local_port is used as source port. + * + * @return Zero 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 -EISCONN if transmission control block is already in use. + * @return -ENOMEM if the receive buffer for the tcb could not be allocated. + * Increase "GNRC_TCP_RCV_BUFFERS". + * @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 resetted by the peer. + */ +int gnrc_tcp_open_active(gnrc_tcp_tcb_t *tcb, const uint8_t address_family, + const uint8_t *target_addr, const uint16_t target_port, + const uint16_t local_port); + +/** + * @brief Opens a connection passively, by waiting for an incomming request. + * + * @pre gnrc_tcp_tcb_init() must have been successfully called. + * @pre tcb must not be NULL. + * @pre if local_addr is not NULL, local_addr must be assigned to a network interface. + * @pre if local_port is not zero. + * + * @note Blocks until a connection has been established (incomming connection request + * to @p local_port) or an error occured. + * + * @param[in,out] tcb This connections Transmission control block. + * @param[in] address_family Address Family of @p local_addr. + * If local_addr == NULL, address_family is ignored. + * @param[in] local_addr If not NULL the connection is bound to the address in @p local_addr. + * If NULL a connection request to every local ip address is valid. + * @param[in] local_port Portnumber that should used for incomming connection requests. + * + * @return Zero 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 use by the tcb. + * @return -EISCONN if transmission control block is already in use. + * @return -ENOMEM if the receive buffer for the tcb could not be allocated. + * Increase "GNRC_TCP_RCV_BUFFERS". + */ +int gnrc_tcp_open_passive(gnrc_tcp_tcb_t *tcb, const uint8_t address_family, + const uint8_t *local_addr, const uint16_t local_port); + +/** + * @brief Transmit Data to Peer. + * + * @pre gnrc_tcp_tcb_init() must have been successfully called. + * @pre tcb must not be NULL. + * @pre data must not be NULL. + * + * @note Blocks until up to @p len bytes were transmitted or an error occured. + * + * @param[in,out] tcb This connections Transmission control block. + * @param[in] data Pointer to the data that should be transmitted. + * @param[in] len Number of bytes that should be transmitted. + * @param[in] user_timeout_duration_us If not zero and there were not data transmitted + * successfully, the function call returns after + * user_timeout_duration_us. If zero not timeout will be + * triggered. + * + * @return On success, the number of successfully transmitted bytes. + * @return -ENOTCONN if connection is not established. + * @return -ECONNRESET if connection was resetted by the peer. + * @return -ECONNABORTED if the connection was aborted. + * @return -ETIMEDOUT if @p user_timeout_duration_us expired. + */ +ssize_t gnrc_tcp_send(gnrc_tcp_tcb_t *tcb, const void *data, const size_t len, + const uint32_t user_timeout_duration_us); + +/** + * @brief Receive Data from the Peer. + * + * @pre gnrc_tcp_tcb_init() must have been successfully called. + * @pre tcb must not be NULL. + * @pre data must not be NULL. + * + * @note Function blocks if user_timeout_duration_us is not zero. + * + * @param[in,out] tcb This connections Transmission control block. + * @param[out] data Pointer to the buffer where the received data + * should be copied into. + * @param[in] max_len Maximum amount to bytes that should be reeived. + * Should not exceed size of @p data. + * @param[in] user_timeout_duration_us Timeout for receive in microseconds. If zero and no data + * is available, the function returns immediately. If not + * zero the function block until data is available or + * user_timeout_duration_us microseconds have passed. + * + * @return On success, the number of bytes read into @p data. + * @return -ENOTCONN if connection is not established. + * @return -EAGAIN if user_timeout_duration_us is zero and no data is available. + * @return -ECONNRESET if connection was resetted by the peer. + * @return -ECONNABORTED if the connection was aborted. + * @return -ETIMEDOUT if @p user_timeout_duration_us expired. + */ +ssize_t gnrc_tcp_recv(gnrc_tcp_tcb_t *tcb, void *data, const size_t max_len, + const uint32_t user_timeout_duration_us); + +/** + * @brief Close a tcp connection. + * + * @pre gnrc_tcp_tcb_init() must have been successfully called. + * @pre tcb must not be NULL. + * + * @param[in,out] tcb This connections Transmission control block. + * + * @return Zero on success. + */ +int gnrc_tcp_close(gnrc_tcp_tcb_t *tcb); + +/** + * @brief Set checksum calculated from tcp and network-layer header in tcp-header. + * + * @param[in] hdr ng_pktsnip that contains tcp header. + * @param[in] pseudo_hdr ng_pktsnip that contains networklayer header. + * + * @return zero on succeed. + * @return -EFAULT if hdr or pseudo_hdr were NULL + * @return -EBADMSG if hdr is not of type GNRC_NETTYPE_TCP + * @return -ENOENT if pseudo_hdr protocol is unsupported. + */ +int gnrc_tcp_calc_csum(const gnrc_pktsnip_t *hdr, const gnrc_pktsnip_t *pseudo_hdr); + +/** + * @brief Adds a tcp header to a given payload. Be carefull, leads to huge headers. + * Allocates all option bytes + * + * @param[in] payload payload that follows the tcp header + * @param[in] src Source port in host byte order + * @param[in] dst Destination port in host byte order + * + * @return NULL, if paket buffer is full + * @return Not NULL on success + */ +gnrc_pktsnip_t *gnrc_tcp_hdr_build(gnrc_pktsnip_t *payload, uint16_t src, uint16_t dst); + +#ifdef __cplusplus +} +#endif + +#endif /* GNRC_TCP_H_ */ +/** @} */ diff --git a/sys/include/net/gnrc/tcp/config.h b/sys/include/net/gnrc/tcp/config.h new file mode 100644 index 0000000000000000000000000000000000000000..fe8f20f2b0a04313a39c2b343c5ceb225d65e2db --- /dev/null +++ b/sys/include/net/gnrc/tcp/config.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @defgroup net_gnrc_tcp TCP + * @ingroup net_gnrc + * @brief RIOT's tcp implementation for the gnrc stack + * + * @{ + * + * @file + * @brief TCP configuration, includes buffersizes, timeout durations + * + * @author Simon Brummer <simon.brummer@haw-hamburg.de> + */ + +#ifndef GNRC_TCP_CONFIG_H_ +#define GNRC_TCP_CONFIG_H_ + +#include "timex.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Status Flags for TCP + * @{ + */ +#define GNRC_TCP_STATUS_PASSIVE (1 << 0) +#define GNRC_TCP_STATUS_ACCEPTED (1 << 1) +#define GNRC_TCP_STATUS_ALLOW_ANY_ADDR (1 << 2) +/** @} */ + +/** + * @brief Timeout Duration for user calls. Default 2 minutes + */ +#ifndef GNRC_TCP_CONNECTION_TIMEOUT_DURATION +#define GNRC_TCP_CONNECTION_TIMEOUT_DURATION (120 * US_PER_SEC) +#endif + +/** + * @brief Maximum Segment Lifetime. Default 30 secounds + */ +#ifndef GNRC_TCP_MSL +#define GNRC_TCP_MSL (30 * US_PER_SEC) +#endif + +/** + * @brief Message queue size for the TCP handling thread + */ +#ifndef GNRC_TCP_MSG_QUEUE_SIZE +#define GNRC_TCP_MSG_QUEUE_SIZE (8U) +#endif + +/** + * @brief Priority of the tcp handling thread, must be lower than the applications prio. + */ +#ifndef GNRC_TCP_PRIO +#define GNRC_TCP_PRIO (THREAD_PRIORITY_MAIN - 2U) +#endif + +/** + * @brief Default stack size for the TCP handling thread + */ +#ifndef GNRC_TCP_STACK_SIZE +#define GNRC_TCP_STACK_SIZE (THREAD_STACKSIZE_DEFAULT) +#endif + +/** + * @brief Maximum Segement Size + */ +#ifndef GNRC_TCP_MSS +#ifdef MODULE_GNRC_IPV6 +#define GNRC_TCP_MSS (1220U) /**< If IPv6 is used. Get MSS = 1280 - IPv6-Hdr - TCP-Hdr = 1220 */ +#else +#define GNRC_TCP_MSS (576U) /**< Default MSS */ +#endif +#endif + +/** + * @brief MSS Multiplicator = Number of MSS sized packets stored in receive buffer + */ +#ifndef GNRC_TCP_MSS_MULTIPLICATOR +#define GNRC_TCP_MSS_MULTIPLICATOR (1U) +#endif + +/** + * @brief Default Window Size + */ +#ifndef GNRC_TCP_DEFAULT_WINDOW +#define GNRC_TCP_DEFAULT_WINDOW (GNRC_TCP_MSS * GNRC_TCP_MSS_MULTIPLICATOR) +#endif + +/** + * @brief Number of preallocated receive buffers + */ +#ifndef GNRC_TCP_RCV_BUFFERS +#define GNRC_TCP_RCV_BUFFERS 1 +#endif + +/** + * @brief Default Receive Buffer Size + */ +#ifndef GNRC_TCP_RCV_BUF_SIZE +#define GNRC_TCP_RCV_BUF_SIZE (GNRC_TCP_DEFAULT_WINDOW) +#endif + +/** + * @brief Lower Bound for RTO = 1 sec (see RFC 6298) + */ +#ifndef GNRC_TCP_RTO_LOWER_BOUND +#define GNRC_TCP_RTO_LOWER_BOUND (1 * US_PER_SEC) +#endif + +/** + * @brief Upper Bound for RTO = 60 sec (see RFC 6298) + */ +#ifndef GNRC_TCP_RTO_UPPER_BOUND +#define GNRC_TCP_RTO_UPPER_BOUND (60 * US_PER_SEC) +#endif + +/** + * @brief Assumes clock granularity for TCP of 10 ms (see RFC 6298) + */ +#ifndef GNRC_TCP_RTO_GRANULARITY +#define GNRC_TCP_RTO_GRANULARITY (10 * MS_PER_SEC) +#endif + +/** + * @brief Alpha value for RTO calculation, default is 1/8 + */ +#ifndef GNRC_TCP_RTO_A_DIV +#define GNRC_TCP_RTO_A_DIV (8U) +#endif + +/** + * @brief Beta value for RTO calculation, default is 1/4 + */ +#ifndef GNRC_TCP_RTO_B_DIV +#define GNRC_TCP_RTO_B_DIV (4U) +#endif + +/** + * @brief K value for RTO calculation, default is 4 + */ +#ifndef GNRC_TCP_RTO_K +#define GNRC_TCP_RTO_K (4U) +#endif + +/** + * @brief Macro to mark is the time measurement is uninitialized + */ +#ifndef GNRC_TCP_RTO_UNINITIALIZED +#define GNRC_TCP_RTO_UNINITIALIZED (-1) +#endif + +/** + * @brief Lower Bound for the duration between probes + */ +#ifndef GNRC_TCP_PROBE_LOWER_BOUND +#define GNRC_TCP_PROBE_LOWER_BOUND (1 * US_PER_SEC) +#endif + +/** + * @brief Upper Bound for the duration between probes + */ +#ifndef GNRC_TCP_PROBE_UPPER_BOUND +#define GNRC_TCP_PROBE_UPPER_BOUND (60 * US_PER_SEC) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* GNRC_TCP_CONFIG_H_ */ +/** @} */ diff --git a/sys/include/net/gnrc/tcp/fsm.h b/sys/include/net/gnrc/tcp/fsm.h new file mode 100644 index 0000000000000000000000000000000000000000..43ca94cdd3a859b7a1724114e4e3fe22f86c554e --- /dev/null +++ b/sys/include/net/gnrc/tcp/fsm.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @defgroup net_gnrc_tcp TCP + * @ingroup net_gnrc + * @brief RIOT's tcp implementation for the gnrc stack + * + * @{ + * + * @file + * @brief Definies states and events for TCP finite state maschine + * + * @author Simon Brummer <brummer.simon@googlemail.com> + */ + +#ifndef GNRC_TCP_FSM_H_ +#define GNRC_TCP_FSM_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The TCP FSM States. + */ +typedef enum { + GNRC_TCP_FSM_STATE_CLOSED, + GNRC_TCP_FSM_STATE_LISTEN, + GNRC_TCP_FSM_STATE_SYN_SENT, + GNRC_TCP_FSM_STATE_SYN_RCVD, + GNRC_TCP_FSM_STATE_ESTABLISHED, + GNRC_TCP_FSM_STATE_CLOSE_WAIT, + GNRC_TCP_FSM_STATE_LAST_ACK, + GNRC_TCP_FSM_STATE_FIN_WAIT_1, + GNRC_TCP_FSM_STATE_FIN_WAIT_2, + GNRC_TCP_FSM_STATE_CLOSING, + GNRC_TCP_FSM_STATE_TIME_WAIT +} gnrc_tcp_fsm_state_t; + +/** + * @brief Events that trigger translations in TCP FSM. + */ +typedef enum { + GNRC_TCP_FSM_EVENT_CALL_OPEN, /* User function call: open */ + GNRC_TCP_FSM_EVENT_CALL_SEND, /* User function call: send */ + GNRC_TCP_FSM_EVENT_CALL_RECV, /* User function call: recv */ + GNRC_TCP_FSM_EVENT_CALL_CLOSE, /* User function call: close */ + GNRC_TCP_FSM_EVENT_CALL_ABORT, /* User function call: abort */ + GNRC_TCP_FSM_EVENT_RCVD_PKT, /* Paket received from peer */ + GNRC_TCP_FSM_EVENT_TIMEOUT_TIMEWAIT, /* Timeout: Timewait */ + GNRC_TCP_FSM_EVENT_TIMEOUT_RETRANSMIT, /* Timeout: Retransmit */ + GNRC_TCP_FSM_EVENT_TIMEOUT_CONNECTION, /* Timeout: Connection */ + GNRC_TCP_FSM_EVENT_SEND_PROBE, /* Send a Zero Window Probe */ + GNRC_TCP_FSM_EVENT_CLEAR_RETRANSMIT /* Clear Retransmission Mechanism */ +} gnrc_tcp_fsm_event_t; + +#ifdef __cplusplus +} +#endif + +#endif /* GNRC_TCP_FSM_H_ */ +/** @} */ diff --git a/sys/include/net/gnrc/tcp/hdr.h b/sys/include/net/gnrc/tcp/hdr.h new file mode 100644 index 0000000000000000000000000000000000000000..dd6eba6e6047b7200f8bff06104863b75a02b1c0 --- /dev/null +++ b/sys/include/net/gnrc/tcp/hdr.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @defgroup net_gnrc_tcp TCP + * @ingroup net_gnrc + * @brief RIOT's tcp implementation for the gnrc stack + * + * @{ + * + * @file + * @brief TCP Header + * + * @author Simon Brummer <brummer.simon@googlemail.com> + */ + +#ifndef GNRC_TCP_HDR_H_ +#define GNRC_TCP_HDR_H_ + +#include "byteorder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief TCP Options could contain up to 10 32-Bit values of Information. + */ +#define TCP_MAX_HDR_OPTIONS 10 + +/** + * @brief TCP header definition + */ +typedef struct __attribute__((packed)) { + network_uint16_t src_port; /**< source port, in network byte order */ + network_uint16_t dst_port; /**< destination port, in network byte order */ + network_uint32_t seq_num; /**< sequence number, in network byte order */ + network_uint32_t ack_num; /**< Acknowledgement number, in network byte order */ + network_uint16_t off_ctl; /**< Data Offset and control Bits in network byte order */ + network_uint16_t window; /**< window, in network byte order */ + network_uint16_t checksum; /**< checksum, in network byte order */ + network_uint16_t urgent_ptr; /**< urgent pointer, in network byte order */ + network_uint32_t options[TCP_MAX_HDR_OPTIONS]; /**< Option Fields (Optional) */ +} tcp_hdr_t; + +#ifdef __cplusplus +} +#endif + +#endif /* GNRC_TCP_TCB_H_ */ +/** @} */ diff --git a/sys/include/net/gnrc/tcp/tcb.h b/sys/include/net/gnrc/tcp/tcb.h new file mode 100644 index 0000000000000000000000000000000000000000..6e8dc15746bf480c869ce752d47d51d0edae3194 --- /dev/null +++ b/sys/include/net/gnrc/tcp/tcb.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @defgroup net_gnrc_tcp TCP + * @ingroup net_gnrc + * @brief RIOT's tcp implementation for the gnrc stack + * + * @{ + * + * @file + * @brief Transmission Control Block definition + * + * @author Simon Brummer <simon.brummer@haw-hamburg.de> + */ + +#ifndef GNRC_TCP_TCB_H_ +#define GNRC_TCP_TCB_H_ + +#include <stdint.h> +#include <ringbuffer.h> +#include <xtimer.h> +#include <mutex.h> +#include <msg.h> +#include "net/gnrc.h" +#include "fsm.h" +#include "config.h" + +#ifdef MODULE_GNRC_IPV6 +#include "net/gnrc/ipv6.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief transmission control block of gnrc_tcp + */ +typedef struct _transmission_control_block { + uint8_t address_family; /**< Address Family of local_addr and peer_addr */ +#ifdef MODULE_GNRC_IPV6 + uint8_t local_addr[sizeof(ipv6_addr_t)]; /**< local IP address */ + uint8_t peer_addr[sizeof(ipv6_addr_t)]; /**< peer IP address */ +#endif + uint16_t local_port; /**< local connections port number */ + uint16_t peer_port; /**< port connections port number */ + gnrc_tcp_fsm_state_t state; /**< Connections state */ + uint8_t status; /**< A connections status flags */ + uint32_t snd_una; /**< Send Unacknowledged */ + uint32_t snd_nxt; /**< Send Next */ + uint16_t snd_wnd; /**< Send Window */ + uint32_t snd_wl1; /**< SeqNo. Last Windowupdate */ + uint32_t snd_wl2; /**< AckNo. Last Windowupdate */ + uint32_t rcv_nxt; /**< Receive Next */ + uint16_t rcv_wnd; /**< Receive Window */ + uint32_t iss; /**< Initial Sequence Number */ + uint32_t irs; /**< Initial Received Sequence Number */ + uint16_t mss; /**< The peers MSS */ + uint32_t rtt_start; /**< Timer value for rtt estimation */ + int32_t rtt_var; /**< Round Trip Time variance */ + int32_t srtt; /**< Smoothed Round Trip Time */ + int32_t rto; /**< Retransmission Timeout Duration */ + uint8_t retries; /**< Number of Retransmissions */ + xtimer_t tim_tout; /**< Timer struct for timeouts */ + msg_t msg_tout; /**< Message, sent on timeouts */ + gnrc_pktsnip_t *pkt_retransmit; /**< Pointer to Packet in "retransmit queue" */ + kernel_pid_t owner; /**< PID of this connection handling thread */ + msg_t msg_queue[GNRC_TCP_MSG_QUEUE_SIZE]; /**< Message queue used for asynchronious operation */ + uint8_t *rcv_buf_raw; /**< Pointer to the receive buffer */ + ringbuffer_t rcv_buf; /**< Receive Buffer data structure */ + mutex_t fsm_lock; /**< Mutex for FSM access synchronization */ + mutex_t function_lock; /**< Mutex for Function call synchronization */ + struct _transmission_control_block *next; /**< Pointer next TCP connection */ +} gnrc_tcp_tcb_t; + +#ifdef __cplusplus +} +#endif +#endif /* GNRC_TCP_TCB_H_ */ +/** @} */ diff --git a/sys/net/gnrc/Makefile b/sys/net/gnrc/Makefile index a974618c5e19c058f7d49a8a3bf4ff7e1d5617ea..b125dda8c6400e1012a6f038a0e4b51ffcf3248f 100644 --- a/sys/net/gnrc/Makefile +++ b/sys/net/gnrc/Makefile @@ -130,6 +130,9 @@ endif ifneq (,$(filter gnrc_udp,$(USEMODULE))) DIRS += transport_layer/udp endif +ifneq (,$(filter gnrc_tcp,$(USEMODULE))) + DIRS += transport_layer/tcp +endif ifneq (,$(filter gnrc_zep,$(USEMODULE))) DIRS += application_layer/zep endif diff --git a/sys/net/gnrc/netreg/gnrc_netreg.c b/sys/net/gnrc/netreg/gnrc_netreg.c index a01c557c72909ab9ad8056a2001754c611acc62a..8ec47e7d336fed66923e7e6fd77ffe172fac78d6 100644 --- a/sys/net/gnrc/netreg/gnrc_netreg.c +++ b/sys/net/gnrc/netreg/gnrc_netreg.c @@ -23,6 +23,7 @@ #include "net/gnrc/icmpv6.h" #include "net/gnrc/ipv6.h" #include "net/gnrc/udp.h" +#include "net/gnrc/tcp.h" #define _INVALID_TYPE(type) (((type) < GNRC_NETTYPE_UNDEF) || ((type) >= GNRC_NETTYPE_NUMOF)) diff --git a/sys/net/gnrc/transport_layer/tcp/Makefile b/sys/net/gnrc/transport_layer/tcp/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d85580dc5ee26658863b30e45246d703fbd83d20 --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/Makefile @@ -0,0 +1,3 @@ +MODULE = gnrc_tcp + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c new file mode 100644 index 0000000000000000000000000000000000000000..3eb8d7d58be69065c061c986c64e63de52e44838 --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c @@ -0,0 +1,616 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @ingroup net_gnrc + * @{ + * + * @file + * @brief GNRC's TCP implementation + * + * @author Simon Brummer <brummer.simon@googlemail.com> + * @} + */ + +#include <stdint.h> +#include <errno.h> +#include <utlist.h> +#include "msg.h" +#include "assert.h" +#include "thread.h" +#include "byteorder.h" +#include "random.h" +#include "xtimer.h" +#include "mutex.h" +#include "ringbuffer.h" +#include "net/af.h" +#include "net/gnrc/nettype.h" +#include "net/gnrc/netapi.h" +#include "net/gnrc/netreg.h" +#include "net/gnrc/pktbuf.h" +#include "net/gnrc/tcp.h" + +#include "internal/fsm.h" +#include "internal/pkt.h" +#include "internal/option.h" +#include "internal/helper.h" +#include "internal/eventloop.h" +#include "internal/rcvbuf.h" + +#ifdef MODULE_GNRC_IPV6 +#include "net/gnrc/ipv6.h" +#endif + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/** + * @brief Allocate memory for TCP thread's stack + */ +#if ENABLE_DEBUG +static char _stack[GNRC_TCP_STACK_SIZE + THREAD_EXTRA_STACKSIZE_PRINTF]; +#else +static char _stack[GNRC_TCP_STACK_SIZE]; +#endif + +/** + * @brief TCPs eventloop pid, declared externally + */ +kernel_pid_t _gnrc_tcp_pid = KERNEL_PID_UNDEF; + +/** + * @brief Head of liked list of active connections + */ +gnrc_tcp_tcb_t *_list_gnrc_tcp_tcb_head; + +/** + * @brief Mutex to protect the connection list + */ +mutex_t _list_gnrc_tcp_tcb_lock; + +/** + * @brief Establishes a new TCP connection + * + * @param[in/out] tcb This connections Transmission control block. + * @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. + * + * @return 0 on success. + * @return -EISCONN if transmission control block is already in use. + * @return -ENOMEM if the receive buffer for the tcb could not be allocated. + * Increase "GNRC_TCP_RCV_BUFFERS". + * @return -EADDRINUSE if @p local_port is already used by another connection. Only active mode. + * @return -ETIMEDOUT if the connection could not be opened. Only active mode. + * @return -ECONNREFUSED if the connection was resetted by the peer. + */ +static int _gnrc_tcp_open(gnrc_tcp_tcb_t *tcb, const uint8_t *target_addr, uint16_t target_port, + const uint8_t *local_addr, uint16_t local_port, uint8_t passive) +{ + msg_t msg; /* Message for incomming Messages */ + msg_t connection_timeout_msg; /* Connection Timeout Message */ + xtimer_t connection_timeout_timer; /* Connection Timeout Timer */ + int8_t ret = 0; /* Return Value */ + + /* Lock the tcb for this function call */ + mutex_lock(&(tcb->function_lock)); + + /* Connection is already connected: Return -EISCONN */ + if (tcb->state != GNRC_TCP_FSM_STATE_CLOSED) { + mutex_unlock(&(tcb->function_lock)); + return -EISCONN; + } + + /* Setup connection (common parts) */ + msg_init_queue(tcb->msg_queue, GNRC_TCP_MSG_QUEUE_SIZE); + tcb->owner = thread_getpid(); + + /* Setup passive connection */ + if (passive){ + /* Set Status Flags */ + tcb->status |= GNRC_TCP_STATUS_PASSIVE; + if (local_addr == NULL) { + tcb->status |= GNRC_TCP_STATUS_ALLOW_ANY_ADDR; + } + /* If local address is specified: Copy it into tcb: only connections to this addr are ok */ + else { + switch (tcb->address_family) { +#ifdef MODULE_GNRC_IPV6 + case AF_INET6: + memcpy(tcb->local_addr, local_addr, sizeof(ipv6_addr_t)); + break; +#endif + } + } + /* Assign Port to listen on, to tcb */ + tcb->local_port = local_port; + } + /* Setup active connection */ + else{ + /* Copy Target Address and Port into tcb structure */ + if (target_addr != NULL) { + switch (tcb->address_family) { + #ifdef MODULE_GNRC_IPV6 + case AF_INET6: + memcpy(tcb->peer_addr, target_addr, sizeof(ipv6_addr_t)); + break; + #endif + } + } + /* Copy Port Information, verfication happens in fsm */ + tcb->local_port = local_port; + tcb->peer_port = target_port; + + /* Setup Timeout: If connection could not be established before */ + /* the timer expired, the connection attempt failed */ + connection_timeout_msg.type = MSG_TYPE_CONNECTION_TIMEOUT; + xtimer_set_msg(&connection_timeout_timer, GNRC_TCP_CONNECTION_TIMEOUT_DURATION, + &connection_timeout_msg, tcb->owner); + } + + /* Call FSM with Event: CALL_OPEN */ + ret = _fsm(tcb, GNRC_TCP_FSM_EVENT_CALL_OPEN, NULL, NULL, 0); + if (ret == -ENOMEM) { + DEBUG("gnrc_tcp.c : gnrc_tcp_connect() : Out of receive buffers.\n"); + } else if(ret == -EADDRINUSE) { + DEBUG("gnrc_tcp.c : gnrc_tcp_connect() : local_port is already in use.\n"); + } + + /* Wait until a connection was established or closed */ + while (ret >= 0 && tcb->state != GNRC_TCP_FSM_STATE_CLOSED + && tcb->state != GNRC_TCP_FSM_STATE_ESTABLISHED + && tcb->state != GNRC_TCP_FSM_STATE_CLOSE_WAIT + ) { + msg_receive(&msg); + switch (msg.type) { + case MSG_TYPE_CONNECTION_TIMEOUT: + DEBUG("gnrc_tcp.c : _gnrc_tcp_open() : CONNECTION_TIMEOUT\n"); + _fsm(tcb, GNRC_TCP_FSM_EVENT_TIMEOUT_CONNECTION, NULL, NULL, 0); + ret = -ETIMEDOUT; + break; + + case MSG_TYPE_NOTIFY_USER: + DEBUG("gnrc_tcp.c : _gnrc_tcp_open() : MSG_TYPE_NOTIFY_USER\n"); + break; + + default: + DEBUG("gnrc_tcp.c : _gnrc_tcp_open() : other message type\n"); + } + } + + /* Cleanup */ + xtimer_remove(&connection_timeout_timer); + if (tcb->state == GNRC_TCP_FSM_STATE_CLOSED && ret == 0) { + ret = -ECONNREFUSED; + } + tcb->owner = KERNEL_PID_UNDEF; + mutex_unlock(&(tcb->function_lock)); + return ret; +} + +/* External GNRC_TCP API */ +int gnrc_tcp_init(void) +{ + /* Guard: Check if thread is already running */ + if (_gnrc_tcp_pid != KERNEL_PID_UNDEF) { + return -1; + } + + /* Initialize Mutex for linked-list synchronization */ + mutex_init(&(_list_gnrc_tcp_tcb_lock)); + + /* Initialize Linked-List for connection storage */ + _list_gnrc_tcp_tcb_head = NULL; + + /* Initialize receive buffers */ + _rcvbuf_init(); + + /* Start TCP processing loop */ + return thread_create(_stack, sizeof(_stack), GNRC_TCP_PRIO, 0, _event_loop, NULL, "gnrc_tcp"); +} + +void gnrc_tcp_tcb_init(gnrc_tcp_tcb_t* tcb) +{ +#ifdef MODULE_GNRC_IPV6 + tcb->address_family = AF_INET6; + ipv6_addr_set_unspecified((ipv6_addr_t *) tcb->local_addr); + ipv6_addr_set_unspecified((ipv6_addr_t *) tcb->peer_addr); +#else + tcb->address_family = AF_UNSPEC; + DEBUG("gnrc_tcp.c : gnrc_tcp_tcb_init() : Address unspec, add netlayer module to makefile\n"); +#endif + tcb->local_port = GNRC_TCP_PORT_UNSPEC; + tcb->peer_port = GNRC_TCP_PORT_UNSPEC; + tcb->state = GNRC_TCP_FSM_STATE_CLOSED; + tcb->status = 0; + tcb->snd_una = 0; + tcb->snd_nxt = 0; + tcb->snd_wnd = 0; + tcb->snd_wl1 = 0; + tcb->snd_wl2 = 0; + tcb->rcv_nxt = 0; + tcb->rcv_wnd = 0; + tcb->iss = 0; + tcb->irs = 0; + tcb->mss = 0; + tcb->rtt_start = 0; + tcb->rtt_var = GNRC_TCP_RTO_UNINITIALIZED; + tcb->srtt = GNRC_TCP_RTO_UNINITIALIZED; + tcb->rto = GNRC_TCP_RTO_UNINITIALIZED; + tcb->retries = 0; + tcb->pkt_retransmit = NULL; + tcb->owner = KERNEL_PID_UNDEF; + tcb->rcv_buf_raw = NULL; + mutex_init(&(tcb->fsm_lock)); + mutex_init(&(tcb->function_lock)); + tcb->next = NULL; +} + +int gnrc_tcp_open_active(gnrc_tcp_tcb_t *tcb, const uint8_t address_family, + const uint8_t *target_addr, const uint16_t target_port, + const uint16_t local_port) +{ + assert(tcb != NULL); + assert(target_addr != NULL); + assert(target_port != GNRC_TCP_PORT_UNSPEC); + + /* Check AF-Family Support from target_addr */ + switch (address_family) { +#ifdef MODULE_GNRC_IPV6 + case AF_INET6: + break; +#endif + default: + return -EAFNOSUPPORT; + } + /* Check if AF-Family for Target Address matches internally used AF-Family */ + if (tcb->address_family != address_family) { + return -EINVAL; + } + return _gnrc_tcp_open(tcb, target_addr, target_port, NULL, local_port, 0); +} + +int gnrc_tcp_open_passive(gnrc_tcp_tcb_t *tcb, const uint8_t address_family, + const uint8_t *local_addr, const uint16_t local_port) +{ + assert(tcb != NULL); + assert(local_port != GNRC_TCP_PORT_UNSPEC); + + /* Check AF-Family support if local address was supplied */ + if (local_addr != NULL) { + switch (address_family) { +#ifdef MODULE_GNRC_IPV6 + case AF_INET6: + break; +#endif + default: + return -EAFNOSUPPORT; + } + /* Check if AF-Family matches internally used AF-Family */ + if (tcb->address_family != address_family) { + return -EINVAL; + } + } + return _gnrc_tcp_open(tcb, NULL, 0, local_addr, local_port, 1); +} + +ssize_t gnrc_tcp_send(gnrc_tcp_tcb_t *tcb, const void *data, const size_t len, + const uint32_t timeout_duration_us) +{ + assert(tcb != NULL); + assert(data != NULL); + + msg_t msg; /* Message for incomming Messages */ + msg_t connection_timeout_msg; /* Connection Timeout Message */ + msg_t probe_timeout_msg; /* Probe Timeout Message */ + msg_t user_timeout_msg; /* User Specified Timeout Message */ + xtimer_t connection_timeout_timer; /* Connection Timeout Timer */ + xtimer_t probe_timeout_timer; /* Probe Timeout Timer */ + xtimer_t user_timeout_timer; /* User Specified Timeout Timer */ + uint32_t probe_timeout_duration_us = 0; /* Probe Timeout Duration in microseconds */ + ssize_t ret = 0; /* Return Value */ + bool probing = false; /* True if this connection is probing */ + + /* Lock the tcb for this function call */ + mutex_lock(&(tcb->function_lock)); + + /* Check if connection is in a valid state */ + if (tcb->state != GNRC_TCP_FSM_STATE_ESTABLISHED + && tcb->state != GNRC_TCP_FSM_STATE_CLOSE_WAIT + ) { + mutex_unlock(&(tcb->function_lock)); + return -ENOTCONN; + } + + /* Re-init message queue, take ownership. FSM can send Messages to this thread now */ + msg_init_queue(tcb->msg_queue, GNRC_TCP_MSG_QUEUE_SIZE); + tcb->owner = thread_getpid(); + + /* Setup Connection Timeout */ + connection_timeout_msg.type = MSG_TYPE_CONNECTION_TIMEOUT; + xtimer_set_msg(&connection_timeout_timer, GNRC_TCP_CONNECTION_TIMEOUT_DURATION, + &connection_timeout_msg, tcb->owner); + + /* Setup User specified timeout if timeout_us is greater than zero */ + if (timeout_duration_us > 0) { + user_timeout_msg.type = MSG_TYPE_USER_SPEC_TIMEOUT; + xtimer_set_msg(&user_timeout_timer, timeout_duration_us, &user_timeout_msg, tcb->owner); + } + + /* Loop until something was sent and acked */ + while (ret == 0 || tcb->pkt_retransmit != NULL) { + /* Check if the connections state is closed. If so, a reset was received */ + if (tcb->state == GNRC_TCP_FSM_STATE_CLOSED) { + ret = -ECONNRESET; + break; + } + + /* If the send window is closed: Setup Probing */ + if (tcb->snd_wnd <= 0) { + /* If this is the first probe: Setup probing duration */ + if (!probing) { + probing = true; + probe_timeout_duration_us = tcb->rto; + } + /* Initialize Probe Timer */ + probe_timeout_msg.type = MSG_TYPE_PROBE_TIMEOUT; + xtimer_set_msg(&probe_timeout_timer, probe_timeout_duration_us, &probe_timeout_msg, + tcb->owner); + } + + /* Try to send data in case there nothing has been sent and we are not probing */ + if (ret == 0 && !probing) { + ret = _fsm(tcb, GNRC_TCP_FSM_EVENT_CALL_SEND, NULL, (void *) data, len); + } + + /* Wait for responses */ + msg_receive(&msg); + switch (msg.type) { + case MSG_TYPE_CONNECTION_TIMEOUT: + DEBUG("gnrc_tcp.c : gnrc_tcp_send() : CONNECTION_TIMEOUT\n"); + _fsm(tcb, GNRC_TCP_FSM_EVENT_TIMEOUT_CONNECTION, NULL, NULL, 0); + ret = -ECONNABORTED; + break; + + case MSG_TYPE_USER_SPEC_TIMEOUT: + DEBUG("gnrc_tcp.c : gnrc_tcp_send() : USER_SPEC_TIMEOUT\n"); + _fsm(tcb, GNRC_TCP_FSM_EVENT_CLEAR_RETRANSMIT, NULL, NULL, 0); + ret = -ETIMEDOUT; + break; + + case MSG_TYPE_PROBE_TIMEOUT: + DEBUG("gnrc_tcp.c : gnrc_tcp_send() : PROBE_TIMEOUT\n"); + /* Send Probe */ + _fsm(tcb, GNRC_TCP_FSM_EVENT_SEND_PROBE, NULL, NULL, 0); + probe_timeout_duration_us += probe_timeout_duration_us; + + /* Boundry check for time interval between probes */ + if (probe_timeout_duration_us < GNRC_TCP_PROBE_LOWER_BOUND) { + probe_timeout_duration_us = GNRC_TCP_PROBE_LOWER_BOUND; + } + else if (probe_timeout_duration_us > GNRC_TCP_PROBE_UPPER_BOUND) { + probe_timeout_duration_us = GNRC_TCP_PROBE_UPPER_BOUND; + } + break; + + case MSG_TYPE_NOTIFY_USER: + DEBUG("gnrc_tcp.c : gnrc_tcp_send() : NOTIFY_USER\n"); + /* Connection is alive: Reset Connection Timeout */ + xtimer_set_msg(&connection_timeout_timer, GNRC_TCP_CONNECTION_TIMEOUT_DURATION, + &connection_timeout_msg, tcb->owner); + + /* If the window re-opened and we are probing: Stop it */ + if (tcb->snd_wnd > 0 && probing) { + probing = false; + xtimer_remove(&probe_timeout_timer); + } + break; + + default: + DEBUG("gnrc_tcp.c : gnrc_tcp_send() : other message type\n"); + } + } + + /* Cleanup */ + xtimer_remove(&probe_timeout_timer); + xtimer_remove(&connection_timeout_timer); + xtimer_remove(&user_timeout_timer); + tcb->owner = KERNEL_PID_UNDEF; + mutex_unlock(&(tcb->function_lock)); + return ret; +} + +ssize_t gnrc_tcp_recv(gnrc_tcp_tcb_t *tcb, void *data, const size_t max_len, + const uint32_t timeout_duration_us) +{ + assert(tcb != NULL); + assert(data != NULL); + + msg_t msg; /* Message for incomming Messages */ + msg_t connection_timeout_msg; /* Connection Timeout Message */ + msg_t user_timeout_msg; /* User Specified Timeout Message */ + xtimer_t connection_timeout_timer; /* Connection Timeout Timer */ + xtimer_t user_timeout_timer; /* User Specified Timeout Timer */ + ssize_t ret = 0; /* Return Value */ + + /* Lock the tcb for this function call */ + mutex_lock(&(tcb->function_lock)); + + /* Check if connection is in a valid state */ + if (tcb->state != GNRC_TCP_FSM_STATE_ESTABLISHED + && tcb->state != GNRC_TCP_FSM_STATE_FIN_WAIT_1 + && tcb->state != GNRC_TCP_FSM_STATE_FIN_WAIT_2 + && tcb->state != GNRC_TCP_FSM_STATE_CLOSE_WAIT + ) { + mutex_unlock(&(tcb->function_lock)); + return -ENOTCONN; + } + + /* If this call is non-blocking (timeout_duration_us == 0): Try to read data and return */ + if (timeout_duration_us == 0) { + ret = _fsm(tcb, GNRC_TCP_FSM_EVENT_CALL_RECV, NULL, data, max_len); + if(ret == 0) { + ret = -EAGAIN; + } + mutex_unlock(&(tcb->function_lock)); + return ret; + } + + /* If this call is blocking, setup messages and timers */ + msg_init_queue(tcb->msg_queue, GNRC_TCP_MSG_QUEUE_SIZE); + tcb->owner = thread_getpid(); + + /* Setup Connection Timeout */ + connection_timeout_msg.type = MSG_TYPE_CONNECTION_TIMEOUT; + xtimer_set_msg(&connection_timeout_timer, GNRC_TCP_CONNECTION_TIMEOUT_DURATION, + &connection_timeout_msg, tcb->owner); + + /* Setup User Specified Timeout */ + user_timeout_msg.type = MSG_TYPE_USER_SPEC_TIMEOUT; + xtimer_set_msg(&user_timeout_timer, timeout_duration_us, &user_timeout_msg, tcb->owner); + + /* Processing Loop */ + while (ret == 0) { + /* Check if the connections state is closed. If so, a reset was received */ + if (tcb->state == GNRC_TCP_FSM_STATE_CLOSED) { + ret = -ECONNRESET; + break; + } + + /* Try to read available data */ + ret = _fsm(tcb, GNRC_TCP_FSM_EVENT_CALL_RECV, NULL, data, max_len); + + /* If there was no data: Wait for next packet or until the timeout fires */ + if (ret <= 0) { + msg_receive(&msg); + switch (msg.type) { + case MSG_TYPE_CONNECTION_TIMEOUT: + DEBUG("gnrc_tcp.c : gnrc_tcp_recv() : CONNECTION_TIMEOUT\n"); + _fsm(tcb, GNRC_TCP_FSM_EVENT_TIMEOUT_CONNECTION, NULL, NULL, 0); + ret = -ECONNABORTED; + break; + + case MSG_TYPE_USER_SPEC_TIMEOUT: + DEBUG("gnrc_tcp.c : gnrc_tcp_send() : USER_SPEC_TIMEOUT\n"); + _fsm(tcb, GNRC_TCP_FSM_EVENT_CLEAR_RETRANSMIT, NULL, NULL, 0); + ret = -ETIMEDOUT; + break; + + case MSG_TYPE_NOTIFY_USER: + DEBUG("gnrc_tcp.c : gnrc_tcp_recv() : NOTIFY_USER\n"); + break; + + default: + DEBUG("gnrc_tcp.c : gnrc_tcp_recv() : other message type\n"); + } + } + } + + /* Cleanup */ + xtimer_remove(&connection_timeout_timer); + xtimer_remove(&user_timeout_timer); + tcb->owner = KERNEL_PID_UNDEF; + mutex_unlock(&(tcb->function_lock)); + return ret; +} + +int gnrc_tcp_close(gnrc_tcp_tcb_t *tcb) +{ + assert(tcb != NULL); + + msg_t msg; /* Message for incomming Messages */ + msg_t connection_timeout_msg; /* Connection Timeout Message */ + xtimer_t connection_timeout_timer; /* Connection Timeout Timer */ + + /* Lock the tcb for this function call */ + mutex_lock(&(tcb->function_lock)); + + /* Start connection teardown if the connection was not closed before */ + if (tcb->state != GNRC_TCP_FSM_STATE_CLOSED) { + /* Take ownership */ + msg_init_queue(tcb->msg_queue, GNRC_TCP_MSG_QUEUE_SIZE); + tcb->owner = thread_getpid(); + + /* Setup Connection Timeout */ + connection_timeout_msg.type = MSG_TYPE_CONNECTION_TIMEOUT; + xtimer_set_msg(&connection_timeout_timer, GNRC_TCP_CONNECTION_TIMEOUT_DURATION, + &connection_timeout_msg, tcb->owner); + + /* Start connection teardown sequence */ + _fsm(tcb, GNRC_TCP_FSM_EVENT_CALL_CLOSE, NULL, NULL, 0); + + /* Loop until the connection has been closed */ + while (tcb->state != GNRC_TCP_FSM_STATE_CLOSED) { + msg_receive(&msg); + switch (msg.type) { + case MSG_TYPE_CONNECTION_TIMEOUT: + DEBUG("gnrc_tcp.c : gnrc_tcp_close() : CONNECTION_TIMEOUT\n"); + _fsm(tcb, GNRC_TCP_FSM_EVENT_TIMEOUT_CONNECTION, NULL, NULL, 0); + break; + + case MSG_TYPE_NOTIFY_USER: + DEBUG("gnrc_tcp.c : gnrc_tcp_close() : NOTIFY_USER\n"); + break; + + default: + DEBUG("gnrc_tcp.c : gnrc_tcp_close() : other message type\n"); + } + } + } + + /* Cleanup */ + xtimer_remove(&connection_timeout_timer); + tcb->owner = KERNEL_PID_UNDEF; + mutex_unlock(&(tcb->function_lock)); + return 0; +} + +int gnrc_tcp_calc_csum(const gnrc_pktsnip_t *hdr, const gnrc_pktsnip_t *pseudo_hdr) +{ + uint16_t csum; + + if ((hdr == NULL) || (pseudo_hdr == NULL)) { + return -EFAULT; + } + if (hdr->type != GNRC_NETTYPE_TCP) { + return -EBADMSG; + } + + csum = _pkt_calc_csum(hdr, pseudo_hdr, hdr->next); + if (csum == 0) { + return -ENOENT; + } + ((tcp_hdr_t *)hdr->data)->checksum = byteorder_htons(csum); + return 0; +} + +gnrc_pktsnip_t *gnrc_tcp_hdr_build(gnrc_pktsnip_t *payload, uint16_t src, uint16_t dst) +{ + gnrc_pktsnip_t *res; + tcp_hdr_t *hdr; + + /* allocate header */ + res = gnrc_pktbuf_add(payload, NULL, sizeof(tcp_hdr_t), GNRC_NETTYPE_TCP); + if (res == NULL) { + DEBUG("tcp: No space left in packet buffer\n"); + return NULL; + } + hdr = (tcp_hdr_t *) res->data; + + /* Clear Header */ + memset(hdr, 0, sizeof(tcp_hdr_t)); + + /* Initialize Header with sane Defaults */ + hdr->src_port = byteorder_htons(src); + hdr->dst_port = byteorder_htons(dst); + hdr->checksum = byteorder_htons(0); + hdr->off_ctl = byteorder_htons(OPTION_OFFSET_MAX); + return res; +} diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_eventloop.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_eventloop.c new file mode 100644 index 0000000000000000000000000000000000000000..17cb911f523d0efa6f1b8451d4703e54a503a84c --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_eventloop.c @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @ingroup net_gnrc + * @{ + * + * @file + * @brief GNRC's TCP event processing loop + * + * @author Simon Brummer <brummer.simon@googlemail.com> + * @} + */ + +#include <utlist.h> +#include <errno.h> +#include "net/af.h" +#include "net/gnrc/pkt.h" +#include "net/gnrc/tcp.h" +#include "net/gnrc/tcp/hdr.h" +#include "internal/pkt.h" +#include "internal/fsm.h" +#include "internal/helper.h" +#include "internal/option.h" +#include "internal/eventloop.h" + +#ifdef MODULE_GNRC_IPV6 +#include "net/gnrc/ipv6.h" +#endif + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/** + * @brief send function, used to pass paket down the network stack + * + * @param[in] pkt paket to pass down the network stack + * + * @return zero on success + * @return negative value on error + * @return -EBADMSG if tcp header is missing + */ +static int _send(gnrc_pktsnip_t *pkt) +{ + /* NOTE: Sending Direction: pkt = nw, nw->next = tcp, tcp->next = payload */ + gnrc_pktsnip_t *tcp; + + /* Search for tcp header */ + LL_SEARCH_SCALAR(pkt, tcp, type, GNRC_NETTYPE_TCP); + if (tcp == NULL) { + DEBUG("gnrc_tcp_eventloop : _send() : tcp header missing.\n"); + gnrc_pktbuf_release(pkt); + return -EBADMSG; + } + + /* Dispatch to network layer */ + if (!gnrc_netapi_dispatch_send(pkt->type, GNRC_NETREG_DEMUX_CTX_ALL, pkt)) { + DEBUG("gnrc_tcp_eventloop : _send() : network layer not found\n"); + gnrc_pktbuf_release(pkt); + } + return 0; +} + +/** + * @brief recv function, used to call fsm on packet reception + * + * @param[in] pkt incomming paket to process + * + * @return zero on success + * @return negative value on error + * @return -EACCES if not able to get write access to packet + * @return -ERANGE if segment offset is less than 5 + * @return -ENOMSG if paket can't be marked + * @return -EINVAL if checksum was invalid + * @return -ENOTCONN if no module is interested in this context + */ +static int _receive(gnrc_pktsnip_t *pkt) +{ + /* NOTE: Receiving direction: pkt = payload, payload->next = tcp, tcp->next = nw */ + uint16_t ctl = 0; + uint16_t src = 0; + uint16_t dst = 0; + uint8_t hdr_size = 0; + uint8_t syn = 0; + gnrc_pktsnip_t *ip = NULL; + gnrc_pktsnip_t *reset = NULL; + gnrc_tcp_tcb_t *tcb = NULL; + tcp_hdr_t *hdr; + + /* Get write access to the TCP Header */ + gnrc_pktsnip_t *tcp = gnrc_pktbuf_start_write(pkt); + if (tcp == NULL) { + DEBUG("gnrc_tcp_eventloop.c : _receive() : can't write to packet\n"); + gnrc_pktbuf_release(pkt); + return -EACCES; + } + pkt = tcp; + +#ifdef MODULE_GNRC_IPV6 + /* Get IP Header, discard packet if doesn't contain an ip header */ + LL_SEARCH_SCALAR(pkt, ip, type, GNRC_NETTYPE_IPV6); + if (ip == NULL) { + DEBUG("gnrc_tcp_eventloop.c : _receive() : pkt contains no IP Header\n"); + gnrc_pktbuf_release(pkt); + return 0; + } +#endif + + /* Get TCP Header */ + LL_SEARCH_SCALAR(pkt, tcp, type, GNRC_NETTYPE_TCP); + if (tcp == NULL) { + DEBUG("gnrc_tcp_eventloop.c : _receive() : pkt contains no TCP Header\n"); + gnrc_pktbuf_release(pkt); + return 0; + } + + /* Extract control bits, src and dst ports and check if SYN is set (not SYN+ACK) */ + hdr = (tcp_hdr_t *)tcp->data; + ctl = byteorder_ntohs(hdr->off_ctl); + src = byteorder_ntohs(hdr->src_port); + dst = byteorder_ntohs(hdr->dst_port); + syn = ((ctl & MSK_SYN_ACK) == MSK_SYN); + + /* Validate Offset */ + if (GET_OFFSET(ctl) < OPTION_OFFSET_BASE) { + DEBUG("gnrc_tcp_eventloop.c : _receive() : unexpected Offset Value\n"); + gnrc_pktbuf_release(pkt); + return -ERANGE; + } + + /* Calculate tcp header size */ + hdr_size = GET_OFFSET(ctl) * 4; + + /* Mark TCP-Header, if it contains any payload */ + if ((pkt->type == GNRC_NETTYPE_TCP) && (pkt->size != hdr_size)) { + tcp = gnrc_pktbuf_mark(pkt, hdr_size, GNRC_NETTYPE_TCP); + if (tcp == NULL) { + DEBUG("gnrc_tcp_eventloop.c : _receive() : Header marking failed\n"); + gnrc_pktbuf_release(pkt); + return -ENOMSG; + } + pkt->type = GNRC_NETTYPE_UNDEF; + } + + /* Validate Checksum */ + if (byteorder_ntohs(hdr->checksum) != _pkt_calc_csum(tcp, ip, pkt)) { + DEBUG("gnrc_tcp_eventloop.c : _receive() : Invalid checksum\n"); + gnrc_pktbuf_release(pkt); + return -EINVAL; + } + + /* Find tcb to de-multiplex this packet to */ + mutex_lock(&_list_gnrc_tcp_tcb_lock); + tcb = _list_gnrc_tcp_tcb_head; + while (tcb) { +#ifdef MODULE_GNRC_IPV6 + /* Check if current tcb is fitting for the incomming packet */ + if (ip->type == GNRC_NETTYPE_IPV6 && tcb->address_family == AF_INET6) { + /* If SYN is set, a connection is listening on that port ... */ + ipv6_addr_t * tmp_addr = NULL; + if (syn && tcb->local_port == dst && tcb->state == GNRC_TCP_FSM_STATE_LISTEN) { + /* ... and local addr is unspec or preconfigured */ + tmp_addr = &((ipv6_hdr_t * )ip->data)->dst; + if (ipv6_addr_equal((ipv6_addr_t *) tcb->local_addr, (ipv6_addr_t *) tmp_addr) + || ipv6_addr_is_unspecified((ipv6_addr_t *) tcb->local_addr) + ) { + break; + } + } + + /* If SYN is not set and the ports match ... */ + if (!syn && tcb->local_port == dst && tcb->peer_port == src) { + /* .. and the IP-Addresses match */ + tmp_addr = &((ipv6_hdr_t * )ip->data)->src; + if (ipv6_addr_equal((ipv6_addr_t *) tcb->peer_addr, (ipv6_addr_t *) tmp_addr)) { + break; + } + } + } +#else + /* Supress compiler warnings if TCP is build without IP-Layer */ + (void) syn; + (void) src; + (void) dst; +#endif + tcb = tcb->next; + } + mutex_unlock(&_list_gnrc_tcp_tcb_lock); + + /* Call FSM with event RCVD_PKT if a fitting connection was found */ + if (tcb != NULL) { + _fsm(tcb, GNRC_TCP_FSM_EVENT_RCVD_PKT, pkt, NULL, 0); + } + /* No fitting connection has been found. Respond with reset */ + else { + DEBUG("gnrc_tcp_eventloop.c : _receive() : Can't find fitting connection\n"); + if ((ctl & MSK_RST) != MSK_RST) { + _pkt_build_reset_from_pkt(&reset, pkt); + gnrc_netapi_send(_gnrc_tcp_pid, reset); + } + return -ENOTCONN; + } + gnrc_pktbuf_release(pkt); + return 0; +} + +void *_event_loop(__attribute__((unused)) void *arg) +{ + msg_t msg; + msg_t reply; + msg_t msg_queue[GNRC_TCP_MSG_QUEUE_SIZE]; + + /* Store pid */ + _gnrc_tcp_pid = thread_getpid(); + + /* Setup reply message */ + reply.type = GNRC_NETAPI_MSG_TYPE_ACK; + reply.content.value = (uint32_t)-ENOTSUP; + + /* Init message queue*/ + msg_init_queue(msg_queue, GNRC_TCP_MSG_QUEUE_SIZE); + + /* Register GNRC_tcp in netreg */ + gnrc_netreg_entry_t entry; + gnrc_netreg_entry_init_pid(&entry, GNRC_NETREG_DEMUX_CTX_ALL, _gnrc_tcp_pid); + gnrc_netreg_register(GNRC_NETTYPE_TCP, &entry); + + /* dispatch NETAPI Messages */ + while (1) { + msg_receive(&msg); + switch (msg.type) { + /* Pass Message up the network stack */ + case GNRC_NETAPI_MSG_TYPE_RCV: + DEBUG("gnrc_tcp_eventloop.c : _event_loop() : GNRC_NETAPI_MSG_TYPE_RCV\n"); + _receive((gnrc_pktsnip_t *)msg.content.ptr); + break; + + /* Pass Message down the network stack */ + case GNRC_NETAPI_MSG_TYPE_SND: + DEBUG("gnrc_tcp_eventloop.c : _event_loop() : GNRC_NETAPI_MSG_TYPE_SND\n"); + _send((gnrc_pktsnip_t *)msg.content.ptr); + break; + + /* Option Set and Get Messages*/ + case GNRC_NETAPI_MSG_TYPE_SET: + case GNRC_NETAPI_MSG_TYPE_GET: + msg_reply(&msg, &reply); + break; + + /* Retransmission Timer expired -> Call FSM with retransmission event */ + case MSG_TYPE_RETRANSMISSION: + DEBUG("gnrc_tcp_eventloop.c : _event_loop() : MSG_TYPE_RETRANSMISSION\n"); + _fsm((gnrc_tcp_tcb_t *)msg.content.ptr, GNRC_TCP_FSM_EVENT_TIMEOUT_RETRANSMIT, + NULL, NULL, 0); + break; + + /* Time Wait Timer expired -> Call FSM with timewait event */ + case MSG_TYPE_TIMEWAIT: + DEBUG("gnrc_tcp_eventloop.c : _event_loop() : MSG_TYPE_TIMEWAIT\n"); + _fsm((gnrc_tcp_tcb_t *)msg.content.ptr, GNRC_TCP_FSM_EVENT_TIMEOUT_TIMEWAIT, + NULL, NULL, 0); + break; + + default: + DEBUG("gnrc_tcp_eventloop.c : _event_loop() : received expected message\n"); + } + } + /* never reached */ + return NULL; +} diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_fsm.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_fsm.c new file mode 100644 index 0000000000000000000000000000000000000000..c9007889cf6daacb674ee609f4e8e651ab46880a --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_fsm.c @@ -0,0 +1,934 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @ingroup net_gnrc + * @{ + * + * @file + * @brief GNRC's TCP finite state maschine + * + * @author Simon Brummer <brummer.simon@googlemail.com> + * @} + */ + +#include "msg.h" +#include "random.h" +#include "ringbuffer.h" +#include "net/af.h" + +#include "internal/fsm.h" +#include "internal/pkt.h" +#include "internal/option.h" +#include "internal/helper.h" +#include "internal/rcvbuf.h" + +#ifdef MODULE_GNRC_IPV6 +#include "net/gnrc/ipv6.h" +#endif + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/** + * @brief Checks if a given portnumber is currently used by a tcb as local_port. + * + * @param[in] portnumber Portnumber that should be checked + * + * @note Must be called from a context where the tcb list ist locked. + * + * @return Zero if @p portnumber is currently not used. + * @return 1 if @p portnumber is used by an tcb. + */ +static int _is_local_port_in_use(const uint16_t portnumber) +{ + gnrc_tcp_tcb_t *iter = NULL; + LL_FOREACH(_list_gnrc_tcp_tcb_head, iter) { + if (iter->local_port == portnumber) { + return 1; + } + } + return 0; +} + +/** + * @brief Generate random, currently unused local port above the well-known ports (> 1024) + * + * @return The generated port number + */ +static uint16_t _get_random_local_port(void) +{ + uint16_t ret = 0; + do { + ret = random_uint32(); + if (ret < 1024) { + continue; + } + } while(_is_local_port_in_use(ret)); + return ret; +} + +/** + * @brief clears retransmit queue + * + * @param[in/out] conn TCP Connection, where the retransmit should be cleared + * + * @return zero on success + */ +static int _clear_retransmit(gnrc_tcp_tcb_t *tcb) +{ + if (tcb->pkt_retransmit != NULL) { + gnrc_pktbuf_release(tcb->pkt_retransmit); + xtimer_remove(&(tcb->tim_tout)); + tcb->pkt_retransmit = NULL; + } + return 0; +} + +/** + * @brief restarts time wait timer + * + * @param[in/out] conn TCP Connection, where the timewait_timer should be restarted + * + * @return Zero on success + */ +static int _restart_timewait_timer(gnrc_tcp_tcb_t* tcb) +{ + xtimer_remove(&tcb->tim_tout); + tcb->msg_tout.type = MSG_TYPE_TIMEWAIT; + tcb->msg_tout.content.ptr = (void *)tcb; + xtimer_set_msg(&tcb->tim_tout, 2 * GNRC_TCP_MSL, &tcb->msg_tout, _gnrc_tcp_pid); + return 0; +} + +/** + * @brief translates fsm into another state + * + * @param[in/out] tcb tcb, that specifies connection + * @param[in] state state to translate in + * @param[out] notify_owner non-negative if the tcb owner should be notified + * + * @return zero on success + */ +static int _transition_to(gnrc_tcp_tcb_t* tcb, gnrc_tcp_fsm_state_t state, bool *notify_owner) +{ + gnrc_tcp_tcb_t *iter = NULL; + uint8_t found = 0; + + switch (state) { + case GNRC_TCP_FSM_STATE_CLOSED: + /* Free Packets in Retransmit queue */ + _clear_retransmit(tcb); + + /* Remove from Connection from active connections */ + mutex_lock(&_list_gnrc_tcp_tcb_lock); + LL_FOREACH(_list_gnrc_tcp_tcb_head, iter) { + if (iter == tcb) { + found = 1; + } + } + if (found) { + LL_DELETE(_list_gnrc_tcp_tcb_head, iter); + } + mutex_unlock(&_list_gnrc_tcp_tcb_lock); + + /* Free potencially allocated Receive Buffer */ + _rcvbuf_release_buffer(tcb); + *notify_owner = true; + break; + + case GNRC_TCP_FSM_STATE_LISTEN: + /* Clear Adress Info */ + switch (tcb->address_family) { +#ifdef MODULE_GNRC_IPV6 + case AF_INET6: + if (tcb->status & GNRC_TCP_STATUS_ALLOW_ANY_ADDR) { + ipv6_addr_set_unspecified((ipv6_addr_t *) tcb->local_addr); + } + ipv6_addr_set_unspecified((ipv6_addr_t *) tcb->peer_addr); + break; +#endif + default: + DEBUG("gnrc_tcp_fsm.c : _transition_to() : Undefined Addresses\n"); + break; + } + tcb->peer_port = GNRC_TCP_PORT_UNSPEC; + + /* Allocate rcv Buffer */ + if (_rcvbuf_get_buffer(tcb) == -ENOMEM) { + return -ENOMEM; + } + + /* Add to Connection to active connections (if not already active) */ + mutex_lock(&_list_gnrc_tcp_tcb_lock); + LL_FOREACH(_list_gnrc_tcp_tcb_head, iter) { + if (iter == tcb) { + found = 1; + } + } + if (!found) { + LL_APPEND(_list_gnrc_tcp_tcb_head, tcb); + } + mutex_unlock(&_list_gnrc_tcp_tcb_lock); + break; + + case GNRC_TCP_FSM_STATE_SYN_SENT: + /* Allocate rcv Buffer */ + if (_rcvbuf_get_buffer(tcb) == -ENOMEM) { + return -ENOMEM; + } + + /* Add to Connections to active connection (if not already active) */ + mutex_lock(&_list_gnrc_tcp_tcb_lock); + LL_FOREACH(_list_gnrc_tcp_tcb_head, iter) { + if (iter == tcb) { + found = 1; + } + } + /* If not already active: Apped tcb but check portnumber first */ + if (!found) { + /* Check if Port Number is not in use */ + if (tcb->local_port != GNRC_TCP_PORT_UNSPEC ) { + + /* If Portnumber is used: return error and release buffer */ + if (_is_local_port_in_use(tcb->local_port)) { + mutex_unlock(&_list_gnrc_tcp_tcb_lock); + _rcvbuf_release_buffer(tcb); + return -EADDRINUSE; + } + } + /* Pick Random Port */ + else { + tcb->local_port = _get_random_local_port(); + } + LL_APPEND(_list_gnrc_tcp_tcb_head, tcb); + } + mutex_unlock(&_list_gnrc_tcp_tcb_lock); + break; + + case GNRC_TCP_FSM_STATE_ESTABLISHED: + *notify_owner = true; + break; + + case GNRC_TCP_FSM_STATE_CLOSE_WAIT: + *notify_owner = true; + break; + + case GNRC_TCP_FSM_STATE_TIME_WAIT: + _restart_timewait_timer(tcb); + break; + + default: + break; + } + tcb->state = state; + return 0; +} + +/** + * @brief FSM Handling Function for active and passive open + * + * @param[in/out] tcb Specifies tcb to use fsm on. + * @param[out] notify_owner non-negative if the tcb owner should be notified + * + * @return zero on success + * @return -ENOMEM Can't allocate receive buffer. + * @return -EADDRINUSE Given local port is already in use + */ +static int _fsm_call_open(gnrc_tcp_tcb_t* tcb, bool *notify_owner) +{ + gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ + uint16_t seq_con = 0; /* Sequence number consumption (out_pkt) */ + int ret = 0; /* Return value */ + + DEBUG("gnrc_tcp_fsm.c : _fsm_call_open()\n"); + tcb->rcv_wnd = GNRC_TCP_DEFAULT_WINDOW; + + if (tcb->status & GNRC_TCP_STATUS_PASSIVE) { + /* Passive Open, T: CLOSED -> LISTEN */ + if (_transition_to(tcb, GNRC_TCP_FSM_STATE_LISTEN, notify_owner) == -ENOMEM){ + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSED, notify_owner); + return -ENOMEM; + } + } + else { + /* Active Open, init tcb values, send SYN, T: CLOSED -> SYN_SENT */ + tcb->iss = random_uint32(); + tcb->snd_nxt = tcb->iss; + tcb->snd_una = tcb->iss; + + /* Translate to SYN_SENT */ + ret = _transition_to(tcb, GNRC_TCP_FSM_STATE_SYN_SENT, notify_owner); + if ( ret < 0) { + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSED, notify_owner); + return ret; + } + + /* Send SYN */ + _pkt_build(tcb, &out_pkt, &seq_con, MSK_SYN, tcb->iss, 0, NULL, 0); + _pkt_setup_retransmit(tcb, out_pkt, false); + _pkt_send(tcb, out_pkt, seq_con, false); + } + return ret; +} + +/** + * @brief FSM Handling Function for sending data. + * + * @param[in/out] tcb Specifies tcb to use fsm on. + * @param[in/out] buf buffer containing data to send. + * @param[in] nByte Maximum Number of Bytes to send. + * + * @return number of bytes that was sent. + */ +static int _fsm_call_send(gnrc_tcp_tcb_t* tcb, void *buf, size_t nByte) +{ + gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ + uint16_t seq_con = 0; /* Sequence number consumption (out_pkt) */ + + DEBUG("gnrc_tcp_fsm.c : _fsm_call_send()\n"); + size_t payload = (tcb->snd_una + tcb->snd_wnd) - tcb->snd_nxt; + + /* We are allowed to send further bytes if window is open */ + if (payload > 0 && tcb->snd_wnd > 0 && tcb->pkt_retransmit == NULL) { + /* Calculate segment size */ + payload = (payload < GNRC_TCP_MSS ? payload : GNRC_TCP_MSS); + payload = (payload < tcb->mss) ? payload : tcb->mss; + payload = (payload < nByte) ? payload : nByte; + + /* Calculate payload size for this segment */ + _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, buf, payload); + _pkt_setup_retransmit(tcb, out_pkt, false); + _pkt_send(tcb, out_pkt, seq_con, false); + return payload; + } + return 0; +} + +/** + * @brief FSM Handling Function for receiving data. + * + * @param[in/out] tcb Specifies tcb to use fsm on. + * @param[in/out] buf buffer to store received data into. + * @param[in] nByte Maximum Number of Bytes to receive. + * + * @return number of bytes that was received. + */ +static int _fsm_call_recv(gnrc_tcp_tcb_t* tcb, void *buf, size_t nByte) +{ + gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ + uint16_t seq_con = 0; /* Sequence number consumption (out_pkt) */ + + DEBUG("gnrc_tcp_fsm.c : _fsm_call_recv()\n"); + if (ringbuffer_empty(&tcb->rcv_buf)) { + return 0; + } + + /* Read up to the requesed amount of data */ + size_t rcvd = ringbuffer_get(&(tcb->rcv_buf), buf, nByte); + + /* If the buffer can store more than the GNRC_TCP_MSS: open Window to available buffersize */ + if (ringbuffer_get_free(&tcb->rcv_buf) >= GNRC_TCP_MSS) { + tcb->rcv_wnd = ringbuffer_get_free(&(tcb->rcv_buf)); + + /* Send ACK to update window on reopening */ + _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, 0, 0); + _pkt_send(tcb, out_pkt, seq_con, false); + } + return rcvd; +} + +/** + * @brief FSM Handling Function for initiating a teardown. + * + * @param[in/out] tcb Specifies tcb to use fsm on. + * @param[out] notify_owner non-negative if the tcb owner should be notified + * + * @return zero on success. + */ +static int _fsm_call_close(gnrc_tcp_tcb_t* tcb, bool *notify_owner) +{ + gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ + uint16_t seq_con = 0; /* Sequence number consumption (out_pkt) */ + + DEBUG("gnrc_tcp_fsm.c : _fsm_call_close()\n"); + if (tcb->state == GNRC_TCP_FSM_STATE_SYN_RCVD + || tcb->state == GNRC_TCP_FSM_STATE_ESTABLISHED + || tcb->state == GNRC_TCP_FSM_STATE_CLOSE_WAIT + ) { + /* Send FIN packet */ + _pkt_build(tcb, &out_pkt, &seq_con, MSK_FIN_ACK, tcb->snd_nxt, tcb->rcv_nxt, NULL, 0); + _pkt_setup_retransmit(tcb, out_pkt, false); + _pkt_send(tcb, out_pkt, seq_con, false); + } + switch (tcb->state) { + case GNRC_TCP_FSM_STATE_LISTEN: + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSED, notify_owner); + break; + + case GNRC_TCP_FSM_STATE_SYN_RCVD: + case GNRC_TCP_FSM_STATE_ESTABLISHED: + _transition_to(tcb, GNRC_TCP_FSM_STATE_FIN_WAIT_1, notify_owner); + break; + + case GNRC_TCP_FSM_STATE_CLOSE_WAIT: + _transition_to(tcb, GNRC_TCP_FSM_STATE_LAST_ACK, notify_owner); + break; + + default: + break; + } + return 0; +} + +/** + * @brief FSM Handling Function for forcefull teardown + * + * @return -EOPNOTSUPP, because function is currently not implemented + */ +static int _fsm_call_abort(void) +{ + DEBUG("gnrc_tcp_fsm.c : _fsm_call_abort()\n"); + DEBUG("gnrc_tcp_fsm.c : _fsm_call_abort() : ABORT not implemented\n"); + return -EOPNOTSUPP; +} + +/** + * @brief FSM Handling Function for processing of a received packet + * + * @param[in/out] tcb Specifies tcb to use fsm on. + * @param[in] in_pkt Packet that should be processed. + * @param[out] notify_owner non-negative if the tcb owner should be notified + * + * @return zero on success. + * @return -ENOMEM Can't allocate receive buffer. + */ +static int _fsm_rcvd_pkt(gnrc_tcp_tcb_t* tcb, gnrc_pktsnip_t *in_pkt, bool *notify_owner) +{ + gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ + uint16_t seq_con = 0; /* Sequence number consumption (out_pkt) */ + gnrc_pktsnip_t *snp = NULL; /* Temporary Packet Snip */ + gnrc_tcp_tcb_t *lst = NULL; /* Temporary tcb pointer */ + uint16_t ctl = 0; /* Received control bits */ + uint32_t seg_seq = 0; /* Received sequence number */ + uint32_t seg_ack = 0; /* Received acknowledgment number */ + uint32_t seg_len = 0; /* Segment length */ + uint32_t pay_len = 0; /* Payload length */ + uint32_t seg_wnd = 0; /* Segment window */ + + DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt()\n"); + /* Search TCP header. */ + LL_SEARCH_SCALAR(in_pkt, snp, type, GNRC_NETTYPE_TCP); + tcp_hdr_t *tcp_hdr = (tcp_hdr_t *) snp->data; + + /* Verify packet options, return if they were faulty */ + if (_option_parse(tcb, tcp_hdr) < 0) { + return 0; + } + + /* Extract header values */ + ctl = byteorder_ntohs(tcp_hdr->off_ctl); + seg_seq = byteorder_ntohl(tcp_hdr->seq_num); + seg_ack = byteorder_ntohl(tcp_hdr->ack_num); + seg_wnd = byteorder_ntohs(tcp_hdr->window); + + /* Extract IPv6-Header */ +#ifdef MODULE_GNRC_IPV6 + LL_SEARCH_SCALAR(in_pkt, snp, type, GNRC_NETTYPE_IPV6); + if (snp == NULL) { + DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt() : incomming packet had no ip header\n"); + return 0; + } + void *ip = snp->data; +#endif + + /* Handle state LISTEN */ + if (tcb->state == GNRC_TCP_FSM_STATE_LISTEN) { + /* 1) Check RST: if set, return */ + if (ctl & MSK_RST) { + return 0; + } + /* 2) Check ACK: if set, send reset with seq_no = ack_no, return */ + if (ctl & MSK_ACK) { + _pkt_build_reset_from_pkt(&out_pkt, in_pkt); + _pkt_send(tcb, out_pkt, 0, false); + return 0; + } + /* 3) Check SYN: Setup incoming connection*/ + if (ctl & MSK_SYN) { + uint16_t src = byteorder_ntohs(tcp_hdr->src_port); + uint16_t dst = byteorder_ntohs(tcp_hdr->dst_port); + + /* Check if SYN Request is handled by another connection */ + lst = _list_gnrc_tcp_tcb_head; + while (lst) { + /* Compare Portnumbers and Network Layer Adresses */ + /* Note: Packets without ip-header were discarded earlier */ + if (lst->local_port == dst && lst->peer_port == src) { +#ifdef MODULE_GNRC_IPV6 + if (snp->type == GNRC_NETTYPE_IPV6 && lst->address_family == AF_INET6) { + ipv6_addr_t *dst_addr = &((ipv6_hdr_t *)ip)->dst; + ipv6_addr_t *src_addr = &((ipv6_hdr_t *)ip)->src; + + if (ipv6_addr_equal((ipv6_addr_t *)lst->local_addr, dst_addr) + && ipv6_addr_equal((ipv6_addr_t *)lst->peer_addr, src_addr) + ) { + break; + } + } +#endif + } + lst = lst->next; + } + /* Return if connection is already handled (port and addresses match) */ + if (lst != NULL) { + DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt() : Connection already handled\n"); + return 0; + } + + /* SYN Request is valid, fill connection struct with connection information */ + /* Note: Packets without ipv6-header were discarded earlier */ +#ifdef MODULE_GNRC_IPV6 + if (snp->type == GNRC_NETTYPE_IPV6 && tcb->address_family == AF_INET6) { + memcpy(tcb->local_addr, &((ipv6_hdr_t *)ip)->dst, sizeof(ipv6_addr_t)); + memcpy(tcb->peer_addr, &((ipv6_hdr_t *)ip)->src, sizeof(ipv6_addr_t)); + } +#else + DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt() : Received Address was not stored\n"); + return 0; +#endif + + tcb->local_port = dst; + tcb->peer_port = src; + tcb->irs = byteorder_ntohl(tcp_hdr->seq_num); + tcb->rcv_nxt = tcb->irs + 1; + tcb->iss = random_uint32(); + tcb->snd_una = tcb->iss; + tcb->snd_nxt = tcb->iss; + tcb->snd_wnd = seg_wnd; + + /* Send SYN+ACK: seq_no = iss, ack_no = rcv_nxt, T: LISTEN -> SYN_RCVD */ + _pkt_build(tcb, &out_pkt, &seq_con, MSK_SYN_ACK, tcb->iss, tcb->rcv_nxt, NULL, 0); + _pkt_setup_retransmit(tcb, out_pkt, false); + _pkt_send(tcb, out_pkt, seq_con, false); + _transition_to(tcb, GNRC_TCP_FSM_STATE_SYN_RCVD, notify_owner); + } + return 0; + } + /* Handle state SYN_SENT */ + else if (tcb->state == GNRC_TCP_FSM_STATE_SYN_SENT) { + /* 1) Check ACK */ + if (ctl & MSK_ACK) { + /* If ACK is not acceptable ...*/ + if (seg_ack <= tcb->iss || seg_ack > tcb->snd_nxt) { + /* ... send Reset if RST is not set else return */ + if ((ctl & MSK_RST) != MSK_RST) { + _pkt_build(tcb, &out_pkt, &seq_con, MSK_RST, seg_ack, 0, NULL, 0); + _pkt_send(tcb, out_pkt, seq_con, false); + } + return 0; + } + } + /* 2) Check RST: If RST set ... */ + if (ctl & MSK_RST) { + /* ... and ACK: Translate to CLOSED, if not return */ + if (ctl & MSK_ACK) { + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSED, notify_owner); + } + return 0; + } + /* 3) Check SYN: Set TCB values accordingly */ + if (ctl & MSK_SYN) { + tcb->rcv_nxt = seg_seq + 1; + tcb->irs = seg_seq; + if (ctl & MSK_ACK) { + tcb->snd_una = seg_ack; + _pkt_acknowledge(tcb, seg_ack); + } + /* Set the local address accordingly */ + /* Note: Packets without ipv6-header were discarded earlier */ +#ifdef MODULE_GNRC_IPV6 + if (snp->type == GNRC_NETTYPE_IPV6 && tcb->address_family == AF_INET6) { + memcpy(tcb->local_addr, &((ipv6_hdr_t *)ip)->dst, sizeof(ipv6_addr_t)); + } +#else + DEBUG("gnrc_tcp_fsm.c : _fsm_rcvd_pkt() : Received Address was not stored\n"); + return 0; +#endif + + /* SYN has been ACKed, reply pure ACK, T: SYN_SENT -> ESTABLISHED */ + if (tcb->snd_una > tcb->iss) { + _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, + NULL, 0); + _pkt_send(tcb, out_pkt, seq_con, false); + _transition_to(tcb, GNRC_TCP_FSM_STATE_ESTABLISHED, notify_owner); + } + /* Simultaneous SYN received send SYN+ACK, T: SYN_SENT -> SYN_RCVD */ + else { + _pkt_build(tcb, &out_pkt, &seq_con, MSK_SYN_ACK, tcb->iss, tcb->rcv_nxt, + NULL, 0); + _pkt_setup_retransmit(tcb, out_pkt, false); + _pkt_send(tcb, out_pkt, seq_con, false); + _transition_to(tcb, GNRC_TCP_FSM_STATE_SYN_RCVD, notify_owner); + } + tcb->snd_wnd = seg_wnd; + tcb->snd_wl1 = seg_seq; + tcb->snd_wl2 = seg_ack; + } + return 0; + } + /* Handle other states */ + else { + seg_len = _pkt_get_seg_len(in_pkt); + pay_len = _pkt_get_pay_len(in_pkt); + /* 1) Verify Sequence Number ... */ + if (!_pkt_chk_seq_num(tcb, seg_seq, pay_len)) { + /* ... if invalid, and RST not set, reply with pure ACK, return */ + if ((ctl & MSK_RST) != MSK_RST) { + _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, + NULL, 0); + _pkt_send(tcb, out_pkt, seq_con, false); + } + return 0; + } + /* 2) Check RST: If RST is set ... */ + if (ctl & MSK_RST) { + /* .. and State is SYN_RCVD and passive Open: SYN_RCVD -> LISTEN */ + if (tcb->state == GNRC_TCP_FSM_STATE_SYN_RCVD + && (tcb->status & GNRC_TCP_STATUS_PASSIVE) + ) { + if (_transition_to(tcb, GNRC_TCP_FSM_STATE_LISTEN, notify_owner) == -ENOMEM) { + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSED, notify_owner); + return -ENOMEM; + } + } + else { + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSED, notify_owner); + } + return 0; + } + /* 3) Check SYN: If SYN is set ... */ + if (ctl & MSK_SYN) { + /* ... send RST, seq_no = snd_nxt, ack_no = rcv_nxt */ + _pkt_build(tcb, &out_pkt, &seq_con, MSK_RST, tcb->snd_nxt, tcb->rcv_nxt, NULL, 0); + _pkt_send(tcb, out_pkt, seq_con, false); + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSED, notify_owner); + return 0; + } + /* 4) Check ACK */ + if (!(ctl & MSK_ACK)) { + return 0; + } + else { + if (tcb->state == GNRC_TCP_FSM_STATE_SYN_RCVD) { + if (LSS_32_BIT(tcb->snd_una, seg_ack) && LEQ_32_BIT(seg_ack, tcb->snd_nxt)) { + tcb->snd_wnd = seg_wnd; + tcb->snd_wl1 = seg_seq; + tcb->snd_wl2 = seg_ack; + _transition_to(tcb, GNRC_TCP_FSM_STATE_ESTABLISHED, notify_owner); + } + else { + _pkt_build(tcb, &out_pkt, &seq_con, MSK_RST, seg_ack, 0, NULL, 0); + _pkt_send(tcb, out_pkt, seq_con, false); + } + } + /* Acknowledgment processing */ + if (tcb->state == GNRC_TCP_FSM_STATE_ESTABLISHED + || tcb->state == GNRC_TCP_FSM_STATE_FIN_WAIT_1 + || tcb->state == GNRC_TCP_FSM_STATE_FIN_WAIT_2 + || tcb->state == GNRC_TCP_FSM_STATE_CLOSE_WAIT + || tcb->state == GNRC_TCP_FSM_STATE_CLOSING + || tcb->state == GNRC_TCP_FSM_STATE_LAST_ACK + ) { + /* Sent data has been acknowledged */ + if (LSS_32_BIT(tcb->snd_una, seg_ack) && LEQ_32_BIT(seg_ack, tcb->snd_nxt)) { + tcb->snd_una = seg_ack; + _pkt_acknowledge(tcb, seg_ack); + } + /* ACK received for something not yet sent: Reply with pure ACK */ + else if (LSS_32_BIT(tcb->snd_nxt, seg_ack)) { + _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, + NULL, 0); + _pkt_send(tcb, out_pkt, seq_con, false); + return 0; + } + /* Update Window */ + if (LEQ_32_BIT(tcb->snd_una, seg_ack) && LEQ_32_BIT(seg_ack, tcb->snd_nxt)) { + if (LSS_32_BIT(tcb->snd_wl1, seg_seq) || (tcb->snd_wl1 == seg_seq + && LEQ_32_BIT(tcb->snd_wl2, seg_ack)) + ) { + tcb->snd_wnd = seg_wnd; + tcb->snd_wl1 = seg_seq; + tcb->snd_wl2 = seg_ack; + + /* Signal User after Window Update */ + *notify_owner = true; + } + } + /* Additional processing */ + /* Check additionaly if previous our sent FIN has been acknowledged */ + if (tcb->state == GNRC_TCP_FSM_STATE_FIN_WAIT_1) { + if (tcb->pkt_retransmit == NULL) { + _transition_to(tcb, GNRC_TCP_FSM_STATE_FIN_WAIT_2, notify_owner); + } + } + /* If retransmission queue is empty, acknowledge close operation */ + if (tcb->state == GNRC_TCP_FSM_STATE_FIN_WAIT_2) { + if (tcb->pkt_retransmit == NULL) { + /* Optional: Unblock user close operation */ + } + } + /* If our FIN has been acknowledged: Translate to TIME_WAIT */ + if (tcb->state == GNRC_TCP_FSM_STATE_CLOSING) { + if (tcb->pkt_retransmit == NULL) { + _transition_to(tcb, GNRC_TCP_FSM_STATE_TIME_WAIT, notify_owner); + } + } + /* If our FIN has been acknowledged: last ACK received, close connection */ + if (tcb->state == GNRC_TCP_FSM_STATE_LAST_ACK) { + if (tcb->pkt_retransmit == NULL) { + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSED, notify_owner); + return 0; + } + } + } + } + /* 5) Check URG */ + /* NOTE: Add Urgent Pointer Processing here ... */ + /* 6) Process Payload, if existing */ + if (pay_len > 0) { + /* Check if State is valid */ + if (tcb->state == GNRC_TCP_FSM_STATE_ESTABLISHED + || tcb->state == GNRC_TCP_FSM_STATE_FIN_WAIT_1 + || tcb->state == GNRC_TCP_FSM_STATE_FIN_WAIT_2 + ) { + /* Search for begin of payload "chain" */ + LL_SEARCH_SCALAR(in_pkt, snp, type, GNRC_NETTYPE_UNDEF); + + /* Add only Data that is expected, to be received */ + if (tcb->rcv_nxt == seg_seq) { + /* Copy contents in to buffer */ + while (snp && snp->type == GNRC_NETTYPE_UNDEF) { + tcb->rcv_nxt += ringbuffer_add(&(tcb->rcv_buf), snp->data, snp->size); + snp = snp->next; + } + /* Shrink Receive Window */ + tcb->rcv_wnd = ringbuffer_get_free(&(tcb->rcv_buf)); + /* Notify Owner because new data is available */ + *notify_owner = true; + } + /* Send pure ACK, if FIN doesn't this already */ + /* NOTE: this is the place to add piggybagging in the future */ + if (!(ctl & MSK_FIN)) { + _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, + NULL, 0); + _pkt_send(tcb, out_pkt, seq_con, false); + } + } + } + /* 7) Check FIN */ + if (ctl & MSK_FIN) { + if (tcb->state == GNRC_TCP_FSM_STATE_CLOSED + || tcb->state == GNRC_TCP_FSM_STATE_LISTEN + || tcb->state == GNRC_TCP_FSM_STATE_SYN_SENT + ) { + return 0; + } + /* Advance rcv_nxt over FIN bit. */ + tcb->rcv_nxt = seg_seq + seg_len; + _pkt_build(tcb, &out_pkt, &seq_con, MSK_ACK, tcb->snd_nxt, tcb->rcv_nxt, NULL, 0); + _pkt_send(tcb, out_pkt, seq_con, false); + + if (tcb->state == GNRC_TCP_FSM_STATE_SYN_RCVD + || tcb->state == GNRC_TCP_FSM_STATE_ESTABLISHED + ) { + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSE_WAIT, notify_owner); + } + else if (tcb->state == GNRC_TCP_FSM_STATE_FIN_WAIT_1) { + if (tcb->pkt_retransmit == NULL) { + _transition_to(tcb, GNRC_TCP_FSM_STATE_TIME_WAIT, notify_owner); + } + else { + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSING, notify_owner); + } + } + else if (tcb->state == GNRC_TCP_FSM_STATE_FIN_WAIT_2) { + _transition_to(tcb, GNRC_TCP_FSM_STATE_TIME_WAIT, notify_owner); + } + else if (tcb->state == GNRC_TCP_FSM_STATE_TIME_WAIT) { + _restart_timewait_timer(tcb); + } + } + } + return 0; +} + +/** + * @brief FSM Handling Function for timewait timeout handling + * + * @param[in/out] tcb Specifies tcb to use fsm on. + * @param[out] notify_owner non-negative if the tcb owner should be notified + * + * @return zero on success. + */ +static int _fsm_timeout_timewait(gnrc_tcp_tcb_t* tcb, bool *notify_owner) +{ + DEBUG("gnrc_tcp_fsm.c : _fsm_timeout_timewait()\n"); + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSED, notify_owner); + return 0; +} + +/** + * @brief FSM Handling Function for retransmissions + * + * @param[in/out] tcb Specifies tcb to use fsm on. + * + * @return zero on success. + */ +static int _fsm_timeout_retransmit(gnrc_tcp_tcb_t* tcb) +{ + DEBUG("gnrc_tcp_fsm.c : _fsm_timeout_retransmit()\n"); + if(tcb->pkt_retransmit != NULL){ + _pkt_setup_retransmit(tcb, tcb->pkt_retransmit, true); + _pkt_send(tcb, tcb->pkt_retransmit, 0, true); + } + else { + DEBUG("gnrc_tcp_fsm.c : _fsm_timeout_retransmit() : Retransmit queue is empty\n"); + } + return 0; +} + +/** + * @brief FSM Handling Function for connection timeout handling + * + * @param[in/out] tcb Specifies tcb to use fsm on. + * @param[out] notify_owner non-negative if the tcb owner should be notified + * + * @return zero on success. + */ +static int _fsm_timeout_connection(gnrc_tcp_tcb_t* tcb, bool *notify_owner) +{ + DEBUG("gnrc_tcp_fsm.c : _fsm_timeout_connection()\n"); + _transition_to(tcb, GNRC_TCP_FSM_STATE_CLOSED, notify_owner); + return 0; +} + +/** + * @brief FSM Handling Function for probe sending + * + * @param[in/out] tcb Specifies tcb to use fsm on. + * + * @return zero on success. + */ +static int _fsm_send_probe(gnrc_tcp_tcb_t* tcb) +{ + gnrc_pktsnip_t *out_pkt = NULL; /* Outgoing packet */ + uint8_t probe_pay[] = { 1 }; /* Probe Payload */ + + DEBUG("gnrc_tcp_fsm.c : _fsm_send_probe()\n"); + /* The Probe sends a already acknowledged Sequence No. with a garbage byte */ + _pkt_build(tcb, &out_pkt, NULL, MSK_ACK, tcb->snd_una - 1, tcb->rcv_nxt, probe_pay, + sizeof(probe_pay)); + _pkt_send(tcb, out_pkt, 0, false); + return 0; +} + +/** + * @brief FSM Handling Function for clearing the retransmit queue. + * + * @param[in/out] tcb Specifies tcb to use fsm on. + * + * @return zero on success. + */ +static int _fsm_clear_retransmit(gnrc_tcp_tcb_t* tcb) +{ + DEBUG("gnrc_tcp_fsm.c : _fsm_clear_retransmit()\n"); + _clear_retransmit(tcb); + return 0; +} + +/** + * @brief real fsm: needs to be protected from the outside + * + * @param[in/out] tcb Specifies tcb to use fsm on. + * @param[in] event current event that triggers fsm translation + * @param[in] in_pkt packet that triggered fsm event. Only in case of RCVD_PKT + * @param[in/out] buf buffer for send and receive functions + * @param[in] nByte number of bytes to send or receive atmost + * @param[out] notify_owner non-negative if the tcb owner should be notified + * + * @return TODO zero on success + * @return -ENOMEM Can't allocate receive buffer. + * @return -EADDRINUSE Given local port is already in use + * @return -EOPNOTSUPP If event is not implemented + */ +static int _fsm_unprotected(gnrc_tcp_tcb_t* tcb, gnrc_tcp_fsm_event_t event, + gnrc_pktsnip_t *in_pkt, void *buf, size_t nByte, bool *notify_owner) +{ + int ret = 0; /* Return Value */ + + DEBUG("gnrc_tcp_fsm.c : _fsm_unprotected()\n"); + switch (event) { + case GNRC_TCP_FSM_EVENT_CALL_OPEN : + ret = _fsm_call_open(tcb, notify_owner); + break; + case GNRC_TCP_FSM_EVENT_CALL_SEND : + ret = _fsm_call_send(tcb, buf, nByte); + break; + case GNRC_TCP_FSM_EVENT_CALL_RECV : + ret = _fsm_call_recv(tcb, buf, nByte); + break; + case GNRC_TCP_FSM_EVENT_CALL_CLOSE : + ret = _fsm_call_close(tcb, notify_owner); + break; + case GNRC_TCP_FSM_EVENT_CALL_ABORT : + ret = _fsm_call_abort(); + break; + case GNRC_TCP_FSM_EVENT_RCVD_PKT : + ret = _fsm_rcvd_pkt(tcb, in_pkt, notify_owner); + break; + case GNRC_TCP_FSM_EVENT_TIMEOUT_TIMEWAIT : + ret = _fsm_timeout_timewait(tcb, notify_owner); + break; + case GNRC_TCP_FSM_EVENT_TIMEOUT_RETRANSMIT : + ret = _fsm_timeout_retransmit(tcb); + break; + case GNRC_TCP_FSM_EVENT_TIMEOUT_CONNECTION : + ret = _fsm_timeout_connection(tcb, notify_owner); + break; + case GNRC_TCP_FSM_EVENT_SEND_PROBE : + ret = _fsm_send_probe(tcb); + break; + case GNRC_TCP_FSM_EVENT_CLEAR_RETRANSMIT : + ret = _fsm_clear_retransmit(tcb); + break; + } + return ret; +} + +int _fsm(gnrc_tcp_tcb_t* tcb, gnrc_tcp_fsm_event_t event, gnrc_pktsnip_t *in_pkt, void *buf, + size_t nByte) +{ + msg_t msg; + int32_t result; + bool notify_owner; + + /* Lock FSM */ + mutex_lock(&(tcb->fsm_lock)); + notify_owner = false; + result = _fsm_unprotected(tcb, event, in_pkt, buf, nByte, ¬ify_owner); + + /* Notify owner if something interesting happend */ + if (notify_owner && tcb->owner != KERNEL_PID_UNDEF) { + msg.type = MSG_TYPE_NOTIFY_USER; + msg_send(&msg, tcb->owner); + } + /* Unlock FSM */ + mutex_unlock(&(tcb->fsm_lock)); + return result; +} diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_option.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_option.c new file mode 100644 index 0000000000000000000000000000000000000000..05ff567460c01a740b8d75db3c4b7a7ed364d772 --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_option.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @ingroup net_gnrc + * @{ + * + * @file + * @brief GNRC's TCP option handling related functions + * + * @author Simon Brummer <brummer.simon@googlemail.com> + * @} + */ +#include "assert.h" +#include "internal/option.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +uint32_t _option_build_mss(uint16_t mss) +{ + return (((uint32_t )OPT_KIND_MSS) << 24) | (((uint32_t) OPT_LENGTH_MSS) << 16) | mss; +} + +uint16_t _option_build_offset_control(uint16_t nopts, uint16_t ctl) +{ + assert(OPTION_OFFSET_BASE <= nopts && nopts <= OPTION_OFFSET_MAX); + return (nopts << 12) | ctl; +} + +int _option_parse(gnrc_tcp_tcb_t* tcb, tcp_hdr_t *hdr) +{ + uint8_t word_idx = 0; + uint8_t byte_idx = 0; + uint8_t word_end = 0; + uint16_t off_ctl = byteorder_ntohs(hdr->off_ctl); + + word_end = GET_OFFSET(off_ctl) - OPTION_OFFSET_BASE; + + while (word_idx < word_end) { + uint32_t word = byteorder_ntohl(hdr->options[word_idx]); + + /* If byte index is not aligned to word index. Fill word with bytes from next word. */ + if (byte_idx) { + word >>= (byte_idx * 8); + word |= (byteorder_ntohl(hdr->options[word_idx + 1]) << ((sizeof(word) - byte_idx) * 8)); + } + + /* Option handling */ + switch (OPT_GET_KIND(word)) { + case OPT_KIND_EOL: + DEBUG("gnrc_tcp_option.c : _option_parse() : Option eol\n"); + return 0; + + case OPT_KIND_NOP: + byte_idx += 1; + DEBUG("gnrc_tcp_option.c : _option_parse() : Option nop\n"); + break; + + case OPT_KIND_MSS: + DEBUG("gnrc_tcp_option.c : _option_parse() : Option mss\n"); + if (OPT_GET_LENGTH(word) == OPT_LENGTH_MSS) { + tcb->mss = OPT_GET_VAL_2B(word); + byte_idx += 4; + } + else { + DEBUG("gnrc_tcp_option.c : _option_parse() : invalid MSS Option length.\n"); + return -1; + } + break; + + /* Add options support HERE */ + default: + DEBUG("gnrc_tcp_option.c : _option_parse() : Unsupported option received\n"); + byte_idx += 1; + } + + /* Update index */ + if (byte_idx >= 4) { + word_idx += 1; + byte_idx -= 4; + } + } + return 0; +} diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_pkt.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_pkt.c new file mode 100644 index 0000000000000000000000000000000000000000..f78e1e19849af5a72459087461f897fa171acff2 --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_pkt.c @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @ingroup net_gnrc + * @{ + * + * @file + * @brief GNRC's TCP paket related functions + * + * @author Simon Brummer <brummer.simon@googlemail.com> + * @} + */ +#include <stdlib.h> +#include <utlist.h> +#include <errno.h> +#include "msg.h" +#include "net/inet_csum.h" +#include "net/gnrc/pktbuf.h" +#include "net/gnrc/tcp.h" +#include "internal/pkt.h" +#include "internal/helper.h" +#include "internal/option.h" +#include "internal/eventloop.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* Check if a sequence number, falls into the receive window */ +#define INSIDE_WND(l_ed, seq_num, r_ed) (LEQ_32_BIT(l_ed, seq_num) && LSS_32_BIT(seq_num, r_ed)) + +/** + * @brief Calculates the maximum of two unsigned numbers + * + * @param [in] x First comparrison value + * @param [in] y Second comparrison value + * + * @return x if x is larger than y, if not y is returned. + */ +static inline uint32_t _max(const uint32_t x, const uint32_t y) +{ + return (x > y) ? x : y; +} + +int _pkt_build_reset_from_pkt(gnrc_pktsnip_t **out_pkt, gnrc_pktsnip_t *in_pkt) +{ + tcp_hdr_t tcp_hdr_out; + + /* Extract headers */ + gnrc_pktsnip_t *tcp_snp; + LL_SEARCH_SCALAR(in_pkt, tcp_snp, type, GNRC_NETTYPE_TCP); + tcp_hdr_t *tcp_hdr_in = (tcp_hdr_t *)tcp_snp->data; +#ifdef MODULE_GNRC_IPV6 + gnrc_pktsnip_t *ip6_snp; + LL_SEARCH_SCALAR(in_pkt, ip6_snp, type, GNRC_NETTYPE_IPV6); + ipv6_hdr_t *ip6_hdr = (ipv6_hdr_t *)ip6_snp->data; +#endif + + /* Setup Header information */ + tcp_hdr_out.src_port = tcp_hdr_in->dst_port; + tcp_hdr_out.dst_port = tcp_hdr_in->src_port; + tcp_hdr_out.checksum = byteorder_htons(0); + tcp_hdr_out.window = byteorder_htons(0); + tcp_hdr_out.urgent_ptr = byteorder_htons(0); + + /* Seq/Ackno and control flags depend on inputs ACK-Flag */ + uint16_t ctl = byteorder_ntohs(tcp_hdr_in->off_ctl); + if (ctl & MSK_ACK) { + tcp_hdr_out.off_ctl = byteorder_htons((OPTION_OFFSET_BASE << 12) | MSK_RST); + tcp_hdr_out.seq_num = tcp_hdr_in->ack_num; + tcp_hdr_out.ack_num = byteorder_htonl(0); + } + else { + uint8_t seq_no = 0; + tcp_hdr_out.off_ctl = byteorder_htons((OPTION_OFFSET_BASE << 12) | MSK_RST_ACK); + tcp_hdr_out.seq_num = byteorder_htonl(0); + if (ctl & MSK_SYN) { + seq_no += 1; + } + if (ctl & MSK_FIN) { + seq_no += 1; + } + uint32_t tmp = byteorder_ntohl(tcp_hdr_in->seq_num); + tcp_hdr_out.ack_num = byteorder_htonl(seq_no + tmp + _pkt_get_pay_len(in_pkt)); + } + + /* Allocate new tcp header */ + tcp_snp = gnrc_pktbuf_add(NULL, &tcp_hdr_out, OPTION_OFFSET_BASE * 4, GNRC_NETTYPE_TCP); + if (tcp_snp == NULL) { + DEBUG("gnrc_tcp_pkt.c : _pkt_build_reset_from_pkt() : Can't alloc buffer for TCP Header\n."); + *(out_pkt) = NULL; + return -ENOMEM; + } + *out_pkt = tcp_snp; + + /* Build new network layer header */ +#ifdef MODULE_GNRC_IPV6 + ip6_snp = gnrc_ipv6_hdr_build(tcp_snp, &(ip6_hdr->dst), &(ip6_hdr->src)); + if (ip6_snp == NULL) { + DEBUG("gnrc_tcp_pkt.c : _pkt_build_reset_from_pkt() : Can't alloc buffer for IPv6 Header.\n"); + gnrc_pktbuf_release(tcp_snp); + *(out_pkt) = NULL; + return -ENOMEM; + } + *out_pkt = ip6_snp; +#else + DEBUG("gnrc_tcp_pkt.c : _pkt_build_reset_from_pkt() : Network Layer Module Missing\n"); +#endif + return 0; +} + +int _pkt_build(gnrc_tcp_tcb_t* tcb, gnrc_pktsnip_t **out_pkt, uint16_t *seq_con, + const uint16_t ctl, const uint32_t seq_num, const uint32_t ack_num, + void *payload, const size_t payload_len) +{ + gnrc_pktsnip_t *pay_snp = NULL; + gnrc_pktsnip_t *tcp_snp = NULL; + tcp_hdr_t tcp_hdr; + uint8_t nopts = 0; + + /* Add payload, if supplied */ + if (payload != NULL && payload_len > 0) { + pay_snp = gnrc_pktbuf_add(pay_snp, payload, payload_len, GNRC_NETTYPE_UNDEF); + if (pay_snp == NULL) { + DEBUG("gnrc_tcp_pkt.c : _pkt_build() : Can't allocate buffer for payload\n."); + *(out_pkt) = NULL; + return -ENOMEM; + } + } + + /* fill tcp-header */ + tcp_hdr.src_port = byteorder_htons(tcb->local_port); + tcp_hdr.dst_port = byteorder_htons(tcb->peer_port); + tcp_hdr.checksum = byteorder_htons(0); + tcp_hdr.seq_num = byteorder_htonl(seq_num); + tcp_hdr.ack_num = byteorder_htonl(ack_num); + tcp_hdr.window = byteorder_htons(tcb->rcv_wnd); + tcp_hdr.urgent_ptr = byteorder_htons(0); + + /* tcp option handling */ + /* If this is a syn-message, send mss option */ + if (ctl & MSK_SYN) { + /* NOTE: MSS usually based on lower layers MTU */ + tcp_hdr.options[nopts] = byteorder_htonl(_option_build_mss(GNRC_TCP_MSS)); + nopts += 1; + } + /* Set offset and control bit accordingly */ + tcp_hdr.off_ctl = byteorder_htons(_option_build_offset_control(OPTION_OFFSET_BASE + nopts, ctl)); + + /* allocate tcp header */ + tcp_snp = gnrc_pktbuf_add(pay_snp, &tcp_hdr, (OPTION_OFFSET_BASE + nopts) * 4, GNRC_NETTYPE_TCP); + if (tcp_snp == NULL) { + DEBUG("gnrc_tcp_pkt.c : _pkt_build() : Can't allocate buffer for TCP Header\n."); + gnrc_pktbuf_release(pay_snp); + *(out_pkt) = NULL; + return -ENOMEM; + } + else { + *(out_pkt) = tcp_snp; + } + + /* Build network layer header */ +#ifdef MODULE_GNRC_IPV6 + gnrc_pktsnip_t* ip6_snp = gnrc_ipv6_hdr_build(tcp_snp, NULL, (ipv6_addr_t *) tcb->peer_addr); + if (ip6_snp == NULL) { + DEBUG("gnrc_tcp_pkt.c : _pkt_build() : Can't allocate buffer for IPv6 Header.\n"); + gnrc_pktbuf_release(tcp_snp); + *(out_pkt) = NULL; + return -ENOMEM; + } + else { + *(out_pkt) = ip6_snp; + } + #else + DEBUG("gnrc_tcp_pkt.c : _pkt_build_reset_from_pkt() : Network Layer Module Missing\n"); +#endif + + /* Calculate Sequence Space Number Consumption for this packet */ + if (seq_con != NULL) { + *seq_con = 0; + if (ctl & MSK_SYN) { + *seq_con += 1; + } + if (ctl & MSK_FIN) { + *seq_con += 1; + } + *seq_con += payload_len; + } + return 0; +} + +int _pkt_send(gnrc_tcp_tcb_t* tcb, gnrc_pktsnip_t *out_pkt, const uint16_t seq_con, + const bool retransmit) +{ + if (out_pkt == NULL) { + DEBUG("gnrc_tcp_pkt.c : _pkt_send() : Packet to send is null\n"); + return -EINVAL; + } + + /* If this is no retransmission, advance sequence number and measure time */ + if (!retransmit) { + tcb->retries = 0; + tcb->snd_nxt += seq_con; + tcb->rtt_start = xtimer_now().ticks32; + } + else { + tcb->retries += 1; + } + + /* Pass packet down the network stack */ + gnrc_netapi_send(_gnrc_tcp_pid, out_pkt); + return 0; +} + +int _pkt_chk_seq_num(const gnrc_tcp_tcb_t* tcb, const uint32_t seq_num, const uint32_t seg_len) +{ + uint32_t l_edge = tcb->rcv_nxt; + uint32_t r_edge = tcb->rcv_nxt + tcb->rcv_wnd; + uint32_t last_seq = seq_num + seg_len - 1; + + /* Possible case 1 */ + /* Segment contains no payload and Receive window is closed and */ + /* Sequence Number is next expected number */ + if (seg_len == 0 && tcb->rcv_wnd == 0 && l_edge == seq_num ) { + return 1; + } + + /* Possible case 2 */ + /* Segment contains no payload and Receive window is open and */ + /* Sequence number falls inside the receive window */ + if (seg_len == 0 && tcb->rcv_wnd > 0 && INSIDE_WND(l_edge, seq_num, r_edge)) { + return 1; + } + + /* Possible case 3 */ + /* Segment contains Payload and Receive window is open and */ + /* Sequence Number overlaps with receive window */ + if (seg_len > 0 && tcb->rcv_wnd > 0 + && (INSIDE_WND(l_edge, seq_num, r_edge) || INSIDE_WND(l_edge, last_seq, r_edge)) + ) { + return 1; + } + + /* Everthing else is not acceptable */ + return 0; +} + +uint32_t _pkt_get_seg_len(gnrc_pktsnip_t *pkt) +{ + uint32_t seq = 0; + uint16_t ctl = 0; + gnrc_pktsnip_t *snp = NULL; + + LL_SEARCH_SCALAR(pkt, snp, type, GNRC_NETTYPE_TCP); + tcp_hdr_t *hdr = (tcp_hdr_t *) snp->data; + ctl = byteorder_ntohs(hdr->off_ctl); + seq = _pkt_get_pay_len(pkt); + if (ctl & MSK_SYN) { + seq += 1; + } + if (ctl & MSK_FIN) { + seq += 1; + } + return seq; +} + +uint32_t _pkt_get_pay_len(gnrc_pktsnip_t *pkt) +{ + uint32_t seg_len = 0; + gnrc_pktsnip_t *snp = NULL; + + LL_SEARCH_SCALAR(pkt, snp, type, GNRC_NETTYPE_UNDEF); + while (snp && snp->type == GNRC_NETTYPE_UNDEF) { + seg_len += snp->size; + snp = snp->next; + } + return seg_len; +} + +int _pkt_setup_retransmit(gnrc_tcp_tcb_t* tcb, gnrc_pktsnip_t *pkt, const bool retransmit) +{ + gnrc_pktsnip_t *snp = NULL; + uint32_t ctl = 0; + uint32_t len = 0; + + /* No packet received */ + if (pkt == NULL) { + DEBUG("gnrc_tcp_pkt.c : _pkt_setup_retransmit() : pkt=NULL\n"); + return -EINVAL; + } + + /* Check if retransmit queue is full and pkt is not already in retransmit queue */ + if (tcb->pkt_retransmit != NULL && tcb->pkt_retransmit != pkt) { + DEBUG("gnrc_tcp_pkt.c : _pkt_setup_retransmit() : Nothing to do\n"); + return -ENOMEM; + } + + /* Extract control bits and segment length */ + LL_SEARCH_SCALAR(pkt, snp, type, GNRC_NETTYPE_TCP); + ctl = byteorder_ntohs(((tcp_hdr_t *) snp->data)->off_ctl); + len = _pkt_get_pay_len(pkt); + + /* Check if pkt contains reset or is a pure ACK, return */ + if ((ctl & MSK_RST) || (((ctl & MSK_SYN_FIN_ACK) == MSK_ACK) && len == 0)) { + return 0; + } + + /* Assign pkt and increase users: every send attempt consumes a user */ + tcb->pkt_retransmit = pkt; + gnrc_pktbuf_hold(pkt, 1); + + /* RTO Adjustment */ + if (!retransmit) { + /* If this is the first transmission: rto is 1 sec (Lower Bound) */ + if (tcb->srtt == GNRC_TCP_RTO_UNINITIALIZED || tcb->rtt_var == GNRC_TCP_RTO_UNINITIALIZED) { + tcb->rto = GNRC_TCP_RTO_LOWER_BOUND; + } + else { + tcb->rto = tcb->srtt + _max(GNRC_TCP_RTO_GRANULARITY, GNRC_TCP_RTO_K * tcb->rtt_var); + } + } + else { + /* If this is a retransmission: Double the rto (Timer Backoff) */ + tcb->rto *= 2; + + /* If the transmission has been tried five times, we assume srtt and rtt_var are bogus */ + /* New measurements must be taken */ + if (tcb->retries >= 5) { + tcb->srtt = GNRC_TCP_RTO_UNINITIALIZED; + tcb->rtt_var = GNRC_TCP_RTO_UNINITIALIZED; + } + } + + /* Perform Boundrychecks on current RTO before usage */ + if (tcb->rto < (int32_t) GNRC_TCP_RTO_LOWER_BOUND) { + tcb->rto = GNRC_TCP_RTO_LOWER_BOUND; + } + else if (tcb->rto > (int32_t) GNRC_TCP_RTO_UPPER_BOUND) { + tcb->rto = GNRC_TCP_RTO_UPPER_BOUND; + } + + /* Setup retransmission timer, msg to TCP thread with ptr to tcb */ + tcb->msg_tout.type = MSG_TYPE_RETRANSMISSION; + tcb->msg_tout.content.ptr = (void *)tcb; + xtimer_set_msg(&tcb->tim_tout, tcb->rto, &tcb->msg_tout, _gnrc_tcp_pid); + return 0; +} + +int _pkt_acknowledge(gnrc_tcp_tcb_t* tcb, const uint32_t ack) +{ + uint32_t seg = 0; + gnrc_pktsnip_t *snp = NULL; + tcp_hdr_t *hdr; + + /* Retransmission Queue is empty. Nothing to ACK there */ + if (tcb->pkt_retransmit == NULL) { + DEBUG("gnrc_tcp_pkt.c : _pkt_acknowledge() : There is no packet to ack\n"); + return -ENODATA; + } + + LL_SEARCH_SCALAR(tcb->pkt_retransmit, snp, type, GNRC_NETTYPE_TCP); + hdr = (tcp_hdr_t *) snp->data; + + /* There must be a packet, waiting to be acknowledged. */ + seg = byteorder_ntohl(hdr->seq_num) + _pkt_get_seg_len(tcb->pkt_retransmit) - 1; + + /* If segment can be acknowledged -> stop timer, release packet from pktbuf and update rto. */ + if (LSS_32_BIT(seg, ack)) { + xtimer_remove(&(tcb->tim_tout)); + gnrc_pktbuf_release(tcb->pkt_retransmit); + tcb->pkt_retransmit = NULL; + + /* Measure Round Trip Time */ + int32_t rtt = xtimer_now().ticks32 - tcb->rtt_start; + + /* Use sample only if ther was no timeroverflow and no retransmission (Karns Alogrithm) */ + if (tcb->retries == 0 && rtt > 0) { + /* If this is the first sample taken */ + if (tcb->srtt == GNRC_TCP_RTO_UNINITIALIZED + && tcb->rtt_var == GNRC_TCP_RTO_UNINITIALIZED + ) { + tcb->srtt = rtt; + tcb->rtt_var = (rtt >> 1); + } + /* If this is a subsequent sample */ + else { + tcb->rtt_var = (tcb->rtt_var / GNRC_TCP_RTO_B_DIV) * (GNRC_TCP_RTO_B_DIV-1); + tcb->rtt_var += abs(tcb->srtt - rtt) / GNRC_TCP_RTO_B_DIV; + tcb->srtt = (tcb->srtt / GNRC_TCP_RTO_A_DIV) * (GNRC_TCP_RTO_A_DIV-1); + tcb->srtt += rtt / GNRC_TCP_RTO_A_DIV; + } + } + } + return 0; +} + +uint16_t _pkt_calc_csum(const gnrc_pktsnip_t *hdr, + const gnrc_pktsnip_t *pseudo_hdr, + const gnrc_pktsnip_t *payload) +{ + uint16_t csum = 0; + uint16_t len = (uint16_t) hdr->size; + + if(pseudo_hdr == NULL) { + return 0; + } + + /* Process payload */ + while (payload && payload != hdr) { + csum = inet_csum(csum, (uint8_t *)payload->data, payload->size); + len += (uint16_t)payload->size; + payload = payload->next; + } + + /* Process tcp-header, before checksum field(Byte 16 to 18) */ + csum = inet_csum(csum, (uint8_t *)hdr->data, 16); + /* Process tcp-header, after checksum field */ + csum = inet_csum(csum, ((uint8_t *)hdr->data) + 18, hdr->size - 18); + + /* Process Network layer Header */ + switch (pseudo_hdr->type) { +#ifdef MODULE_GNRC_IPV6 + case GNRC_NETTYPE_IPV6: + csum = ipv6_hdr_inet_csum(csum, pseudo_hdr->data, PROTNUM_TCP, len); + break; +#endif + default: + return 0; + } + return ~csum; +} + +/* Cleanup, defines */ +#undef INSIDE_WND diff --git a/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_rcvbuf.c b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_rcvbuf.c new file mode 100644 index 0000000000000000000000000000000000000000..cbe72312285c59e1e123ae1a1c190baf98de5268 --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/gnrc_tcp_rcvbuf.c @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 Simon Brummer <brummer.simon@googlemail.com> + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @brief Implementation of tcp_internal/rcvbuf.h + * + * @author Brummer Simon <brummer.simon@googlemail.com> + */ +#include <errno.h> +#include "internal/rcvbuf.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +rcvbuf_t _static_buf; /**< Staticly allocated receive buffers */ + +void _rcvbuf_init(void) +{ + DEBUG("gnrc_tcp_rcvbuf.c : _rcvbuf_init() : Entry\n"); + mutex_init(&(_static_buf.lock)); + for (int i=0; i<GNRC_TCP_RCV_BUFFERS; i++) { + _static_buf.entries[i].used = 0; + } +} + +static void* _rcvbuf_alloc(void) +{ + void *result = NULL; + DEBUG("gnrc_tcp_rcvbuf.c : _rcvbuf_alloc() : Entry\n"); + mutex_lock(&(_static_buf.lock)); + for (int i=0; i<GNRC_TCP_RCV_BUFFERS; i++) { + if (_static_buf.entries[i].used == 0) { + _static_buf.entries[i].used = 1; + result = (void *)(_static_buf.entries[i].buffer); + break; + } + } + mutex_unlock(&(_static_buf.lock)); + return result; +} + +static void _rcvbuf_free(void * const buf) +{ + DEBUG("gnrc_tcp_rcvbuf.c : _rcvbuf_free() : Entry\n"); + mutex_lock(&(_static_buf.lock)); + for (int i=0; i<GNRC_TCP_RCV_BUFFERS; i++) { + if (_static_buf.entries[i].used == 1 && buf == _static_buf.entries[i].buffer) { + _static_buf.entries[i].used = 0; + } + } + mutex_unlock(&(_static_buf.lock)); +} + +int _rcvbuf_get_buffer(gnrc_tcp_tcb_t* tcb) +{ + if (tcb->rcv_buf_raw == NULL) { + tcb->rcv_buf_raw = _rcvbuf_alloc(); + if (tcb->rcv_buf_raw == NULL) { + DEBUG("gnrc_tcp_rcvbuf.c : _rcvbuf_get_buffer() : Can't allocate rcv_buf_raw\n"); + return -ENOMEM; + } + else { + ringbuffer_init(&tcb->rcv_buf, (char *) tcb->rcv_buf_raw, GNRC_TCP_RCV_BUF_SIZE); + } + } + return 0; +} + +void _rcvbuf_release_buffer(gnrc_tcp_tcb_t* tcb) +{ + if (tcb->rcv_buf_raw != NULL) { + _rcvbuf_free(tcb->rcv_buf_raw); + tcb->rcv_buf_raw = NULL; + } +} diff --git a/sys/net/gnrc/transport_layer/tcp/internal/eventloop.h b/sys/net/gnrc/transport_layer/tcp/internal/eventloop.h new file mode 100644 index 0000000000000000000000000000000000000000..922b4e6827f3c08096f9944e5a133c77bcd2abbb --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/internal/eventloop.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @defgroup net_gnrc_tcp TCP + * @ingroup net_gnrc + * @brief RIOT's tcp implementation for the gnrc stack + * + * @{ + * + * @file + * @brief Definition for gnrc tcp event processing loop + * + * @author Simon Brummer <brummer.simon@googlemail.com> + */ + +#ifndef GNRC_TCP_INTERNAL_EVENTLOOP_H_ +#define GNRC_TCP_INTERNAL_EVENTLOOP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief PID of tcp event handling thread + */ +extern kernel_pid_t _gnrc_tcp_pid; + +/** + * @brief TCP's mein processing thread. + * + * @param[in] arg arguments, unused + * + * @return Never returns, its an endless loop + */ +void *_event_loop(__attribute__((unused)) void *arg); + +#ifdef __cplusplus +} +#endif + +#endif /* GNRC_TCP_INTERNAL_EVENTLOOP_H_ */ +/** @} */ diff --git a/sys/net/gnrc/transport_layer/tcp/internal/fsm.h b/sys/net/gnrc/transport_layer/tcp/internal/fsm.h new file mode 100644 index 0000000000000000000000000000000000000000..461f781f67396d9fd542ce02d205ecffe36f4e40 --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/internal/fsm.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @defgroup net_gnrc_tcp TCP + * @ingroup net_gnrc + * @brief RIOT's tcp implementation for the gnrc stack + * + * @{ + * + * @file + * @brief Definies function to manipulate a connections state + * + * @author Simon Brummer <brummer.simon@googlemail.com> + */ + +#ifndef GNRC_TCP_INTERNAL_FSM_H_ +#define GNRC_TCP_INTERNAL_FSM_H_ + +#include <errno.h> +#include "net/gnrc/pktbuf.h" +#include "net/gnrc/pkt.h" +#include "net/gnrc/tcp/fsm.h" +#include "net/gnrc/tcp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief PID of tcp event handling thread + */ +extern kernel_pid_t _gnrc_tcp_pid; + +/** + * @brief TCP finite state maschine + * + * @param[in,out] tcb specifies connection to use fsm on. + * @param[in] event current event that triggers fsm translation + * @param[in] in_pkt packet that triggered fsm event. Only in case of RCVD_PKT + * @param[in,out] buf buffer for send and receive functions + * @param[in] nByte number of bytes to send or receive atmost + * + * @return Zero on success + * @return Positive Number, number of bytes sent from or copied into buf. + * @return -ENOSYS if event is not implemented + */ +int _fsm(gnrc_tcp_tcb_t* tcb, gnrc_tcp_fsm_event_t event, gnrc_pktsnip_t *in_pkt, void *buf, + size_t nByte); + +#ifdef __cplusplus +} +#endif + +#endif /* GNRC_TCP_INTERNAL_FSM_H_ */ +/** @} */ diff --git a/sys/net/gnrc/transport_layer/tcp/internal/helper.h b/sys/net/gnrc/transport_layer/tcp/internal/helper.h new file mode 100644 index 0000000000000000000000000000000000000000..1b19e60f441dcb6217cf1750def552ae5ffc3b12 --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/internal/helper.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @defgroup net_gnrc_tcp TCP + * @ingroup net_gnrc + * @brief RIOT's tcp implementation for the gnrc stack + * + * @{ + * + * @file + * @brief Helperfunctions and defines + * + * @author Simon Brummer <brummer.simon@googlemail.com> + */ + +#ifndef GNRC_TCP_INTERNAL_HELPER_H_ +#define GNRC_TCP_INTERNAL_HELPER_H_ + +#include "net/gnrc/netapi.h" +#include "net/gnrc/tcp/hdr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Bitmasks for control bit handling + * @{ + */ +#define MSK_FIN 0x0001 +#define MSK_SYN 0x0002 +#define MSK_RST 0x0004 +#define MSK_PSH 0x0008 +#define MSK_ACK 0x0010 +#define MSK_URG 0x0020 +#define MSK_FIN_ACK 0x0011 +#define MSK_SYN_ACK 0x0012 +#define MSK_RST_ACK 0x0014 +#define MSK_SYN_FIN_ACK 0x0013 +#define MSK_FIN_ACK_PSH 0x0019 +#define MSK_CTL 0x003F +#define MSK_OFFSET 0xF000 +/** @} */ + +/** + * @brief Type field values for TCP internal Message Passing. + * @{ + */ +#define MSG_TYPE_CONNECTION_TIMEOUT (GNRC_NETAPI_MSG_TYPE_ACK + 101) +#define MSG_TYPE_PROBE_TIMEOUT (GNRC_NETAPI_MSG_TYPE_ACK + 102) +#define MSG_TYPE_USER_SPEC_TIMEOUT (GNRC_NETAPI_MSG_TYPE_ACK + 103) +#define MSG_TYPE_RETRANSMISSION (GNRC_NETAPI_MSG_TYPE_ACK + 104) +#define MSG_TYPE_TIMEWAIT (GNRC_NETAPI_MSG_TYPE_ACK + 105) +#define MSG_TYPE_NOTIFY_USER (GNRC_NETAPI_MSG_TYPE_ACK + 106) +/** @} */ + +/** + * @brief Overflow tolerant comparision operators for sequence and + acknowledgement number comparision + * @{ + */ +#define LSS_32_BIT(x, y) (((int32_t) (x)) - ((int32_t) (y)) < 0) +#define LEQ_32_BIT(x, y) (((int32_t) (x)) - ((int32_t) (y)) <= 0) +#define GRT_32_BIT(x, y) (!LEQ_32_BIT(x, y)) +#define GEQ_32_BIT(x, y) (!LSS_32_BOT(x, y)) +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* GNRC_TCP_INTERNAL_HELPER_H_ */ +/** @} */ diff --git a/sys/net/gnrc/transport_layer/tcp/internal/option.h b/sys/net/gnrc/transport_layer/tcp/internal/option.h new file mode 100644 index 0000000000000000000000000000000000000000..ca85af18c2355dbaf771eeb7c44054ec1b8492dc --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/internal/option.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @defgroup net_gnrc_tcp TCP + * @ingroup net_gnrc + * @brief RIOT's tcp implementation for the gnrc stack + * + * @{ + * + * @file + * @brief Defines and Macros for TCP option handling + * + * @author Simon Brummer <brummer.simon@googlemail.com> + */ + +#ifndef GNRC_TCP_INTERNAL_OPTION_H_ +#define GNRC_TCP_INTERNAL_OPTION_H_ + +#include "helper.h" +#include "net/gnrc/tcp.h" +#include "net/gnrc/tcp/hdr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief TCP Option field boundries + * @{ + */ +#define OPTION_OFFSET_BASE (0x5) +#define OPTION_OFFSET_MAX (0xF) +/** @} */ + +/** + * @brief Extract offset value from offet and ctl bit field. + */ +#define GET_OFFSET( x ) (((x) & MSK_OFFSET) >> 12) + +/** + * @brief TCP Option Kind Field Values + * @{ + */ +#define OPT_KIND_EOL (00) /**< End of List */ +#define OPT_KIND_NOP (01) /**< No Operatrion */ +#define OPT_KIND_MSS (02) /**< Maximum Segment Size */ +/** @} */ + +/** + * @brief TCP Option Length Field Values + * @{ + */ +#define OPT_LENGTH_MSS (04) /**< MSS Option Size is 4 byte */ +/** @} */ + +/** + * @brief Extract kind field value from 4-byte option field + */ +#define OPT_GET_KIND( x ) (((x) & 0xFF000000) >> 24) + +/** + * @brief Extract length field value from 4-byte option field + */ +#define OPT_GET_LENGTH( x ) (((x) & 0x00FF0000) >> 16) + +/** + * @brief Extract two byte option value from 4-byte option field + */ +#define OPT_GET_VAL_2B( x ) (((x) & 0x0000FFFF)) + +/** + * @brief Helper Function to build the MSS Option + * + * @param[in] mss tcp header to be checked + * + * @return Valid MSS Option. + */ +uint32_t _option_build_mss(uint16_t mss); + +/** + * @brief Helper Function to build the combined option and control flag field + * + * @param[in] nopts Number of Options + * @param[in] ctl Control Flags + * + * @return Valid option size and control field. + */ +uint16_t _option_build_offset_control(uint16_t nopts, uint16_t ctl); + +/** + * @brief Parses options of a given tcp-header pktsnip. + * + * @param[out] tcb transmission control block to memorize options. + * @param[in] hdr tcp header to be checked + * + * @return Zero on success + * @return A negative value on error + */ +int _option_parse(gnrc_tcp_tcb_t* tcb, tcp_hdr_t *hdr); + +#ifdef __cplusplus +} +#endif + +#endif /* GNRC_TCP_INTERNAL_OPTION_H_*/ +/** @} */ diff --git a/sys/net/gnrc/transport_layer/tcp/internal/pkt.h b/sys/net/gnrc/transport_layer/tcp/internal/pkt.h new file mode 100644 index 0000000000000000000000000000000000000000..d63ff9c7ed321f107bbc2f81d2cc2f5a5507f6e7 --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/internal/pkt.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @defgroup net_gnrc_tcp TCP + * @ingroup net_gnrc + * @brief RIOT's tcp implementation for the gnrc stack + * + * @{ + * + * @file + * @brief Functions for TCP's paket handling + * + * @author Simon Brummer <brummer.simon@googlemail.com> + */ + +#ifndef GNRC_TCP_INTERNAL_PKT_H_ +#define GNRC_TCP_INTERNAL_PKT_H_ + +#include "net/conn/tcp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Build a reset packet from an incomming packet. + * + * This function builds a reset from an incomming packet + * for cases where the connection has not been established + * + * @param[out] out_pkt outgoing reset packet + * @param[in] in_pkt incomming packet + * + * @return Zero on success + * @return -ENOMEM if pktbuf is full. + */ +int _pkt_build_reset_from_pkt(gnrc_pktsnip_t **out_pkt, gnrc_pktsnip_t *in_pkt); + +/** + * @brief Build and allocate a tcp paket in paketbuffer, conn stores pointer to new paket. + * + * @param[in,out] tcb This connections Transmission control block. + * @param[out] out_pkt Pointer to paket to build + * @param[out] seq_con Number of Bytes, the packet will consume in sequence number spce + * @param[in] ctl control bits to set in pkt + * @param[in] seq_num sequence number to use in new paket + * @param[in] ack_num acknowledgment number to use in new paket + * @param[in] payload pointer to payload buffer + * @param[in] payload_len payload size + * + * @return Zero on success. + * @return -ENOMEM if pktbuf is full. + */ +int _pkt_build(gnrc_tcp_tcb_t* tcb, gnrc_pktsnip_t **out_pkt, uint16_t *seq_con, + const uint16_t ctl, const uint32_t seq_num, const uint32_t ack_num, + void *payload, const size_t payload_len); + +/** + * @brief Sends a packet to the peer + * + * @param[in,out] tcb This connections Transmission control block. + * @param[in] out_pkt pointer to paket to send + * @param[in] seq_con sequence number consumption of the paket to send + * @param[in] retransmit is this a retransmission ? + * + * @return Zero on success. + * @return -EINVAL if out_pkt was NULL + */ +int _pkt_send(gnrc_tcp_tcb_t* tcb, gnrc_pktsnip_t *out_pkt, const uint16_t seq_con, + const bool retransmit); + +/** + * @brief Checks sequence number + * + * @param[in,out] tcb This connections Transmission control block. + * @param[in] seq_num sequence number from the segment + * @param[in] seg_len length of a segments payload + * + * @return Zero if the sequence number is invalid + * @return Non-zero if the sequence number is acceptable + */ +int _pkt_chk_seq_num(const gnrc_tcp_tcb_t* tcb, const uint32_t seq_num, const uint32_t seg_len); + +/** + * @brief Extracts the length of a segment + * + * @param[in] pkt Packet to calculate payload length + * + * @return number consumption in sequence number space + */ +uint32_t _pkt_get_seg_len(gnrc_pktsnip_t *pkt); + +/** + * @brief Calculates a segments payload length + * + * @param[in] pkt Packet to calculate payload length + * + * @return the segments payload length in bytes + */ +uint32_t _pkt_get_pay_len(gnrc_pktsnip_t *pkt); + +/** + * @brief Adds a paket to the retransmission mechanism + * + * @param[in,out] tcb This connections Transmission control block. + * @param[in] pkt paket to add to the retransmission mechanism + * @param[in] retransmit is this a retransmission ? + * + * @return Zero on success + * @return -ENOMEM if the retransmission queue is full + * @return -EINVAL if pkt is null + */ +int _pkt_setup_retransmit(gnrc_tcp_tcb_t* tcb, gnrc_pktsnip_t *pkt, const bool retransmit); + +/** + * @brief Acknowledges and removes packet from the retransmission mechanism + * + * @param[in,out] tcb This connections Transmission control block. + * @param[in] ack Acknowldegment number used to acknowledge packets + * + * @return Zero on success + * @return -ENODATA if there is nothing to acknowledge + */ +int _pkt_acknowledge(gnrc_tcp_tcb_t* tcb, const uint32_t ack); + +/** + * @brief Calculates checksum over payload, tcp-header and network layer header + * + * @param[in] hdr gnrc_pktsnip_t to tcp-header + * @param[in] pseudo_hdr gnrc_pktsnip_t to network layer header + * @param[in] payload gnrc_pktsnip_t to payload + * + * @return non zero checksum if given network layer is supported + * @return zero if given network layer is not supported + */ +uint16_t _pkt_calc_csum(const gnrc_pktsnip_t *hdr, const gnrc_pktsnip_t *pseudo_hdr, + const gnrc_pktsnip_t *payload); + +#ifdef __cplusplus +} +#endif + +#endif /* GNRC_TCP_INTERNAL_PKT_H_ */ +/** @} */ diff --git a/sys/net/gnrc/transport_layer/tcp/internal/rcvbuf.h b/sys/net/gnrc/transport_layer/tcp/internal/rcvbuf.h new file mode 100644 index 0000000000000000000000000000000000000000..39a68afe0af22b1ec85f677609a6af21e03280e1 --- /dev/null +++ b/sys/net/gnrc/transport_layer/tcp/internal/rcvbuf.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 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. + */ + +/** + * @defgroup net_gnrc_tcp TCP + * @ingroup net_gnrc + * @brief RIOT's tcp implementation for the gnrc stack + * + * @{ + * + * @file + * @brief Functions for allocating and freeing the receive buffer + * + * @author Simon Brummer <brummer.simon@googlemail.com> + * @} + */ + #ifndef GNRC_TCP_INTERNAL_RCVBUF_H_ + #define GNRC_TCP_INTERNAL_RCVBUF_H_ + +#include <stdint.h> +#include "mutex.h" +#include "ringbuffer.h" +#include "net/gnrc/tcp/config.h" +#include "net/gnrc/tcp/tcb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Struct for a single connections receive buffer + * @internal + */ +typedef struct rcvbuf_entry { + uint8_t used; /**< Is entry currently in use */ + uint8_t buffer[GNRC_TCP_RCV_BUF_SIZE]; /**< Raw Buffer Data */ +} rcvbuf_entry_t; + +/** + * @brief Stuct holding receive buffers + * @internal + */ +typedef struct rcvbuf { + mutex_t lock; /**< Lock for synchronization */ + rcvbuf_entry_t entries[GNRC_TCP_RCV_BUFFERS]; /**< Number of receive buffers */ +} rcvbuf_t; + +/** + * @brief Initializes global receive Buffer + * @internal + */ +void _rcvbuf_init(void); + +/** + * @brief Initializes and assigns receive Buffer to tcb. + * + * @param[in] tcb Transmission control block that should hold the buffer. + * + * @return zero on success + * @return -ENOMEM If receive buffer is out of memory. + */ +int _rcvbuf_get_buffer(gnrc_tcp_tcb_t* tcb); + +/** + * @brief Free allocated receive buffer + * + * @param[in] tcb Transmission control block that buffer should be freed. + */ +void _rcvbuf_release_buffer(gnrc_tcp_tcb_t* tcb); + +#ifdef __cplusplus +} +#endif + +#endif /* GNRC_TCP_INTERNAL_RCVBUF_H_ */ +/** @} */