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/turn/chan.c b/src/turn/chan.c
new file mode 100644
index 0000000..2a6cf70
--- /dev/null
+++ b/src/turn/chan.c
@@ -0,0 +1,305 @@
+/**
+ * @file chan.c  TURN Channels handling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_md5.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_turn.h>
+#include "turnc.h"
+
+
+enum {
+	CHAN_LIFETIME = 600,
+	CHAN_REFRESH = 250,
+	CHAN_NUMB_MIN = 0x4000,
+	CHAN_NUMB_MAX = 0x7fff
+};
+
+
+struct channels {
+	struct hash *ht_numb;
+	struct hash *ht_peer;
+	uint16_t nr;
+};
+
+
+struct chan {
+	struct le he_numb;
+	struct le he_peer;
+	struct loop_state ls;
+	uint16_t nr;
+	struct sa peer;
+	struct tmr tmr;
+	struct turnc *turnc;
+	struct stun_ctrans *ct;
+	turnc_chan_h *ch;
+	void *arg;
+};
+
+
+static int chanbind_request(struct chan *chan, bool reset_ls);
+
+
+static void channels_destructor(void *data)
+{
+	struct channels *c = data;
+
+	/* flush from primary hash */
+	hash_flush(c->ht_numb);
+
+	mem_deref(c->ht_numb);
+	mem_deref(c->ht_peer);
+}
+
+
+static void chan_destructor(void *data)
+{
+	struct chan *chan = data;
+
+	tmr_cancel(&chan->tmr);
+	mem_deref(chan->ct);
+	hash_unlink(&chan->he_numb);
+	hash_unlink(&chan->he_peer);
+}
+
+
+static bool numb_hash_cmp_handler(struct le *le, void *arg)
+{
+	const struct chan *chan = le->data;
+	const uint16_t *nr = arg;
+
+	return chan->nr == *nr;
+}
+
+
+static bool peer_hash_cmp_handler(struct le *le, void *arg)
+{
+	const struct chan *chan = le->data;
+
+	return sa_cmp(&chan->peer, arg, SA_ALL);
+}
+
+
+static void timeout(void *arg)
+{
+	struct chan *chan = arg;
+	int err;
+
+	err = chanbind_request(chan, true);
+	if (err)
+		chan->turnc->th(err, 0, NULL, NULL, NULL, NULL,
+				chan->turnc->arg);
+}
+
+
+static void chanbind_resp_handler(int err, uint16_t scode, const char *reason,
+				  const struct stun_msg *msg, void *arg)
+{
+	struct chan *chan = arg;
+
+	if (err || turnc_request_loops(&chan->ls, scode))
+		goto out;
+
+	switch (scode) {
+
+	case 0:
+		tmr_start(&chan->tmr, CHAN_REFRESH * 1000, timeout, chan);
+		if (chan->ch) {
+			chan->ch(chan->arg);
+			chan->ch  = NULL;
+			chan->arg = NULL;
+		}
+		return;
+
+	case 401:
+	case 438:
+		err = turnc_keygen(chan->turnc, msg);
+		if (err)
+			break;
+
+		err = chanbind_request(chan, false);
+		if (err)
+			break;
+
+		return;
+
+	default:
+		break;
+	}
+
+ out:
+	chan->turnc->th(err, scode, reason, NULL, NULL, msg, chan->turnc->arg);
+}
+
+
+static int chanbind_request(struct chan *chan, bool reset_ls)
+{
+	struct turnc *t = chan->turnc;
+
+	if (reset_ls)
+		turnc_loopstate_reset(&chan->ls);
+
+	return stun_request(&chan->ct, t->stun, t->proto, t->sock, &t->srv, 0,
+			    STUN_METHOD_CHANBIND,
+			    t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash),
+			    false, chanbind_resp_handler, chan, 6,
+			    STUN_ATTR_CHANNEL_NUMBER, &chan->nr,
+			    STUN_ATTR_XOR_PEER_ADDR, &chan->peer,
+			    STUN_ATTR_USERNAME, t->realm ? t->username : NULL,
+			    STUN_ATTR_REALM, t->realm,
+			    STUN_ATTR_NONCE, t->nonce,
+			    STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+/**
+ * Add a TURN Channel for a peer
+ *
+ * @param turnc TURN Client
+ * @param peer  Peer IP-address
+ * @param ch    Channel handler
+ * @param arg   Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int turnc_add_chan(struct turnc *turnc, const struct sa *peer,
+		   turnc_chan_h *ch, void *arg)
+{
+	struct chan *chan;
+	int err;
+
+	if (!turnc || !peer)
+		return EINVAL;
+
+	if (turnc->chans->nr >= CHAN_NUMB_MAX)
+		return ERANGE;
+
+	if (turnc_chan_find_peer(turnc, peer))
+		return 0;
+
+	chan = mem_zalloc(sizeof(*chan), chan_destructor);
+	if (!chan)
+		return ENOMEM;
+
+	chan->nr = turnc->chans->nr++;
+	chan->peer = *peer;
+
+	hash_append(turnc->chans->ht_numb, chan->nr, &chan->he_numb, chan);
+	hash_append(turnc->chans->ht_peer, sa_hash(peer, SA_ALL),
+		    &chan->he_peer, chan);
+
+	tmr_init(&chan->tmr);
+	chan->turnc = turnc;
+	chan->ch = ch;
+	chan->arg = arg;
+
+	err = chanbind_request(chan, true);
+	if (err)
+		mem_deref(chan);
+
+	return err;
+}
+
+
+int turnc_chan_hash_alloc(struct channels **cp, uint32_t bsize)
+{
+	struct channels *c;
+	int err;
+
+	if (!cp)
+		return EINVAL;
+
+	c = mem_zalloc(sizeof(*c), channels_destructor);
+	if (!c)
+		return ENOMEM;
+
+	err = hash_alloc(&c->ht_numb, bsize);
+	if (err)
+		goto out;
+
+	err = hash_alloc(&c->ht_peer, bsize);
+	if (err)
+		goto out;
+
+	c->nr = CHAN_NUMB_MIN;
+
+ out:
+	if (err)
+		mem_deref(c);
+	else
+		*cp = c;
+
+	return err;
+}
+
+
+struct chan *turnc_chan_find_numb(const struct turnc *turnc, uint16_t nr)
+{
+	if (!turnc)
+		return NULL;
+
+	return list_ledata(hash_lookup(turnc->chans->ht_numb, nr,
+				       numb_hash_cmp_handler, &nr));
+}
+
+
+struct chan *turnc_chan_find_peer(const struct turnc *turnc,
+				  const struct sa *peer)
+{
+	if (!turnc)
+		return NULL;
+
+	return list_ledata(hash_lookup(turnc->chans->ht_peer,
+				       sa_hash(peer, SA_ALL),
+				       peer_hash_cmp_handler, (void *)peer));
+}
+
+
+uint16_t turnc_chan_numb(const struct chan *chan)
+{
+	return chan ? chan->nr : 0;
+}
+
+
+const struct sa *turnc_chan_peer(const struct chan *chan)
+{
+	return chan ? &chan->peer : NULL;
+}
+
+
+int turnc_chan_hdr_encode(const struct chan_hdr *hdr, struct mbuf *mb)
+{
+	int err;
+
+	if (!hdr || !mb)
+		return EINVAL;
+
+	err  = mbuf_write_u16(mb, htons(hdr->nr));
+	err |= mbuf_write_u16(mb, htons(hdr->len));
+
+	return err;
+}
+
+
+int turnc_chan_hdr_decode(struct chan_hdr *hdr, struct mbuf *mb)
+{
+	if (!hdr || !mb)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < sizeof(*hdr))
+		return ENOENT;
+
+	hdr->nr  = ntohs(mbuf_read_u16(mb));
+	hdr->len = ntohs(mbuf_read_u16(mb));
+
+	return 0;
+}
diff --git a/src/turn/mod.mk b/src/turn/mod.mk
new file mode 100644
index 0000000..eb20f18
--- /dev/null
+++ b/src/turn/mod.mk
@@ -0,0 +1,9 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS	+= turn/chan.c
+SRCS	+= turn/perm.c
+SRCS	+= turn/turnc.c
diff --git a/src/turn/perm.c b/src/turn/perm.c
new file mode 100644
index 0000000..c80650a
--- /dev/null
+++ b/src/turn/perm.c
@@ -0,0 +1,182 @@
+/**
+ * @file perm.c  TURN permission handling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_md5.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_turn.h>
+#include "turnc.h"
+
+
+enum {
+	PERM_LIFETIME = 300,
+	PERM_REFRESH = 250,
+};
+
+
+struct perm {
+	struct le he;
+	struct loop_state ls;
+	struct sa peer;
+	struct tmr tmr;
+	struct turnc *turnc;
+	struct stun_ctrans *ct;
+	turnc_perm_h *ph;
+	void *arg;
+};
+
+
+static int createperm_request(struct perm *perm, bool reset_ls);
+
+
+static void destructor(void *arg)
+{
+	struct perm *perm = arg;
+
+	tmr_cancel(&perm->tmr);
+	mem_deref(perm->ct);
+	hash_unlink(&perm->he);
+}
+
+
+static bool hash_cmp_handler(struct le *le, void *arg)
+{
+	const struct perm *perm = le->data;
+
+	return sa_cmp(&perm->peer, arg, SA_ADDR);
+}
+
+
+static struct perm *perm_find(const struct turnc *turnc, const struct sa *peer)
+{
+	return list_ledata(hash_lookup(turnc->perms, sa_hash(peer, SA_ADDR),
+				       hash_cmp_handler, (void *)peer));
+}
+
+
+static void timeout(void *arg)
+{
+	struct perm *perm = arg;
+	int err;
+
+	err = createperm_request(perm, true);
+	if (err)
+		perm->turnc->th(err, 0, NULL, NULL, NULL, NULL,
+				perm->turnc->arg);
+}
+
+
+static void createperm_resp_handler(int err, uint16_t scode,
+				    const char *reason,
+				    const struct stun_msg *msg, void *arg)
+{
+	struct perm *perm = arg;
+
+	if (err || turnc_request_loops(&perm->ls, scode))
+		goto out;
+
+	switch (scode) {
+
+	case 0:
+		tmr_start(&perm->tmr, PERM_REFRESH * 1000, timeout, perm);
+		if (perm->ph) {
+			perm->ph(perm->arg);
+			perm->ph  = NULL;
+			perm->arg = NULL;
+		}
+		return;
+
+	case 401:
+	case 438:
+		err = turnc_keygen(perm->turnc, msg);
+		if (err)
+			break;
+
+		err = createperm_request(perm, false);
+		if (err)
+			break;
+
+		return;
+
+	default:
+		break;
+	}
+
+ out:
+	perm->turnc->th(err, scode, reason, NULL, NULL, msg, perm->turnc->arg);
+}
+
+
+static int createperm_request(struct perm *perm, bool reset_ls)
+{
+	struct turnc *t = perm->turnc;
+
+	if (reset_ls)
+		turnc_loopstate_reset(&perm->ls);
+
+	return stun_request(&perm->ct, t->stun, t->proto, t->sock, &t->srv, 0,
+			    STUN_METHOD_CREATEPERM,
+			    t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash),
+			    false, createperm_resp_handler, perm, 5,
+			    STUN_ATTR_XOR_PEER_ADDR, &perm->peer,
+			    STUN_ATTR_USERNAME, t->realm ? t->username : NULL,
+			    STUN_ATTR_REALM, t->realm,
+			    STUN_ATTR_NONCE, t->nonce,
+			    STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+/**
+ * Add TURN Permission for a peer
+ *
+ * @param turnc TURN Client
+ * @param peer  Peer IP-address
+ * @param ph    Permission handler
+ * @param arg   Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int turnc_add_perm(struct turnc *turnc, const struct sa *peer,
+		   turnc_perm_h *ph, void *arg)
+{
+	struct perm *perm;
+	int err;
+
+	if (!turnc || !peer)
+		return EINVAL;
+
+	if (perm_find(turnc, peer))
+		return 0;
+
+	perm = mem_zalloc(sizeof(*perm), destructor);
+	if (!perm)
+		return ENOMEM;
+
+	hash_append(turnc->perms, sa_hash(peer, SA_ADDR), &perm->he, perm);
+	tmr_init(&perm->tmr);
+	perm->peer = *peer;
+	perm->turnc = turnc;
+	perm->ph = ph;
+	perm->arg = arg;
+
+	err = createperm_request(perm, true);
+	if (err)
+		mem_deref(perm);
+
+	return err;
+}
+
+
+int turnc_perm_hash_alloc(struct hash **ht, uint32_t bsize)
+{
+	return hash_alloc(ht, bsize);
+}
diff --git a/src/turn/turnc.c b/src/turn/turnc.c
new file mode 100644
index 0000000..b103dba
--- /dev/null
+++ b/src/turn/turnc.c
@@ -0,0 +1,682 @@
+/**
+ * @file turnc.c  TURN Client implementation
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_md5.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_srtp.h>
+#include <re_tls.h>
+#include <re_stun.h>
+#include <re_turn.h>
+#include "turnc.h"
+
+
+#define DEBUG_MODULE "turnc"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** TURN Client protocol values */
+enum {
+	PERM_HASH_SIZE = 16,
+	CHAN_HASH_SIZE = 16,
+	FAILC_MAX = 16, /**< Maximum number of request errors for loopcheck. */
+	STUN_ATTR_ADDR4_SIZE = 8,
+	STUN_ATTR_ADDR6_SIZE = 20,
+};
+
+
+static const uint8_t sendind_tid[STUN_TID_SIZE];
+
+static int allocate_request(struct turnc *t);
+static int refresh_request(struct turnc *t, uint32_t lifetime, bool reset_ls,
+			   stun_resp_h *resph, void *arg);
+static void refresh_resp_handler(int err, uint16_t scode, const char *reason,
+				 const struct stun_msg *msg, void *arg);
+
+
+static void destructor(void *arg)
+{
+	struct turnc *turnc = arg;
+
+	if (turnc->allocated)
+		(void)refresh_request(turnc, 0, true, NULL, NULL);
+
+	tmr_cancel(&turnc->tmr);
+	mem_deref(turnc->ct);
+
+	hash_flush(turnc->perms);
+	mem_deref(turnc->perms);
+	mem_deref(turnc->chans);
+	mem_deref(turnc->username);
+	mem_deref(turnc->password);
+	mem_deref(turnc->nonce);
+	mem_deref(turnc->realm);
+	mem_deref(turnc->stun);
+	mem_deref(turnc->uh);
+	mem_deref(turnc->sock);
+}
+
+
+static void timeout(void *arg)
+{
+	struct turnc *turnc = arg;
+	int err;
+
+	err = refresh_request(turnc, turnc->lifetime, true,
+			      refresh_resp_handler, turnc);
+	if (err)
+		turnc->th(err, 0, NULL, NULL, NULL, NULL, turnc->arg);
+}
+
+
+static void refresh_timer(struct turnc *turnc)
+{
+	const uint32_t t = turnc->lifetime*1000*3/4;
+
+	DEBUG_INFO("Start refresh timer.. %u seconds\n", t/1000);
+
+	tmr_start(&turnc->tmr, t, timeout, turnc);
+}
+
+
+static void allocate_resp_handler(int err, uint16_t scode, const char *reason,
+				  const struct stun_msg *msg, void *arg)
+{
+	struct stun_attr *map = NULL, *rel = NULL, *ltm, *alt;
+	struct turnc *turnc = arg;
+
+	if (err || turnc_request_loops(&turnc->ls, scode))
+		goto out;
+
+	switch (scode) {
+
+	case 0:
+		map = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+		rel = stun_msg_attr(msg, STUN_ATTR_XOR_RELAY_ADDR);
+		ltm = stun_msg_attr(msg, STUN_ATTR_LIFETIME);
+		if (!rel || !map) {
+			DEBUG_WARNING("xor_mapped/relay addr attr missing\n");
+			err = EPROTO;
+			break;
+		}
+
+		if (ltm)
+			turnc->lifetime = ltm->v.lifetime;
+
+		turnc->allocated = true;
+		refresh_timer(turnc);
+		break;
+
+	case 300:
+		if (turnc->proto == IPPROTO_TCP ||
+		    turnc->proto == STUN_TRANSP_DTLS)
+			break;
+
+		alt = stun_msg_attr(msg, STUN_ATTR_ALT_SERVER);
+		if (!alt)
+			break;
+
+		turnc->psrv = turnc->srv;
+		turnc->srv = alt->v.alt_server;
+
+		err = allocate_request(turnc);
+		if (err)
+			break;
+
+		return;
+
+	case 401:
+	case 438:
+		err = turnc_keygen(turnc, msg);
+		if (err)
+			break;
+
+		err = allocate_request(turnc);
+		if (err)
+			break;
+
+		return;
+
+	default:
+		break;
+	}
+
+ out:
+	turnc->th(err, scode, reason,
+		  rel ? &rel->v.xor_relay_addr : NULL,
+		  map ? &map->v.xor_mapped_addr : NULL,
+		  msg,
+		  turnc->arg);
+}
+
+
+static int allocate_request(struct turnc *t)
+{
+	const uint8_t proto = IPPROTO_UDP;
+
+	return stun_request(&t->ct, t->stun, t->proto, t->sock, &t->srv, 0,
+			    STUN_METHOD_ALLOCATE,
+			    t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash),
+			    false, allocate_resp_handler, t, 6,
+			    STUN_ATTR_LIFETIME, &t->lifetime,
+			    STUN_ATTR_REQ_TRANSPORT, &proto,
+			    STUN_ATTR_USERNAME, t->realm ? t->username : NULL,
+			    STUN_ATTR_REALM, t->realm,
+			    STUN_ATTR_NONCE, t->nonce,
+			    STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+static void refresh_resp_handler(int err, uint16_t scode, const char *reason,
+				 const struct stun_msg *msg, void *arg)
+{
+	struct turnc *turnc = arg;
+	struct stun_attr *ltm;
+
+	if (err || turnc_request_loops(&turnc->ls, scode))
+		goto out;
+
+	switch (scode) {
+
+	case 0:
+		ltm = stun_msg_attr(msg, STUN_ATTR_LIFETIME);
+		if (ltm)
+			turnc->lifetime = ltm->v.lifetime;
+		refresh_timer(turnc);
+		return;
+
+	case 401:
+	case 438:
+		err = turnc_keygen(turnc, msg);
+		if (err)
+			break;
+
+		err = refresh_request(turnc, turnc->lifetime, false,
+				      refresh_resp_handler, turnc);
+		if (err)
+			break;
+
+		return;
+
+	default:
+		break;
+	}
+
+ out:
+	turnc->th(err, scode, reason, NULL, NULL, msg, turnc->arg);
+}
+
+
+static int refresh_request(struct turnc *t, uint32_t lifetime, bool reset_ls,
+			   stun_resp_h *resph, void *arg)
+{
+	if (!t)
+		return EINVAL;
+
+	if (reset_ls)
+		turnc_loopstate_reset(&t->ls);
+
+	if (t->ct)
+		t->ct = mem_deref(t->ct);
+
+	return stun_request(&t->ct, t->stun, t->proto, t->sock, &t->srv, 0,
+			    STUN_METHOD_REFRESH,
+			    t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash),
+			    false, resph, arg, 5,
+			    STUN_ATTR_LIFETIME, &lifetime,
+			    STUN_ATTR_USERNAME, t->realm ? t->username : NULL,
+			    STUN_ATTR_REALM, t->realm,
+			    STUN_ATTR_NONCE, t->nonce,
+			    STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+static inline size_t stun_indlen(const struct sa *sa)
+{
+	size_t len = STUN_HEADER_SIZE + STUN_ATTR_HEADER_SIZE * 2;
+
+	switch (sa_af(sa)) {
+
+	case AF_INET:
+		len += STUN_ATTR_ADDR4_SIZE;
+		break;
+
+#ifdef HAVE_INET6
+	case AF_INET6:
+		len += STUN_ATTR_ADDR6_SIZE;
+		break;
+#endif
+	}
+
+	return len;
+}
+
+
+static bool udp_send_handler(int *err, struct sa *dst, struct mbuf *mb,
+			     void *arg)
+{
+	struct turnc *turnc = arg;
+	size_t pos, indlen;
+	struct chan *chan;
+
+	if (mb->pos < CHAN_HDR_SIZE)
+		return false;
+
+	chan = turnc_chan_find_peer(turnc, dst);
+	if (chan) {
+		struct chan_hdr hdr;
+
+		hdr.nr  = turnc_chan_numb(chan);
+		hdr.len = mbuf_get_left(mb);
+
+		mb->pos -= CHAN_HDR_SIZE;
+		*err = turnc_chan_hdr_encode(&hdr, mb);
+		mb->pos -= CHAN_HDR_SIZE;
+
+		*dst = turnc->srv;
+
+		return false;
+	}
+
+	indlen = stun_indlen(dst);
+
+	if (mb->pos < indlen)
+		return false;
+
+	mb->pos -= indlen;
+	pos = mb->pos;
+	*err = stun_msg_encode(mb, STUN_METHOD_SEND, STUN_CLASS_INDICATION,
+			       sendind_tid, NULL, NULL, 0, false, 0x00, 2,
+			       STUN_ATTR_XOR_PEER_ADDR, dst,
+			       STUN_ATTR_DATA, mb);
+	mb->pos = pos;
+
+	*dst = turnc->srv;
+
+	return false;
+}
+
+
+static bool udp_recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+	struct stun_attr *peer, *data;
+	struct stun_unknown_attr ua;
+	struct turnc *turnc = arg;
+	struct stun_msg *msg;
+	bool hdld = true;
+
+	if (!sa_cmp(&turnc->srv, src, SA_ALL) &&
+	    !sa_cmp(&turnc->psrv, src, SA_ALL))
+		return false;
+
+	if (stun_msg_decode(&msg, mb, &ua)) {
+
+		struct chan_hdr hdr;
+		struct chan *chan;
+
+		if (turnc_chan_hdr_decode(&hdr, mb))
+			return true;
+
+		if (mbuf_get_left(mb) < hdr.len)
+			return true;
+
+		chan = turnc_chan_find_numb(turnc, hdr.nr);
+		if (!chan)
+			return true;
+
+		*src = *turnc_chan_peer(chan);
+
+		return false;
+	}
+
+	switch (stun_msg_class(msg)) {
+
+	case STUN_CLASS_INDICATION:
+		if (ua.typec > 0)
+			break;
+
+		if (stun_msg_method(msg) != STUN_METHOD_DATA)
+			break;
+
+		peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR);
+		data = stun_msg_attr(msg, STUN_ATTR_DATA);
+		if (!peer || !data)
+			break;
+
+		*src = peer->v.xor_peer_addr;
+
+		mb->pos = data->v.data.pos;
+		mb->end = data->v.data.end;
+
+		hdld = false;
+		break;
+
+	case STUN_CLASS_ERROR_RESP:
+	case STUN_CLASS_SUCCESS_RESP:
+		(void)stun_ctrans_recv(turnc->stun, msg, &ua);
+		break;
+
+	default:
+		break;
+	}
+
+	mem_deref(msg);
+
+	return hdld;
+}
+
+
+/**
+ * Allocate a TURN Client
+ *
+ * @param turncp    Pointer to allocated TURN Client
+ * @param conf      Optional STUN Configuration
+ * @param proto     Transport Protocol
+ * @param sock      Transport socket
+ * @param layer     Transport layer
+ * @param srv       TURN Server IP-address
+ * @param username  Authentication username
+ * @param password  Authentication password
+ * @param lifetime  Allocate lifetime in [seconds]
+ * @param th        TURN handler
+ * @param arg       Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int turnc_alloc(struct turnc **turncp, const struct stun_conf *conf, int proto,
+		void *sock, int layer, const struct sa *srv,
+		const char *username, const char *password,
+		uint32_t lifetime, turnc_h *th, void *arg)
+{
+	struct turnc *turnc;
+	int err;
+
+	if (!turncp || !sock || !srv || !username || !password || !th)
+		return EINVAL;
+
+	turnc = mem_zalloc(sizeof(*turnc), destructor);
+	if (!turnc)
+		return ENOMEM;
+
+	err = stun_alloc(&turnc->stun, conf, NULL, NULL);
+	if (err)
+		goto out;
+
+	err = str_dup(&turnc->username, username);
+	if (err)
+		goto out;
+
+	err = str_dup(&turnc->password, password);
+	if (err)
+		goto out;
+
+	err = turnc_perm_hash_alloc(&turnc->perms, PERM_HASH_SIZE);
+	if (err)
+		goto out;
+
+	err =  turnc_chan_hash_alloc(&turnc->chans, CHAN_HASH_SIZE);
+	if (err)
+		goto out;
+
+	tmr_init(&turnc->tmr);
+	turnc->proto = proto;
+	turnc->sock = mem_ref(sock);
+	turnc->psrv = *srv;
+	turnc->srv = *srv;
+	turnc->lifetime = lifetime;
+	turnc->th = th;
+	turnc->arg = arg;
+
+	switch (proto) {
+
+	case IPPROTO_UDP:
+		err = udp_register_helper(&turnc->uh, sock, layer,
+					  udp_send_handler, udp_recv_handler,
+					  turnc);
+		break;
+
+	default:
+		err = 0;
+		break;
+	}
+
+	if (err)
+		goto out;
+
+	err = allocate_request(turnc);
+	if (err)
+		goto out;
+
+ out:
+	if (err)
+		mem_deref(turnc);
+	else
+		*turncp = turnc;
+
+	return err;
+}
+
+
+int turnc_send(struct turnc *turnc, const struct sa *dst, struct mbuf *mb)
+{
+	size_t pos, indlen;
+	struct chan *chan;
+	int err;
+
+	if (!turnc || !dst || !mb)
+		return EINVAL;
+
+	chan = turnc_chan_find_peer(turnc, dst);
+	if (chan) {
+		struct chan_hdr hdr;
+
+		if (mb->pos < CHAN_HDR_SIZE)
+			return EINVAL;
+
+		hdr.nr  = turnc_chan_numb(chan);
+		hdr.len = mbuf_get_left(mb);
+
+		mb->pos -= CHAN_HDR_SIZE;
+		pos = mb->pos;
+
+		err = turnc_chan_hdr_encode(&hdr, mb);
+		if (err)
+			return err;
+
+		if (turnc->proto == IPPROTO_TCP) {
+
+			mb->pos = mb->end;
+
+			/* padding */
+			while (hdr.len++ & 0x03) {
+				err = mbuf_write_u8(mb, 0x00);
+				if (err)
+					return err;
+			}
+		}
+
+		mb->pos = pos;
+	}
+	else {
+		indlen = stun_indlen(dst);
+
+		if (mb->pos < indlen)
+			return EINVAL;
+
+		mb->pos -= indlen;
+		pos = mb->pos;
+
+		err = stun_msg_encode(mb, STUN_METHOD_SEND,
+				      STUN_CLASS_INDICATION, sendind_tid,
+				      NULL, NULL, 0, false, 0x00, 2,
+				      STUN_ATTR_XOR_PEER_ADDR, dst,
+				      STUN_ATTR_DATA, mb);
+		if (err)
+			return err;
+
+		mb->pos = pos;
+	}
+
+	switch (turnc->proto) {
+
+	case IPPROTO_UDP:
+		err = udp_send(turnc->sock, &turnc->srv, mb);
+		break;
+
+	case IPPROTO_TCP:
+		err = tcp_send(turnc->sock, mb);
+		break;
+
+#ifdef USE_DTLS
+	case STUN_TRANSP_DTLS:
+		err = dtls_send(turnc->sock, mb);
+		break;
+#endif
+
+	default:
+		err = EPROTONOSUPPORT;
+		break;
+	}
+
+	return err;
+}
+
+
+int turnc_recv(struct turnc *turnc, struct sa *src, struct mbuf *mb)
+{
+	struct stun_attr *peer, *data;
+	struct stun_unknown_attr ua;
+	struct stun_msg *msg;
+	int err = 0;
+
+	if (!turnc || !src || !mb)
+		return EINVAL;
+
+	if (stun_msg_decode(&msg, mb, &ua)) {
+
+		struct chan_hdr hdr;
+		struct chan *chan;
+
+		if (turnc_chan_hdr_decode(&hdr, mb))
+			return EBADMSG;
+
+		if (mbuf_get_left(mb) < hdr.len)
+			return EBADMSG;
+
+		chan = turnc_chan_find_numb(turnc, hdr.nr);
+		if (!chan)
+			return EBADMSG;
+
+		*src = *turnc_chan_peer(chan);
+
+		return 0;
+	}
+
+	switch (stun_msg_class(msg)) {
+
+	case STUN_CLASS_INDICATION:
+		if (ua.typec > 0) {
+			err = ENOSYS;
+			break;
+		}
+
+		if (stun_msg_method(msg) != STUN_METHOD_DATA) {
+			err = ENOSYS;
+			break;
+		}
+
+		peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR);
+		data = stun_msg_attr(msg, STUN_ATTR_DATA);
+		if (!peer || !data) {
+			err = EPROTO;
+			break;
+		}
+
+		*src = peer->v.xor_peer_addr;
+
+		mb->pos = data->v.data.pos;
+		mb->end = data->v.data.end;
+		break;
+
+	case STUN_CLASS_ERROR_RESP:
+	case STUN_CLASS_SUCCESS_RESP:
+		(void)stun_ctrans_recv(turnc->stun, msg, &ua);
+		mb->pos = mb->end;
+		break;
+
+	default:
+		err = ENOSYS;
+		break;
+	}
+
+	mem_deref(msg);
+
+	return err;
+}
+
+
+bool turnc_request_loops(struct loop_state *ls, uint16_t scode)
+{
+	bool loop = false;
+
+	switch (scode) {
+
+	case 0:
+		ls->failc = 0;
+		break;
+
+	default:
+		if (ls->last_scode == scode)
+			loop = true;
+		/*@fallthrough@*/
+	case 300:
+		if (++ls->failc >= FAILC_MAX)
+			loop = true;
+
+		break;
+	}
+
+	ls->last_scode = scode;
+
+	return loop;
+}
+
+
+void turnc_loopstate_reset(struct loop_state *ls)
+{
+	if (!ls)
+		return;
+
+	ls->last_scode = 0;
+	ls->failc = 0;
+}
+
+
+int turnc_keygen(struct turnc *turnc, const struct stun_msg *msg)
+{
+	struct stun_attr *realm, *nonce;
+
+	realm = stun_msg_attr(msg, STUN_ATTR_REALM);
+	nonce = stun_msg_attr(msg, STUN_ATTR_NONCE);
+	if (!realm || !nonce)
+		return EPROTO;
+
+	mem_deref(turnc->realm);
+	mem_deref(turnc->nonce);
+	turnc->realm = mem_ref(realm->v.realm);
+	turnc->nonce = mem_ref(nonce->v.nonce);
+
+	return md5_printf(turnc->md5_hash, "%s:%s:%s",
+			  turnc->username, turnc->realm, turnc->password);
+}
diff --git a/src/turn/turnc.h b/src/turn/turnc.h
new file mode 100644
index 0000000..5f672ac
--- /dev/null
+++ b/src/turn/turnc.h
@@ -0,0 +1,70 @@
+/**
+ * @file turnc.h  Internal TURN interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <time.h>
+
+
+struct loop_state {
+	uint32_t failc;
+	uint16_t last_scode;
+};
+
+struct channels;
+
+/** Defines a TURN Client */
+struct turnc {
+	struct loop_state ls;          /**< Loop state                      */
+	struct udp_helper *uh;         /**< UDP Helper for the TURN Socket  */
+	struct stun_ctrans *ct;        /**< Pending STUN Client Transaction */
+	char *username;                /**< Authentication username         */
+	char *password;                /**< Authentication password         */
+	struct sa psrv;                /**< Previous TURN Server address    */
+	struct sa srv;                 /**< TURN Server address             */
+	void *sock;                    /**< Transport socket                */
+	int proto;                     /**< Transport protocol              */
+	struct stun *stun;             /**< STUN Instance                   */
+	uint32_t lifetime;             /**< Allocation lifetime in [seconds]*/
+	struct tmr tmr;                /**< Allocation refresh timer        */
+	turnc_h *th;                   /**< Turn client handler             */
+	void *arg;                     /**< Handler argument                */
+	uint8_t md5_hash[MD5_SIZE];    /**< Cached MD5-sum of credentials   */
+	char *nonce;                   /**< Saved NONCE value from server   */
+	char *realm;                   /**< Saved REALM value from server   */
+	struct hash *perms;            /**< Hash-table of permissions       */
+	struct channels *chans;        /**< TURN Channels                   */
+	bool allocated;                /**< Allocation was done flag        */
+};
+
+
+/* Util */
+bool turnc_request_loops(struct loop_state *ls, uint16_t scode);
+void turnc_loopstate_reset(struct loop_state *ls);
+int  turnc_keygen(struct turnc *turnc, const struct stun_msg *msg);
+
+
+/* Permission */
+int turnc_perm_hash_alloc(struct hash **ht, uint32_t bsize);
+
+
+/* Channels */
+enum {
+	CHAN_HDR_SIZE = 4,
+};
+
+struct chan_hdr {
+	uint16_t nr;
+	uint16_t len;
+};
+
+struct chan;
+
+int turnc_chan_hash_alloc(struct channels **cp, uint32_t bsize);
+struct chan *turnc_chan_find_numb(const struct turnc *turnc, uint16_t nr);
+struct chan *turnc_chan_find_peer(const struct turnc *turnc,
+				  const struct sa *peer);
+uint16_t turnc_chan_numb(const struct chan *chan);
+const struct sa *turnc_chan_peer(const struct chan *chan);
+int turnc_chan_hdr_encode(const struct chan_hdr *hdr, struct mbuf *mb);
+int turnc_chan_hdr_decode(struct chan_hdr *hdr, struct mbuf *mb);