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 "???";
+ }
+}