1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/net/gnrc/transport_layer/tcp/gnrc_tcp.c
2017-01-22 09:27:58 +01:00

617 lines
21 KiB
C

/*
* 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;
}