Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b
Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/src/turn/turnc.c b/src/turn/turnc.c
new file mode 100644
index 0000000..b103dba
--- /dev/null
+++ b/src/turn/turnc.c
@@ -0,0 +1,682 @@
+/**
+ * @file turnc.c TURN Client implementation
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_md5.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_srtp.h>
+#include <re_tls.h>
+#include <re_stun.h>
+#include <re_turn.h>
+#include "turnc.h"
+
+
+#define DEBUG_MODULE "turnc"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** TURN Client protocol values */
+enum {
+ PERM_HASH_SIZE = 16,
+ CHAN_HASH_SIZE = 16,
+ FAILC_MAX = 16, /**< Maximum number of request errors for loopcheck. */
+ STUN_ATTR_ADDR4_SIZE = 8,
+ STUN_ATTR_ADDR6_SIZE = 20,
+};
+
+
+static const uint8_t sendind_tid[STUN_TID_SIZE];
+
+static int allocate_request(struct turnc *t);
+static int refresh_request(struct turnc *t, uint32_t lifetime, bool reset_ls,
+ stun_resp_h *resph, void *arg);
+static void refresh_resp_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg);
+
+
+static void destructor(void *arg)
+{
+ struct turnc *turnc = arg;
+
+ if (turnc->allocated)
+ (void)refresh_request(turnc, 0, true, NULL, NULL);
+
+ tmr_cancel(&turnc->tmr);
+ mem_deref(turnc->ct);
+
+ hash_flush(turnc->perms);
+ mem_deref(turnc->perms);
+ mem_deref(turnc->chans);
+ mem_deref(turnc->username);
+ mem_deref(turnc->password);
+ mem_deref(turnc->nonce);
+ mem_deref(turnc->realm);
+ mem_deref(turnc->stun);
+ mem_deref(turnc->uh);
+ mem_deref(turnc->sock);
+}
+
+
+static void timeout(void *arg)
+{
+ struct turnc *turnc = arg;
+ int err;
+
+ err = refresh_request(turnc, turnc->lifetime, true,
+ refresh_resp_handler, turnc);
+ if (err)
+ turnc->th(err, 0, NULL, NULL, NULL, NULL, turnc->arg);
+}
+
+
+static void refresh_timer(struct turnc *turnc)
+{
+ const uint32_t t = turnc->lifetime*1000*3/4;
+
+ DEBUG_INFO("Start refresh timer.. %u seconds\n", t/1000);
+
+ tmr_start(&turnc->tmr, t, timeout, turnc);
+}
+
+
+static void allocate_resp_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct stun_attr *map = NULL, *rel = NULL, *ltm, *alt;
+ struct turnc *turnc = arg;
+
+ if (err || turnc_request_loops(&turnc->ls, scode))
+ goto out;
+
+ switch (scode) {
+
+ case 0:
+ map = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ rel = stun_msg_attr(msg, STUN_ATTR_XOR_RELAY_ADDR);
+ ltm = stun_msg_attr(msg, STUN_ATTR_LIFETIME);
+ if (!rel || !map) {
+ DEBUG_WARNING("xor_mapped/relay addr attr missing\n");
+ err = EPROTO;
+ break;
+ }
+
+ if (ltm)
+ turnc->lifetime = ltm->v.lifetime;
+
+ turnc->allocated = true;
+ refresh_timer(turnc);
+ break;
+
+ case 300:
+ if (turnc->proto == IPPROTO_TCP ||
+ turnc->proto == STUN_TRANSP_DTLS)
+ break;
+
+ alt = stun_msg_attr(msg, STUN_ATTR_ALT_SERVER);
+ if (!alt)
+ break;
+
+ turnc->psrv = turnc->srv;
+ turnc->srv = alt->v.alt_server;
+
+ err = allocate_request(turnc);
+ if (err)
+ break;
+
+ return;
+
+ case 401:
+ case 438:
+ err = turnc_keygen(turnc, msg);
+ if (err)
+ break;
+
+ err = allocate_request(turnc);
+ if (err)
+ break;
+
+ return;
+
+ default:
+ break;
+ }
+
+ out:
+ turnc->th(err, scode, reason,
+ rel ? &rel->v.xor_relay_addr : NULL,
+ map ? &map->v.xor_mapped_addr : NULL,
+ msg,
+ turnc->arg);
+}
+
+
+static int allocate_request(struct turnc *t)
+{
+ const uint8_t proto = IPPROTO_UDP;
+
+ return stun_request(&t->ct, t->stun, t->proto, t->sock, &t->srv, 0,
+ STUN_METHOD_ALLOCATE,
+ t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash),
+ false, allocate_resp_handler, t, 6,
+ STUN_ATTR_LIFETIME, &t->lifetime,
+ STUN_ATTR_REQ_TRANSPORT, &proto,
+ STUN_ATTR_USERNAME, t->realm ? t->username : NULL,
+ STUN_ATTR_REALM, t->realm,
+ STUN_ATTR_NONCE, t->nonce,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+static void refresh_resp_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct turnc *turnc = arg;
+ struct stun_attr *ltm;
+
+ if (err || turnc_request_loops(&turnc->ls, scode))
+ goto out;
+
+ switch (scode) {
+
+ case 0:
+ ltm = stun_msg_attr(msg, STUN_ATTR_LIFETIME);
+ if (ltm)
+ turnc->lifetime = ltm->v.lifetime;
+ refresh_timer(turnc);
+ return;
+
+ case 401:
+ case 438:
+ err = turnc_keygen(turnc, msg);
+ if (err)
+ break;
+
+ err = refresh_request(turnc, turnc->lifetime, false,
+ refresh_resp_handler, turnc);
+ if (err)
+ break;
+
+ return;
+
+ default:
+ break;
+ }
+
+ out:
+ turnc->th(err, scode, reason, NULL, NULL, msg, turnc->arg);
+}
+
+
+static int refresh_request(struct turnc *t, uint32_t lifetime, bool reset_ls,
+ stun_resp_h *resph, void *arg)
+{
+ if (!t)
+ return EINVAL;
+
+ if (reset_ls)
+ turnc_loopstate_reset(&t->ls);
+
+ if (t->ct)
+ t->ct = mem_deref(t->ct);
+
+ return stun_request(&t->ct, t->stun, t->proto, t->sock, &t->srv, 0,
+ STUN_METHOD_REFRESH,
+ t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash),
+ false, resph, arg, 5,
+ STUN_ATTR_LIFETIME, &lifetime,
+ STUN_ATTR_USERNAME, t->realm ? t->username : NULL,
+ STUN_ATTR_REALM, t->realm,
+ STUN_ATTR_NONCE, t->nonce,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+static inline size_t stun_indlen(const struct sa *sa)
+{
+ size_t len = STUN_HEADER_SIZE + STUN_ATTR_HEADER_SIZE * 2;
+
+ switch (sa_af(sa)) {
+
+ case AF_INET:
+ len += STUN_ATTR_ADDR4_SIZE;
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ len += STUN_ATTR_ADDR6_SIZE;
+ break;
+#endif
+ }
+
+ return len;
+}
+
+
+static bool udp_send_handler(int *err, struct sa *dst, struct mbuf *mb,
+ void *arg)
+{
+ struct turnc *turnc = arg;
+ size_t pos, indlen;
+ struct chan *chan;
+
+ if (mb->pos < CHAN_HDR_SIZE)
+ return false;
+
+ chan = turnc_chan_find_peer(turnc, dst);
+ if (chan) {
+ struct chan_hdr hdr;
+
+ hdr.nr = turnc_chan_numb(chan);
+ hdr.len = mbuf_get_left(mb);
+
+ mb->pos -= CHAN_HDR_SIZE;
+ *err = turnc_chan_hdr_encode(&hdr, mb);
+ mb->pos -= CHAN_HDR_SIZE;
+
+ *dst = turnc->srv;
+
+ return false;
+ }
+
+ indlen = stun_indlen(dst);
+
+ if (mb->pos < indlen)
+ return false;
+
+ mb->pos -= indlen;
+ pos = mb->pos;
+ *err = stun_msg_encode(mb, STUN_METHOD_SEND, STUN_CLASS_INDICATION,
+ sendind_tid, NULL, NULL, 0, false, 0x00, 2,
+ STUN_ATTR_XOR_PEER_ADDR, dst,
+ STUN_ATTR_DATA, mb);
+ mb->pos = pos;
+
+ *dst = turnc->srv;
+
+ return false;
+}
+
+
+static bool udp_recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct stun_attr *peer, *data;
+ struct stun_unknown_attr ua;
+ struct turnc *turnc = arg;
+ struct stun_msg *msg;
+ bool hdld = true;
+
+ if (!sa_cmp(&turnc->srv, src, SA_ALL) &&
+ !sa_cmp(&turnc->psrv, src, SA_ALL))
+ return false;
+
+ if (stun_msg_decode(&msg, mb, &ua)) {
+
+ struct chan_hdr hdr;
+ struct chan *chan;
+
+ if (turnc_chan_hdr_decode(&hdr, mb))
+ return true;
+
+ if (mbuf_get_left(mb) < hdr.len)
+ return true;
+
+ chan = turnc_chan_find_numb(turnc, hdr.nr);
+ if (!chan)
+ return true;
+
+ *src = *turnc_chan_peer(chan);
+
+ return false;
+ }
+
+ switch (stun_msg_class(msg)) {
+
+ case STUN_CLASS_INDICATION:
+ if (ua.typec > 0)
+ break;
+
+ if (stun_msg_method(msg) != STUN_METHOD_DATA)
+ break;
+
+ peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR);
+ data = stun_msg_attr(msg, STUN_ATTR_DATA);
+ if (!peer || !data)
+ break;
+
+ *src = peer->v.xor_peer_addr;
+
+ mb->pos = data->v.data.pos;
+ mb->end = data->v.data.end;
+
+ hdld = false;
+ break;
+
+ case STUN_CLASS_ERROR_RESP:
+ case STUN_CLASS_SUCCESS_RESP:
+ (void)stun_ctrans_recv(turnc->stun, msg, &ua);
+ break;
+
+ default:
+ break;
+ }
+
+ mem_deref(msg);
+
+ return hdld;
+}
+
+
+/**
+ * Allocate a TURN Client
+ *
+ * @param turncp Pointer to allocated TURN Client
+ * @param conf Optional STUN Configuration
+ * @param proto Transport Protocol
+ * @param sock Transport socket
+ * @param layer Transport layer
+ * @param srv TURN Server IP-address
+ * @param username Authentication username
+ * @param password Authentication password
+ * @param lifetime Allocate lifetime in [seconds]
+ * @param th TURN handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int turnc_alloc(struct turnc **turncp, const struct stun_conf *conf, int proto,
+ void *sock, int layer, const struct sa *srv,
+ const char *username, const char *password,
+ uint32_t lifetime, turnc_h *th, void *arg)
+{
+ struct turnc *turnc;
+ int err;
+
+ if (!turncp || !sock || !srv || !username || !password || !th)
+ return EINVAL;
+
+ turnc = mem_zalloc(sizeof(*turnc), destructor);
+ if (!turnc)
+ return ENOMEM;
+
+ err = stun_alloc(&turnc->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ err = str_dup(&turnc->username, username);
+ if (err)
+ goto out;
+
+ err = str_dup(&turnc->password, password);
+ if (err)
+ goto out;
+
+ err = turnc_perm_hash_alloc(&turnc->perms, PERM_HASH_SIZE);
+ if (err)
+ goto out;
+
+ err = turnc_chan_hash_alloc(&turnc->chans, CHAN_HASH_SIZE);
+ if (err)
+ goto out;
+
+ tmr_init(&turnc->tmr);
+ turnc->proto = proto;
+ turnc->sock = mem_ref(sock);
+ turnc->psrv = *srv;
+ turnc->srv = *srv;
+ turnc->lifetime = lifetime;
+ turnc->th = th;
+ turnc->arg = arg;
+
+ switch (proto) {
+
+ case IPPROTO_UDP:
+ err = udp_register_helper(&turnc->uh, sock, layer,
+ udp_send_handler, udp_recv_handler,
+ turnc);
+ break;
+
+ default:
+ err = 0;
+ break;
+ }
+
+ if (err)
+ goto out;
+
+ err = allocate_request(turnc);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(turnc);
+ else
+ *turncp = turnc;
+
+ return err;
+}
+
+
+int turnc_send(struct turnc *turnc, const struct sa *dst, struct mbuf *mb)
+{
+ size_t pos, indlen;
+ struct chan *chan;
+ int err;
+
+ if (!turnc || !dst || !mb)
+ return EINVAL;
+
+ chan = turnc_chan_find_peer(turnc, dst);
+ if (chan) {
+ struct chan_hdr hdr;
+
+ if (mb->pos < CHAN_HDR_SIZE)
+ return EINVAL;
+
+ hdr.nr = turnc_chan_numb(chan);
+ hdr.len = mbuf_get_left(mb);
+
+ mb->pos -= CHAN_HDR_SIZE;
+ pos = mb->pos;
+
+ err = turnc_chan_hdr_encode(&hdr, mb);
+ if (err)
+ return err;
+
+ if (turnc->proto == IPPROTO_TCP) {
+
+ mb->pos = mb->end;
+
+ /* padding */
+ while (hdr.len++ & 0x03) {
+ err = mbuf_write_u8(mb, 0x00);
+ if (err)
+ return err;
+ }
+ }
+
+ mb->pos = pos;
+ }
+ else {
+ indlen = stun_indlen(dst);
+
+ if (mb->pos < indlen)
+ return EINVAL;
+
+ mb->pos -= indlen;
+ pos = mb->pos;
+
+ err = stun_msg_encode(mb, STUN_METHOD_SEND,
+ STUN_CLASS_INDICATION, sendind_tid,
+ NULL, NULL, 0, false, 0x00, 2,
+ STUN_ATTR_XOR_PEER_ADDR, dst,
+ STUN_ATTR_DATA, mb);
+ if (err)
+ return err;
+
+ mb->pos = pos;
+ }
+
+ switch (turnc->proto) {
+
+ case IPPROTO_UDP:
+ err = udp_send(turnc->sock, &turnc->srv, mb);
+ break;
+
+ case IPPROTO_TCP:
+ err = tcp_send(turnc->sock, mb);
+ break;
+
+#ifdef USE_DTLS
+ case STUN_TRANSP_DTLS:
+ err = dtls_send(turnc->sock, mb);
+ break;
+#endif
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ return err;
+}
+
+
+int turnc_recv(struct turnc *turnc, struct sa *src, struct mbuf *mb)
+{
+ struct stun_attr *peer, *data;
+ struct stun_unknown_attr ua;
+ struct stun_msg *msg;
+ int err = 0;
+
+ if (!turnc || !src || !mb)
+ return EINVAL;
+
+ if (stun_msg_decode(&msg, mb, &ua)) {
+
+ struct chan_hdr hdr;
+ struct chan *chan;
+
+ if (turnc_chan_hdr_decode(&hdr, mb))
+ return EBADMSG;
+
+ if (mbuf_get_left(mb) < hdr.len)
+ return EBADMSG;
+
+ chan = turnc_chan_find_numb(turnc, hdr.nr);
+ if (!chan)
+ return EBADMSG;
+
+ *src = *turnc_chan_peer(chan);
+
+ return 0;
+ }
+
+ switch (stun_msg_class(msg)) {
+
+ case STUN_CLASS_INDICATION:
+ if (ua.typec > 0) {
+ err = ENOSYS;
+ break;
+ }
+
+ if (stun_msg_method(msg) != STUN_METHOD_DATA) {
+ err = ENOSYS;
+ break;
+ }
+
+ peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR);
+ data = stun_msg_attr(msg, STUN_ATTR_DATA);
+ if (!peer || !data) {
+ err = EPROTO;
+ break;
+ }
+
+ *src = peer->v.xor_peer_addr;
+
+ mb->pos = data->v.data.pos;
+ mb->end = data->v.data.end;
+ break;
+
+ case STUN_CLASS_ERROR_RESP:
+ case STUN_CLASS_SUCCESS_RESP:
+ (void)stun_ctrans_recv(turnc->stun, msg, &ua);
+ mb->pos = mb->end;
+ break;
+
+ default:
+ err = ENOSYS;
+ break;
+ }
+
+ mem_deref(msg);
+
+ return err;
+}
+
+
+bool turnc_request_loops(struct loop_state *ls, uint16_t scode)
+{
+ bool loop = false;
+
+ switch (scode) {
+
+ case 0:
+ ls->failc = 0;
+ break;
+
+ default:
+ if (ls->last_scode == scode)
+ loop = true;
+ /*@fallthrough@*/
+ case 300:
+ if (++ls->failc >= FAILC_MAX)
+ loop = true;
+
+ break;
+ }
+
+ ls->last_scode = scode;
+
+ return loop;
+}
+
+
+void turnc_loopstate_reset(struct loop_state *ls)
+{
+ if (!ls)
+ return;
+
+ ls->last_scode = 0;
+ ls->failc = 0;
+}
+
+
+int turnc_keygen(struct turnc *turnc, const struct stun_msg *msg)
+{
+ struct stun_attr *realm, *nonce;
+
+ realm = stun_msg_attr(msg, STUN_ATTR_REALM);
+ nonce = stun_msg_attr(msg, STUN_ATTR_NONCE);
+ if (!realm || !nonce)
+ return EPROTO;
+
+ mem_deref(turnc->realm);
+ mem_deref(turnc->nonce);
+ turnc->realm = mem_ref(realm->v.realm);
+ turnc->nonce = mem_ref(nonce->v.nonce);
+
+ return md5_printf(turnc->md5_hash, "%s:%s:%s",
+ turnc->username, turnc->realm, turnc->password);
+}