Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b

Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/src/tcp/tcp.c b/src/tcp/tcp.c
new file mode 100644
index 0000000..f146b87
--- /dev/null
+++ b/src/tcp/tcp.c
@@ -0,0 +1,1378 @@
+/**
+ * @file tcp.c  Transport Control Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_IO_H
+#include <io.h>
+#endif
+#if !defined(WIN32)
+#define __USE_POSIX 1  /**< Use POSIX flag */
+#define __USE_XOPEN2K 1/**< Use POSIX.1:2001 code */
+#define __USE_MISC 1
+#include <netdb.h>
+#endif
+#ifdef __APPLE__
+#include "TargetConditionals.h"
+#endif
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_main.h>
+#include <re_sa.h>
+#include <re_net.h>
+#include <re_tcp.h>
+
+
+#define DEBUG_MODULE "tcp"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Platform independent buffer type cast */
+#ifdef WIN32
+#define BUF_CAST (char *)
+#define SOK_CAST (int)
+#define SIZ_CAST (int)
+#define close closesocket
+#else
+#define BUF_CAST
+#define SOK_CAST
+#define SIZ_CAST
+#endif
+
+
+enum {
+	TCP_TXQSZ_DEFAULT = 524288,
+	TCP_RXSZ_DEFAULT  = 8192
+};
+
+
+/** Defines a listening TCP socket */
+struct tcp_sock {
+	int fd;               /**< Listening file descriptor         */
+	int fdc;              /**< Cached connection file descriptor */
+	tcp_conn_h *connh;    /**< TCP Connect handler               */
+	void *arg;            /**< Handler argument                  */
+};
+
+
+/** Defines a TCP connection */
+struct tcp_conn {
+	struct list helpers;  /**< List of TCP-helpers               */
+	struct list sendq;    /**< Sending queue                     */
+	int fdc;              /**< Connection file descriptor        */
+	tcp_estab_h *estabh;  /**< Connection established handler    */
+	tcp_send_h *sendh;    /**< Data send handler                 */
+	tcp_recv_h *recvh;    /**< Data receive handler              */
+	tcp_close_h *closeh;  /**< Connection close handler          */
+	void *arg;            /**< Handler argument                  */
+	size_t rxsz;          /**< Maximum receive chunk size        */
+	size_t txqsz;
+	size_t txqsz_max;
+	bool active;          /**< We are connecting flag            */
+	bool connected;       /**< Connection is connected flag      */
+};
+
+
+/** Defines a TCP-Connection Helper */
+struct tcp_helper {
+	struct le le;
+	int layer;
+	tcp_helper_estab_h *estabh;
+	tcp_helper_send_h *sendh;
+	tcp_helper_recv_h *recvh;
+	void *arg;
+};
+
+
+struct tcp_qent {
+	struct le le;
+	struct mbuf mb;
+};
+
+
+static void tcp_recv_handler(int flags, void *arg);
+
+
+static bool helper_estab_handler(int *err, bool active, void *arg)
+{
+	(void)err;
+	(void)active;
+	(void)arg;
+	return false;
+}
+
+
+static bool helper_send_handler(int *err, struct mbuf *mb, void *arg)
+{
+	(void)err;
+	(void)mb;
+	(void)arg;
+	return false;
+}
+
+
+static bool helper_recv_handler(int *err, struct mbuf *mb, bool *estab,
+				void *arg)
+{
+	(void)err;
+	(void)mb;
+	(void)estab;
+	(void)arg;
+	return false;
+}
+
+
+static void sock_destructor(void *data)
+{
+	struct tcp_sock *ts = data;
+
+	if (ts->fd >= 0) {
+		fd_close(ts->fd);
+		(void)close(ts->fd);
+	}
+	if (ts->fdc >= 0)
+		(void)close(ts->fdc);
+}
+
+
+static void conn_destructor(void *data)
+{
+	struct tcp_conn *tc = data;
+
+	list_flush(&tc->helpers);
+	list_flush(&tc->sendq);
+
+	if (tc->fdc >= 0) {
+		fd_close(tc->fdc);
+		(void)close(tc->fdc);
+	}
+}
+
+
+static void helper_destructor(void *data)
+{
+	struct tcp_helper *th = data;
+
+	list_unlink(&th->le);
+}
+
+
+static void qent_destructor(void *arg)
+{
+	struct tcp_qent *qe = arg;
+
+	list_unlink(&qe->le);
+	mem_deref(qe->mb.buf);
+}
+
+
+static int enqueue(struct tcp_conn *tc, struct mbuf *mb)
+{
+	const size_t n = mbuf_get_left(mb);
+	struct tcp_qent *qe;
+	int err;
+
+	if (tc->txqsz + n > tc->txqsz_max)
+		return ENOSPC;
+
+	if (!tc->sendq.head && !tc->sendh) {
+
+		err = fd_listen(tc->fdc, FD_READ | FD_WRITE,
+				tcp_recv_handler, tc);
+		if (err)
+			return err;
+	}
+
+	qe = mem_zalloc(sizeof(*qe), qent_destructor);
+	if (!qe)
+		return ENOMEM;
+
+	list_append(&tc->sendq, &qe->le, qe);
+
+	mbuf_init(&qe->mb);
+
+	err = mbuf_write_mem(&qe->mb, mbuf_buf(mb), n);
+	qe->mb.pos = 0;
+
+	if (err)
+		mem_deref(qe);
+	else
+		tc->txqsz += qe->mb.end;
+
+	return err;
+}
+
+
+static int dequeue(struct tcp_conn *tc)
+{
+	struct tcp_qent *qe = list_ledata(tc->sendq.head);
+	ssize_t n;
+#ifdef MSG_NOSIGNAL
+	const int flags = MSG_NOSIGNAL; /* disable SIGPIPE signal */
+#else
+	const int flags = 0;
+#endif
+	if (!qe) {
+		if (tc->sendh)
+			tc->sendh(tc->arg);
+
+		return 0;
+	}
+
+	n = send(tc->fdc, BUF_CAST mbuf_buf(&qe->mb),
+		 qe->mb.end - qe->mb.pos, flags);
+	if (n < 0) {
+		if (EAGAIN == errno)
+			return 0;
+#ifdef WIN32
+		if (WSAEWOULDBLOCK == WSAGetLastError())
+			return 0;
+#endif
+		return errno;
+	}
+
+	tc->txqsz  -= n;
+	qe->mb.pos += n;
+
+	if (qe->mb.pos >= qe->mb.end)
+		mem_deref(qe);
+
+	return 0;
+}
+
+
+static void conn_close(struct tcp_conn *tc, int err)
+{
+	list_flush(&tc->sendq);
+	tc->txqsz = 0;
+
+	/* Stop polling */
+	if (tc->fdc >= 0) {
+		fd_close(tc->fdc);
+		(void)close(tc->fdc);
+		tc->fdc = -1;
+	}
+
+	if (tc->closeh)
+		tc->closeh(err, tc->arg);
+}
+
+
+static void tcp_recv_handler(int flags, void *arg)
+{
+	struct tcp_conn *tc = arg;
+	struct mbuf *mb = NULL;
+	bool hlp_estab = false;
+	struct le *le;
+	ssize_t n;
+	int err;
+	socklen_t err_len = sizeof(err);
+
+	if (flags & FD_EXCEPT) {
+		DEBUG_INFO("recv handler: got FD_EXCEPT on fd=%d\n", tc->fdc);
+	}
+
+	/* check for any errors */
+	if (-1 == getsockopt(tc->fdc, SOL_SOCKET, SO_ERROR,
+			     BUF_CAST &err, &err_len)) {
+		DEBUG_WARNING("recv handler: getsockopt: (%m)\n", errno);
+		return;
+	}
+
+	if (err) {
+		conn_close(tc, err);
+		return;
+	}
+#if 0
+	if (EINPROGRESS != err && EALREADY != err) {
+		DEBUG_WARNING("recv handler: Socket error (%m)\n", err);
+		return;
+	}
+#endif
+
+	if (flags & FD_WRITE) {
+
+		if (tc->connected) {
+
+			uint32_t nrefs;
+
+			mem_ref(tc);
+
+			err = dequeue(tc);
+
+			nrefs = mem_nrefs(tc);
+			mem_deref(tc);
+
+			/* check if connection was deref'd from send handler */
+			if (nrefs == 1)
+				return;
+
+			if (err) {
+				conn_close(tc, err);
+				return;
+			}
+
+			if (!tc->sendq.head && !tc->sendh) {
+
+				err = fd_listen(tc->fdc, FD_READ,
+						tcp_recv_handler, tc);
+				if (err) {
+					conn_close(tc, err);
+					return;
+				}
+			}
+
+			if (flags & FD_READ)
+				goto read;
+
+			return;
+		}
+
+		tc->connected = true;
+
+		err = fd_listen(tc->fdc, FD_READ, tcp_recv_handler, tc);
+		if (err) {
+			DEBUG_WARNING("recv handler: fd_listen(): %m\n", err);
+			conn_close(tc, err);
+			return;
+		}
+
+		le = tc->helpers.head;
+		while (le) {
+			struct tcp_helper *th = le->data;
+
+			le = le->next;
+
+			if (th->estabh(&err, tc->active, th->arg) || err) {
+				if (err)
+					conn_close(tc, err);
+				return;
+			}
+		}
+
+		if (tc->estabh)
+			tc->estabh(tc->arg);
+
+		return;
+	}
+
+ read:
+	mb = mbuf_alloc(tc->rxsz);
+	if (!mb)
+		return;
+
+	n = recv(tc->fdc, BUF_CAST mb->buf, mb->size, 0);
+	if (0 == n) {
+		mem_deref(mb);
+		conn_close(tc, 0);
+		return;
+	}
+	else if (n < 0) {
+		DEBUG_WARNING("recv handler: recv(): %m\n", errno);
+		goto out;
+	}
+
+	mb->end = n;
+
+	le = tc->helpers.head;
+	while (le) {
+		struct tcp_helper *th = le->data;
+		bool hdld = false;
+
+		le = le->next;
+
+		if (hlp_estab) {
+
+			hdld |= th->estabh(&err, tc->active, th->arg);
+			if (err) {
+				conn_close(tc, err);
+				goto out;
+			}
+		}
+
+		if (mb->pos < mb->end) {
+
+		        hdld |= th->recvh(&err, mb, &hlp_estab, th->arg);
+			if (err) {
+				conn_close(tc, err);
+				goto out;
+			}
+		}
+
+		if (hdld)
+			goto out;
+	}
+
+	mbuf_trim(mb);
+
+	if (hlp_estab && tc->estabh) {
+
+		uint32_t nrefs;
+
+		mem_ref(tc);
+
+		tc->estabh(tc->arg);
+
+		nrefs = mem_nrefs(tc);
+		mem_deref(tc);
+
+		/* check if connection was deref'ed from establish handler */
+		if (nrefs == 1)
+			goto out;
+	}
+
+	if (mb->pos < mb->end && tc->recvh) {
+		tc->recvh(mb, tc->arg);
+	}
+
+ out:
+	mem_deref(mb);
+}
+
+
+static struct tcp_conn *conn_alloc(tcp_estab_h *eh, tcp_recv_h *rh,
+				   tcp_close_h *ch, void *arg)
+{
+	struct tcp_conn *tc;
+
+	tc = mem_zalloc(sizeof(*tc), conn_destructor);
+	if (!tc)
+		return NULL;
+
+	list_init(&tc->helpers);
+
+	tc->fdc    = -1;
+	tc->rxsz   = TCP_RXSZ_DEFAULT;
+	tc->txqsz_max = TCP_TXQSZ_DEFAULT;
+	tc->estabh = eh;
+	tc->recvh  = rh;
+	tc->closeh = ch;
+	tc->arg    = arg;
+
+	return tc;
+}
+
+
+static void tcp_sockopt_set(int fd)
+{
+#ifdef SO_LINGER
+	const struct linger dl = {0, 0};
+	int err;
+
+	err = setsockopt(fd, SOL_SOCKET, SO_LINGER, BUF_CAST &dl, sizeof(dl));
+	if (err) {
+		DEBUG_WARNING("sockopt: SO_LINGER (%m)\n", err);
+	}
+#else
+	(void)fd;
+#endif
+}
+
+
+/**
+ * Handler for incoming TCP connections.
+ *
+ * @param flags  Event flags.
+ * @param arg    Handler argument.
+ */
+static void tcp_conn_handler(int flags, void *arg)
+{
+	struct sa peer;
+	struct tcp_sock *ts = arg;
+	int err;
+
+	(void)flags;
+
+	sa_init(&peer, AF_UNSPEC);
+
+	if (ts->fdc >= 0)
+		(void)close(ts->fdc);
+
+	ts->fdc = SOK_CAST accept(ts->fd, &peer.u.sa, &peer.len);
+	if (-1 == ts->fdc) {
+
+#if TARGET_OS_IPHONE
+		if (EAGAIN == errno) {
+
+			struct tcp_sock *ts_new;
+			struct sa laddr;
+
+			err = tcp_sock_local_get(ts, &laddr);
+			if (err)
+				return;
+
+			if (ts->fd >= 0) {
+				fd_close(ts->fd);
+				(void)close(ts->fd);
+				ts->fd = -1;
+			}
+
+			err = tcp_listen(&ts_new, &laddr, NULL, NULL);
+			if (err)
+				return;
+
+			ts->fd = ts_new->fd;
+			ts_new->fd = -1;
+
+			mem_deref(ts_new);
+
+			fd_listen(ts->fd, FD_READ, tcp_conn_handler, ts);
+		}
+#endif
+
+		return;
+	}
+
+	err = net_sockopt_blocking_set(ts->fdc, false);
+	if (err) {
+		DEBUG_WARNING("conn handler: nonblock set: %m\n", err);
+		(void)close(ts->fdc);
+		ts->fdc = -1;
+		return;
+	}
+
+	tcp_sockopt_set(ts->fdc);
+
+	if (ts->connh)
+		ts->connh(&peer, ts->arg);
+}
+
+
+/**
+ * Create a TCP Socket
+ *
+ * @param tsp   Pointer to returned TCP Socket
+ * @param local Local listen address (NULL for any)
+ * @param ch    Incoming connection handler
+ * @param arg   Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_sock_alloc(struct tcp_sock **tsp, const struct sa *local,
+		   tcp_conn_h *ch, void *arg)
+{
+	struct addrinfo hints, *res = NULL, *r;
+	char addr[64] = "";
+	char serv[6] = "0";
+	struct tcp_sock *ts = NULL;
+	int error, err;
+
+	if (!tsp)
+		return EINVAL;
+
+	ts = mem_zalloc(sizeof(*ts), sock_destructor);
+	if (!ts)
+		return ENOMEM;
+
+	ts->fd  = -1;
+	ts->fdc = -1;
+
+	if (local) {
+		(void)re_snprintf(addr, sizeof(addr), "%H",
+				  sa_print_addr, local);
+		(void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local));
+	}
+
+	memset(&hints, 0, sizeof(hints));
+	/* set-up hints structure */
+	hints.ai_family   = PF_UNSPEC;
+	hints.ai_flags    = AI_PASSIVE | AI_NUMERICHOST;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+
+	error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res);
+	if (error) {
+#ifdef WIN32
+		DEBUG_WARNING("listen: getaddrinfo: wsaerr=%d\n",
+			      WSAGetLastError());
+#endif
+		DEBUG_WARNING("listen: getaddrinfo: %s:%s error=%d (%s)\n",
+			      addr, serv, error, gai_strerror(error));
+		err = EADDRNOTAVAIL;
+		goto out;
+	}
+
+	err = EINVAL;
+	for (r = res; r; r = r->ai_next) {
+		int fd = -1;
+
+		if (ts->fd >= 0)
+			continue;
+
+		fd = SOK_CAST socket(r->ai_family, SOCK_STREAM, IPPROTO_TCP);
+		if (fd < 0) {
+			err = errno;
+			continue;
+		}
+
+		(void)net_sockopt_reuse_set(fd, true);
+
+		err = net_sockopt_blocking_set(fd, false);
+		if (err) {
+			DEBUG_WARNING("listen: nonblock set: %m\n", err);
+			(void)close(fd);
+			continue;
+		}
+
+		tcp_sockopt_set(fd);
+
+		/* OK */
+		ts->fd = fd;
+		err = 0;
+		break;
+	}
+
+	freeaddrinfo(res);
+
+	if (-1 == ts->fd)
+		goto out;
+
+	ts->connh = ch;
+	ts->arg   = arg;
+
+ out:
+	if (err)
+		mem_deref(ts);
+	else
+		*tsp = ts;
+
+	return err;
+}
+
+
+/**
+ * Bind to a TCP Socket
+ *
+ * @param ts    TCP Socket
+ * @param local Local bind address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_sock_bind(struct tcp_sock *ts, const struct sa *local)
+{
+	struct addrinfo hints, *res = NULL, *r;
+	char addr[64] = "";
+	char serv[NI_MAXSERV] = "0";
+	int error, err;
+
+	if (!ts || ts->fd<0)
+		return EINVAL;
+
+	if (local) {
+		(void)re_snprintf(addr, sizeof(addr), "%H",
+				  sa_print_addr, local);
+		(void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local));
+	}
+
+	memset(&hints, 0, sizeof(hints));
+	/* set-up hints structure */
+	hints.ai_family   = PF_UNSPEC;
+	hints.ai_flags    = AI_PASSIVE | AI_NUMERICHOST;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+
+	error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res);
+	if (error) {
+#ifdef WIN32
+		DEBUG_WARNING("sock_bind: getaddrinfo: wsaerr=%d\n",
+			      WSAGetLastError());
+#endif
+		DEBUG_WARNING("sock_bind: getaddrinfo: %s:%s error=%d (%s)\n",
+			      addr, serv, error, gai_strerror(error));
+		return EADDRNOTAVAIL;
+	}
+
+	err = EINVAL;
+	for (r = res; r; r = r->ai_next) {
+
+		if (bind(ts->fd, r->ai_addr, SIZ_CAST r->ai_addrlen) < 0) {
+			err = errno;
+			DEBUG_WARNING("sock_bind: bind: %m (af=%d, %J)\n",
+				      err, r->ai_family, local);
+			continue;
+		}
+
+		/* OK */
+		err = 0;
+		break;
+	}
+
+	freeaddrinfo(res);
+
+	return err;
+}
+
+
+/**
+ * Listen on a TCP Socket
+ *
+ * @param ts       TCP Socket
+ * @param backlog  Maximum length the queue of pending connections
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_sock_listen(struct tcp_sock *ts, int backlog)
+{
+	int err;
+
+	if (!ts)
+		return EINVAL;
+
+	if (ts->fd < 0) {
+		DEBUG_WARNING("sock_listen: invalid fd\n");
+		return EBADF;
+	}
+
+	if (listen(ts->fd, backlog) < 0) {
+		err = errno;
+		DEBUG_WARNING("sock_listen: listen(): %m\n", err);
+		return err;
+	}
+
+	return fd_listen(ts->fd, FD_READ, tcp_conn_handler, ts);
+}
+
+
+/**
+ * Accept an incoming TCP Connection
+ *
+ * @param tcp Returned TCP Connection object
+ * @param ts  Corresponding TCP Socket
+ * @param eh  TCP Connection Established handler
+ * @param rh  TCP Connection Receive data handler
+ * @param ch  TCP Connection close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_accept(struct tcp_conn **tcp, struct tcp_sock *ts, tcp_estab_h *eh,
+	       tcp_recv_h *rh, tcp_close_h *ch, void *arg)
+{
+	struct tcp_conn *tc;
+	int err;
+
+	if (!tcp || !ts || ts->fdc < 0)
+		return EINVAL;
+
+	tc = conn_alloc(eh, rh, ch, arg);
+	if (!tc)
+		return ENOMEM;
+
+	/* Transfer ownership to TCP connection */
+	tc->fdc = ts->fdc;
+	ts->fdc = -1;
+
+	err = fd_listen(tc->fdc, FD_READ | FD_WRITE | FD_EXCEPT,
+			tcp_recv_handler, tc);
+	if (err) {
+		DEBUG_WARNING("accept: fd_listen(): %m\n", err);
+	}
+
+	if (err)
+		mem_deref(tc);
+	else
+		*tcp = tc;
+
+	return err;
+}
+
+
+/**
+ * Reject an incoming TCP Connection
+ *
+ * @param ts  Corresponding TCP Socket
+ */
+void tcp_reject(struct tcp_sock *ts)
+{
+	if (!ts)
+		return;
+
+	if (ts->fdc >= 0) {
+		(void)close(ts->fdc);
+		ts->fdc = -1;
+	}
+}
+
+
+/**
+ * Allocate a TCP Connection
+ *
+ * @param tcp  Returned TCP Connection object
+ * @param peer Network address of peer
+ * @param eh   TCP Connection Established handler
+ * @param rh   TCP Connection Receive data handler
+ * @param ch   TCP Connection close handler
+ * @param arg  Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_conn_alloc(struct tcp_conn **tcp,
+		   const struct sa *peer, tcp_estab_h *eh,
+		   tcp_recv_h *rh, tcp_close_h *ch, void *arg)
+{
+	struct tcp_conn *tc;
+	struct addrinfo hints, *res = NULL, *r;
+	char addr[64];
+	char serv[NI_MAXSERV] = "0";
+	int error, err;
+
+	if (!tcp || !sa_isset(peer, SA_ALL))
+		return EINVAL;
+
+	tc = conn_alloc(eh, rh, ch, arg);
+	if (!tc)
+		return ENOMEM;
+
+	memset(&hints, 0, sizeof(hints));
+	/* set-up hints structure */
+	hints.ai_family   = PF_UNSPEC;
+	hints.ai_flags    = AI_PASSIVE | AI_NUMERICHOST;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+
+	(void)re_snprintf(addr, sizeof(addr), "%H",
+			  sa_print_addr, peer);
+	(void)re_snprintf(serv, sizeof(serv), "%u", sa_port(peer));
+
+	error = getaddrinfo(addr, serv, &hints, &res);
+	if (error) {
+		DEBUG_WARNING("connect: getaddrinfo(): (%s)\n",
+			      gai_strerror(error));
+		err = EADDRNOTAVAIL;
+		goto out;
+	}
+
+	err = EINVAL;
+	for (r = res; r; r = r->ai_next) {
+
+		tc->fdc = SOK_CAST socket(r->ai_family, SOCK_STREAM,
+					  IPPROTO_TCP);
+		if (tc->fdc < 0) {
+			err = errno;
+			continue;
+		}
+
+		err = net_sockopt_blocking_set(tc->fdc, false);
+		if (err) {
+			DEBUG_WARNING("connect: nonblock set: %m\n", err);
+			(void)close(tc->fdc);
+			tc->fdc = -1;
+			continue;
+		}
+
+		tcp_sockopt_set(tc->fdc);
+
+		err = 0;
+		break;
+	}
+
+	freeaddrinfo(res);
+
+ out:
+	if (err)
+		mem_deref(tc);
+	else
+		*tcp = tc;
+
+	return err;
+}
+
+
+/**
+ * Bind a TCP Connection to a local address
+ *
+ * @param tc    TCP Connection object
+ * @param local Local bind address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_conn_bind(struct tcp_conn *tc, const struct sa *local)
+{
+	struct addrinfo hints, *res = NULL, *r;
+	char addr[64] = "";
+	char serv[NI_MAXSERV] = "0";
+	int error, err;
+
+	if (!tc)
+		return EINVAL;
+
+	if (local) {
+		(void)re_snprintf(addr, sizeof(addr), "%H",
+				  sa_print_addr, local);
+		(void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local));
+	}
+
+	memset(&hints, 0, sizeof(hints));
+	/* set-up hints structure */
+	hints.ai_family   = PF_UNSPEC;
+	hints.ai_flags    = AI_PASSIVE | AI_NUMERICHOST;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+
+	error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res);
+	if (error) {
+		DEBUG_WARNING("conn_bind: getaddrinfo(): (%s)\n",
+			      gai_strerror(error));
+		return EADDRNOTAVAIL;
+	}
+
+	err = EINVAL;
+	for (r = res; r; r = r->ai_next) {
+
+		(void)net_sockopt_reuse_set(tc->fdc, true);
+
+		/* bind to local address */
+		if (bind(tc->fdc, r->ai_addr, SIZ_CAST r->ai_addrlen) < 0) {
+
+			/* Special case for mingw32/wine */
+			if (0 == errno) {
+				goto ok;
+			}
+
+			err = errno;
+			DEBUG_WARNING("conn_bind: bind(): %J: %m\n",
+				      local, err);
+			continue;
+		}
+
+	ok:
+		/* OK */
+		err = 0;
+		break;
+	}
+
+	freeaddrinfo(res);
+
+	if (err) {
+		DEBUG_WARNING("conn_bind failed: %J (%m)\n", local, err);
+	}
+
+	return err;
+}
+
+
+/**
+ * Connect to a remote peer
+ *
+ * @param tc   TCP Connection object
+ * @param peer Network address of peer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_conn_connect(struct tcp_conn *tc, const struct sa *peer)
+{
+	struct addrinfo hints, *res = NULL, *r;
+	char addr[64];
+	char serv[NI_MAXSERV];
+	int error, err = 0;
+
+	if (!tc || !sa_isset(peer, SA_ALL))
+		return EINVAL;
+
+	tc->active = true;
+
+	if (tc->fdc < 0) {
+		DEBUG_WARNING("invalid fd\n");
+		return EBADF;
+	}
+
+	memset(&hints, 0, sizeof(hints));
+	/* set-up hints structure */
+	hints.ai_family   = PF_UNSPEC;
+	hints.ai_flags    = AI_PASSIVE | AI_NUMERICHOST;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_protocol = IPPROTO_TCP;
+
+	(void)re_snprintf(addr, sizeof(addr), "%H",
+			  sa_print_addr, peer);
+	(void)re_snprintf(serv, sizeof(serv), "%u", sa_port(peer));
+
+	error = getaddrinfo(addr, serv, &hints, &res);
+	if (error) {
+		DEBUG_WARNING("connect: getaddrinfo(): (%s)\n",
+			      gai_strerror(error));
+		return EADDRNOTAVAIL;
+	}
+
+	for (r = res; r; r = r->ai_next) {
+		struct sockaddr *sa = r->ai_addr;
+
+	again:
+		if (0 == connect(tc->fdc, sa, SIZ_CAST r->ai_addrlen)) {
+			err = 0;
+			goto out;
+		}
+		else {
+#ifdef WIN32
+			/* Special error handling for Windows */
+			if (WSAEWOULDBLOCK == WSAGetLastError()) {
+				err = 0;
+				goto out;
+			}
+#endif
+
+			/* Special case for mingw32/wine */
+			if (0 == errno) {
+				err = 0;
+				goto out;
+			}
+
+			if (EINTR == errno)
+				goto again;
+
+			if (EINPROGRESS != errno && EALREADY != errno) {
+				err = errno;
+				DEBUG_INFO("connect: connect() %J: %m\n",
+					   peer, err);
+			}
+		}
+	}
+
+ out:
+	freeaddrinfo(res);
+
+	if (err)
+		return err;
+
+	return fd_listen(tc->fdc, FD_READ | FD_WRITE | FD_EXCEPT,
+			 tcp_recv_handler, tc);
+}
+
+
+static int tcp_send_internal(struct tcp_conn *tc, struct mbuf *mb,
+			     struct le *le)
+{
+	int err = 0;
+	ssize_t n;
+#ifdef MSG_NOSIGNAL
+	const int flags = MSG_NOSIGNAL; /* disable SIGPIPE signal */
+#else
+	const int flags = 0;
+#endif
+
+	if (tc->fdc < 0)
+		return ENOTCONN;
+
+	if (!mbuf_get_left(mb)) {
+		DEBUG_WARNING("send: empty mbuf (pos=%u end=%u)\n",
+			      mb->pos, mb->end);
+		return EINVAL;
+	}
+
+	/* call helpers in reverse order */
+	while (le) {
+		struct tcp_helper *th = le->data;
+
+		le = le->prev;
+
+		if (th->sendh(&err, mb, th->arg) || err)
+			return err;
+	}
+
+	if (tc->sendq.head)
+		return enqueue(tc, mb);
+
+	n = send(tc->fdc, BUF_CAST mbuf_buf(mb), mb->end - mb->pos, flags);
+	if (n < 0) {
+
+		if (EAGAIN == errno)
+			return enqueue(tc, mb);
+
+#ifdef WIN32
+		if (WSAEWOULDBLOCK == WSAGetLastError())
+			return enqueue(tc, mb);
+#endif
+		err = errno;
+
+		DEBUG_WARNING("send: write(): %m (fdc=%d)\n", err, tc->fdc);
+
+#ifdef WIN32
+		DEBUG_WARNING("WIN32 error: %d\n", WSAGetLastError());
+#endif
+
+		return err;
+	}
+
+	if ((size_t)n < mb->end - mb->pos) {
+
+		mb->pos += n;
+		err = enqueue(tc, mb);
+		mb->pos -= n;
+
+		return err;
+	}
+
+	return 0;
+}
+
+
+/**
+ * Send data on a TCP Connection to a remote peer
+ *
+ * @param tc TCP Connection
+ * @param mb Buffer to send
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_send(struct tcp_conn *tc, struct mbuf *mb)
+{
+	if (!tc || !mb)
+		return EINVAL;
+
+	return tcp_send_internal(tc, mb, tc->helpers.tail);
+}
+
+
+/**
+ * Send data on a TCP Connection to a remote peer bypassing this
+ * helper and the helpers above it.
+ *
+ * @param tc TCP Connection
+ * @param mb Buffer to send
+ * @param th TCP Helper
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_send_helper(struct tcp_conn *tc, struct mbuf *mb,
+		    struct tcp_helper *th)
+{
+	if (!tc || !mb || !th)
+		return EINVAL;
+
+	return tcp_send_internal(tc, mb, th->le.prev);
+}
+
+
+/**
+ * Set the send handler on a TCP Connection, which will be called
+ * every time it is ready to send data
+ *
+ * @param tc    TCP Connection
+ * @param sendh TCP Send handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_set_send(struct tcp_conn *tc, tcp_send_h *sendh)
+{
+	if (!tc)
+		return EINVAL;
+
+	tc->sendh = sendh;
+
+	if (tc->sendq.head || !sendh)
+		return 0;
+
+	return fd_listen(tc->fdc, FD_READ | FD_WRITE, tcp_recv_handler, tc);
+}
+
+
+/**
+ * Set handlers on a TCP Connection
+ *
+ * @param tc  TCP Connection
+ * @param eh  TCP Connection Established handler
+ * @param rh  TCP Connection Receive data handler
+ * @param ch  TCP Connection Close handler
+ * @param arg Handler argument
+ */
+void tcp_set_handlers(struct tcp_conn *tc, tcp_estab_h *eh, tcp_recv_h *rh,
+		      tcp_close_h *ch, void *arg)
+{
+	if (!tc)
+		return;
+
+	tc->estabh = eh;
+	tc->recvh  = rh;
+	tc->closeh = ch;
+	tc->arg    = arg;
+}
+
+
+/**
+ * Get local network address of TCP Socket
+ *
+ * @param ts    TCP Socket
+ * @param local Returned local network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_sock_local_get(const struct tcp_sock *ts, struct sa *local)
+{
+	if (!ts || !local)
+		return EINVAL;
+
+	sa_init(local, AF_UNSPEC);
+
+	if (getsockname(ts->fd, &local->u.sa, &local->len) < 0) {
+		DEBUG_WARNING("local get: getsockname(): %m\n", errno);
+		return errno;
+	}
+
+	return 0;
+}
+
+
+/**
+ * Get local network address of TCP Connection
+ *
+ * @param tc    TCP Connection
+ * @param local Returned local network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_conn_local_get(const struct tcp_conn *tc, struct sa *local)
+{
+	if (!tc || !local)
+		return EINVAL;
+
+	sa_init(local, AF_UNSPEC);
+
+	if (getsockname(tc->fdc, &local->u.sa, &local->len) < 0) {
+		DEBUG_WARNING("conn local get: getsockname(): %m\n", errno);
+		return errno;
+	}
+
+	return 0;
+}
+
+
+/**
+ * Get remote peer network address of TCP Connection
+ *
+ * @param tc    TCP Connection
+ * @param peer Returned remote peer network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_conn_peer_get(const struct tcp_conn *tc, struct sa *peer)
+{
+	if (!tc || !peer)
+		return EINVAL;
+
+	sa_init(peer, AF_UNSPEC);
+
+	if (getpeername(tc->fdc, &peer->u.sa, &peer->len) < 0) {
+		DEBUG_WARNING("conn peer get: getpeername(): %m\n", errno);
+		return errno;
+	}
+
+	return 0;
+}
+
+
+/**
+ * Set the maximum receive chunk size on a TCP Connection
+ *
+ * @param tc   TCP Connection
+ * @param rxsz Maximum receive chunk size
+ */
+void tcp_conn_rxsz_set(struct tcp_conn *tc, size_t rxsz)
+{
+	if (!tc)
+		return;
+
+	tc->rxsz = rxsz;
+}
+
+
+/**
+ * Set the maximum send queue size on a TCP Connection
+ *
+ * @param tc    TCP Connection
+ * @param txqsz Maximum send queue size
+ */
+void tcp_conn_txqsz_set(struct tcp_conn *tc, size_t txqsz)
+{
+	if (!tc)
+		return;
+
+	tc->txqsz_max = txqsz;
+}
+
+
+/**
+ * Get the file descriptor of a TCP Connection
+ *
+ * @param tc TCP-Connection
+ *
+ * @return File destriptor, or -1 if errors
+ */
+int tcp_conn_fd(const struct tcp_conn *tc)
+{
+	return tc ? tc->fdc : -1;
+}
+
+
+/**
+ * Get the current length of the transmit queue on a TCP Connection
+ *
+ * @param tc TCP-Connection
+ *
+ * @return Current transmit queue length, or 0 if errors
+ */
+size_t tcp_conn_txqsz(const struct tcp_conn *tc)
+{
+	return tc ? tc->txqsz : 0;
+}
+
+
+static bool sort_handler(struct le *le1, struct le *le2, void *arg)
+{
+	struct tcp_helper *th1 = le1->data, *th2 = le2->data;
+	(void)arg;
+
+	return th1->layer <= th2->layer;
+}
+
+
+/**
+ * Register a new TCP-helper on a TCP-Connection
+ *
+ * @param thp   Pointer to allocated TCP helper
+ * @param tc    TCP Connection
+ * @param layer Protocol layer; higher number means higher up in stack
+ * @param eh    Established handler
+ * @param sh    Send handler
+ * @param rh    Receive handler
+ * @param arg   Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_register_helper(struct tcp_helper **thp, struct tcp_conn *tc,
+			int layer,
+			tcp_helper_estab_h *eh, tcp_helper_send_h *sh,
+			tcp_helper_recv_h *rh, void *arg)
+{
+	struct tcp_helper *th;
+
+	if (!tc)
+		return EINVAL;
+
+	th = mem_zalloc(sizeof(*th), helper_destructor);
+	if (!th)
+		return ENOMEM;
+
+	list_append(&tc->helpers, &th->le, th);
+
+	th->layer  = layer;
+	th->estabh = eh ? eh : helper_estab_handler;
+	th->sendh  = sh ? sh : helper_send_handler;
+	th->recvh  = rh ? rh : helper_recv_handler;
+	th->arg = arg;
+
+	list_sort(&tc->helpers, sort_handler, NULL);
+
+	if (thp)
+		*thp = th;
+
+	return 0;
+}