diff --git a/Makefile.dep b/Makefile.dep
index d0305449d8503450804ae94f16fb79f9c76a798d..a09ccd12897f369ae5d8893c62a9b4a7a2519894 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -436,6 +436,10 @@ ifneq (,$(filter lwip_sock_ip,$(USEMODULE)))
   USEMODULE += lwip_raw
 endif
 
+ifneq (,$(filter lwip_sock_tcp,$(USEMODULE)))
+  USEMODULE += lwip_tcp
+endif
+
 ifneq (,$(filter lwip_sock_udp,$(USEMODULE)))
   USEMODULE += lwip_udp
 endif
diff --git a/pkg/lwip/Makefile.include b/pkg/lwip/Makefile.include
index feb293bd91e023d118632596096433168666b2a8..b2ffbcd8c03a0ccf47fedbfec3088844fa32fdba 100644
--- a/pkg/lwip/Makefile.include
+++ b/pkg/lwip/Makefile.include
@@ -25,6 +25,9 @@ endif
 ifneq (,$(filter lwip_sock_ip,$(USEMODULE)))
   DIRS += $(RIOTBASE)/pkg/lwip/contrib/sock/ip
 endif
+ifneq (,$(filter lwip_sock_tcp,$(USEMODULE)))
+  DIRS += $(RIOTBASE)/pkg/lwip/contrib/sock/tcp
+endif
 ifneq (,$(filter lwip_sock_udp,$(USEMODULE)))
   DIRS += $(RIOTBASE)/pkg/lwip/contrib/sock/udp
 endif
diff --git a/pkg/lwip/contrib/sock/lwip_sock.c b/pkg/lwip/contrib/sock/lwip_sock.c
index 4a31281e62ace163edbe39789ad3f9a1f9bf257e..a46790b04c41dfdf9da1e798a35b71d8395ee41b 100644
--- a/pkg/lwip/contrib/sock/lwip_sock.c
+++ b/pkg/lwip/contrib/sock/lwip_sock.c
@@ -295,6 +295,11 @@ int lwip_sock_create(struct netconn **conn, const struct _sock_tl_ep *local,
          * local->port) demand binding */
         if (bind) {
             switch (netconn_bind(*conn, &local_addr, local_port)) {
+#if LWIP_TCP
+                case ERR_BUF:
+                    res = -ENOMEM;
+                    break;
+#endif
                 case ERR_USE:
                     res = -EADDRINUSE;
                     break;
@@ -311,6 +316,24 @@ int lwip_sock_create(struct netconn **conn, const struct _sock_tl_ep *local,
         }
         if (remote != NULL) {
             switch (netconn_connect(*conn, &remote_addr, remote_port)) {
+#if LWIP_TCP
+                case ERR_BUF:
+                    res = -ENOMEM;
+                    break;
+                case ERR_INPROGRESS:
+                    res = -EINPROGRESS;
+                    break;
+                case ERR_ISCONN:
+                    res = -EISCONN;
+                    break;
+                case ERR_IF:
+                case ERR_RTE:
+                    res = -ENETUNREACH;
+                    break;
+                case ERR_ABRT:
+                    res = -ETIMEDOUT;
+                    break;
+#endif
                 case ERR_USE:
                     res = -EADDRINUSE;
                     break;
@@ -403,6 +426,7 @@ int lwip_sock_get_addr(struct netconn *conn, struct _sock_tl_ep *ep, u8_t local)
     return 0;
 }
 
+#if defined(MODULE_LWIP_SOCK_UDP) || defined(MODULE_LWIP_SOCK_IP)
 int lwip_sock_recv(struct netconn *conn, uint32_t timeout, struct netbuf **buf)
 {
     int res;
@@ -441,6 +465,7 @@ int lwip_sock_recv(struct netconn *conn, uint32_t timeout, struct netbuf **buf)
 #endif
     return res;
 }
+#endif /* defined(MODULE_LWIP_SOCK_UDP) || defined(MODULE_LWIP_SOCK_IP) */
 
 ssize_t lwip_sock_send(struct netconn **conn, const void *data, size_t len,
                        int proto, const struct _sock_tl_ep *remote, int type)
@@ -494,15 +519,20 @@ ssize_t lwip_sock_send(struct netconn **conn, const void *data, size_t len,
         netbuf_delete(buf);
         return -ENOTCONN;
     }
+    res = len;  /* set for non-TCP calls */
     if (remote != NULL) {
         err = netconn_sendto(tmp, buf, &remote_addr, remote_port);
     }
+#if LWIP_TCP
+    else if (tmp->type & NETCONN_TCP) {
+        err = netconn_write_partly(tmp, data, len, 0, (size_t *)(&res));
+    }
+#endif /* LWIP_TCP */
     else {
         err = netconn_send(tmp, buf);
     }
     switch (err) {
         case ERR_OK:
-            res = len;
             if (conn != NULL) {
                 *conn = tmp;
             }
diff --git a/pkg/lwip/contrib/sock/tcp/Makefile b/pkg/lwip/contrib/sock/tcp/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..2a6b23441f29019e1ba483662fca5f72c6b2f000
--- /dev/null
+++ b/pkg/lwip/contrib/sock/tcp/Makefile
@@ -0,0 +1,3 @@
+MODULE := lwip_sock_tcp
+
+include $(RIOTBASE)/Makefile.base
diff --git a/pkg/lwip/contrib/sock/tcp/lwip_sock_tcp.c b/pkg/lwip/contrib/sock/tcp/lwip_sock_tcp.c
new file mode 100644
index 0000000000000000000000000000000000000000..df879ac3a9ea3d688e507c9762a9e53371472d0f
--- /dev/null
+++ b/pkg/lwip/contrib/sock/tcp/lwip_sock_tcp.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2016 Freie Universität Berlin
+ *
+ * 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
+ * @author  Martine Lenders <m.lenders@fu-berlin.de>
+ */
+
+#include "mutex.h"
+
+#include "net/sock/tcp.h"
+#include "timex.h"
+
+#include "lwip/sock_internal.h"
+#include "lwip/api.h"
+#include "lwip/opt.h"
+
+static inline void _tcp_sock_init(sock_tcp_t *sock, struct netconn *conn,
+                                  sock_tcp_queue_t *queue)
+{
+    mutex_init(&sock->mutex);
+    mutex_lock(&sock->mutex);
+    netconn_set_noautorecved(conn, 1);
+    sock->conn = conn;
+    sock->queue = queue;
+    sock->last_buf = NULL;
+    sock->last_offset = 0;
+    mutex_unlock(&sock->mutex);
+}
+
+int sock_tcp_connect(sock_tcp_t *sock, const sock_tcp_ep_t *remote,
+                     uint16_t local_port, uint16_t flags)
+{
+    assert(sock != NULL);
+    assert((remote != NULL) && (remote->port != 0));
+
+    int res;
+    struct netconn *tmp = NULL;
+    struct _sock_tl_ep local = { .family = remote->family,
+                                 .netif = remote->netif,
+                                 .port = local_port };
+
+    if ((res = lwip_sock_create(&tmp, &local, (struct _sock_tl_ep *)remote, 0,
+                                flags, NETCONN_TCP)) == 0) {
+        _tcp_sock_init(sock, tmp, NULL);
+    }
+    return res;
+}
+
+int sock_tcp_listen(sock_tcp_queue_t *queue, const sock_tcp_ep_t *local,
+                    sock_tcp_t *queue_array, unsigned queue_len,
+                    uint16_t flags)
+{
+    assert(queue != NULL);
+    assert((local != NULL) && (local->port != 0));
+    assert((queue_array != NULL) && (queue_len != 0));
+
+    int res;
+    struct netconn *tmp = NULL;
+
+    if (queue_len > USHRT_MAX) {
+        return -EFAULT;
+    }
+    if ((res = lwip_sock_create(&tmp, (struct _sock_tl_ep *)local, NULL, 0,
+                                flags, NETCONN_TCP)) < 0) {
+        return res;
+    }
+    assert(tmp != NULL); /* just in case lwIP is trolling */
+    mutex_init(&queue->mutex);
+    mutex_lock(&queue->mutex);
+    queue->conn = tmp;
+    queue->array = queue_array;
+    queue->len = queue_len;
+    queue->used = 0;
+    memset(queue->array, 0, sizeof(sock_tcp_t) * queue_len);
+    mutex_unlock(&queue->mutex);
+    switch (netconn_listen_with_backlog(queue->conn, queue->len)) {
+        case ERR_OK:
+            break;
+        case ERR_MEM:
+            return -ENOMEM;
+        case ERR_USE:
+            return -EADDRINUSE;
+        case ERR_VAL:
+            return -EINVAL;
+        default:
+            assert(false); /* should not happen since queue->conn is not closed
+                            * and we have a TCP conn */
+            break;
+    }
+    return 0;
+}
+
+void sock_tcp_disconnect(sock_tcp_t *sock)
+{
+    assert(sock != NULL);
+    mutex_lock(&sock->mutex);
+    if (sock->conn != NULL) {
+        netconn_close(sock->conn);
+        netconn_delete(sock->conn);
+        sock->conn = NULL;
+        /* if sock came from a sock_tcp_queue_t: since sock is a pointer in it's
+         * array it is also deleted from there, but we need to decrement the used
+         * counter */
+        if (sock->queue != NULL) {
+            assert(sock->queue->used > 0);
+            sock->queue->used--;
+            sock->queue = NULL;
+        }
+    }
+    mutex_unlock(&sock->mutex);
+    memset(&sock->mutex, 0, sizeof(mutex_t));
+}
+
+void sock_tcp_stop_listen(sock_tcp_queue_t *queue)
+{
+    assert(queue != NULL);
+    mutex_lock(&queue->mutex);
+    if (queue->conn != NULL) {
+        netconn_close(queue->conn);
+        netconn_delete(queue->conn);
+        queue->conn = NULL;
+        /* sever connections established through this queue */
+        for (unsigned i = 0; i < queue->len; i++) {
+            sock_tcp_disconnect(&queue->array[i]);
+        }
+        queue->array = NULL;
+        queue->len = 0;
+        queue->used = 0;
+    }
+    mutex_unlock(&queue->mutex);
+    memset(&queue->mutex, 0, sizeof(mutex_t));
+}
+
+int sock_tcp_get_local(sock_tcp_t *sock, sock_tcp_ep_t *ep)
+{
+    int res = 0;
+    assert(sock != NULL);
+    mutex_lock(&sock->mutex);
+    if ((sock->conn == NULL) || lwip_sock_get_addr(sock->conn,
+                                                   (struct _sock_tl_ep *)ep,
+                                                   1)) {
+        res = -EADDRNOTAVAIL;
+    }
+    mutex_unlock(&sock->mutex);
+    return res;
+}
+
+int sock_tcp_get_remote(sock_tcp_t *sock, sock_tcp_ep_t *ep)
+{
+    int res = 0;
+    assert(sock != NULL);
+    mutex_lock(&sock->mutex);
+    if ((sock->conn == NULL) || lwip_sock_get_addr(sock->conn,
+                                                   (struct _sock_tl_ep *)ep,
+                                                   0)) {
+        res = -ENOTCONN;
+    }
+    mutex_unlock(&sock->mutex);
+    return res;
+}
+
+int sock_tcp_queue_get_local(sock_tcp_queue_t *queue, sock_tcp_ep_t *ep)
+{
+    int res = 0;
+
+    assert(queue != NULL);
+    mutex_lock(&queue->mutex);
+    if ((queue->conn == NULL) || lwip_sock_get_addr(queue->conn,
+                                                    (struct _sock_tl_ep *)ep,
+                                                    1)) {
+        res = -EADDRNOTAVAIL;
+    }
+    mutex_unlock(&queue->mutex);
+    return res;
+}
+
+int sock_tcp_accept(sock_tcp_queue_t *queue, sock_tcp_t **sock,
+                    uint32_t timeout)
+{
+    struct netconn *tmp = NULL;
+    int res = 0;
+
+    assert((queue != NULL) && (sock != NULL));
+    if (queue->conn == NULL) {
+        return -EINVAL;
+    }
+    if (timeout == 0) {
+        if (!mutex_trylock(&queue->mutex)) {
+            return -EAGAIN;
+        }
+    }
+    else if (timeout != 0) {
+        mutex_lock(&queue->mutex);
+    }
+    if (queue->used < queue->len) {
+#if LWIP_SO_RCVTIMEO
+        if ((timeout != 0) && (timeout != SOCK_NO_TIMEOUT)) {
+            netconn_set_recvtimeout(queue->conn, timeout / US_PER_MS);
+        }
+        else
+#endif
+        if ((timeout == 0) && !cib_avail(&queue->conn->acceptmbox.mbox.cib)) {
+            mutex_unlock(&queue->mutex);
+            return -EAGAIN;
+        }
+        switch (netconn_accept(queue->conn, &tmp)) {
+            case ERR_OK:
+                for (unsigned short i = 0; i < queue->len; i++) {
+                    sock_tcp_t *s = &queue->array[i];
+                    if (s->conn == NULL) {
+                        _tcp_sock_init(s, tmp, queue);
+                        queue->used++;
+                        assert(queue->used > 0);
+                        *sock = s;
+                        break;
+                    }
+                }
+                break;
+            case ERR_ABRT:
+                res = -ECONNABORTED;
+                break;
+            case ERR_MEM:
+                res = -ENOMEM;
+                break;
+#if LWIP_SO_RCVTIMEO
+            case ERR_TIMEOUT:
+                res = -ETIMEDOUT;
+                break;
+#endif
+            default:
+                assert(false);
+                res = -1;
+                break;
+        }
+    }
+    else {
+        res = -ENOMEM;
+    }
+#if LWIP_SO_RCVTIMEO
+    netconn_set_recvtimeout(queue->conn, 0);
+#endif
+    mutex_unlock(&queue->mutex);
+    return res;
+}
+
+ssize_t sock_tcp_read(sock_tcp_t *sock, void *data, size_t max_len,
+                      uint32_t timeout)
+{
+    uint8_t *data_ptr = data;
+    struct pbuf *buf;
+    ssize_t offset = 0, res = 0;
+    bool done = false;
+
+    assert((sock != NULL) && (data != NULL) && (max_len > 0));
+    if (sock->conn == NULL) {
+        return -ENOTCONN;
+    }
+    if (timeout == 0) {
+        if (!mutex_trylock(&sock->mutex)) {
+            return -EAGAIN;
+        }
+    }
+    else {
+        mutex_lock(&sock->mutex);
+    }
+#if LWIP_SO_RCVTIMEO
+    if ((timeout != 0) && (timeout != SOCK_NO_TIMEOUT)) {
+        netconn_set_recvtimeout(sock->conn, timeout / US_PER_MS);
+    }
+    else
+#endif
+    if ((timeout == 0) && !cib_avail(&sock->conn->recvmbox.mbox.cib)) {
+        mutex_unlock(&sock->mutex);
+        return -EAGAIN;
+    }
+    while (!done) {
+        uint16_t copylen, buf_len;
+        if (sock->last_buf != NULL) {
+            buf = sock->last_buf;
+        }
+        else {
+            err_t err;
+            if ((err = netconn_recv_tcp_pbuf(sock->conn, &buf)) < 0) {
+                switch (err) {
+                    case ERR_ABRT:
+                        res = -ECONNABORTED;
+                        break;
+                    case ERR_CONN:
+                        res = -EADDRNOTAVAIL;
+                        break;
+                    case ERR_RST:
+                    case ERR_CLSD:
+                        res = -ECONNRESET;
+                        break;
+                    case ERR_MEM:
+                        res = -ENOMEM;
+                        break;
+#if LWIP_SO_RCVTIMEO
+                    case ERR_TIMEOUT:
+                        res = -ETIMEDOUT;
+                        break;
+#endif
+                    default:
+                        /* no applicable error */
+                        res = -1;
+                        break;
+                }
+                break;
+            }
+            sock->last_buf = buf;
+        }
+        buf_len = buf->tot_len - sock->last_offset;
+        copylen = (buf_len > max_len) ? (uint16_t)max_len : buf_len;
+        pbuf_copy_partial(buf, data_ptr + offset, copylen, sock->last_offset);
+        offset += copylen;
+        max_len -= copylen; /* should be 0 at minimum due to copylen setting above */
+        if (max_len == 0) {
+            done = true;
+            res = offset;   /* in case offset == 0 */
+        }
+        /* post-process buf */
+        if (buf_len > copylen) {
+            /* there is still data in the buffer */
+            sock->last_buf = buf;
+            sock->last_offset = copylen;
+        }
+        else {
+            sock->last_buf = NULL;
+            sock->last_offset = 0;
+            pbuf_free(buf);
+            break;
+        }
+    }
+    if (offset > 0) {
+        /* inform lwIP how much we receive*/
+        netconn_recved(sock->conn, (u32_t)offset);
+        res = offset;   /* we received data so return it */
+    }
+    /* unset flags */
+#if LWIP_SO_RCVTIMEO
+    netconn_set_recvtimeout(sock->conn, 0);
+#endif
+    netconn_set_nonblocking(sock->conn, false);
+    mutex_unlock(&sock->mutex);
+    return res;
+}
+
+ssize_t sock_tcp_write(sock_tcp_t *sock, const void *data, size_t len)
+{
+    struct netconn *conn;
+    int res = 0;
+
+    assert(sock != NULL);
+    assert((len == 0) || (data != NULL)); /* (len != 0) => (data != NULL) */
+    mutex_lock(&sock->mutex);
+    if (sock->conn == NULL) {
+        mutex_unlock(&sock->mutex);
+        return -ENOTCONN;
+    }
+    conn = sock->conn;
+    mutex_unlock(&sock->mutex); /* we won't change anything to sock here
+                                   (lwip_sock_send neither, since it remote is
+                                   NULL) so we can leave the mutex */
+    res = lwip_sock_send(&conn, data, len, 0, NULL, NETCONN_TCP);
+    return res;
+}
+
+/** @} */
diff --git a/pkg/lwip/include/lwip/sock_internal.h b/pkg/lwip/include/lwip/sock_internal.h
index 1ac5860eecbdbecdf5179fcfe5371ef87495b7f2..f47d39f0c1c9ee4fa1873c24f143be30d6cfe061 100644
--- a/pkg/lwip/include/lwip/sock_internal.h
+++ b/pkg/lwip/include/lwip/sock_internal.h
@@ -34,6 +34,14 @@
 extern "C" {
 #endif
 
+/**
+ * @brief   Configures @ref sock_tcp_accept() timeout in milliseconds
+ *          (0 by default, which means no timeout)
+ */
+#ifndef LWIP_SOCK_TCP_ACCEPT_TIMEOUT
+#define LWIP_SOCK_TCP_ACCEPT_TIMEOUT    (0)
+#endif
+
 /**
  * @brief Internal helper functions for lwIP
  * @internal
@@ -44,7 +52,9 @@ int lwip_sock_create(struct netconn **conn, const struct _sock_tl_ep *local,
                       uint16_t flags, int type);
 uint16_t lwip_sock_bind_addr_to_netif(const ip_addr_t *bind_addr);
 int lwip_sock_get_addr(struct netconn *conn, struct _sock_tl_ep *ep, u8_t local);
+#if defined(MODULE_LWIP_SOCK_UDP) || defined(MODULE_LWIP_SOCK_IP)
 int lwip_sock_recv(struct netconn *conn, uint32_t timeout, struct netbuf **buf);
+#endif
 ssize_t lwip_sock_send(struct netconn **conn, const void *data, size_t len,
                        int proto, const struct _sock_tl_ep *remote, int type);
 /**
diff --git a/pkg/lwip/include/sock_types.h b/pkg/lwip/include/sock_types.h
index 02cae53a8d48c8a7b40d26d93dd62e34e225d056..ddb890d18bac5edadf5fac68d37298e1f98fd952 100644
--- a/pkg/lwip/include/sock_types.h
+++ b/pkg/lwip/include/sock_types.h
@@ -33,6 +33,29 @@ struct sock_ip {
     struct netconn *conn;
 };
 
+/**
+ * @brief   TCP sock type
+ * @internal
+ */
+struct sock_tcp {
+    struct netconn *conn;
+    struct sock_tcp_queue *queue;
+    mutex_t mutex;
+    struct pbuf *last_buf;
+    ssize_t last_offset;
+};
+
+/**
+ * @brief   TCP queue type
+ */
+struct sock_tcp_queue {
+    struct netconn *conn;
+    struct sock_tcp *array;
+    mutex_t mutex;
+    unsigned short len;
+    unsigned short used;
+};
+
 /**
  * @brief   UDP sock type
  * @internal
diff --git a/tests/lwip_sock_tcp/Makefile b/tests/lwip_sock_tcp/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..f0bf0a59ebeab42193125255b854e217ef7f2dec
--- /dev/null
+++ b/tests/lwip_sock_tcp/Makefile
@@ -0,0 +1,49 @@
+APPLICATION = lwip_sock_tcp
+
+include ../Makefile.tests_common
+
+# lwIP's memory management doesn't seem to work on non 32-bit platforms at the
+# moment.
+BOARD_BLACKLIST := arduino-uno arduino-duemilanove arduino-mega2560 chronos \
+                   msb-430 msb-430h telosb waspmote-pro wsn430-v1_3b \
+                   wsn430-v1_4 z1
+BOARD_INSUFFICIENT_MEMORY = nucleo-f030 nucleo32-f042 nucleo-f334 \
+                            stm32f0discovery weio
+
+LWIP_IPV4 ?= 0
+
+ifneq (0, $(LWIP_IPV4))
+  USEMODULE += ipv4_addr
+  USEMODULE += lwip_arp
+  USEMODULE += lwip_ipv4
+  CFLAGS += -DETHARP_SUPPORT_STATIC_ENTRIES=1
+  LWIP_IPV6 ?= 0
+else
+  LWIP_IPV6 ?= 1
+endif
+
+ifneq (0, $(LWIP_IPV6))
+  USEMODULE += ipv6_addr
+  USEMODULE += lwip_ipv6_autoconfig
+endif
+
+USEMODULE += inet_csum
+USEMODULE += lwip_ethernet lwip_netdev2
+USEMODULE += lwip_sock_tcp
+USEMODULE += netdev2_eth
+USEMODULE += netdev2_test
+USEMODULE += ps
+
+DISABLE_MODULE += auto_init
+
+CFLAGS += -DDEVELHELP
+CFLAGS += -DSO_REUSE
+CFLAGS += -DLWIP_SO_RCVTIMEO
+CFLAGS += -DLWIP_SOCK_TCP_ACCEPT_TIMEOUT=500
+CFLAGS += -DLWIP_NETIF_LOOPBACK=1
+CFLAGS += -DLWIP_HAVE_LOOPIF=1
+
+include $(RIOTBASE)/Makefile.include
+
+test:
+	./tests/01-run.py
diff --git a/tests/lwip_sock_tcp/README.md b/tests/lwip_sock_tcp/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..513116b6ececa7e437a28f01b3e544b2007a21a0
--- /dev/null
+++ b/tests/lwip_sock_tcp/README.md
@@ -0,0 +1,33 @@
+Tests for lwIP's sock_tcp port
+==============================
+
+This tests the `sock_tcp` port of lwIP. There is no network device needed since
+a [virtual device](http://doc.riot-os.org/group__sys__netdev2__test.html) is
+provided at the backend.
+
+These tests test both IPv4 and IPv6 capabilities. They can be activated by
+the `LWIP_IPV4` and `LWIP_IPV6` environment variables to a non-zero value.
+IPv6 is activated by default:
+
+```sh
+make all test
+# or
+LWIP_IPV6=1 make all test
+```
+
+To just test IPv4 set the `LWIP_IPV4` to a non-zero value (IPv6 will be
+deactivated automatically):
+
+```sh
+LWIP_IPV4=1 make all test
+```
+
+To test both set the `LWIP_IPV4` and `LWIP_IPV6` to a non-zero value:
+
+```sh
+LWIP_IPV4=1 LWIP_IPV6=1 make all test
+```
+
+Since lwIP uses a lot of macro magic to activate/deactivate these capabilities
+it is advisable to **test all three configurations individually** (just IPv4,
+just IPv6, IPv4/IPv6 dual stack mode).
diff --git a/tests/lwip_sock_tcp/constants.h b/tests/lwip_sock_tcp/constants.h
new file mode 100644
index 0000000000000000000000000000000000000000..1d768d2e22b10c20819f391d0d51e790a86a9dcc
--- /dev/null
+++ b/tests/lwip_sock_tcp/constants.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 Freie Universität Berlin
+ *
+ * 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
+ * @ingroup
+ * @brief
+ * @{
+ *
+ * @file
+ * @brief
+ *
+ * @author  Martine Lenders <m.lenders@fu-berlin.de>
+ */
+#ifndef CONSTANTS_H_
+#define CONSTANTS_H_
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define _TEST_PORT_LOCAL    (0x2c94)
+#define _TEST_PORT_REMOTE   (0xa615)
+#define _TEST_NETIF         (1)
+#define _TEST_TIMEOUT       (1000000U)
+#define _TEST_ADDR4_LOCAL   (0xc0a84f96U)   /* 192.168.79.150 */
+#define _TEST_ADDR4_REMOTE  (0x7f000001U)   /* 127.0.0.1 */
+#define _TEST_ADDR4_WRONG   (0x254c6b4cU)
+#define _TEST_ADDR4_MASK    (0xffffff00U)   /* 255.255.255.0 */
+#define _TEST_ADDR4_GW      (0UL)           /* so we can test unreachability */
+#define _TEST_ADDR6_LOCAL   { 0x2f, 0xc4, 0x11, 0x5a, 0xe6, 0x91, 0x8d, 0x5d, \
+                              0x8c, 0xd1, 0x47, 0x07, 0xb7, 0x6f, 0x9b, 0x48 }
+#define _TEST_ADDR6_REMOTE  { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+                              0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }
+#define _TEST_ADDR6_WRONG   { 0x2a, 0xce, 0x5d, 0x4e, 0xc8, 0xbf, 0x86, 0xf7, \
+                              0x85, 0x49, 0xb4, 0x19, 0xf2, 0x28, 0xde, 0x9b }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CONSTANTS_H_ */
+/** @} */
diff --git a/tests/lwip_sock_tcp/main.c b/tests/lwip_sock_tcp/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..85291bd599a755d9153e23ded00157bdf41fa07c
--- /dev/null
+++ b/tests/lwip_sock_tcp/main.c
@@ -0,0 +1,1192 @@
+/*
+ * Copyright (C) 2016 Freie Universität Berlin
+ *
+ * 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     tests
+ * @{
+ *
+ * @file
+ * @brief       Test for UDP socks
+ *
+ * @author      Martine Lenders <m.lenders@fu-berlin.de>
+ * @}
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/uio.h>
+
+#include "net/ipv6/addr.h"
+#include "net/sock/tcp.h"
+#include "sched.h"
+#include "thread.h"
+#include "xtimer.h"
+
+#include "constants.h"
+#include "stack.h"
+
+#define _TEST_BUFFER_SIZE   (128)
+#define _QUEUE_SIZE         (1)
+
+#define _MSG_QUEUE_SIZE     (4)
+#define _CLIENT_BUF_SIZE    (128)
+#define _SERVER_BUF_SIZE    (128)
+#define _SERVER_QUEUE_SIZE  (1)
+#define _CLIENT_MSG_START   (0xe307)
+#define _CLIENT_MSG_READ    (0xe308)
+#define _CLIENT_MSG_WRITE   (0xe309)
+#define _CLIENT_MSG_STOP    (0xe30a)
+#define _SERVER_MSG_START   (0xe30b)
+#define _SERVER_MSG_ACCEPT  (0xe30c)
+#define _SERVER_MSG_READ    (0xe30d)
+#define _SERVER_MSG_WRITE   (0xe30e)
+#define _SERVER_MSG_CLOSE   (0xe30f)
+#define _SERVER_MSG_STOP    (0xe310)
+
+static uint8_t _test_buffer[_TEST_BUFFER_SIZE];
+
+static char _client_stack[THREAD_STACKSIZE_DEFAULT];
+static char _server_stack[THREAD_STACKSIZE_DEFAULT];
+static uint8_t _client_buf[_CLIENT_BUF_SIZE];
+static uint8_t _server_buf[_SERVER_BUF_SIZE];
+static msg_t _client_msg_queue[_MSG_QUEUE_SIZE];
+static msg_t _server_msg_queue[_MSG_QUEUE_SIZE];
+static sock_tcp_t _sock, _client_sock;
+static sock_tcp_t _queue_array[_QUEUE_SIZE];
+static sock_tcp_t _server_queue_array[_SERVER_QUEUE_SIZE];
+static sock_tcp_queue_t _queue, _server_queue;
+static sock_tcp_ep_t _server_addr;
+static kernel_pid_t _server, _client;
+
+#define CALL(fn)            puts("Calling " # fn); fn; tear_down()
+
+static void *_server_func(void *arg);
+static void *_client_func(void *arg);
+
+static void tear_down(void)
+{
+    msg_t msg = { .type = _CLIENT_MSG_STOP };
+    msg_send(&msg, _client);
+    msg.type = _SERVER_MSG_STOP;
+    msg_send(&msg, _server);
+    sock_tcp_disconnect(&_sock);
+    sock_tcp_stop_listen(&_queue);
+    memset(&_sock, 0, sizeof(_sock));
+    memset(&_queue, 0, sizeof(_queue));
+    memset(&_server_addr, 0, sizeof(_server_addr));
+}
+
+#ifdef MODULE_LWIP_IPV4
+#ifdef SO_REUSE
+static void test_tcp_connect4__EADDRINUSE(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const uint16_t local_port = _TEST_PORT_REMOTE;
+
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);    /* start server on _TEST_PORT_REMOTE */
+
+    assert(-EADDRINUSE == sock_tcp_connect(&_sock, &remote, local_port, 0));
+}
+#endif
+
+static void test_tcp_connect4__EAFNOSUPPORT(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+
+    assert(-EAFNOSUPPORT == sock_tcp_connect(&_sock, &remote, 0,
+                                             SOCK_FLAGS_REUSE_EP));
+}
+
+/* ECONNREFUSED does not apply for lwIP; netconn_connect does not wait for
+ * connection build-up */
+
+static void test_tcp_connect4__EINVAL_addr(void)
+{
+    static const sock_tcp_ep_t remote = { .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+
+    assert(-EINVAL == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+}
+
+static void test_tcp_connect4__EINVAL_netif(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = (_TEST_NETIF + 1) };
+
+    assert(-EINVAL == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+}
+
+/* ENETUNREACH not testable in given loopback setup */
+/* ETIMEDOUT doesn't work because lwIP's connect doesn't timeout */
+
+static void test_tcp_connect4__success_without_port(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = _TEST_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    sock_tcp_ep_t ep;
+
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);    /* start server on _TEST_PORT_REMOTE */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    assert(0 == sock_tcp_get_remote(&_sock, &ep));
+    assert(AF_INET == ep.family);
+    assert(HTONL(_TEST_ADDR4_REMOTE) == ep.addr.ipv4_u32);
+    assert(SOCK_ADDR_ANY_NETIF == ep.netif);
+    assert(_TEST_PORT_REMOTE == ep.port);
+}
+static void test_tcp_connect4__success_local_port(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const uint16_t local_port = _TEST_PORT_LOCAL;
+    sock_tcp_ep_t ep;
+
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);    /* start server on _TEST_PORT_REMOTE */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, local_port, SOCK_FLAGS_REUSE_EP));
+    assert(0 == sock_tcp_get_local(&_sock, &ep));
+    assert(AF_INET == ep.family);
+    assert(_TEST_PORT_LOCAL == ep.port);
+    assert(0 == sock_tcp_get_remote(&_sock, &ep));
+    assert(AF_INET == ep.family);
+    assert(HTONL(_TEST_ADDR4_REMOTE) == ep.addr.ipv4_u32);
+    assert(SOCK_ADDR_ANY_NETIF == ep.netif);
+    assert(_TEST_PORT_REMOTE == ep.port);
+}
+
+#ifdef SO_REUSE
+static void test_tcp_listen4__EADDRINUSE(void)
+{
+    const sock_tcp_ep_t local = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_LOCAL) },
+                                         .family = AF_INET,
+                                         .port = _TEST_PORT_LOCAL,
+                                         .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_LOCAL;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);    /* start server on _TEST_PORT_LOCAL */
+
+    assert(-EADDRINUSE == sock_tcp_listen(&_queue, &local, _queue_array,
+                                          _QUEUE_SIZE, 0));
+}
+#endif
+
+static void test_tcp_listen4__EAFNOSUPPORT(void)
+{
+    const sock_tcp_ep_t local = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_LOCAL) },
+                                         .port = _TEST_PORT_LOCAL,
+                                         .netif = SOCK_ADDR_ANY_NETIF };
+
+    assert(-EAFNOSUPPORT == sock_tcp_listen(&_queue, &local, _queue_array,
+                                            _QUEUE_SIZE, 0));
+}
+
+static void test_tcp_listen4__EINVAL(void)
+{
+    const sock_tcp_ep_t local = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_LOCAL) },
+                                         .family = AF_INET,
+                                         .port = _TEST_PORT_LOCAL,
+                                         .netif = (_TEST_NETIF + 1) };
+
+    assert(-EINVAL == sock_tcp_listen(&_queue, &local, _queue_array,
+                                      _QUEUE_SIZE, 0));
+}
+
+static void test_tcp_listen4__success_any_netif(void)
+{
+    const sock_tcp_ep_t local = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_LOCAL) },
+                                         .family = AF_INET,
+                                         .port = _TEST_PORT_LOCAL,
+                                         .netif = SOCK_ADDR_ANY_NETIF };
+    sock_tcp_ep_t ep;
+
+    assert(0 == sock_tcp_listen(&_queue, &local, _queue_array,
+                                _QUEUE_SIZE, 0));
+    assert(0 == sock_tcp_queue_get_local(&_queue, &ep));
+    assert(AF_INET == ep.family);
+    assert(HTONL(_TEST_ADDR4_LOCAL) == ep.addr.ipv4_u32);
+    assert(SOCK_ADDR_ANY_NETIF == ep.netif);
+    assert(_TEST_PORT_LOCAL == ep.port);
+}
+
+static void test_tcp_listen4__success_spec_netif(void)
+{
+    static const sock_tcp_ep_t local = { .family = AF_INET,
+                                         .port = _TEST_PORT_LOCAL,
+                                         .netif = _TEST_NETIF };
+    sock_tcp_ep_t ep;
+
+    assert(0 == sock_tcp_listen(&_queue, &local, _queue_array,
+                                _QUEUE_SIZE, 0));
+    assert(0 == sock_tcp_queue_get_local(&_queue, &ep));
+    assert(AF_INET == ep.family);
+    assert(_TEST_NETIF == ep.netif);
+    assert(_TEST_PORT_LOCAL == ep.port);
+}
+
+/* ECONNABORTED can't be tested in this setup */
+
+static void test_tcp_accept4__EAGAIN(void)
+{
+    static const sock_tcp_ep_t local = { .family = AF_INET,
+                                         .port = _TEST_PORT_LOCAL };
+    sock_tcp_t *sock;
+
+    assert(0 == sock_tcp_listen(&_queue, &local, _queue_array,
+                                _QUEUE_SIZE, SOCK_FLAGS_REUSE_EP));
+    assert(-EAGAIN == sock_tcp_accept(&_queue, &sock, 0));
+}
+
+static void test_tcp_accept4__EINVAL(void)
+{
+    sock_tcp_t *sock;
+
+    assert(-EINVAL == sock_tcp_accept(&_queue, &sock, SOCK_NO_TIMEOUT));
+}
+
+static void test_tcp_accept4__ETIMEDOUT(void)
+{
+    static const sock_tcp_ep_t local = { .family = AF_INET,
+                                         .port = _TEST_PORT_LOCAL };
+    sock_tcp_t *sock;
+
+    assert(0 == sock_tcp_listen(&_queue, &local, _queue_array,
+                                _QUEUE_SIZE, SOCK_FLAGS_REUSE_EP));
+    puts(" * Calling sock_tcp_accept()");
+    assert(-ETIMEDOUT == sock_tcp_accept(&_queue, &sock, _TEST_TIMEOUT));
+    printf(" * (timed out with timeout %u)\n", _TEST_TIMEOUT);
+}
+
+static void test_tcp_accept4__success(void)
+{
+    static const sock_tcp_ep_t local = { .family = AF_INET,
+                                         .port = _TEST_PORT_LOCAL };
+    msg_t msg = { .type = _CLIENT_MSG_START,
+                  .content = { .value = _TEST_PORT_REMOTE } };
+    sock_tcp_ep_t ep;
+    sock_tcp_t *sock;
+
+    _server_addr.addr.ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE);  /* loopback */
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_LOCAL;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    assert(0 == sock_tcp_listen(&_queue, &local, _queue_array,
+                                _QUEUE_SIZE, 0));
+    msg_send(&msg, _client);    /* start client on _TEST_PORT_REMOTE, connecting
+                                 * to _TEST_PORT_LOCAL */
+    assert(0 == sock_tcp_accept(&_queue, &sock, SOCK_NO_TIMEOUT));
+    assert(0 == sock_tcp_get_local(sock, &ep));
+    assert(AF_INET == ep.family);
+    assert(_TEST_PORT_LOCAL == ep.port);
+    assert(0 == sock_tcp_get_remote(sock, &ep));
+    assert(AF_INET == ep.family);
+    assert(HTONL(_TEST_ADDR4_REMOTE) == ep.addr.ipv4_u32);
+    assert(SOCK_ADDR_ANY_NETIF == ep.netif);
+    assert(_TEST_PORT_REMOTE == ep.port);
+}
+
+/* ECONNABORTED can't be tested in this setup */
+
+static void test_tcp_read4__EAGAIN(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    assert(-EAGAIN == sock_tcp_read(&_sock, _test_buffer, sizeof(_test_buffer), 0));
+}
+
+static void test_tcp_read4__ECONNRESET(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    msg.type = _SERVER_MSG_CLOSE;
+    msg_send(&msg, _server);        /* close connection at server side */
+    assert(-ECONNRESET == sock_tcp_read(&_sock, _test_buffer,
+                                        sizeof(_test_buffer),
+                                        SOCK_NO_TIMEOUT));
+}
+
+static void test_tcp_read4__ENOTCONN(void)
+{
+    assert(-ENOTCONN == sock_tcp_read(&_sock, _test_buffer,
+                                      sizeof(_test_buffer), SOCK_NO_TIMEOUT));
+}
+
+static void test_tcp_read4__ETIMEDOUT(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    puts(" * Calling sock_tcp_read()");
+    assert(-ETIMEDOUT == sock_tcp_read(&_sock, _test_buffer, sizeof(_test_buffer),
+                                       _TEST_TIMEOUT));
+    printf(" * (timed out with timeout %u)\n", _TEST_TIMEOUT);
+}
+
+static void test_tcp_read4__success(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const struct iovec exp_data = { .iov_base = "Hello!",
+                                           .iov_len = sizeof("Hello!") };
+
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    msg.type = _SERVER_MSG_WRITE;
+    msg.content.ptr = (void *)&exp_data;
+    msg_send(&msg, _server);        /* write expected data at server */
+    assert(((ssize_t)exp_data.iov_len) == sock_tcp_read(&_sock, _test_buffer,
+                                                        sizeof(_test_buffer),
+                                                        SOCK_NO_TIMEOUT));
+    assert(memcmp(exp_data.iov_base, _test_buffer, exp_data.iov_len) == 0);
+}
+
+static void test_tcp_read4__success_with_timeout(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const struct iovec exp_data = { .iov_base = "Hello!",
+                                           .iov_len = sizeof("Hello!") };
+
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    msg.type = _SERVER_MSG_WRITE;
+    msg.content.ptr = (void *)&exp_data;
+    msg_send(&msg, _server);        /* write expected data at server */
+    assert(((ssize_t)exp_data.iov_len) == sock_tcp_read(&_sock, _test_buffer,
+                                                        sizeof(_test_buffer),
+                                                        _TEST_TIMEOUT));
+    assert(memcmp(exp_data.iov_base, _test_buffer, exp_data.iov_len) == 0);
+}
+
+static void test_tcp_read4__success_non_blocking(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const struct iovec exp_data = { .iov_base = "Hello!",
+                                           .iov_len = sizeof("Hello!") };
+
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    msg.type = _SERVER_MSG_WRITE;
+    msg.content.ptr = (void *)&exp_data;
+    msg_send(&msg, _server);        /* write expected data at server */
+    assert(((ssize_t)exp_data.iov_len) == sock_tcp_read(&_sock, _test_buffer,
+                                                        sizeof(_test_buffer),
+                                                        0));
+    assert(memcmp(exp_data.iov_base, _test_buffer, exp_data.iov_len) == 0);
+}
+
+/* ENOTCONN not applicable since lwIP always tries to send */
+
+static void test_tcp_write4__ENOTCONN(void)
+{
+    assert(-ENOTCONN == sock_tcp_write(&_sock, "Hello!", sizeof("Hello!")));
+}
+
+static void test_tcp_write4__success(void)
+{
+    const sock_tcp_ep_t remote = { .addr = { .ipv4_u32 = HTONL(_TEST_ADDR4_REMOTE) },
+                                          .family = AF_INET,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const struct iovec exp_data = { .iov_base = "Hello!",
+                                           .iov_len = sizeof("Hello!") };
+
+    _server_addr.family = AF_INET;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    msg.type = _SERVER_MSG_READ;
+    msg.content.ptr = (void *)&exp_data;
+    msg_send(&msg, _server);        /* write expected data at server */
+    assert(((ssize_t)exp_data.iov_len) == sock_tcp_write(&_sock, "Hello!",
+                                                        sizeof("Hello!")));
+    assert(memcmp(exp_data.iov_base, _test_buffer, exp_data.iov_len) == 0);
+    xtimer_usleep(5000);            /* wait for server */
+}
+#endif /* MODULE_LWIP_IPV4 */
+
+#ifdef MODULE_LWIP_IPV6
+#ifdef SO_REUSE
+static void test_tcp_connect6__EADDRINUSE(void)
+{
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const uint16_t local_port = _TEST_PORT_REMOTE;
+
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);    /* start server on _TEST_PORT_REMOTE */
+
+    assert(-EADDRINUSE == sock_tcp_connect(&_sock, &remote, local_port, 0));
+}
+#endif
+
+static void test_tcp_connect6__EAFNOSUPPORT(void)
+{
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+
+    assert(-EAFNOSUPPORT == sock_tcp_connect(&_sock, &remote, 0,
+                                             SOCK_FLAGS_REUSE_EP));
+}
+
+/* ECONNREFUSED does not apply for lwIP; netconn_connect does not wait for
+ * connection build-up */
+
+static void test_tcp_connect6__EINVAL_addr(void)
+{
+    static const sock_tcp_ep_t remote = { .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+
+    assert(-EINVAL == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+}
+
+static void test_tcp_connect6__EINVAL_netif(void)
+{
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = (_TEST_NETIF + 1) };
+
+    assert(-EINVAL == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+}
+
+/* ENETUNREACH not testable in given loopback setup */
+/* ETIMEDOUT doesn't work because lwIP's connect doesn't timeout */
+
+static void test_tcp_connect6__success_without_port(void)
+{
+    static const ipv6_addr_t remote_addr = { .u8 = _TEST_ADDR6_REMOTE };
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = _TEST_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    sock_tcp_ep_t ep;
+
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);    /* start server on _TEST_PORT_REMOTE */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    assert(0 == sock_tcp_get_remote(&_sock, &ep));
+    assert(AF_INET6 == ep.family);
+    assert(memcmp(&remote_addr, &ep.addr.ipv6, sizeof(ipv6_addr_t)) == 0);
+    assert(SOCK_ADDR_ANY_NETIF == ep.netif);
+    assert(_TEST_PORT_REMOTE == ep.port);
+}
+static void test_tcp_connect6__success_local_port(void)
+{
+    static const ipv6_addr_t remote_addr = { .u8 = _TEST_ADDR6_REMOTE };
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const uint16_t local_port = _TEST_PORT_LOCAL;
+    sock_tcp_ep_t ep;
+
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);    /* start server on _TEST_PORT_REMOTE */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, local_port, SOCK_FLAGS_REUSE_EP));
+    assert(0 == sock_tcp_get_local(&_sock, &ep));
+    assert(AF_INET6 == ep.family);
+    assert(_TEST_PORT_LOCAL == ep.port);
+    assert(0 == sock_tcp_get_remote(&_sock, &ep));
+    assert(AF_INET6 == ep.family);
+    assert(memcmp(&remote_addr, &ep.addr.ipv6, sizeof(ipv6_addr_t)) == 0);
+    assert(SOCK_ADDR_ANY_NETIF == ep.netif);
+    assert(_TEST_PORT_REMOTE == ep.port);
+}
+
+#ifdef SO_REUSE
+static void test_tcp_listen6__EADDRINUSE(void)
+{
+    static const sock_tcp_ep_t local = { .addr = { .ipv6 = _TEST_ADDR6_LOCAL },
+                                         .family = AF_INET6,
+                                         .port = _TEST_PORT_LOCAL,
+                                         .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_LOCAL;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);    /* start server on _TEST_PORT_LOCAL */
+
+    assert(-EADDRINUSE == sock_tcp_listen(&_queue, &local, _queue_array,
+                                          _QUEUE_SIZE, 0));
+}
+#endif
+
+static void test_tcp_listen6__EAFNOSUPPORT(void)
+{
+    static const sock_tcp_ep_t local = { .addr = { .ipv6 = _TEST_ADDR6_LOCAL },
+                                         .port = _TEST_PORT_LOCAL,
+                                         .netif = SOCK_ADDR_ANY_NETIF };
+
+    assert(-EAFNOSUPPORT == sock_tcp_listen(&_queue, &local, _queue_array,
+                                            _QUEUE_SIZE, 0));
+}
+
+static void test_tcp_listen6__EINVAL(void)
+{
+    static const sock_tcp_ep_t local = { .addr = { .ipv6 = _TEST_ADDR6_LOCAL },
+                                         .family = AF_INET6,
+                                         .port = _TEST_PORT_LOCAL,
+                                         .netif = (_TEST_NETIF + 1) };
+
+    assert(-EINVAL == sock_tcp_listen(&_queue, &local, _queue_array,
+                                      _QUEUE_SIZE, 0));
+}
+
+static void test_tcp_listen6__success_any_netif(void)
+{
+    static const ipv6_addr_t local_addr = { .u8 = _TEST_ADDR6_LOCAL };
+    static const sock_tcp_ep_t local = { .addr = { .ipv6 = _TEST_ADDR6_LOCAL },
+                                         .family = AF_INET6,
+                                         .port = _TEST_PORT_LOCAL,
+                                         .netif = SOCK_ADDR_ANY_NETIF };
+    sock_tcp_ep_t ep;
+
+    assert(0 == sock_tcp_listen(&_queue, &local, _queue_array,
+                                _QUEUE_SIZE, 0));
+    assert(0 == sock_tcp_queue_get_local(&_queue, &ep));
+    assert(AF_INET6 == ep.family);
+    assert(memcmp(&local_addr, &ep.addr.ipv6, sizeof(ipv6_addr_t)) == 0);
+    assert(SOCK_ADDR_ANY_NETIF == ep.netif);
+    assert(_TEST_PORT_LOCAL == ep.port);
+}
+
+static void test_tcp_listen6__success_spec_netif(void)
+{
+    static const sock_tcp_ep_t local = { .family = AF_INET6,
+                                         .port = _TEST_PORT_LOCAL,
+                                         .netif = _TEST_NETIF };
+    sock_tcp_ep_t ep;
+
+    assert(0 == sock_tcp_listen(&_queue, &local, _queue_array,
+                                _QUEUE_SIZE, 0));
+    assert(0 == sock_tcp_queue_get_local(&_queue, &ep));
+    assert(AF_INET6 == ep.family);
+    assert(_TEST_NETIF == ep.netif);
+    assert(_TEST_PORT_LOCAL == ep.port);
+}
+
+/* ECONNABORTED can't be tested in this setup */
+
+static void test_tcp_accept6__EAGAIN(void)
+{
+    static const sock_tcp_ep_t local = { .family = AF_INET6,
+                                         .port = _TEST_PORT_LOCAL };
+    sock_tcp_t *sock;
+
+    assert(0 == sock_tcp_listen(&_queue, &local, _queue_array,
+                                _QUEUE_SIZE, 0));
+    assert(-EAGAIN == sock_tcp_accept(&_queue, &sock, 0));
+}
+
+static void test_tcp_accept6__EINVAL(void)
+{
+    sock_tcp_t *sock;
+
+    assert(-EINVAL == sock_tcp_accept(&_queue, &sock, SOCK_NO_TIMEOUT));
+}
+
+static void test_tcp_accept6__ETIMEDOUT(void)
+{
+    static const sock_tcp_ep_t local = { .family = AF_INET6,
+                                         .port = _TEST_PORT_LOCAL };
+    sock_tcp_t *sock;
+
+    assert(0 == sock_tcp_listen(&_queue, &local, _queue_array,
+                                _QUEUE_SIZE, 0));
+    puts(" * Calling sock_tcp_accept()");
+    assert(-ETIMEDOUT == sock_tcp_accept(&_queue, &sock, _TEST_TIMEOUT));
+    printf(" * (timed out with timeout %u)\n", _TEST_TIMEOUT);
+}
+
+static void test_tcp_accept6__success(void)
+{
+    static const ipv6_addr_t remote_addr = { .u8 = _TEST_ADDR6_REMOTE };
+    static const sock_tcp_ep_t local = { .family = AF_INET6,
+                                         .port = _TEST_PORT_LOCAL };
+    msg_t msg = { .type = _CLIENT_MSG_START,
+                  .content = { .value = _TEST_PORT_REMOTE } };
+    sock_tcp_ep_t ep;
+    sock_tcp_t *sock;
+
+    _server_addr.addr.ipv6[15] = 1; /* make unspecified address to loopback */
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_LOCAL;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    assert(0 == sock_tcp_listen(&_queue, &local, _queue_array,
+                                _QUEUE_SIZE, 0));
+    msg_send(&msg, _client);    /* start client on _TEST_PORT_REMOTE, connecting
+                                 * to _TEST_PORT_LOCAL */
+    assert(0 == sock_tcp_accept(&_queue, &sock, SOCK_NO_TIMEOUT));
+    assert(0 == sock_tcp_get_local(sock, &ep));
+    assert(AF_INET6 == ep.family);
+    assert(_TEST_PORT_LOCAL == ep.port);
+    assert(0 == sock_tcp_get_remote(sock, &ep));
+    assert(AF_INET6 == ep.family);
+    assert(memcmp(&remote_addr, &ep.addr.ipv6, sizeof(ipv6_addr_t)) == 0);
+    assert(SOCK_ADDR_ANY_NETIF == ep.netif);
+    assert(_TEST_PORT_REMOTE == ep.port);
+}
+
+/* ECONNABORTED can't be tested in this setup */
+
+static void test_tcp_read6__EAGAIN(void)
+{
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    assert(-EAGAIN == sock_tcp_read(&_sock, _test_buffer, sizeof(_test_buffer), 0));
+}
+
+static void test_tcp_read6__ECONNRESET(void)
+{
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    msg.type = _SERVER_MSG_CLOSE;
+    msg_send(&msg, _server);        /* close connection at server side */
+    assert(-ECONNRESET == sock_tcp_read(&_sock, _test_buffer,
+                                        sizeof(_test_buffer), SOCK_NO_TIMEOUT));
+}
+
+static void test_tcp_read6__ENOTCONN(void)
+{
+    assert(-ENOTCONN == sock_tcp_read(&_sock, _test_buffer,
+                                      sizeof(_test_buffer), SOCK_NO_TIMEOUT));
+}
+
+static void test_tcp_read6__ETIMEDOUT(void)
+{
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    puts(" * Calling sock_tcp_read()");
+    assert(-ETIMEDOUT == sock_tcp_read(&_sock, _test_buffer,
+                                       sizeof(_test_buffer), _TEST_TIMEOUT));
+    printf(" * (timed out with timeout %u)\n", _TEST_TIMEOUT);
+}
+static void test_tcp_read6__success(void)
+{
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const struct iovec exp_data = { .iov_base = "Hello!",
+                                           .iov_len = sizeof("Hello!") };
+
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    msg.type = _SERVER_MSG_WRITE;
+    msg.content.ptr = (void *)&exp_data;
+    msg_send(&msg, _server);        /* write expected data at server */
+    assert(((ssize_t)exp_data.iov_len) == sock_tcp_read(&_sock, _test_buffer,
+                                                        sizeof(_test_buffer),
+                                                        SOCK_NO_TIMEOUT));
+    assert(memcmp(exp_data.iov_base, _test_buffer, exp_data.iov_len) == 0);
+}
+
+static void test_tcp_read6__success_with_timeout(void)
+{
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const struct iovec exp_data = { .iov_base = "Hello!",
+                                           .iov_len = sizeof("Hello!") };
+
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    msg.type = _SERVER_MSG_WRITE;
+    msg.content.ptr = (void *)&exp_data;
+    msg_send(&msg, _server);        /* write expected data at server */
+    assert(((ssize_t)exp_data.iov_len) == sock_tcp_read(&_sock, _test_buffer,
+                                                        sizeof(_test_buffer),
+                                                        _TEST_TIMEOUT));
+    assert(memcmp(exp_data.iov_base, _test_buffer, exp_data.iov_len) == 0);
+}
+
+static void test_tcp_read6__success_non_blocking(void)
+{
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const struct iovec exp_data = { .iov_base = "Hello!",
+                                           .iov_len = sizeof("Hello!") };
+
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    msg.type = _SERVER_MSG_WRITE;
+    msg.content.ptr = (void *)&exp_data;
+    msg_send(&msg, _server);        /* write expected data at server */
+    assert(((ssize_t)exp_data.iov_len) == sock_tcp_read(&_sock, _test_buffer,
+                                                        sizeof(_test_buffer),
+                                                        0));
+    assert(memcmp(exp_data.iov_base, _test_buffer, exp_data.iov_len) == 0);
+}
+
+/* ENOTCONN not applicable since lwIP always tries to send */
+
+static void test_tcp_write6__ENOTCONN(void)
+{
+    assert(-ENOTCONN == sock_tcp_write(&_sock, "Hello!", sizeof("Hello!")));
+}
+
+static void test_tcp_write6__success(void)
+{
+    static const sock_tcp_ep_t remote = { .addr = { .ipv6 = _TEST_ADDR6_REMOTE },
+                                          .family = AF_INET6,
+                                          .port = _TEST_PORT_REMOTE,
+                                          .netif = SOCK_ADDR_ANY_NETIF };
+    msg_t msg = { .type = _SERVER_MSG_START };
+    static const struct iovec exp_data = { .iov_base = "Hello!",
+                                           .iov_len = sizeof("Hello!") };
+
+    _server_addr.family = AF_INET6;
+    _server_addr.port = _TEST_PORT_REMOTE;
+    _server_addr.netif = SOCK_ADDR_ANY_NETIF;
+
+    msg_send(&msg, _server);        /* start server on _TEST_PORT_LOCAL */
+    msg.type = _SERVER_MSG_ACCEPT;
+    msg_send(&msg, _server);        /* let server accept */
+
+    assert(0 == sock_tcp_connect(&_sock, &remote, 0, SOCK_FLAGS_REUSE_EP));
+    msg.type = _SERVER_MSG_READ;
+    msg.content.ptr = (void *)&exp_data;
+    msg_send(&msg, _server);        /* write expected data at server */
+    assert(((ssize_t)exp_data.iov_len) == sock_tcp_write(&_sock, "Hello!",
+                                                        sizeof("Hello!")));
+    assert(memcmp(exp_data.iov_base, _test_buffer, exp_data.iov_len) == 0);
+    xtimer_usleep(5000);            /* wait for server */
+}
+#endif /* MODULE_LWIP_IPV6 */
+
+int main(void)
+{
+    uint8_t code = 0;
+
+#ifdef SO_REUSE
+    code |= 1;
+#endif
+#ifdef MODULE_LWIP_IPV4
+    code |= (1 << 4);
+#endif
+#ifdef MODULE_LWIP_IPV6
+    code |= (1 << 6);
+#endif
+    printf("code 0x%02x\n", code);
+    xtimer_init();
+    _net_init();
+    assert(0 < thread_create(_client_stack, sizeof(_client_stack),
+                             THREAD_PRIORITY_MAIN - 1, THREAD_CREATE_STACKTEST,
+                             _client_func, NULL, "tcp_client"));
+    assert(0 < thread_create(_server_stack, sizeof(_server_stack),
+                             THREAD_PRIORITY_MAIN - 2, THREAD_CREATE_STACKTEST,
+                             _server_func, NULL, "tcp_server"));
+    tear_down();
+#ifdef MODULE_LWIP_IPV4
+#ifdef SO_REUSE
+    CALL(test_tcp_connect4__EADDRINUSE());
+#endif
+    CALL(test_tcp_connect4__EAFNOSUPPORT());
+    /* ECONNREFUSED does not apply for lwIP; netconn_connect does not wait for
+     * connection build-up */
+    CALL(test_tcp_connect4__EINVAL_addr());
+    CALL(test_tcp_connect4__EINVAL_netif());
+    /* ENETUNREACH not testable in given loopback setup */
+    /* ETIMEDOUT doesn't work because lwIP's connect doesn't timeout */
+    CALL(test_tcp_connect4__success_without_port());
+    CALL(test_tcp_connect4__success_local_port());
+#ifdef SO_REUSE
+    CALL(test_tcp_listen4__EADDRINUSE());
+#endif
+    CALL(test_tcp_listen4__EAFNOSUPPORT());
+    CALL(test_tcp_listen4__EINVAL());
+    CALL(test_tcp_listen4__success_any_netif());
+    CALL(test_tcp_listen4__success_spec_netif());
+    /* sock_tcp_disconnect() is tested in tear_down() */
+    /* sock_tcp_stop_listen() is tested in tear_down() */
+    /* sock_tcp_get_local() is tested in sock_tcp_connect() tests */
+    /* sock_tcp_get_remote() is tested in sock_tcp_connect() tests */
+    /* sock_tcp_queue_get_local() is tested in sock_tcp_listen() tests */
+    /* ECONNABORTED can't be tested in this setup */
+    CALL(test_tcp_accept4__EAGAIN());
+    CALL(test_tcp_accept4__EINVAL());
+    CALL(test_tcp_accept4__ETIMEDOUT());
+    CALL(test_tcp_accept4__success());
+    /* ECONNABORTED can't be tested in this setup */
+    CALL(test_tcp_read4__EAGAIN());
+    CALL(test_tcp_read4__ECONNRESET());
+    CALL(test_tcp_read4__ENOTCONN());
+    CALL(test_tcp_read4__ETIMEDOUT());
+    CALL(test_tcp_read4__success());
+    CALL(test_tcp_read4__success_with_timeout());
+    CALL(test_tcp_read4__success_non_blocking());
+    /* ECONNABORTED can't be tested in this setup */
+    /* ENOTCONN not applicable since lwIP always tries to send */
+    CALL(test_tcp_write4__ENOTCONN());
+    CALL(test_tcp_write4__success());
+#endif /* MODULE_LWIP_IPV4 */
+#ifdef MODULE_LWIP_IPV6
+#ifdef SO_REUSE
+    CALL(test_tcp_connect6__EADDRINUSE());
+#endif
+    CALL(test_tcp_connect6__EAFNOSUPPORT());
+    /* ECONNREFUSED does not apply for lwIP; netconn_connect does not wait for
+     * connection build-up */
+    CALL(test_tcp_connect6__EINVAL_addr());
+    CALL(test_tcp_connect6__EINVAL_netif());
+    /* ENETUNREACH not testable in given loopback setup */
+    /* ETIMEDOUT doesn't work because lwIP's connect doesn't timeout */
+    CALL(test_tcp_connect6__success_without_port());
+    CALL(test_tcp_connect6__success_local_port());
+#ifdef SO_REUSE
+    CALL(test_tcp_listen6__EADDRINUSE());
+#endif
+    CALL(test_tcp_listen6__EAFNOSUPPORT());
+    CALL(test_tcp_listen6__EINVAL());
+    CALL(test_tcp_listen6__success_any_netif());
+    CALL(test_tcp_listen6__success_spec_netif());
+    /* sock_tcp_disconnect() is tested in tear_down() */
+    /* sock_tcp_stop_listen() is tested in tear_down() */
+    /* sock_tcp_get_local() is tested in sock_tcp_connect() tests */
+    /* sock_tcp_get_remote() is tested in sock_tcp_connect() tests */
+    /* sock_tcp_queue_get_local() is tested in sock_tcp_listen() tests */
+    /* ECONNABORTED can't be tested in this setup */
+    CALL(test_tcp_accept6__EAGAIN());
+    CALL(test_tcp_accept6__EINVAL());
+    CALL(test_tcp_accept6__ETIMEDOUT());
+    CALL(test_tcp_accept6__success());
+    /* ECONNABORTED can't be tested in this setup */
+    CALL(test_tcp_read6__EAGAIN());
+    CALL(test_tcp_read6__ECONNRESET());
+    CALL(test_tcp_read6__ENOTCONN());
+    CALL(test_tcp_read6__ETIMEDOUT());
+    CALL(test_tcp_read6__success());
+    CALL(test_tcp_read6__success_with_timeout());
+    CALL(test_tcp_read6__success_non_blocking());
+    /* ECONNABORTED can't be tested in this setup */
+    /* ENOTCONN not applicable since lwIP always tries to send */
+    CALL(test_tcp_write6__ENOTCONN());
+    CALL(test_tcp_write6__success());
+#endif /* MODULE_LWIP_IPV6 */
+
+    puts("ALL TESTS SUCCESSFUL");
+
+    return 0;
+}
+
+static void *_server_func(void *arg)
+{
+    bool server_started = false;
+    sock_tcp_t *sock = NULL;
+
+    (void)arg;
+    msg_init_queue(_server_msg_queue, _MSG_QUEUE_SIZE);
+    _server = sched_active_pid;
+    while (1) {
+        msg_t msg;
+
+        msg_receive(&msg);
+        switch (msg.type) {
+            case _SERVER_MSG_START:
+                if (!server_started) {
+                    assert(0 == sock_tcp_listen(&_server_queue, &_server_addr,
+                                                _server_queue_array,
+                                                _SERVER_QUEUE_SIZE,
+                                                SOCK_FLAGS_REUSE_EP));
+                    server_started = true;
+                }
+                break;
+            case _SERVER_MSG_ACCEPT:
+                if (server_started) {
+                    assert(0 == sock_tcp_accept(&_server_queue, &sock,
+                                                SOCK_NO_TIMEOUT));
+                }
+                break;
+            case _SERVER_MSG_READ:
+                if (sock != NULL) {
+                    const struct iovec *exp = msg.content.ptr;
+
+                    assert(((ssize_t)exp->iov_len) ==
+                           sock_tcp_read(sock, _server_buf, sizeof(_server_buf),
+                                         SOCK_NO_TIMEOUT));
+                    assert(memcmp(exp->iov_base, _server_buf, exp->iov_len) == 0);
+                }
+                break;
+            case _SERVER_MSG_WRITE:
+                if (sock != NULL) {
+                    const struct iovec *data = msg.content.ptr;
+
+                    assert(((ssize_t)data->iov_len) ==
+                           sock_tcp_write(sock, data->iov_base, data->iov_len));
+                }
+                break;
+            case _SERVER_MSG_CLOSE:
+                if (sock != NULL) {
+                    sock_tcp_disconnect(sock);
+                    sock = NULL;
+                }
+                break;
+            case _SERVER_MSG_STOP:
+                if (server_started) {
+                    sock_tcp_stop_listen(&_server_queue);
+                    server_started = false;
+                    /* sock_tcp_stop_listen is also supposed to close sock */
+                    sock = NULL;
+                }
+                break;
+            default:
+                break;
+        }
+    }
+    return NULL;
+}
+
+static void *_client_func(void *arg)
+{
+    bool client_started = false;
+
+    (void)arg;
+    msg_init_queue(_client_msg_queue, _MSG_QUEUE_SIZE);
+    _client = sched_active_pid;
+    while (1) {
+        msg_t msg;
+
+        msg_receive(&msg);
+        switch (msg.type) {
+            case _CLIENT_MSG_START:
+                if (!client_started) {
+                    const uint16_t local_port = (uint16_t)msg.content.value;
+                    assert(0 == sock_tcp_connect(&_client_sock, &_server_addr,
+                                                 local_port, SOCK_FLAGS_REUSE_EP));
+                    client_started = true;
+                }
+                break;
+            case _CLIENT_MSG_READ:
+                if (client_started) {
+                    const struct iovec *exp = msg.content.ptr;
+
+                    assert(((ssize_t)exp->iov_len) ==
+                           sock_tcp_read(&_client_sock, _client_buf,
+                                         sizeof(_client_buf), SOCK_NO_TIMEOUT));
+                    assert(memcmp(exp->iov_base, _client_buf, exp->iov_len) == 0);
+                }
+                break;
+            case _CLIENT_MSG_WRITE:
+                if (client_started) {
+                    const struct iovec *data = msg.content.ptr;
+
+                    assert(((ssize_t)data->iov_len) ==
+                           sock_tcp_write(&_client_sock, data->iov_base,
+                                          data->iov_len));
+                }
+                break;
+            case _CLIENT_MSG_STOP:
+                if (client_started) {
+                    sock_tcp_disconnect(&_client_sock);
+                    memset(&_client_sock, 0, sizeof(sock_tcp_t));
+                    client_started = false;
+                }
+                break;
+            default:
+                break;
+        }
+    }
+    return NULL;
+}
diff --git a/tests/lwip_sock_tcp/stack.c b/tests/lwip_sock_tcp/stack.c
new file mode 100644
index 0000000000000000000000000000000000000000..879f751b21f5d776e6aa73a1fb2d89ea845a307f
--- /dev/null
+++ b/tests/lwip_sock_tcp/stack.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 Freie Universität Berlin
+ *
+ * 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
+ * @author  Martine Lenders <mlenders@inf.fu-berlin.de>
+ */
+
+
+#include "xtimer.h"
+
+#include "lwip.h"
+#include "lwip/netif.h"
+
+#include "stack.h"
+
+void _net_init(void)
+{
+    xtimer_init();
+    lwip_bootstrap();
+}
+
+/** @} */
diff --git a/tests/lwip_sock_tcp/stack.h b/tests/lwip_sock_tcp/stack.h
new file mode 100644
index 0000000000000000000000000000000000000000..57221f65d6b97f92e29cde7779b2af4dcc2facf5
--- /dev/null
+++ b/tests/lwip_sock_tcp/stack.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 Freie Universität Berlin
+ *
+ * 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
+ * @ingroup
+ * @brief
+ * @{
+ *
+ * @file
+ * @brief
+ *
+ * @author  Martine Lenders <mlenders@inf.fu-berlin.de>
+ */
+#ifndef STACK_H_
+#define STACK_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Initializes networking for tests
+ */
+void _net_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* STACK_H_ */
+/** @} */
diff --git a/tests/lwip_sock_tcp/tests/01-run.py b/tests/lwip_sock_tcp/tests/01-run.py
new file mode 100755
index 0000000000000000000000000000000000000000..d024d6494fd34ab0793b702172a16a6d6c4f683a
--- /dev/null
+++ b/tests/lwip_sock_tcp/tests/01-run.py
@@ -0,0 +1,141 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2016 Freie Universität Berlin
+#
+# This file is subject to the terms and conditions of the GNU Lesser
+# General Public License v2.1. See the file LICENSE in the top level
+# directory for more details.
+
+import os
+import sys
+
+from datetime import datetime
+
+sys.path.append(os.path.join(os.environ['RIOTBASE'], 'dist/tools/testrunner'))
+import testrunner
+
+class InvalidTimeout(Exception):
+    pass
+
+def _reuse_tests(code):
+    return code & 1
+
+def _ipv6_tests(code):
+    return code & (1 << 6)
+
+def _ipv4_tests(code):
+    return code & (1 << 4)
+
+def testfunc(child):
+    child.expect(u"code (0x[0-9a-f]{2})")
+    code = int(child.match.group(1), base=16)
+    if _ipv4_tests(code):
+        if _reuse_tests(code):
+            child.expect_exact("Calling test_tcp_connect4__EADDRINUSE()")
+        child.expect_exact("Calling test_tcp_connect4__EAFNOSUPPORT()")
+        child.expect_exact("Calling test_tcp_connect4__EINVAL_addr()")
+        child.expect_exact("Calling test_tcp_connect4__EINVAL_netif()")
+        child.expect_exact("Calling test_tcp_connect4__success_without_port()")
+        child.expect_exact("Calling test_tcp_connect4__success_local_port()")
+        if _reuse_tests(code):
+            child.expect_exact("Calling test_tcp_listen4__EADDRINUSE()")
+        child.expect_exact("Calling test_tcp_listen4__EAFNOSUPPORT()")
+        child.expect_exact("Calling test_tcp_listen4__EINVAL()")
+        child.expect_exact("Calling test_tcp_listen4__success_any_netif()")
+        child.expect_exact("Calling test_tcp_listen4__success_spec_netif()")
+        child.expect_exact("Calling test_tcp_accept4__EAGAIN()")
+        child.expect_exact("Calling test_tcp_accept4__EINVAL()")
+        child.expect_exact("Calling test_tcp_accept4__ETIMEDOUT()")
+        start = datetime.now()
+        child.expect_exact(" * Calling sock_tcp_accept()")
+        child.expect(u" \\* \\(timed out with timeout (\\d+)\\)")
+        exp_diff = int(child.match.group(1))
+        stop = datetime.now()
+        diff = (stop - start)
+        diff = (diff.seconds * 1000000) + diff.microseconds
+        # fail within 5% of expected
+        if diff > (exp_diff + (exp_diff * 0.05)) or \
+           diff < (exp_diff - (exp_diff * 0.05)):
+            raise InvalidTimeout("Invalid timeout %d (expected %d)" % (diff, exp_diff));
+        else:
+            print("Timed out correctly: %d (expected %d)" % (diff, exp_diff))
+        child.expect_exact("Calling test_tcp_accept4__success()")
+        child.expect_exact("Calling test_tcp_read4__EAGAIN()")
+        child.expect_exact("Calling test_tcp_read4__ECONNRESET()")
+        child.expect_exact("Calling test_tcp_read4__ENOTCONN()")
+        child.expect_exact("Calling test_tcp_read4__ETIMEDOUT()")
+        start = datetime.now()
+        child.expect_exact(" * Calling sock_tcp_read()")
+        child.expect(u" \\* \\(timed out with timeout (\\d+)\\)")
+        exp_diff = int(child.match.group(1))
+        stop = datetime.now()
+        diff = (stop - start)
+        diff = (diff.seconds * 1000000) + diff.microseconds
+        # fail within 5% of expected
+        if diff > (exp_diff + (exp_diff * 0.05)) or \
+           diff < (exp_diff - (exp_diff * 0.05)):
+            raise InvalidTimeout("Invalid timeout %d (expected %d)" % (diff, exp_diff));
+        else:
+            print("Timed out correctly: %d (expected %d)" % (diff, exp_diff))
+        child.expect_exact("Calling test_tcp_read4__success()")
+        child.expect_exact("Calling test_tcp_read4__success_with_timeout()")
+        child.expect_exact("Calling test_tcp_read4__success_non_blocking()")
+        child.expect_exact("Calling test_tcp_write4__ENOTCONN()")
+        child.expect_exact("Calling test_tcp_write4__success()")
+    if _ipv6_tests(code):
+        if _reuse_tests(code):
+            child.expect_exact("Calling test_tcp_connect6__EADDRINUSE()")
+        child.expect_exact("Calling test_tcp_connect6__EAFNOSUPPORT()")
+        child.expect_exact("Calling test_tcp_connect6__EINVAL_addr()")
+        child.expect_exact("Calling test_tcp_connect6__EINVAL_netif()")
+        child.expect_exact("Calling test_tcp_connect6__success_without_port()")
+        child.expect_exact("Calling test_tcp_connect6__success_local_port()")
+        if _reuse_tests(code):
+            child.expect_exact("Calling test_tcp_listen6__EADDRINUSE()")
+        child.expect_exact("Calling test_tcp_listen6__EAFNOSUPPORT()")
+        child.expect_exact("Calling test_tcp_listen6__EINVAL()")
+        child.expect_exact("Calling test_tcp_listen6__success_any_netif()")
+        child.expect_exact("Calling test_tcp_listen6__success_spec_netif()")
+        child.expect_exact("Calling test_tcp_accept6__EAGAIN()")
+        child.expect_exact("Calling test_tcp_accept6__EINVAL()")
+        child.expect_exact("Calling test_tcp_accept6__ETIMEDOUT()")
+        start = datetime.now()
+        child.expect_exact(" * Calling sock_tcp_accept()")
+        child.expect(u" \\* \\(timed out with timeout (\\d+)\\)")
+        exp_diff = int(child.match.group(1))
+        stop = datetime.now()
+        diff = (stop - start)
+        diff = (diff.seconds * 1000000) + diff.microseconds
+        # fail within 5% of expected
+        if diff > (exp_diff + (exp_diff * 0.05)) or \
+           diff < (exp_diff - (exp_diff * 0.05)):
+            raise InvalidTimeout("Invalid timeout %d (expected %d)" % (diff, exp_diff));
+        else:
+            print("Timed out correctly: %d (expected %d)" % (diff, exp_diff))
+        child.expect_exact("Calling test_tcp_accept6__success()")
+        child.expect_exact("Calling test_tcp_read6__EAGAIN()")
+        child.expect_exact("Calling test_tcp_read6__ECONNRESET()")
+        child.expect_exact("Calling test_tcp_read6__ENOTCONN()")
+        child.expect_exact("Calling test_tcp_read6__ETIMEDOUT()")
+        start = datetime.now()
+        child.expect_exact(" * Calling sock_tcp_read()")
+        child.expect(u" \\* \\(timed out with timeout (\\d+)\\)")
+        exp_diff = int(child.match.group(1))
+        stop = datetime.now()
+        diff = (stop - start)
+        diff = (diff.seconds * 1000000) + diff.microseconds
+        # fail within 5% of expected
+        if diff > (exp_diff + (exp_diff * 0.05)) or \
+           diff < (exp_diff - (exp_diff * 0.05)):
+            raise InvalidTimeout("Invalid timeout %d (expected %d)" % (diff, exp_diff));
+        else:
+            print("Timed out correctly: %d (expected %d)" % (diff, exp_diff))
+        child.expect_exact("Calling test_tcp_read6__success()")
+        child.expect_exact("Calling test_tcp_read6__success_with_timeout()")
+        child.expect_exact("Calling test_tcp_read6__success_non_blocking()")
+        child.expect_exact("Calling test_tcp_write6__ENOTCONN()")
+        child.expect_exact("Calling test_tcp_write6__success()")
+    child.expect_exact(u"ALL TESTS SUCCESSFUL")
+
+if __name__ == "__main__":
+    sys.exit(testrunner.run(testfunc, timeout=60))