blob: 2a6cf70e82094efbb07670654eb78320e0ea982f [file] [log] [blame]
/**
* @file chan.c TURN Channels handling
*
* Copyright (C) 2010 Creytiv.com
*/
#include <re_types.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_list.h>
#include <re_hash.h>
#include <re_tmr.h>
#include <re_sa.h>
#include <re_md5.h>
#include <re_udp.h>
#include <re_stun.h>
#include <re_turn.h>
#include "turnc.h"
enum {
CHAN_LIFETIME = 600,
CHAN_REFRESH = 250,
CHAN_NUMB_MIN = 0x4000,
CHAN_NUMB_MAX = 0x7fff
};
struct channels {
struct hash *ht_numb;
struct hash *ht_peer;
uint16_t nr;
};
struct chan {
struct le he_numb;
struct le he_peer;
struct loop_state ls;
uint16_t nr;
struct sa peer;
struct tmr tmr;
struct turnc *turnc;
struct stun_ctrans *ct;
turnc_chan_h *ch;
void *arg;
};
static int chanbind_request(struct chan *chan, bool reset_ls);
static void channels_destructor(void *data)
{
struct channels *c = data;
/* flush from primary hash */
hash_flush(c->ht_numb);
mem_deref(c->ht_numb);
mem_deref(c->ht_peer);
}
static void chan_destructor(void *data)
{
struct chan *chan = data;
tmr_cancel(&chan->tmr);
mem_deref(chan->ct);
hash_unlink(&chan->he_numb);
hash_unlink(&chan->he_peer);
}
static bool numb_hash_cmp_handler(struct le *le, void *arg)
{
const struct chan *chan = le->data;
const uint16_t *nr = arg;
return chan->nr == *nr;
}
static bool peer_hash_cmp_handler(struct le *le, void *arg)
{
const struct chan *chan = le->data;
return sa_cmp(&chan->peer, arg, SA_ALL);
}
static void timeout(void *arg)
{
struct chan *chan = arg;
int err;
err = chanbind_request(chan, true);
if (err)
chan->turnc->th(err, 0, NULL, NULL, NULL, NULL,
chan->turnc->arg);
}
static void chanbind_resp_handler(int err, uint16_t scode, const char *reason,
const struct stun_msg *msg, void *arg)
{
struct chan *chan = arg;
if (err || turnc_request_loops(&chan->ls, scode))
goto out;
switch (scode) {
case 0:
tmr_start(&chan->tmr, CHAN_REFRESH * 1000, timeout, chan);
if (chan->ch) {
chan->ch(chan->arg);
chan->ch = NULL;
chan->arg = NULL;
}
return;
case 401:
case 438:
err = turnc_keygen(chan->turnc, msg);
if (err)
break;
err = chanbind_request(chan, false);
if (err)
break;
return;
default:
break;
}
out:
chan->turnc->th(err, scode, reason, NULL, NULL, msg, chan->turnc->arg);
}
static int chanbind_request(struct chan *chan, bool reset_ls)
{
struct turnc *t = chan->turnc;
if (reset_ls)
turnc_loopstate_reset(&chan->ls);
return stun_request(&chan->ct, t->stun, t->proto, t->sock, &t->srv, 0,
STUN_METHOD_CHANBIND,
t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash),
false, chanbind_resp_handler, chan, 6,
STUN_ATTR_CHANNEL_NUMBER, &chan->nr,
STUN_ATTR_XOR_PEER_ADDR, &chan->peer,
STUN_ATTR_USERNAME, t->realm ? t->username : NULL,
STUN_ATTR_REALM, t->realm,
STUN_ATTR_NONCE, t->nonce,
STUN_ATTR_SOFTWARE, stun_software);
}
/**
* Add a TURN Channel for a peer
*
* @param turnc TURN Client
* @param peer Peer IP-address
* @param ch Channel handler
* @param arg Handler argument
*
* @return 0 if success, otherwise errorcode
*/
int turnc_add_chan(struct turnc *turnc, const struct sa *peer,
turnc_chan_h *ch, void *arg)
{
struct chan *chan;
int err;
if (!turnc || !peer)
return EINVAL;
if (turnc->chans->nr >= CHAN_NUMB_MAX)
return ERANGE;
if (turnc_chan_find_peer(turnc, peer))
return 0;
chan = mem_zalloc(sizeof(*chan), chan_destructor);
if (!chan)
return ENOMEM;
chan->nr = turnc->chans->nr++;
chan->peer = *peer;
hash_append(turnc->chans->ht_numb, chan->nr, &chan->he_numb, chan);
hash_append(turnc->chans->ht_peer, sa_hash(peer, SA_ALL),
&chan->he_peer, chan);
tmr_init(&chan->tmr);
chan->turnc = turnc;
chan->ch = ch;
chan->arg = arg;
err = chanbind_request(chan, true);
if (err)
mem_deref(chan);
return err;
}
int turnc_chan_hash_alloc(struct channels **cp, uint32_t bsize)
{
struct channels *c;
int err;
if (!cp)
return EINVAL;
c = mem_zalloc(sizeof(*c), channels_destructor);
if (!c)
return ENOMEM;
err = hash_alloc(&c->ht_numb, bsize);
if (err)
goto out;
err = hash_alloc(&c->ht_peer, bsize);
if (err)
goto out;
c->nr = CHAN_NUMB_MIN;
out:
if (err)
mem_deref(c);
else
*cp = c;
return err;
}
struct chan *turnc_chan_find_numb(const struct turnc *turnc, uint16_t nr)
{
if (!turnc)
return NULL;
return list_ledata(hash_lookup(turnc->chans->ht_numb, nr,
numb_hash_cmp_handler, &nr));
}
struct chan *turnc_chan_find_peer(const struct turnc *turnc,
const struct sa *peer)
{
if (!turnc)
return NULL;
return list_ledata(hash_lookup(turnc->chans->ht_peer,
sa_hash(peer, SA_ALL),
peer_hash_cmp_handler, (void *)peer));
}
uint16_t turnc_chan_numb(const struct chan *chan)
{
return chan ? chan->nr : 0;
}
const struct sa *turnc_chan_peer(const struct chan *chan)
{
return chan ? &chan->peer : NULL;
}
int turnc_chan_hdr_encode(const struct chan_hdr *hdr, struct mbuf *mb)
{
int err;
if (!hdr || !mb)
return EINVAL;
err = mbuf_write_u16(mb, htons(hdr->nr));
err |= mbuf_write_u16(mb, htons(hdr->len));
return err;
}
int turnc_chan_hdr_decode(struct chan_hdr *hdr, struct mbuf *mb)
{
if (!hdr || !mb)
return EINVAL;
if (mbuf_get_left(mb) < sizeof(*hdr))
return ENOENT;
hdr->nr = ntohs(mbuf_read_u16(mb));
hdr->len = ntohs(mbuf_read_u16(mb));
return 0;
}