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/stun/addr.c b/src/stun/addr.c
new file mode 100644
index 0000000..812942a
--- /dev/null
+++ b/src/stun/addr.c
@@ -0,0 +1,124 @@
+/**
+ * @file stun/addr.c  STUN Address encoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+static void in6_xor_tid(uint8_t *in6, const uint8_t *tid)
+{
+	int i;
+
+	/* XOR with Magic Cookie (alignment safe) */
+	in6[0] ^= 0x21;
+	in6[1] ^= 0x12;
+	in6[2] ^= 0xa4;
+	in6[3] ^= 0x42;
+
+	for (i=0; i<STUN_TID_SIZE; i++)
+		in6[4+i] ^= tid[i];
+}
+
+
+int stun_addr_encode(struct mbuf *mb, const struct sa *addr,
+		     const uint8_t *tid)
+{
+#ifdef HAVE_INET6
+	uint8_t addr6[16];
+#endif
+	uint16_t port;
+	uint32_t addr4;
+	int err = 0;
+
+	if (!mb || !addr)
+		return EINVAL;
+
+	port = tid ? sa_port(addr)^(STUN_MAGIC_COOKIE>>16) : sa_port(addr);
+
+	switch (sa_af(addr)) {
+
+	case AF_INET:
+		addr4 = tid ? sa_in(addr)^STUN_MAGIC_COOKIE : sa_in(addr);
+
+		err |= mbuf_write_u8(mb, 0);
+		err |= mbuf_write_u8(mb, STUN_AF_IPv4);
+		err |= mbuf_write_u16(mb, htons(port));
+		err |= mbuf_write_u32(mb, htonl(addr4));
+		break;
+
+#ifdef HAVE_INET6
+	case AF_INET6:
+		sa_in6(addr, addr6);
+		if (tid)
+			in6_xor_tid(addr6, tid);
+
+		err |= mbuf_write_u8(mb, 0);
+		err |= mbuf_write_u8(mb, STUN_AF_IPv6);
+		err |= mbuf_write_u16(mb, htons(port));
+		err |= mbuf_write_mem(mb, addr6, 16);
+		break;
+#endif
+	default:
+		err = EAFNOSUPPORT;
+		break;
+	}
+
+	return err;
+}
+
+
+int stun_addr_decode(struct mbuf *mb, struct sa *addr, const uint8_t *tid)
+{
+	uint8_t family, addr6[16];
+	uint32_t addr4;
+	uint16_t port;
+
+	if (!mb || !addr)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < 4)
+		return EBADMSG;
+
+	(void)mbuf_read_u8(mb);
+	family = mbuf_read_u8(mb);
+	port = ntohs(mbuf_read_u16(mb));
+
+	if (tid)
+		port ^= STUN_MAGIC_COOKIE>>16;
+
+	switch (family) {
+
+	case STUN_AF_IPv4:
+		if (mbuf_get_left(mb) < 4)
+			return EBADMSG;
+
+		addr4 = ntohl(mbuf_read_u32(mb));
+		if (tid)
+			addr4 ^= STUN_MAGIC_COOKIE;
+
+		sa_set_in(addr, addr4, port);
+		break;
+
+	case STUN_AF_IPv6:
+		if (mbuf_get_left(mb) < 16)
+			return EBADMSG;
+
+		(void)mbuf_read_mem(mb, addr6, 16);
+		if (tid)
+			in6_xor_tid(addr6, tid);
+
+		sa_set_in6(addr, addr6, port);
+		break;
+
+	default:
+		return EAFNOSUPPORT;
+	}
+
+	return 0;
+}
diff --git a/src/stun/attr.c b/src/stun/attr.c
new file mode 100644
index 0000000..5dc3da9
--- /dev/null
+++ b/src/stun/attr.c
@@ -0,0 +1,502 @@
+/**
+ * @file stun/attr.c  STUN Attributes
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_sys.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+static int str_decode(struct mbuf *mb, char **str, size_t len)
+{
+	if (mbuf_get_left(mb) < len)
+		return EBADMSG;
+
+	return mbuf_strdup(mb, str, len);
+}
+
+
+static void destructor(void *arg)
+{
+	struct stun_attr *attr = arg;
+
+	switch (attr->type) {
+
+	case STUN_ATTR_USERNAME:
+	case STUN_ATTR_REALM:
+	case STUN_ATTR_NONCE:
+	case STUN_ATTR_SOFTWARE:
+		mem_deref(attr->v.str);
+		break;
+
+	case STUN_ATTR_ERR_CODE:
+		mem_deref(attr->v.err_code.reason);
+		break;
+
+	case STUN_ATTR_DATA:
+	case STUN_ATTR_PADDING:
+		mem_deref(attr->v.mb.buf);
+		break;
+	}
+
+	list_unlink(&attr->le);
+}
+
+
+int stun_attr_encode(struct mbuf *mb, uint16_t type, const void *v,
+		     const uint8_t *tid, uint8_t padding)
+{
+	const struct stun_change_req *ch_req = v;
+	const struct stun_errcode *err_code = v;
+	const struct stun_unknown_attr *ua = v;
+	const uint32_t *num32 = v;
+	const uint16_t *num16 = v;
+	const uint8_t *num8 = v;
+	const struct mbuf *mbd = v;
+	size_t start, len;
+	uint32_t i, n;
+	int err = 0;
+
+	if (!mb || !v)
+		return EINVAL;
+
+	mb->pos += 4;
+	start = mb->pos;
+
+	switch (type) {
+
+	case STUN_ATTR_MAPPED_ADDR:
+	case STUN_ATTR_ALT_SERVER:
+	case STUN_ATTR_RESP_ORIGIN:
+	case STUN_ATTR_OTHER_ADDR:
+		tid = NULL;
+		/*@fallthrough@*/
+	case STUN_ATTR_XOR_PEER_ADDR:
+	case STUN_ATTR_XOR_RELAY_ADDR:
+	case STUN_ATTR_XOR_MAPPED_ADDR:
+		err |= stun_addr_encode(mb, v, tid);
+		break;
+
+	case STUN_ATTR_CHANGE_REQ:
+		n = (uint32_t)ch_req->ip << 2 | (uint32_t)ch_req->port << 1;
+		err |= mbuf_write_u32(mb, htonl(n));
+		break;
+
+	case STUN_ATTR_USERNAME:
+	case STUN_ATTR_REALM:
+	case STUN_ATTR_NONCE:
+	case STUN_ATTR_SOFTWARE:
+		err |= mbuf_write_str(mb, v);
+		break;
+
+	case STUN_ATTR_MSG_INTEGRITY:
+		err |= mbuf_write_mem(mb, v, 20);
+		break;
+
+	case STUN_ATTR_ERR_CODE:
+		err |= mbuf_write_u16(mb, 0x00);
+		err |= mbuf_write_u8(mb,  err_code->code/100);
+		err |= mbuf_write_u8(mb,  err_code->code%100);
+		err |= mbuf_write_str(mb, err_code->reason);
+		break;
+
+	case STUN_ATTR_UNKNOWN_ATTR:
+		for (i=0; i<ua->typec; i++)
+			err |= mbuf_write_u16(mb, htons(ua->typev[i]));
+		break;
+
+	case STUN_ATTR_CHANNEL_NUMBER:
+	case STUN_ATTR_RESP_PORT:
+		err |= mbuf_write_u16(mb, htons(*num16));
+		err |= mbuf_write_u16(mb, 0x0000);
+		break;
+
+	case STUN_ATTR_LIFETIME:
+	case STUN_ATTR_PRIORITY:
+	case STUN_ATTR_FINGERPRINT:
+		err |= mbuf_write_u32(mb, htonl(*num32));
+		break;
+
+	case STUN_ATTR_DATA:
+	case STUN_ATTR_PADDING:
+		if (mb == mbd) {
+			mb->pos = mb->end;
+			break;
+		}
+		err |= mbuf_write_mem(mb, mbuf_buf(mbd), mbuf_get_left(mbd));
+		break;
+
+	case STUN_ATTR_REQ_ADDR_FAMILY:
+	case STUN_ATTR_REQ_TRANSPORT:
+		err |= mbuf_write_u8(mb, *num8);
+		err |= mbuf_write_u8(mb, 0x00);
+		err |= mbuf_write_u16(mb, 0x0000);
+		break;
+
+	case STUN_ATTR_EVEN_PORT:
+		err |= mbuf_write_u8(mb, ((struct stun_even_port *)v)->r << 7);
+		break;
+
+	case STUN_ATTR_DONT_FRAGMENT:
+	case STUN_ATTR_USE_CAND:
+		 /* no value */
+		break;
+
+	case STUN_ATTR_RSV_TOKEN:
+	case STUN_ATTR_CONTROLLED:
+	case STUN_ATTR_CONTROLLING:
+		err |= mbuf_write_u64(mb, sys_htonll(*(uint64_t *)v));
+		break;
+
+	default:
+		err = EINVAL;
+		break;
+	}
+
+	/* header */
+	len = mb->pos - start;
+
+	mb->pos = start - 4;
+	err |= mbuf_write_u16(mb, htons(type));
+	err |= mbuf_write_u16(mb, htons(len));
+	mb->pos += len;
+
+	/* padding */
+	while ((mb->pos - start) & 0x03)
+		err |= mbuf_write_u8(mb, padding);
+
+	return err;
+}
+
+
+int stun_attr_decode(struct stun_attr **attrp, struct mbuf *mb,
+		     const uint8_t *tid, struct stun_unknown_attr *ua)
+{
+	struct stun_attr *attr;
+	size_t start, len;
+	uint32_t i, n;
+	int err = 0;
+
+	if (!mb || !attrp)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < 4)
+		return EBADMSG;
+
+	attr = mem_zalloc(sizeof(*attr), destructor);
+	if (!attr)
+		return ENOMEM;
+
+	attr->type = ntohs(mbuf_read_u16(mb));
+	len = ntohs(mbuf_read_u16(mb));
+
+	if (mbuf_get_left(mb) < len)
+		goto badmsg;
+
+	start = mb->pos;
+
+	switch (attr->type) {
+
+	case STUN_ATTR_MAPPED_ADDR:
+	case STUN_ATTR_ALT_SERVER:
+	case STUN_ATTR_RESP_ORIGIN:
+	case STUN_ATTR_OTHER_ADDR:
+		tid = NULL;
+		/*@fallthrough@*/
+	case STUN_ATTR_XOR_PEER_ADDR:
+	case STUN_ATTR_XOR_RELAY_ADDR:
+	case STUN_ATTR_XOR_MAPPED_ADDR:
+		err = stun_addr_decode(mb, &attr->v.sa, tid);
+		break;
+
+	case STUN_ATTR_CHANGE_REQ:
+		if (len != 4)
+			goto badmsg;
+
+		n = ntohl(mbuf_read_u32(mb));
+		attr->v.change_req.ip   = (n >> 2) & 0x1;
+		attr->v.change_req.port = (n >> 1) & 0x1;
+		break;
+
+	case STUN_ATTR_USERNAME:
+	case STUN_ATTR_REALM:
+	case STUN_ATTR_NONCE:
+	case STUN_ATTR_SOFTWARE:
+		err = str_decode(mb, &attr->v.str, len);
+		break;
+
+	case STUN_ATTR_MSG_INTEGRITY:
+		if (len != 20)
+			goto badmsg;
+
+		err = mbuf_read_mem(mb, attr->v.msg_integrity, 20);
+		break;
+
+	case STUN_ATTR_ERR_CODE:
+		if (len < 4)
+			goto badmsg;
+
+		mb->pos += 2;
+		attr->v.err_code.code  = (mbuf_read_u8(mb) & 0x7) * 100;
+		attr->v.err_code.code += mbuf_read_u8(mb);
+		err = str_decode(mb, &attr->v.err_code.reason, len - 4);
+		break;
+
+	case STUN_ATTR_UNKNOWN_ATTR:
+		for (i=0; i<len/2; i++) {
+			uint16_t type = ntohs(mbuf_read_u16(mb));
+
+			if (i >= ARRAY_SIZE(attr->v.unknown_attr.typev))
+				continue;
+
+			attr->v.unknown_attr.typev[i] = type;
+			attr->v.unknown_attr.typec++;
+		}
+		break;
+
+	case STUN_ATTR_CHANNEL_NUMBER:
+	case STUN_ATTR_RESP_PORT:
+		if (len < 2)
+			goto badmsg;
+
+	        attr->v.uint16 = ntohs(mbuf_read_u16(mb));
+		break;
+
+	case STUN_ATTR_LIFETIME:
+	case STUN_ATTR_PRIORITY:
+	case STUN_ATTR_FINGERPRINT:
+		if (len != 4)
+			goto badmsg;
+
+	        attr->v.uint32 = ntohl(mbuf_read_u32(mb));
+		break;
+
+	case STUN_ATTR_DATA:
+	case STUN_ATTR_PADDING:
+		attr->v.mb.buf  = mem_ref(mb->buf);
+		attr->v.mb.size = mb->size;
+		attr->v.mb.pos  = mb->pos;
+		attr->v.mb.end  = mb->pos + len;
+		mb->pos += len;
+		break;
+
+	case STUN_ATTR_REQ_ADDR_FAMILY:
+	case STUN_ATTR_REQ_TRANSPORT:
+		if (len < 1)
+			goto badmsg;
+
+	        attr->v.uint8 = mbuf_read_u8(mb);
+		break;
+
+	case STUN_ATTR_EVEN_PORT:
+		if (len < 1)
+			goto badmsg;
+
+		attr->v.even_port.r = (mbuf_read_u8(mb) >> 7) & 0x1;
+		break;
+
+	case STUN_ATTR_DONT_FRAGMENT:
+	case STUN_ATTR_USE_CAND:
+		if (len > 0)
+			goto badmsg;
+
+		/* no value */
+		break;
+
+	case STUN_ATTR_RSV_TOKEN:
+	case STUN_ATTR_CONTROLLING:
+	case STUN_ATTR_CONTROLLED:
+		if (len != 8)
+			goto badmsg;
+
+	        attr->v.uint64 = sys_ntohll(mbuf_read_u64(mb));
+		break;
+
+	default:
+		mb->pos += len;
+
+		if (attr->type >= 0x8000)
+			break;
+
+		if (ua && ua->typec < ARRAY_SIZE(ua->typev))
+			ua->typev[ua->typec++] = attr->type;
+		break;
+	}
+
+	if (err)
+		goto error;
+
+	/* padding */
+	while (((mb->pos - start) & 0x03) && mbuf_get_left(mb))
+		++mb->pos;
+
+	*attrp = attr;
+
+	return 0;
+
+ badmsg:
+	err = EBADMSG;
+ error:
+	mem_deref(attr);
+
+	return err;
+}
+
+
+/**
+ * Get the name of a STUN attribute
+ *
+ * @param type STUN attribute type
+ *
+ * @return String with attribute name
+ */
+const char *stun_attr_name(uint16_t type)
+{
+	switch (type) {
+
+	case STUN_ATTR_MAPPED_ADDR:       return "MAPPED-ADDRESS";
+	case STUN_ATTR_CHANGE_REQ:        return "CHANGE-REQUEST";
+	case STUN_ATTR_USERNAME:          return "USERNAME";
+	case STUN_ATTR_MSG_INTEGRITY:     return "MESSAGE-INTEGRITY";
+	case STUN_ATTR_ERR_CODE:          return "ERROR-CODE";
+	case STUN_ATTR_UNKNOWN_ATTR:      return "UNKNOWN-ATTRIBUTE";
+	case STUN_ATTR_CHANNEL_NUMBER:    return "CHANNEL-NUMBER";
+	case STUN_ATTR_LIFETIME:          return "LIFETIME";
+	case STUN_ATTR_XOR_PEER_ADDR:     return "XOR-PEER-ADDRESS";
+	case STUN_ATTR_DATA:              return "DATA";
+	case STUN_ATTR_REALM:             return "REALM";
+	case STUN_ATTR_NONCE:             return "NONCE";
+	case STUN_ATTR_XOR_RELAY_ADDR:    return "XOR-RELAYED-ADDRESS";
+	case STUN_ATTR_REQ_ADDR_FAMILY:   return "REQUESTED-ADDRESS-FAMILY";
+	case STUN_ATTR_EVEN_PORT:         return "EVEN_PORT";
+	case STUN_ATTR_REQ_TRANSPORT:     return "REQUESTED-TRANSPORT";
+	case STUN_ATTR_DONT_FRAGMENT:     return "DONT-FRAGMENT";
+	case STUN_ATTR_XOR_MAPPED_ADDR:   return "XOR-MAPPED-ADDRESS";
+	case STUN_ATTR_RSV_TOKEN:         return "RESERVATION-TOKEN";
+	case STUN_ATTR_PRIORITY:          return "PRIORITY";
+	case STUN_ATTR_USE_CAND:          return "USE-CANDIDATE";
+	case STUN_ATTR_PADDING:           return "PADDING";
+	case STUN_ATTR_RESP_PORT:         return "RESPONSE-PORT";
+	case STUN_ATTR_SOFTWARE:          return "SOFTWARE";
+	case STUN_ATTR_ALT_SERVER:        return "ALTERNATE-SERVER";
+	case STUN_ATTR_FINGERPRINT:       return "FINGERPRINT";
+	case STUN_ATTR_CONTROLLING:       return "ICE-CONTROLLING";
+	case STUN_ATTR_CONTROLLED:        return "ICE-CONTROLLED";
+	case STUN_ATTR_RESP_ORIGIN:       return "RESPONSE-ORIGIN";
+	case STUN_ATTR_OTHER_ADDR:        return "OTHER-ADDR";
+	default:                          return "???";
+	}
+}
+
+
+void stun_attr_dump(const struct stun_attr *a)
+{
+	uint32_t i;
+	size_t len;
+
+	if (!a)
+		return;
+
+	(void)re_printf(" %-25s", stun_attr_name(a->type));
+
+	switch (a->type) {
+
+	case STUN_ATTR_MAPPED_ADDR:
+	case STUN_ATTR_XOR_PEER_ADDR:
+	case STUN_ATTR_XOR_RELAY_ADDR:
+	case STUN_ATTR_XOR_MAPPED_ADDR:
+	case STUN_ATTR_ALT_SERVER:
+	case STUN_ATTR_RESP_ORIGIN:
+	case STUN_ATTR_OTHER_ADDR:
+		(void)re_printf("%J", &a->v.sa);
+		break;
+
+	case STUN_ATTR_CHANGE_REQ:
+		(void)re_printf("ip=%u port=%u", a->v.change_req.ip,
+				a->v.change_req.port);
+		break;
+
+	case STUN_ATTR_USERNAME:
+	case STUN_ATTR_REALM:
+	case STUN_ATTR_NONCE:
+	case STUN_ATTR_SOFTWARE:
+		(void)re_printf("%s", a->v.str);
+		break;
+
+	case STUN_ATTR_MSG_INTEGRITY:
+		(void)re_printf("%w", a->v.msg_integrity,
+				sizeof(a->v.msg_integrity));
+		break;
+
+	case STUN_ATTR_ERR_CODE:
+		(void)re_printf("%u %s", a->v.err_code.code,
+				a->v.err_code.reason);
+		break;
+
+	case STUN_ATTR_UNKNOWN_ATTR:
+		for (i=0; i<a->v.unknown_attr.typec; i++)
+			(void)re_printf("0x%04x ", a->v.unknown_attr.typev[i]);
+		break;
+
+	case STUN_ATTR_CHANNEL_NUMBER:
+		(void)re_printf("0x%04x", a->v.uint16);
+		break;
+
+	case STUN_ATTR_LIFETIME:
+	case STUN_ATTR_PRIORITY:
+		(void)re_printf("%u", a->v.uint32);
+		break;
+
+	case STUN_ATTR_DATA:
+	case STUN_ATTR_PADDING:
+		len = min(mbuf_get_left(&a->v.mb), 16);
+		(void)re_printf("%w%s (%zu bytes)", mbuf_buf(&a->v.mb), len,
+				mbuf_get_left(&a->v.mb) > 16 ? "..." : "",
+				mbuf_get_left(&a->v.mb));
+		break;
+
+	case STUN_ATTR_REQ_ADDR_FAMILY:
+	case STUN_ATTR_REQ_TRANSPORT:
+		(void)re_printf("%u", a->v.uint8);
+		break;
+
+	case STUN_ATTR_EVEN_PORT:
+		(void)re_printf("r=%u", a->v.even_port.r);
+		break;
+
+	case STUN_ATTR_DONT_FRAGMENT:
+	case STUN_ATTR_USE_CAND:
+		/* no value */
+		break;
+
+	case STUN_ATTR_RSV_TOKEN:
+		(void)re_printf("0x%016llx", a->v.rsv_token);
+		break;
+
+	case STUN_ATTR_RESP_PORT:
+		(void)re_printf("%u", a->v.uint16);
+		break;
+
+	case STUN_ATTR_FINGERPRINT:
+		(void)re_printf("0x%08x", a->v.fingerprint);
+		break;
+
+	case STUN_ATTR_CONTROLLING:
+	case STUN_ATTR_CONTROLLED:
+		(void)re_printf("%llu", a->v.uint64);
+		break;
+
+	default:
+		(void)re_printf("???");
+		break;
+	}
+
+	(void)re_printf("\n");
+}
diff --git a/src/stun/ctrans.c b/src/stun/ctrans.c
new file mode 100644
index 0000000..e164b9d
--- /dev/null
+++ b/src/stun/ctrans.c
@@ -0,0 +1,386 @@
+/**
+ * @file stun/ctrans.c  STUN Client transactions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_srtp.h>
+#include <re_tls.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_md5.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+struct stun_ctrans {
+	struct le le;
+	struct tmr tmr;
+	struct sa dst;
+	uint8_t tid[STUN_TID_SIZE];
+	struct stun_ctrans **ctp;
+	uint8_t *key;
+	size_t keylen;
+	void *sock;
+	struct mbuf *mb;
+	size_t pos;
+	struct stun *stun;
+	stun_resp_h *resph;
+	void *arg;
+	int proto;
+	uint32_t txc;
+	uint32_t ival;
+	uint16_t met;
+};
+
+
+static void completed(struct stun_ctrans *ct, int err, uint16_t scode,
+		      const char *reason, const struct stun_msg *msg)
+{
+	stun_resp_h *resph = ct->resph;
+	void *arg = ct->arg;
+
+	list_unlink(&ct->le);
+	tmr_cancel(&ct->tmr);
+
+	if (ct->ctp) {
+		*ct->ctp = NULL;
+		ct->ctp = NULL;
+	}
+
+	ct->resph = NULL;
+
+	/* must be destroyed before calling handler */
+	mem_deref(ct);
+
+	if (resph)
+		resph(err, scode, reason, msg, arg);
+}
+
+
+static void destructor(void *arg)
+{
+	struct stun_ctrans *ct = arg;
+
+	list_unlink(&ct->le);
+	tmr_cancel(&ct->tmr);
+	mem_deref(ct->key);
+	mem_deref(ct->sock);
+	mem_deref(ct->mb);
+}
+
+
+static void timeout_handler(void *arg)
+{
+	struct stun_ctrans *ct = arg;
+	const struct stun_conf *cfg = stun_conf(ct->stun);
+	int err = ETIMEDOUT;
+
+	if (ct->txc++ >= cfg->rc)
+		goto error;
+
+	ct->mb->pos = ct->pos;
+
+	err = stun_send(ct->proto, ct->sock, &ct->dst, ct->mb);
+	if (err)
+		goto error;
+
+	ct->ival = (ct->txc >= cfg->rc) ? cfg->rto * cfg->rm : ct->ival * 2;
+
+	tmr_start(&ct->tmr, ct->ival, timeout_handler, ct);
+	return;
+
+ error:
+	completed(ct, err, 0, NULL, NULL);
+}
+
+
+static bool match_handler(struct le *le, void *arg)
+{
+	struct stun_ctrans *ct = le->data;
+	struct stun_msg *msg = arg;
+
+	if (ct->met != stun_msg_method(msg))
+		return false;
+
+	if (memcmp(ct->tid, stun_msg_tid(msg), STUN_TID_SIZE))
+		return false;
+
+	return true;
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+	struct stun *stun = arg;
+	(void)src;
+
+	(void)stun_recv(stun, mb);
+}
+
+
+static void tcp_recv_handler(struct mbuf *mb, void *arg)
+{
+	struct stun_ctrans *ct = arg;
+
+	(void)stun_recv(ct->stun, mb);
+}
+
+
+static void tcp_estab_handler(void *arg)
+{
+	struct stun_ctrans *ct = arg;
+	int err;
+
+	err = tcp_send(ct->sock, ct->mb);
+	if (!err)
+		return;
+
+	completed(ct, err, 0, NULL, NULL);
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+	struct stun_ctrans *ct = arg;
+
+	completed(ct, err, 0, NULL, NULL);
+}
+
+
+/**
+ * Handle an incoming STUN message to a Client Transaction
+ *
+ * @param stun STUN instance
+ * @param msg  STUN message
+ * @param ua   Unknown attributes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_ctrans_recv(struct stun *stun, const struct stun_msg *msg,
+		     const struct stun_unknown_attr *ua)
+{
+	struct stun_errcode ec = {0, "OK"};
+	struct stun_attr *errcode;
+	struct stun_ctrans *ct;
+	int err = 0, herr = 0;
+
+	if (!stun || !msg || !ua)
+		return EINVAL;
+
+	switch (stun_msg_class(msg)) {
+
+	case STUN_CLASS_ERROR_RESP:
+		errcode = stun_msg_attr(msg, STUN_ATTR_ERR_CODE);
+		if (!errcode)
+			herr = EPROTO;
+		else
+			ec = errcode->v.err_code;
+		/*@fallthrough@*/
+
+	case STUN_CLASS_SUCCESS_RESP:
+		ct = list_ledata(list_apply(&stun->ctl, true,
+					    match_handler, (void *)msg));
+		if (!ct) {
+			err = ENOENT;
+			break;
+		}
+
+		switch (ec.code) {
+
+		case 401:
+		case 438:
+			break;
+
+		default:
+			if (!ct->key)
+				break;
+
+			err = stun_msg_chk_mi(msg, ct->key, ct->keylen);
+			break;
+		}
+
+		if (err)
+			break;
+
+		if (!herr && ua->typec > 0)
+			herr = EPROTO;
+
+		completed(ct, herr, ec.code, ec.reason, msg);
+		break;
+
+	default:
+		break;
+	}
+
+	return err;
+}
+
+
+int stun_ctrans_request(struct stun_ctrans **ctp, struct stun *stun, int proto,
+			void *sock, const struct sa *dst, struct mbuf *mb,
+			const uint8_t tid[], uint16_t met, const uint8_t *key,
+			size_t keylen, stun_resp_h *resph, void *arg)
+{
+	struct stun_ctrans *ct;
+	int err = 0;
+
+	if (!stun || !mb)
+		return EINVAL;
+
+	ct = mem_zalloc(sizeof(*ct), destructor);
+	if (!ct)
+		return ENOMEM;
+
+	list_append(&stun->ctl, &ct->le, ct);
+	memcpy(ct->tid, tid, STUN_TID_SIZE);
+	ct->proto = proto;
+	ct->sock  = mem_ref(sock);
+	ct->mb    = mem_ref(mb);
+	ct->pos   = mb->pos;
+	ct->stun  = stun;
+	ct->met   = met;
+
+	if (key) {
+		ct->key = mem_alloc(keylen, NULL);
+		if (!ct->key) {
+			err = ENOMEM;
+			goto out;
+		}
+
+		memcpy(ct->key, key, keylen);
+		ct->keylen = keylen;
+	}
+
+	switch (proto) {
+
+	case IPPROTO_UDP:
+		if (!dst) {
+			err = EINVAL;
+			break;
+		}
+
+		ct->dst = *dst;
+		ct->ival = stun_conf(stun)->rto;
+		tmr_start(&ct->tmr, ct->ival, timeout_handler, ct);
+
+		if (!sock) {
+			err = udp_listen((struct udp_sock **)&ct->sock, NULL,
+					 udp_recv_handler, stun);
+			if (err)
+				break;
+		}
+
+		ct->txc = 1;
+		err = udp_send(ct->sock, dst, mb);
+		break;
+
+	case IPPROTO_TCP:
+		ct->txc = stun_conf(stun)->rc;
+		tmr_start(&ct->tmr, stun_conf(stun)->ti, timeout_handler, ct);
+		if (sock) {
+			err = tcp_send(sock, mb);
+			break;
+		}
+
+		err = tcp_connect((struct tcp_conn **)&ct->sock, dst,
+				  tcp_estab_handler, tcp_recv_handler,
+				  tcp_close_handler, ct);
+		break;
+
+#ifdef USE_DTLS
+	case STUN_TRANSP_DTLS:
+		if (!sock) {
+			err = EINVAL;
+			break;
+		}
+
+		ct->ival = stun_conf(stun)->rto;
+		tmr_start(&ct->tmr, ct->ival, timeout_handler, ct);
+
+		ct->txc = 1;
+		err = dtls_send(ct->sock, mb);
+		break;
+#endif
+
+	default:
+		err = EPROTONOSUPPORT;
+		break;
+	}
+
+ out:
+	if (!err) {
+		if (ctp) {
+			ct->ctp = ctp;
+			*ctp = ct;
+		}
+
+		ct->resph = resph;
+		ct->arg   = arg;
+	}
+	else
+		mem_deref(ct);
+
+	return err;
+}
+
+
+static bool close_handler(struct le *le, void *arg)
+{
+	struct stun_ctrans *ct = le->data;
+	(void)arg;
+
+	completed(ct, ECONNABORTED, 0, NULL, NULL);
+
+	return false;
+}
+
+
+void stun_ctrans_close(struct stun *stun)
+{
+	if (!stun)
+		return;
+
+	(void)list_apply(&stun->ctl, true, close_handler, NULL);
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+	struct stun_ctrans *ct = le->data;
+	struct re_printf *pf = arg;
+	int err = 0;
+
+	err |= re_hprintf(pf, " method=%s", stun_method_name(ct->met));
+	err |= re_hprintf(pf, " tid=%w", ct->tid, sizeof(ct->tid));
+	err |= re_hprintf(pf, " rto=%ums", stun_conf(ct->stun)->rto);
+	err |= re_hprintf(pf, " tmr=%llu", tmr_get_expire(&ct->tmr));
+	err |= re_hprintf(pf, " n=%u", ct->txc);
+	err |= re_hprintf(pf, " interval=%u", ct->ival);
+	err |= re_hprintf(pf, "\n");
+
+	return 0 != err;
+}
+
+
+int stun_ctrans_debug(struct re_printf *pf, const struct stun *stun)
+{
+	int err;
+
+	if (!stun)
+		return 0;
+
+	err = re_hprintf(pf, "STUN client transactions: (%u)\n",
+			 list_count(&stun->ctl));
+
+	(void)list_apply(&stun->ctl, true, debug_handler, pf);
+
+	return err;
+}
diff --git a/src/stun/dnsdisc.c b/src/stun/dnsdisc.c
new file mode 100644
index 0000000..c79b2c4
--- /dev/null
+++ b/src/stun/dnsdisc.c
@@ -0,0 +1,293 @@
+/**
+ * @file dnsdisc.c  DNS Discovery of a STUN Server
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#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_sa.h>
+#include <re_dns.h>
+#include <re_stun.h>
+
+
+#define DEBUG_MODULE "dnsdisc"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** DNS Query */
+struct stun_dns {
+	char domain[256];      /**< Cached domain name      */
+	stun_dns_h *dnsh;      /**< DNS Response handler    */
+	void *arg;             /**< Handler argument        */
+	struct sa srv;         /**< Resolved server address */
+	struct dnsc *dnsc;     /**< DNS Client              */
+	struct dns_query *dq;  /**< Current DNS query       */
+	int af;                /**< Preferred Address family*/
+	uint16_t port;         /**< Default Port            */
+};
+
+const char *stun_proto_udp = "udp";   /**< UDP Protocol  */
+const char *stun_proto_tcp = "tcp";   /**< TCP Protocol  */
+
+const char *stun_usage_binding   = "stun";  /**< Binding usage */
+const char *stuns_usage_binding  = "stuns"; /**< Binding usage TLS */
+const char *stun_usage_relay     = "turn";
+const char *stuns_usage_relay    = "turns";
+const char *stun_usage_behavior  = "stun-behavior";
+const char *stuns_usage_behavior = "stun-behaviors";
+
+
+static void resolved(const struct stun_dns *dns, int err)
+{
+	stun_dns_h *dnsh = dns->dnsh;
+	void *dnsh_arg = dns->arg;
+
+	DEBUG_INFO("resolved: %J (%m)\n", &dns->srv, err);
+
+	dnsh(err, &dns->srv, dnsh_arg);
+}
+
+
+static void a_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+		      struct list *authl, struct list *addl, void *arg)
+{
+	struct stun_dns *dns = arg;
+	struct dnsrr *rr;
+
+	(void)hdr;
+	(void)authl;
+	(void)addl;
+
+	/* Find A answers */
+	rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_A, DNS_CLASS_IN, false);
+	if (!rr) {
+		err = err ? err : EDESTADDRREQ;
+		goto out;
+	}
+
+	sa_set_in(&dns->srv, rr->rdata.a.addr, sa_port(&dns->srv));
+
+	DEBUG_INFO("A answer: %j\n", &dns->srv);
+
+ out:
+	resolved(dns, err);
+}
+
+
+#ifdef HAVE_INET6
+static void aaaa_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+			 struct list *authl, struct list *addl, void *arg)
+{
+	struct stun_dns *dns = arg;
+	struct dnsrr *rr;
+
+	(void)hdr;
+	(void)authl;
+	(void)addl;
+
+	/* Find A answers */
+	rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_AAAA, DNS_CLASS_IN, false);
+	if (!rr) {
+		err = err ? err : EDESTADDRREQ;
+		goto out;
+	}
+
+	sa_set_in6(&dns->srv, rr->rdata.aaaa.addr, sa_port(&dns->srv));
+
+	DEBUG_INFO("AAAA answer: %j\n", &dns->srv);
+
+ out:
+	resolved(dns, err);
+}
+#endif
+
+
+static int a_or_aaaa_query(struct stun_dns *dns, const char *name)
+{
+	dns->dq = mem_deref(dns->dq);
+
+	switch (dns->af) {
+
+	case AF_INET:
+		return dnsc_query(&dns->dq, dns->dnsc, name, DNS_TYPE_A,
+				  DNS_CLASS_IN, true, a_handler, dns);
+
+#ifdef HAVE_INET6
+	case AF_INET6:
+		return dnsc_query(&dns->dq, dns->dnsc, name, DNS_TYPE_AAAA,
+				  DNS_CLASS_IN, true, aaaa_handler, dns);
+#endif
+
+	default:
+		return EAFNOSUPPORT;
+	}
+}
+
+
+static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+			struct list *authl, struct list *addl, void *arg)
+{
+	struct stun_dns *dns = arg;
+	struct dnsrr *rr, *arr;
+
+	(void)hdr;
+	(void)authl;
+
+	dns_rrlist_sort(ansl, DNS_TYPE_SRV, (size_t)dns->arg);
+
+	/* Find SRV answers */
+	rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_SRV, DNS_CLASS_IN, false);
+	if (!rr) {
+		DEBUG_INFO("no SRV entry, trying A lookup on \"%s\"\n",
+			   dns->domain);
+
+		sa_set_in(&dns->srv, 0, dns->port);
+
+		err = a_or_aaaa_query(dns, dns->domain);
+		if (err)
+			goto out;
+
+		return;
+	}
+
+	DEBUG_INFO("SRV answer: %s:%u\n", rr->rdata.srv.target,
+		   rr->rdata.srv.port);
+
+	/* Look for Additional information */
+	switch (dns->af) {
+
+	case AF_INET:
+		arr = dns_rrlist_find(addl, rr->rdata.srv.target,
+				      DNS_TYPE_A, DNS_CLASS_IN, true);
+		if (arr) {
+			sa_set_in(&dns->srv, arr->rdata.a.addr,
+				  rr->rdata.srv.port);
+			DEBUG_INFO("additional A: %j\n", &dns->srv);
+			goto out;
+		}
+		break;
+
+#ifdef HAVE_INET6
+	case AF_INET6:
+		arr = dns_rrlist_find(addl, rr->rdata.srv.target,
+				      DNS_TYPE_AAAA, DNS_CLASS_IN, true);
+		if (arr) {
+			sa_set_in6(&dns->srv, arr->rdata.aaaa.addr,
+				  rr->rdata.srv.port);
+			DEBUG_INFO("additional AAAA: %j\n", &dns->srv);
+			goto out;
+		}
+		break;
+#endif
+	}
+
+	sa_set_in(&dns->srv, 0, rr->rdata.srv.port);
+
+	err = a_or_aaaa_query(dns, rr->rdata.srv.target);
+	if (err) {
+		DEBUG_WARNING("SRV: A lookup failed (%m)\n", err);
+		goto out;
+	}
+
+	DEBUG_INFO("SRV handler: doing A/AAAA lookup..\n");
+
+	return;
+
+ out:
+	resolved(dns, err);
+}
+
+
+static void dnsdisc_destructor(void *data)
+{
+	struct stun_dns *dns = data;
+
+	mem_deref(dns->dq);
+}
+
+
+/**
+ * Do a DNS Discovery of a STUN Server
+ *
+ * @param dnsp    Pointer to allocated DNS Discovery object
+ * @param dnsc    DNS Client
+ * @param service Name of service to discover (e.g. "stun")
+ * @param proto   Transport protocol (e.g. "udp")
+ * @param af      Preferred Address Family
+ * @param domain  Domain name or IP address of STUN server
+ * @param port    Port number (if 0 do SRV lookup)
+ * @param dnsh    DNS Response handler
+ * @param arg     Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_server_discover(struct stun_dns **dnsp, struct dnsc *dnsc,
+			 const char *service, const char *proto,
+			 int af, const char *domain, uint16_t port,
+			 stun_dns_h *dnsh, void *arg)
+{
+	struct stun_dns *dns;
+	int err;
+
+	if (!dnsp || !service || !proto || !domain || !domain[0] || !dnsh)
+		return EINVAL;
+
+	dns = mem_zalloc(sizeof(*dns), dnsdisc_destructor);
+	if (!dns)
+		return ENOMEM;
+
+	dns->port = service[strlen(service)-1] == 's' ? STUNS_PORT : STUN_PORT;
+	dns->dnsh = dnsh;
+	dns->arg  = arg;
+	dns->dnsc = dnsc;
+	dns->af   = af;
+
+	/* Numeric IP address - no lookup */
+	if (0 == sa_set_str(&dns->srv, domain, port ? port : dns->port)) {
+
+		DEBUG_INFO("IP (%s)\n", domain);
+
+		resolved(dns, 0);
+		err = 0;
+		goto out; /* free now */
+	}
+	/* Port specified - use AAAA or A lookup */
+	else if (port) {
+		sa_set_in(&dns->srv, 0, port);
+		DEBUG_INFO("resolving A query: (%s)\n", domain);
+
+		err = a_or_aaaa_query(dns, domain);
+		if (err) {
+			DEBUG_WARNING("%s: A/AAAA lookup failed (%m)\n",
+				      domain, err);
+			goto out;
+		}
+	}
+	/* SRV lookup */
+	else {
+		char q[256];
+		str_ncpy(dns->domain, domain, sizeof(dns->domain));
+		(void)re_snprintf(q, sizeof(q), "_%s._%s.%s", service, proto,
+				  domain);
+		DEBUG_INFO("resolving SRV query: (%s)\n", q);
+		err = dnsc_query(&dns->dq, dnsc, q, DNS_TYPE_SRV, DNS_CLASS_IN,
+				 true, srv_handler, dns);
+		if (err) {
+			DEBUG_WARNING("%s: SRV lookup failed (%m)\n", q, err);
+			goto out;
+		}
+	}
+
+	*dnsp = dns;
+
+	return 0;
+
+ out:
+	mem_deref(dns);
+	return err;
+}
diff --git a/src/stun/hdr.c b/src/stun/hdr.c
new file mode 100644
index 0000000..d38e000
--- /dev/null
+++ b/src/stun/hdr.c
@@ -0,0 +1,82 @@
+/**
+ * @file stun/hdr.c  STUN Header encoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+int stun_hdr_encode(struct mbuf *mb, const struct stun_hdr *hdr)
+{
+	int err = 0;
+
+	if (!mb || !hdr)
+		return EINVAL;
+
+	err |= mbuf_write_u16(mb, htons(hdr->type & 0x3fff));
+	err |= mbuf_write_u16(mb, htons(hdr->len));
+	err |= mbuf_write_u32(mb, htonl(hdr->cookie));
+	err |= mbuf_write_mem(mb, hdr->tid, sizeof(hdr->tid));
+
+	return err;
+}
+
+
+int stun_hdr_decode(struct mbuf *mb, struct stun_hdr *hdr)
+{
+	if (!mb || !hdr)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < STUN_HEADER_SIZE)
+		return EBADMSG;
+
+	hdr->type = ntohs(mbuf_read_u16(mb));
+	if (hdr->type & 0xc000)
+		return EBADMSG;
+
+	hdr->len = ntohs(mbuf_read_u16(mb));
+	if (hdr->len & 0x3)
+		return EBADMSG;
+
+	hdr->cookie = ntohl(mbuf_read_u32(mb));
+	(void)mbuf_read_mem(mb, hdr->tid, sizeof(hdr->tid));
+
+	if (mbuf_get_left(mb) < hdr->len)
+		return EBADMSG;
+
+	return 0;
+}
+
+
+const char *stun_class_name(uint16_t class)
+{
+	switch (class) {
+
+	case STUN_CLASS_REQUEST:      return "Request";
+	case STUN_CLASS_INDICATION:   return "Indication";
+	case STUN_CLASS_SUCCESS_RESP: return "Success Response";
+	case STUN_CLASS_ERROR_RESP:   return "Error Response";
+	default:                      return "???";
+	}
+}
+
+
+const char *stun_method_name(uint16_t method)
+{
+	switch (method) {
+
+	case STUN_METHOD_BINDING:    return "Binding";
+	case STUN_METHOD_ALLOCATE:   return "Allocate";
+	case STUN_METHOD_REFRESH:    return "Refresh";
+	case STUN_METHOD_SEND:       return "Send";
+	case STUN_METHOD_DATA:       return "Data";
+	case STUN_METHOD_CREATEPERM: return "CreatePermission";
+	case STUN_METHOD_CHANBIND:   return "ChannelBind";
+	default:                     return "???";
+	}
+}
diff --git a/src/stun/ind.c b/src/stun/ind.c
new file mode 100644
index 0000000..cd6f364
--- /dev/null
+++ b/src/stun/ind.c
@@ -0,0 +1,68 @@
+/**
+ * @file ind.c  STUN Indication
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_sys.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+/**
+ * Send a STUN Indication message
+ *
+ * @param proto   Transport Protocol
+ * @param sock    Socket; UDP (struct udp_sock) or TCP (struct tcp_conn)
+ * @param dst     Destination network address
+ * @param presz   Number of bytes in preamble, if sending over TURN
+ * @param method  STUN Method
+ * @param key     Authentication key (optional)
+ * @param keylen  Number of bytes in authentication key
+ * @param fp      Use STUN Fingerprint attribute
+ * @param attrc   Number of attributes to encode (variable arguments)
+ * @param ...     Variable list of attribute-tuples
+ *                Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_indication(int proto, void *sock, const struct sa *dst, size_t presz,
+		    uint16_t method, const uint8_t *key, size_t keylen,
+		    bool fp, uint32_t attrc, ...)
+{
+	uint8_t tid[STUN_TID_SIZE];
+	struct mbuf *mb;
+	va_list ap;
+	uint32_t i;
+	int err;
+
+	if (!sock)
+		return EINVAL;
+
+	mb = mbuf_alloc(2048);
+	if (!mb)
+		return ENOMEM;
+
+	for (i=0; i<STUN_TID_SIZE; i++)
+		tid[i] = rand_u32();
+
+	va_start(ap, attrc);
+	mb->pos = presz;
+	err = stun_msg_vencode(mb, method, STUN_CLASS_INDICATION, tid, NULL,
+			       key, keylen, fp, 0x00, attrc, ap);
+	va_end(ap);
+	if (err)
+		goto out;
+
+	mb->pos = presz;
+	err = stun_send(proto, sock, dst, mb);
+
+ out:
+	mem_deref(mb);
+
+	return err;
+}
diff --git a/src/stun/keepalive.c b/src/stun/keepalive.c
new file mode 100644
index 0000000..f7337a4
--- /dev/null
+++ b/src/stun/keepalive.c
@@ -0,0 +1,248 @@
+/**
+ * @file stun/keepalive.c  STUN usage for NAT Keepalives
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_stun.h>
+
+
+#define DEBUG_MODULE "keepalive"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Defines a STUN Keepalive session */
+struct stun_keepalive {
+	struct stun_ctrans *ct;   /**< STUN client transaction              */
+	struct stun *stun;        /**< STUN instance                        */
+	struct udp_helper *uh;
+	int proto;
+	void *sock;
+	struct sa dst;
+	struct tmr tmr;           /**< Refresh timer                        */
+	uint32_t interval;        /**< Refresh interval in seconds          */
+	stun_mapped_addr_h *mah;  /**< Mapped address handler               */
+	void *arg;                /**< Handler argument                     */
+	struct sa map;            /**< Mapped IP address and port           */
+	struct sa xormap;         /**< XOR-Mapped IP address and port       */
+	struct sa curmap;         /**< Currently mapped IP address and port */
+};
+
+static void timeout(void *arg);
+
+
+static void keepalive_destructor(void *data)
+{
+	struct stun_keepalive *ska = data;
+
+	tmr_cancel(&ska->tmr);
+
+	mem_deref(ska->ct);
+	mem_deref(ska->uh);
+	mem_deref(ska->sock);
+	mem_deref(ska->stun);
+}
+
+
+static void call_handler(struct stun_keepalive *ska, int err,
+			 const struct sa *map)
+{
+	if (ska->mah)
+		ska->mah(err, map, ska->arg);
+}
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+				  const struct stun_msg *msg, void *arg)
+{
+	struct stun_keepalive *ska = arg;
+	struct stun_attr *attr;
+	(void)reason;
+
+	/* Restart timer */
+	if (ska->interval > 0)
+		tmr_start(&ska->tmr, ska->interval*1000, timeout, ska);
+
+	if (err || scode) {
+		/* Clear current mapped addr to force new notification */
+		sa_set_in(&ska->curmap, 0, 0);
+
+		goto out;
+	}
+
+	attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+	if (!attr)
+		attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+
+	if (!attr) {
+		err = ENOENT;
+		goto out;
+	}
+
+	if (!sa_cmp(&ska->curmap, &attr->v.sa, SA_ALL)) {
+		ska->curmap = attr->v.sa;
+		call_handler(ska, 0, &ska->curmap);
+	}
+
+ out:
+	if (err)
+		call_handler(ska, err, NULL);
+}
+
+
+static void timeout(void *arg)
+{
+	struct stun_keepalive *ska = arg;
+	int err;
+
+	if (ska->ct)
+		ska->ct = mem_deref(ska->ct);
+
+	err = stun_request(&ska->ct, ska->stun, ska->proto, ska->sock,
+			   &ska->dst, 0, STUN_METHOD_BINDING, NULL, 0, false,
+			   stun_response_handler, ska, 1,
+			   STUN_ATTR_SOFTWARE, stun_software);
+	if (0 == err)
+		return;
+
+	/* Restart timer */
+	if (ska->interval > 0)
+		tmr_start(&ska->tmr, ska->interval*1000, timeout, ska);
+
+	/* Error */
+	call_handler(ska, err, NULL);
+}
+
+
+static bool udp_recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+	struct stun_keepalive *ska = arg;
+	struct stun_unknown_attr ua;
+	struct stun_msg *msg;
+	size_t pos = mb->pos;
+	bool hdld;
+
+	if (!sa_cmp(&ska->dst, src, SA_ALL))
+		return false;
+
+	if (stun_msg_decode(&msg, mb, &ua))
+		return false;
+
+	if (stun_msg_method(msg) != STUN_METHOD_BINDING) {
+		hdld = false;
+		mb->pos = pos;
+		goto out;
+	}
+
+	switch (stun_msg_class(msg)) {
+
+	case STUN_CLASS_ERROR_RESP:
+	case STUN_CLASS_SUCCESS_RESP:
+		(void)stun_ctrans_recv(ska->stun, msg, &ua);
+		hdld = true;
+		break;
+
+	default:
+		hdld = false;
+		mb->pos = pos;
+		break;
+	}
+
+ out:
+	mem_deref(msg);
+
+	return hdld;
+}
+
+
+/**
+ * Allocate a new STUN keepalive session
+ *
+ * @param skap  Pointer to keepalive object
+ * @param proto Transport protocol
+ * @param sock  Socket
+ * @param layer Protocol layer
+ * @param dst   Destination address
+ * @param conf  Configuration
+ * @param mah   Mapped address handler
+ * @param arg   Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_keepalive_alloc(struct stun_keepalive **skap,
+			 int proto, void *sock, int layer,
+			 const struct sa *dst, const struct stun_conf *conf,
+			 stun_mapped_addr_h *mah, void *arg)
+{
+	struct stun_keepalive *ska;
+	int err;
+
+	if (!skap)
+		return EINVAL;
+
+	ska = mem_zalloc(sizeof(*ska), keepalive_destructor);
+	if (!ska)
+		return ENOMEM;
+
+	err = stun_alloc(&ska->stun, conf, NULL, NULL);
+	if (err)
+		goto out;
+
+	tmr_init(&ska->tmr);
+
+	ska->proto = proto;
+	ska->sock = mem_ref(sock);
+	ska->mah = mah;
+	ska->arg = arg;
+
+	if (dst)
+		ska->dst = *dst;
+
+	switch (proto) {
+
+	case IPPROTO_UDP:
+		err = udp_register_helper(&ska->uh, sock, layer,
+					  NULL, udp_recv_handler, ska);
+		break;
+
+	default:
+		err = 0;
+		break;
+	}
+
+ out:
+	if (err)
+		mem_deref(ska);
+	else
+		*skap = ska;
+
+	return err;
+}
+
+
+/**
+ * Enable or disable keepalive timer
+ *
+ * @param ska      Keepalive object
+ * @param interval Interval in seconds (0 to disable)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+void stun_keepalive_enable(struct stun_keepalive *ska, uint32_t interval)
+{
+	if (!ska)
+		return;
+
+	ska->interval = interval;
+
+	tmr_cancel(&ska->tmr);
+	if (interval > 0)
+		tmr_start(&ska->tmr, 1, timeout, ska);
+}
diff --git a/src/stun/mod.mk b/src/stun/mod.mk
new file mode 100644
index 0000000..945c77f
--- /dev/null
+++ b/src/stun/mod.mk
@@ -0,0 +1,18 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS	+= stun/addr.c
+SRCS	+= stun/attr.c
+SRCS	+= stun/ctrans.c
+SRCS	+= stun/dnsdisc.c
+SRCS	+= stun/hdr.c
+SRCS	+= stun/ind.c
+SRCS	+= stun/keepalive.c
+SRCS	+= stun/msg.c
+SRCS	+= stun/rep.c
+SRCS	+= stun/req.c
+SRCS	+= stun/stun.c
+SRCS	+= stun/stunstr.c
diff --git a/src/stun/msg.c b/src/stun/msg.c
new file mode 100644
index 0000000..dda3d8d
--- /dev/null
+++ b/src/stun/msg.c
@@ -0,0 +1,485 @@
+/**
+ * @file stun/msg.c  STUN message encoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_md5.h>
+#include <re_sha.h>
+#include <re_hmac.h>
+#include <re_crc32.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+enum {
+	MI_SIZE = 24,
+	FP_SIZE = 8
+};
+
+
+/**
+   Defines a STUN Message object
+
+   <pre>
+
+   .---------------------.      /|\           /|\
+   | STUN Header         |       |             |
+   |---------------------|       |             |
+   |         ....        |       |--------.    |
+   |      N Attributes   |       |        |    |----.
+   |         ....        |      \|/       |    |    |
+   |---------------------|                |    |    |
+   |  MESSAGE-INTEGRITY  | <-(HMAC-SHA1)--'   \|/   |
+   |---------------------|                          |
+   |     FINGERPRINT     | <-(CRC-32)---------------'
+   '---------------------'
+   </pre>
+*/
+struct stun_msg {
+	struct stun_hdr hdr;
+	struct list attrl;
+	struct mbuf *mb;
+	size_t start;
+};
+
+
+static uint32_t fingerprint(const uint8_t *buf, size_t len)
+{
+	return (uint32_t)crc32(0, buf, (unsigned int)len) ^ 0x5354554e;
+}
+
+
+static void destructor(void *arg)
+{
+	struct stun_msg *msg = arg;
+
+	list_flush(&msg->attrl);
+	mem_deref(msg->mb);
+}
+
+
+/**
+ * Decode a buffer to a STUN Message
+ *
+ * @param msgpp Pointer to allocation STUN message
+ * @param mb    Buffer containing the raw STUN packet
+ * @param ua    Unknown attributes (optional)
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * @note `mb' will be referenced
+ */
+int stun_msg_decode(struct stun_msg **msgpp, struct mbuf *mb,
+		    struct stun_unknown_attr *ua)
+{
+	struct stun_msg *msg;
+	struct stun_hdr hdr;
+	size_t start, extra;
+	int err;
+
+	if (!msgpp || !mb)
+		return EINVAL;
+
+	start = mb->pos;
+
+	err = stun_hdr_decode(mb, &hdr);
+	if (err) {
+		mb->pos = start;
+		return err;
+	}
+
+	msg = mem_zalloc(sizeof(*msg), destructor);
+	if (!msg) {
+		mb->pos = start;
+		return ENOMEM;
+	}
+
+	msg->hdr = hdr;
+	msg->mb = mem_ref(mb);
+	msg->start = start;
+
+	if (ua)
+		ua->typec = 0;
+
+	/* mbuf_get_left(mb) >= hdr.len checked in stun_hdr_decode() above */
+	extra = mbuf_get_left(mb) - hdr.len;
+
+	while (mbuf_get_left(mb) - extra >= 4) {
+
+		struct stun_attr *attr;
+
+		err = stun_attr_decode(&attr, mb, hdr.tid, ua);
+		if (err)
+			break;
+
+		list_append(&msg->attrl, &attr->le, attr);
+	}
+
+	if (err)
+		mem_deref(msg);
+	else
+		*msgpp = msg;
+
+	mb->pos = start;
+
+	return err;
+}
+
+
+/**
+ * Get the STUN message type
+ *
+ * @param msg STUN Message
+ *
+ * @return STUN Message type
+ */
+uint16_t stun_msg_type(const struct stun_msg *msg)
+{
+	return msg ? msg->hdr.type : 0;
+}
+
+
+/**
+ * Get the STUN message class
+ *
+ * @param msg STUN Message
+ *
+ * @return STUN Message class
+ */
+uint16_t stun_msg_class(const struct stun_msg *msg)
+{
+	return STUN_CLASS(stun_msg_type(msg));
+}
+
+
+/**
+ * Get the STUN message method
+ *
+ * @param msg STUN Message
+ *
+ * @return STUN Message method
+ */
+uint16_t stun_msg_method(const struct stun_msg *msg)
+{
+	return STUN_METHOD(stun_msg_type(msg));
+}
+
+
+/**
+ * Get the STUN message Transaction-ID
+ *
+ * @param msg STUN Message
+ *
+ * @return STUN Message Transaction-ID
+ */
+const uint8_t *stun_msg_tid(const struct stun_msg *msg)
+{
+	return msg ? msg->hdr.tid : NULL;
+}
+
+
+/**
+ * Check if a STUN Message has the magic cookie
+ *
+ * @param msg STUN Message
+ *
+ * @return true if Magic Cookie, otherwise false
+ */
+bool stun_msg_mcookie(const struct stun_msg *msg)
+{
+	return msg && (STUN_MAGIC_COOKIE == msg->hdr.cookie);
+}
+
+
+/**
+ * Lookup a STUN attribute in a STUN message
+ *
+ * @param msg  STUN Message
+ * @param type STUN Attribute type
+ *
+ * @return STUN Attribute if found, otherwise NULL
+ */
+struct stun_attr *stun_msg_attr(const struct stun_msg *msg, uint16_t type)
+{
+	struct le *le = msg ? list_head(&msg->attrl) : NULL;
+
+	while (le) {
+		struct stun_attr *attr = le->data;
+
+		le = le->next;
+
+		if (attr->type == type)
+			return attr;
+	}
+
+	return NULL;
+}
+
+
+/**
+ * Apply a function handler to all STUN attribute
+ *
+ * @param msg  STUN Message
+ * @param h    Attribute handler
+ * @param arg  Handler argument
+ *
+ * @return STUN attribute if handler returned true, otherwise NULL
+ */
+struct stun_attr *stun_msg_attr_apply(const struct stun_msg *msg,
+				      stun_attr_h *h, void *arg)
+{
+	struct le *le = msg ? list_head(&msg->attrl) : NULL;
+
+	while (le) {
+		struct stun_attr *attr = le->data;
+
+		le = le->next;
+
+		if (h && h(attr, arg))
+			return (attr);
+	}
+
+	return NULL;
+}
+
+
+/**
+ * Encode a STUN message
+ *
+ * @param mb      Buffer to encode message into
+ * @param method  STUN Method
+ * @param class   STUN Method class
+ * @param tid     Transaction ID
+ * @param ec      STUN error code (optional)
+ * @param key     Authentication key (optional)
+ * @param keylen  Number of bytes in authentication key
+ * @param fp      Use STUN Fingerprint attribute
+ * @param padding Padding byte
+ * @param attrc   Number of attributes to encode (variable arguments)
+ * @param ap      Variable list of attribute-tuples
+ *                Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_msg_vencode(struct mbuf *mb, uint16_t method, uint8_t class,
+		     const uint8_t *tid, const struct stun_errcode *ec,
+		     const uint8_t *key, size_t keylen, bool fp,
+		     uint8_t padding, uint32_t attrc, va_list ap)
+{
+	struct stun_hdr hdr;
+	size_t start;
+	int err = 0;
+	uint32_t i;
+
+	if (!mb || !tid)
+		return EINVAL;
+
+	start = mb->pos;
+	mb->pos += STUN_HEADER_SIZE;
+
+	hdr.type   = STUN_TYPE(method, class);
+	hdr.cookie = STUN_MAGIC_COOKIE;
+	memcpy(hdr.tid, tid, STUN_TID_SIZE);
+
+	if (ec)
+		err |= stun_attr_encode(mb, STUN_ATTR_ERR_CODE, ec,
+					NULL, padding);
+
+	for (i=0; i<attrc; i++) {
+
+		uint16_t type = va_arg(ap, int);
+		const void *v = va_arg(ap, const void *);
+
+		if (!v)
+			continue;
+
+		err |= stun_attr_encode(mb, type, v, hdr.tid, padding);
+	}
+
+	/* header */
+	hdr.len = mb->pos - start - STUN_HEADER_SIZE + (key ? MI_SIZE : 0);
+	mb->pos = start;
+	err |= stun_hdr_encode(mb, &hdr);
+	mb->pos += hdr.len - (key ? MI_SIZE : 0);
+
+	if (key) {
+		uint8_t mi[20];
+
+		mb->pos = start;
+		hmac_sha1(key, keylen, mbuf_buf(mb), mbuf_get_left(mb),
+			  mi, sizeof(mi));
+
+		mb->pos += STUN_HEADER_SIZE + hdr.len - MI_SIZE;
+		err |= stun_attr_encode(mb, STUN_ATTR_MSG_INTEGRITY, mi,
+					NULL, padding);
+	}
+
+	if (fp) {
+		uint32_t fprnt;
+
+		/* header */
+		hdr.len = mb->pos - start - STUN_HEADER_SIZE + FP_SIZE;
+		mb->pos = start;
+		err |= stun_hdr_encode(mb, &hdr);
+
+		mb->pos = start;
+		fprnt = fingerprint(mbuf_buf(mb), mbuf_get_left(mb));
+
+		mb->pos += STUN_HEADER_SIZE + hdr.len - FP_SIZE;
+		err |= stun_attr_encode(mb, STUN_ATTR_FINGERPRINT, &fprnt,
+					NULL, padding);
+	}
+
+	return err;
+}
+
+
+/**
+ * Encode a STUN message
+ *
+ * @param mb      Buffer to encode message into
+ * @param method  STUN Method
+ * @param class   STUN Method class
+ * @param tid     Transaction ID
+ * @param ec      STUN error code (optional)
+ * @param key     Authentication key (optional)
+ * @param keylen  Number of bytes in authentication key
+ * @param fp      Use STUN Fingerprint attribute
+ * @param padding Padding byte
+ * @param attrc   Number of attributes to encode (variable arguments)
+ * @param ...     Variable list of attribute-tuples
+ *                Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_msg_encode(struct mbuf *mb, uint16_t method, uint8_t class,
+		    const uint8_t *tid, const struct stun_errcode *ec,
+		    const uint8_t *key, size_t keylen, bool fp,
+		    uint8_t padding, uint32_t attrc, ...)
+{
+	va_list ap;
+	int err;
+
+	va_start(ap, attrc);
+	err = stun_msg_vencode(mb, method, class, tid, ec, key, keylen, fp,
+			       padding, attrc, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+/**
+ * Verify the Message-Integrity of a STUN message
+ *
+ * @param msg    STUN Message
+ * @param key    Authentication key
+ * @param keylen Number of bytes in authentication key
+ *
+ * @return 0 if verified, otherwise errorcode
+ */
+int stun_msg_chk_mi(const struct stun_msg *msg, const uint8_t *key,
+		    size_t keylen)
+{
+	uint8_t hmac[SHA_DIGEST_LENGTH];
+	struct stun_attr *mi, *fp;
+
+	if (!msg)
+		return EINVAL;
+
+	mi = stun_msg_attr(msg, STUN_ATTR_MSG_INTEGRITY);
+	if (!mi)
+		return EPROTO;
+
+	msg->mb->pos = msg->start;
+
+	fp = stun_msg_attr(msg, STUN_ATTR_FINGERPRINT);
+	if (fp) {
+		((struct stun_msg *)msg)->hdr.len -= FP_SIZE;
+		(void)stun_hdr_encode(msg->mb, &msg->hdr);
+		msg->mb->pos -= STUN_HEADER_SIZE;
+	}
+
+	hmac_sha1(key, keylen, mbuf_buf(msg->mb),
+		  STUN_HEADER_SIZE + msg->hdr.len - MI_SIZE,
+		  hmac, sizeof(hmac));
+
+	if (fp) {
+		((struct stun_msg *)msg)->hdr.len += FP_SIZE;
+		(void)stun_hdr_encode(msg->mb, &msg->hdr);
+		msg->mb->pos -= STUN_HEADER_SIZE;
+	}
+
+	if (memcmp(mi->v.msg_integrity, hmac, SHA_DIGEST_LENGTH))
+		return EBADMSG;
+
+	return 0;
+}
+
+
+/**
+ * Check the Fingerprint of a STUN message
+ *
+ * @param msg STUN Message
+ *
+ * @return 0 if fingerprint matches, otherwise errorcode
+ */
+int stun_msg_chk_fingerprint(const struct stun_msg *msg)
+{
+	struct stun_attr *fp;
+	uint32_t fprnt;
+
+	if (!msg)
+		return EINVAL;
+
+	fp = stun_msg_attr(msg, STUN_ATTR_FINGERPRINT);
+	if (!fp)
+		return EPROTO;
+
+	msg->mb->pos = msg->start;
+
+	fprnt = fingerprint(mbuf_buf(msg->mb),
+			    STUN_HEADER_SIZE + msg->hdr.len - FP_SIZE);
+
+	if (fprnt != fp->v.fingerprint)
+		return EBADMSG;
+
+	return 0;
+}
+
+
+static bool attr_print(const struct stun_attr *attr, void *arg)
+{
+	(void)arg;
+
+	stun_attr_dump(attr);
+
+	return false;
+}
+
+
+/**
+ * Print a STUN message to STDOUT
+ *
+ * @param msg STUN Message
+ */
+void stun_msg_dump(const struct stun_msg *msg)
+{
+	if (!msg)
+		return;
+
+	(void)re_printf("%s %s (len=%u cookie=%08x tid=%w)\n",
+			stun_method_name(stun_msg_method(msg)),
+			stun_class_name(stun_msg_class(msg)),
+			msg->hdr.len, msg->hdr.cookie,
+			msg->hdr.tid, sizeof(msg->hdr.tid));
+
+	stun_msg_attr_apply(msg, attr_print, NULL);
+}
diff --git a/src/stun/rep.c b/src/stun/rep.c
new file mode 100644
index 0000000..11ffbfd
--- /dev/null
+++ b/src/stun/rep.c
@@ -0,0 +1,121 @@
+/**
+ * @file stun/rep.c  STUN reply
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+/**
+ * Send a STUN response message
+ *
+ * @param proto   Transport Protocol
+ * @param sock    Socket; UDP (struct udp_sock) or TCP (struct tcp_conn)
+ * @param dst     Destination network address
+ * @param presz   Number of bytes in preamble, if sending over TURN
+ * @param req     Matching STUN request
+ * @param key     Authentication key (optional)
+ * @param keylen  Number of bytes in authentication key
+ * @param fp      Use STUN Fingerprint attribute
+ * @param attrc   Number of attributes to encode (variable arguments)
+ * @param ...     Variable list of attribute-tuples
+ *                Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_reply(int proto, void *sock, const struct sa *dst, size_t presz,
+	       const struct stun_msg *req, const uint8_t *key,
+	       size_t keylen, bool fp, uint32_t attrc, ...)
+{
+	struct mbuf *mb = NULL;
+	int err = ENOMEM;
+	va_list ap;
+
+	if (!sock || !req)
+		return EINVAL;
+
+	mb = mbuf_alloc(256);
+	if (!mb)
+		goto out;
+
+	va_start(ap, attrc);
+	mb->pos = presz;
+	err = stun_msg_vencode(mb, stun_msg_method(req),
+			       STUN_CLASS_SUCCESS_RESP, stun_msg_tid(req),
+			       NULL, key, keylen, fp, 0x00, attrc, ap);
+	va_end(ap);
+	if (err)
+		goto out;
+
+	mb->pos = presz;
+	err = stun_send(proto, sock, dst, mb);
+
+ out:
+	mem_deref(mb);
+
+	return err;
+}
+
+
+/**
+ * Send a STUN error response
+ *
+ * @param proto   Transport Protocol
+ * @param sock    Socket; UDP (struct udp_sock) or TCP (struct tcp_conn)
+ * @param dst     Destination network address
+ * @param presz   Number of bytes in preamble, if sending over TURN
+ * @param req     Matching STUN request
+ * @param scode   Status code
+ * @param reason  Reason string
+ * @param key     Authentication key (optional)
+ * @param keylen  Number of bytes in authentication key
+ * @param fp      Use STUN Fingerprint attribute
+ * @param attrc   Number of attributes to encode (variable arguments)
+ * @param ...     Variable list of attribute-tuples
+ *                Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_ereply(int proto, void *sock, const struct sa *dst, size_t presz,
+		const struct stun_msg *req, uint16_t scode,
+		const char *reason, const uint8_t *key, size_t keylen,
+		bool fp, uint32_t attrc, ...)
+{
+	struct stun_errcode ec;
+	struct mbuf *mb = NULL;
+	int err = ENOMEM;
+	va_list ap;
+
+	if (!sock || !req || !scode || !reason)
+		return EINVAL;
+
+	mb = mbuf_alloc(256);
+	if (!mb)
+		goto out;
+
+	ec.code = scode;
+	ec.reason = (char *)reason;
+
+	va_start(ap, attrc);
+	mb->pos = presz;
+	err = stun_msg_vencode(mb, stun_msg_method(req), STUN_CLASS_ERROR_RESP,
+			       stun_msg_tid(req), &ec, key, keylen,
+			       fp, 0x00, attrc, ap);
+	va_end(ap);
+	if (err)
+		goto out;
+
+	mb->pos = presz;
+	err = stun_send(proto, sock, dst, mb);
+
+ out:
+	mem_deref(mb);
+
+	return err;
+}
diff --git a/src/stun/req.c b/src/stun/req.c
new file mode 100644
index 0000000..32e127d
--- /dev/null
+++ b/src/stun/req.c
@@ -0,0 +1,76 @@
+/**
+ * @file stun/req.c  STUN request
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_sys.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+/**
+ * Send a STUN request using a client transaction
+ *
+ * @param ctp     Pointer to allocated client transaction (optional)
+ * @param stun    STUN Instance
+ * @param proto   Transport Protocol
+ * @param sock    Socket; UDP (struct udp_sock) or TCP (struct tcp_conn)
+ * @param dst     Destination network address
+ * @param presz   Number of bytes in preamble, if sending over TURN
+ * @param method  STUN Method
+ * @param key     Authentication key (optional)
+ * @param keylen  Number of bytes in authentication key
+ * @param fp      Use STUN Fingerprint attribute
+ * @param resph   Response handler
+ * @param arg     Response handler argument
+ * @param attrc   Number of attributes to encode (variable arguments)
+ * @param ...     Variable list of attribute-tuples
+ *                Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_request(struct stun_ctrans **ctp, struct stun *stun, int proto,
+		 void *sock, const struct sa *dst, size_t presz,
+		 uint16_t method, const uint8_t *key, size_t keylen, bool fp,
+		 stun_resp_h *resph, void *arg, uint32_t attrc, ...)
+{
+	uint8_t tid[STUN_TID_SIZE];
+	struct mbuf *mb;
+	uint32_t i;
+	va_list ap;
+	int err;
+
+	if (!stun)
+		return EINVAL;
+
+	mb = mbuf_alloc(512);
+	if (!mb)
+		return ENOMEM;
+
+	for (i=0; i<STUN_TID_SIZE; i++)
+		tid[i] = rand_u32();
+
+	va_start(ap, attrc);
+	mb->pos = presz;
+	err = stun_msg_vencode(mb, method, STUN_CLASS_REQUEST,
+			       tid, NULL, key, keylen, fp, 0x00, attrc, ap);
+	va_end(ap);
+	if (err)
+		goto out;
+
+	mb->pos = presz;
+	err = stun_ctrans_request(ctp, stun, proto, sock, dst, mb, tid, method,
+				  key, keylen, resph, arg);
+	if (err)
+		goto out;
+
+ out:
+	mem_deref(mb);
+
+	return err;
+}
diff --git a/src/stun/stun.c b/src/stun/stun.c
new file mode 100644
index 0000000..6dfdd97
--- /dev/null
+++ b/src/stun/stun.c
@@ -0,0 +1,188 @@
+/**
+ * @file stun.c  STUN stack
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_srtp.h>
+#include <re_tls.h>
+#include <re_sys.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+const char *stun_software = "libre v" VERSION " (" ARCH "/" OS ")";
+
+
+static const struct stun_conf conf_default = {
+	STUN_DEFAULT_RTO,
+	STUN_DEFAULT_RC,
+	STUN_DEFAULT_RM,
+	STUN_DEFAULT_TI,
+	0x00
+};
+
+
+static void destructor(void *arg)
+{
+	struct stun *stun = arg;
+
+	stun_ctrans_close(stun);
+}
+
+
+/**
+ * Allocate a new STUN instance
+ *
+ * @param stunp Pointer to allocated STUN instance
+ * @param conf  STUN configuration (optional)
+ * @param indh  STUN Indication handler (optional)
+ * @param arg   STUN Indication handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_alloc(struct stun **stunp, const struct stun_conf *conf,
+	       stun_ind_h *indh, void *arg)
+{
+	struct stun *stun;
+
+	if (!stunp)
+		return EINVAL;
+
+	stun = mem_zalloc(sizeof(*stun), destructor);
+	if (!stun)
+		return ENOMEM;
+
+	stun->conf = conf ? *conf : conf_default;
+	stun->indh = indh;
+	stun->arg  = arg;
+
+	*stunp = stun;
+
+	return 0;
+}
+
+
+/**
+ * Get STUN configuration object
+ *
+ * @param stun STUN Instance
+ *
+ * @return STUN configuration
+ */
+struct stun_conf *stun_conf(struct stun *stun)
+{
+	return stun ? &stun->conf : NULL;
+}
+
+
+/**
+ * Send a STUN message
+ *
+ * @param proto Transport protocol (IPPROTO_UDP or IPPROTO_TCP)
+ * @param sock  Socket, UDP (struct udp_sock) or TCP (struct tcp_conn)
+ * @param dst   Destination network address (UDP only)
+ * @param mb    Buffer containing the STUN message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_send(int proto, void *sock, const struct sa *dst, struct mbuf *mb)
+{
+	int err;
+
+	if (!sock || !mb)
+		return EINVAL;
+
+	switch (proto) {
+
+	case IPPROTO_UDP:
+		err = udp_send(sock, dst, mb);
+		break;
+
+	case IPPROTO_TCP:
+		err = tcp_send(sock, mb);
+		break;
+
+#ifdef USE_DTLS
+	case STUN_TRANSP_DTLS:
+		err = dtls_send(sock, mb);
+		break;
+#endif
+
+	default:
+		err = EPROTONOSUPPORT;
+		break;
+	}
+
+	return err;
+}
+
+
+/**
+ * Receive a STUN message
+ *
+ * @param stun STUN Instance
+ * @param mb   Buffer containing STUN message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_recv(struct stun *stun, struct mbuf *mb)
+{
+	struct stun_unknown_attr ua;
+	struct stun_msg *msg;
+	int err;
+
+	if (!stun || !mb)
+		return EINVAL;
+
+	err = stun_msg_decode(&msg, mb, &ua);
+	if (err)
+		return err;
+
+	switch (stun_msg_class(msg)) {
+
+	case STUN_CLASS_INDICATION:
+		if (ua.typec > 0)
+			break;
+
+		if (stun->indh)
+			stun->indh(msg, stun->arg);
+		break;
+
+	case STUN_CLASS_ERROR_RESP:
+	case STUN_CLASS_SUCCESS_RESP:
+		err = stun_ctrans_recv(stun, msg, &ua);
+		break;
+
+	default:
+		break;
+	}
+
+	mem_deref(msg);
+
+	return err;
+}
+
+
+/**
+ * Print STUN instance debug information
+ *
+ * @param pf   Print function
+ * @param stun STUN Instance
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_debug(struct re_printf *pf, const struct stun *stun)
+{
+	if (!stun)
+		return 0;
+
+	return re_hprintf(pf, "STUN debug:\n%H", stun_ctrans_debug, stun);
+}
diff --git a/src/stun/stun.h b/src/stun/stun.h
new file mode 100644
index 0000000..cda84fa
--- /dev/null
+++ b/src/stun/stun.h
@@ -0,0 +1,64 @@
+/**
+ * @file stun.h  Internal STUN interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** STUN Protocol values */
+enum {
+	STUN_MAGIC_COOKIE = 0x2112a442 /**< Magic Cookie for 3489bis        */
+};
+
+
+/** Calculate STUN message type from method and class */
+#define STUN_TYPE(method, class)	    \
+	((method)&0x0f80) << 2 |	    \
+	((method)&0x0070) << 1 |	    \
+	((method)&0x000f) << 0 |	    \
+	((class)&0x2)     << 7 |	    \
+	((class)&0x1)     << 4
+
+
+#define STUN_CLASS(type) \
+	((type >> 7 | type >> 4) & 0x3)
+
+
+#define STUN_METHOD(type) \
+	((type&0x3e00)>>2 | (type&0x00e0)>>1 | (type&0x000f))
+
+
+struct stun_hdr {
+	uint16_t type;               /**< Message type   */
+	uint16_t len;                /**< Payload length */
+	uint32_t cookie;             /**< Magic cookie   */
+	uint8_t tid[STUN_TID_SIZE];  /**< Transaction ID */
+};
+
+
+struct stun {
+	struct list ctl;
+	struct stun_conf conf;
+	stun_ind_h *indh;
+	void *arg;
+};
+
+int stun_hdr_encode(struct mbuf *mb, const struct stun_hdr *hdr);
+int stun_hdr_decode(struct mbuf *mb, struct stun_hdr *hdr);
+
+int stun_attr_encode(struct mbuf *mb, uint16_t type, const void *v,
+		     const uint8_t *tid, uint8_t padding);
+int stun_attr_decode(struct stun_attr **attrp, struct mbuf *mb,
+		     const uint8_t *tid, struct stun_unknown_attr *ua);
+void stun_attr_dump(const struct stun_attr *a);
+
+int  stun_addr_encode(struct mbuf *mb, const struct sa *addr,
+		      const uint8_t *tid);
+int  stun_addr_decode(struct mbuf *mb, struct sa *addr, const uint8_t *tid);
+
+int stun_ctrans_request(struct stun_ctrans **ctp, struct stun *stun, int proto,
+			void *sock, const struct sa *dst, struct mbuf *mb,
+			const uint8_t tid[], uint16_t met, const uint8_t *key,
+			size_t keylen, stun_resp_h *resph, void *arg);
+void stun_ctrans_close(struct stun *stun);
+int  stun_ctrans_debug(struct re_printf *pf, const struct stun *stun);
diff --git a/src/stun/stunstr.c b/src/stun/stunstr.c
new file mode 100644
index 0000000..2d0de09
--- /dev/null
+++ b/src/stun/stunstr.c
@@ -0,0 +1,46 @@
+/**
+ * @file stunstr.c  STUN Strings
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_stun.h>
+
+
+/* STUN Reason Phrase */
+const char *stun_reason_300 = "Try Alternate";
+const char *stun_reason_400 = "Bad Request";
+const char *stun_reason_401 = "Unauthorized";
+const char *stun_reason_403 = "Forbidden";
+const char *stun_reason_420 = "Unknown Attribute";
+const char *stun_reason_437 = "Allocation Mismatch";
+const char *stun_reason_438 = "Stale Nonce";
+const char *stun_reason_440 = "Address Family not Supported";
+const char *stun_reason_441 = "Wrong Credentials";
+const char *stun_reason_442 = "Unsupported Transport Protocol";
+const char *stun_reason_443 = "Peer Address Family Mismatch";
+const char *stun_reason_486 = "Allocation Quota Reached";
+const char *stun_reason_500 = "Server Error";
+const char *stun_reason_508 = "Insufficient Capacity";
+
+
+/**
+ * Get the name of a given STUN Transport
+ *
+ * @param tp STUN Transport
+ *
+ * @return Name of the corresponding STUN Transport
+ */
+const char *stun_transp_name(enum stun_transp tp)
+{
+	switch (tp) {
+
+	case STUN_TRANSP_UDP:  return "UDP";
+	case STUN_TRANSP_TCP:  return "TCP";
+	case STUN_TRANSP_DTLS: return "DTLS";
+	default:               return "???";
+	}
+}