coturn/src/server/ns_turn_allocation.c
2014-06-03 22:55:24 +00:00

710 lines
17 KiB
C

/*
* Copyright (C) 2011, 2012, 2013 Citrix Systems
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "ns_turn_allocation.h"
/////////////// Permission forward declarations /////////////////
static void init_turn_permission_hashtable(turn_permission_hashtable *map);
static void free_turn_permission_hashtable(turn_permission_hashtable *map);
static turn_permission_info* get_from_turn_permission_hashtable(turn_permission_hashtable *map, const ioa_addr *addr);
/////////////// ALLOCATION //////////////////////////////////////
void init_allocation(void *owner, allocation* a, ur_map *tcp_connections) {
if(a) {
ns_bzero(a,sizeof(allocation));
a->owner = owner;
a->tcp_connections = tcp_connections;
init_turn_permission_hashtable(&(a->addr_to_perm));
}
}
void clear_allocation(allocation *a)
{
if (a) {
if(a->is_valid)
turn_report_allocation_delete(a);
if(a->tcs.elems) {
size_t i;
size_t sz = a->tcs.sz;
for(i=0;i<sz;++i) {
tcp_connection *tc = a->tcs.elems[i];
if(tc) {
delete_tcp_connection(tc);
a->tcs.elems[i] = NULL;
break;
}
}
turn_free(a->tcs.elems,sz*sizeof(tcp_connection*));
a->tcs.elems = NULL;
}
a->tcs.sz = 0;
clear_ioa_socket_session_if(a->relay_session.s, a->owner);
clear_ts_ur_session_data(&(a->relay_session));
IOA_EVENT_DEL(a->lifetime_ev);
/* The order is important here: */
free_turn_permission_hashtable(&(a->addr_to_perm));
ch_map_clean(&(a->chns));
a->is_valid=0;
}
}
ts_ur_session *get_relay_session(allocation *a)
{
return &(a->relay_session);
}
ioa_socket_handle get_relay_socket(allocation *a)
{
return a->relay_session.s;
}
void set_allocation_lifetime_ev(allocation *a, turn_time_t exp_time, ioa_timer_handle ev)
{
if (a) {
IOA_EVENT_DEL(a->lifetime_ev);
a->expiration_time = exp_time;
a->lifetime_ev = ev;
}
}
int is_allocation_valid(const allocation* a) {
if(a) return a->is_valid;
else return 0;
}
void set_allocation_valid(allocation* a, int value) {
if(a) a->is_valid=value;
}
turn_permission_info* allocation_get_permission(allocation* a, const ioa_addr *addr) {
if(a) {
return get_from_turn_permission_hashtable(&(a->addr_to_perm), addr);
}
return NULL;
}
///////////////////////////// TURN_PERMISSION /////////////////////////////////
static int delete_channel_info_from_allocation_map(ur_map_key_type key, ur_map_value_type value);
void turn_permission_clean(turn_permission_info* tinfo)
{
if (tinfo && tinfo->allocated) {
if(!(tinfo->lifetime_ev)) {
TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (1) permission to be cleaned\n",__FUNCTION__);
}
IOA_EVENT_DEL(tinfo->lifetime_ev);
lm_map_foreach(&(tinfo->chns), (foreachcb_type) delete_channel_info_from_allocation_map);
lm_map_clean(&(tinfo->chns));
ns_bzero(tinfo,sizeof(turn_permission_info));
}
}
static void init_turn_permission_hashtable(turn_permission_hashtable *map)
{
if (map)
ns_bzero(map,sizeof(turn_permission_hashtable));
}
static void free_turn_permission_hashtable(turn_permission_hashtable *map)
{
if(map) {
size_t i;
for(i=0;i<TURN_PERMISSION_HASHTABLE_SIZE;++i) {
turn_permission_array *parray = &(map->table[i]);
{
size_t j;
for(j=0;j<TURN_PERMISSION_ARRAY_SIZE;++j) {
turn_permission_slot *slot = &(parray->main_slots[j]);
if(slot->info.allocated) {
turn_permission_clean(&(slot->info));
}
}
}
if(parray->extra_slots) {
size_t j;
for(j=0;j<parray->extra_sz;++j) {
turn_permission_slot *slot = parray->extra_slots[j];
if(slot) {
if(slot->info.allocated) {
turn_permission_clean(&(slot->info));
}
turn_free(slot,sizeof(turn_permission_slot));
}
}
turn_free(parray->extra_slots, parray->extra_sz * sizeof(turn_permission_slot*));
parray->extra_slots = NULL;
}
parray->extra_sz = 0;
}
}
}
static turn_permission_info* get_from_turn_permission_hashtable(turn_permission_hashtable *map, const ioa_addr *addr)
{
if (!addr || !map)
return NULL;
u32bits index = addr_hash_no_port(addr) & (TURN_PERMISSION_HASHTABLE_SIZE-1);
turn_permission_array *parray = &(map->table[index]);
{
size_t i;
for (i = 0; i < TURN_PERMISSION_ARRAY_SIZE; ++i) {
turn_permission_slot *slot = &(parray->main_slots[i]);
if (slot->info.allocated && addr_eq_no_port(&(slot->info.addr), addr)) {
return &(slot->info);
}
}
}
if(parray->extra_slots) {
size_t i;
size_t sz = parray->extra_sz;
for (i = 0; i < sz; ++i) {
turn_permission_slot *slot = parray->extra_slots[i];
if (slot->info.allocated && addr_eq_no_port(&(slot->info.addr), addr)) {
return &(slot->info);
}
}
}
return NULL;
}
static void ch_info_clean(ch_info* c) {
if (c) {
if (c->kernel_channel) {
DELETE_TURN_CHANNEL_KERNEL(c->kernel_channel);
c->kernel_channel = 0;
}
IOA_EVENT_DEL(c->lifetime_ev);
ns_bzero(c,sizeof(ch_info));
}
}
static int delete_channel_info_from_allocation_map(ur_map_key_type key, ur_map_value_type value)
{
UNUSED_ARG(key);
if(value) {
ch_info* chn = (ch_info*)value;
if(chn->chnum <1) {
TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (0) channel to be cleaned: chnum<1\n",__FUNCTION__);
}
ch_info_clean(chn);
}
return 0;
}
void turn_channel_delete(ch_info* chn)
{
if(chn) {
int port = addr_get_port(&(chn->peer_addr));
if(port<1) {
char s[129];
addr_to_string(&(chn->peer_addr),(u08bits*)s);
TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (1) channel to be cleaned: port is empty: %s\n",__FUNCTION__,s);
}
{
turn_permission_info* tinfo = (turn_permission_info*)chn->owner;
if(tinfo) {
lm_map_del(&(tinfo->chns), (ur_map_key_type)port,NULL);
} else {
TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (2) channel to be cleaned: permission is empty\n",__FUNCTION__);
}
}
delete_channel_info_from_allocation_map((ur_map_key_type)port,(ur_map_value_type)chn);
}
}
ch_info* allocation_get_new_ch_info(allocation* a, u16bits chnum, ioa_addr* peer_addr)
{
turn_permission_info* tinfo = get_from_turn_permission_hashtable(&(a->addr_to_perm), peer_addr);
if (!tinfo)
tinfo = allocation_add_permission(a, peer_addr);
ch_info* chn = ch_map_get(&(a->chns), chnum, 1);
chn->allocated = 1;
chn->chnum = chnum;
chn->port = addr_get_port(peer_addr);
addr_cpy(&(chn->peer_addr), peer_addr);
chn->owner = tinfo;
lm_map_put(&(tinfo->chns), (ur_map_key_type) addr_get_port(peer_addr), (ur_map_value_type) chn);
return chn;
}
ch_info* allocation_get_ch_info(allocation* a, u16bits chnum) {
return ch_map_get(&(a->chns), chnum, 0);
}
ch_info* allocation_get_ch_info_by_peer_addr(allocation* a, ioa_addr* peer_addr) {
turn_permission_info* tinfo = get_from_turn_permission_hashtable(&(a->addr_to_perm), peer_addr);
if(tinfo) {
return get_turn_channel(tinfo,peer_addr);
}
return NULL;
}
u16bits get_turn_channel_number(turn_permission_info* tinfo, ioa_addr *addr)
{
if (tinfo) {
ur_map_value_type t = 0;
if (lm_map_get(&(tinfo->chns), (ur_map_key_type)addr_get_port(addr), &t) && t) {
ch_info* chn = (ch_info*) t;
if (STUN_VALID_CHANNEL(chn->chnum)) {
return chn->chnum;
}
}
}
return 0;
}
ch_info *get_turn_channel(turn_permission_info* tinfo, ioa_addr *addr)
{
if (tinfo) {
ur_map_value_type t = 0;
if (lm_map_get(&(tinfo->chns), (ur_map_key_type)addr_get_port(addr), &t) && t) {
ch_info* chn = (ch_info*) t;
if (STUN_VALID_CHANNEL(chn->chnum)) {
return chn;
}
}
}
return NULL;
}
turn_permission_hashtable *allocation_get_turn_permission_hashtable(allocation *a)
{
return &(a->addr_to_perm);
}
turn_permission_info* allocation_add_permission(allocation *a, const ioa_addr* addr)
{
if (a && addr) {
turn_permission_hashtable *map = &(a->addr_to_perm);
u32bits hash = addr_hash_no_port(addr);
size_t fds = (size_t) (hash & (TURN_PERMISSION_HASHTABLE_SIZE-1));
turn_permission_array *parray = &(map->table[fds]);
turn_permission_slot *slot = NULL;
{
size_t i;
for(i=0;i<TURN_PERMISSION_ARRAY_SIZE;++i) {
slot = &(parray->main_slots[i]);
if(!(slot->info.allocated)) {
break;
} else {
slot=NULL;
}
}
}
if(!slot) {
size_t old_sz = parray->extra_sz;
turn_permission_slot **slots = parray->extra_slots;
if(slots) {
size_t i;
for(i=0;i<old_sz;++i) {
slot = slots[i];
if(!(slot->info.allocated)) {
break;
} else {
slot=NULL;
}
}
}
if(!slot) {
size_t old_sz_mem = old_sz * sizeof(turn_permission_slot*);
parray->extra_slots = (turn_permission_slot **) turn_realloc(parray->extra_slots,
old_sz_mem, old_sz_mem + sizeof(turn_permission_slot*));
slots = parray->extra_slots;
parray->extra_sz = old_sz + 1;
slots[old_sz] = (turn_permission_slot *)turn_malloc(sizeof(turn_permission_slot));
slot = slots[old_sz];
}
}
ns_bzero(slot,sizeof(turn_permission_slot));
slot->info.allocated = 1;
turn_permission_info *elem = &(slot->info);
addr_cpy(&(elem->addr), addr);
elem->owner = a;
return elem;
} else {
return NULL;
}
}
ch_info *ch_map_get(ch_map* map, u16bits chnum, int new_chn)
{
ch_info *ret = NULL;
if(map) {
size_t index = (size_t)(chnum & (CH_MAP_HASH_SIZE-1));
ch_map_array *a = &(map->table[index]);
size_t i;
for(i=0;i<CH_MAP_ARRAY_SIZE;++i) {
ch_info *chi = &(a->main_chns[i]);
if(chi->allocated) {
if(!new_chn && (chi->chnum == chnum)) {
return chi;
}
} else if(new_chn) {
return chi;
}
}
size_t old_sz = a->extra_sz;
if(old_sz && a->extra_chns) {
for(i=0;i<old_sz;++i) {
ch_info *chi = a->extra_chns[i];
if(chi) {
if(chi->allocated) {
if(!new_chn && (chi->chnum == chnum)) {
return chi;
}
} else if(new_chn) {
return chi;
}
}
}
}
if(new_chn) {
size_t old_sz_mem = old_sz * sizeof(ch_info*);
a->extra_chns = (ch_info**)turn_realloc(a->extra_chns,old_sz_mem,old_sz_mem + sizeof(ch_info*));
a->extra_chns[old_sz] = (ch_info*)turn_malloc(sizeof(ch_info));
ns_bzero(a->extra_chns[old_sz],sizeof(ch_info));
a->extra_sz += 1;
return a->extra_chns[old_sz];
}
}
return ret;
}
void ch_map_clean(ch_map* map)
{
if(map) {
size_t index;
for(index = 0; index < CH_MAP_HASH_SIZE; ++index) {
ch_map_array *a = &(map->table[index]);
size_t i;
for(i=0;i<CH_MAP_ARRAY_SIZE;++i) {
ch_info *chi = &(a->main_chns[i]);
if(chi->allocated) {
ch_info_clean(chi);
}
}
if(a->extra_chns) {
size_t sz = a->extra_sz;
for(i=0;i<sz;++i) {
ch_info *chi = a->extra_chns[i];
if(chi) {
if(chi->allocated) {
ch_info_clean(chi);
}
turn_free(chi,sizeof(ch_info));
a->extra_chns[i] = NULL;
}
}
turn_free(a->extra_chns, sizeof(ch_info*)*sz);
a->extra_chns = NULL;
}
a->extra_sz = 0;
}
}
}
////////////////// TCP connections ///////////////////////////////
static void set_new_tc_id(u08bits server_id, tcp_connection *tc) {
allocation *a = (allocation*)(tc->owner);
ur_map *map = a->tcp_connections;
u32bits newid;
u32bits sid = server_id;
sid = sid<<24;
do {
newid = 0;
while (!newid) {
newid = (u32bits)turn_random();
if(!newid) {
continue;
}
newid = newid & 0x00FFFFFF;
if(!newid) {
continue;
}
newid = newid | sid;
}
} while(ur_map_get(map, (ur_map_key_type)newid, NULL));
tc->id = newid;
ur_map_put(map, (ur_map_key_type)newid, (ur_map_value_type)tc);
}
tcp_connection *create_tcp_connection(u08bits server_id, allocation *a, stun_tid *tid, ioa_addr *peer_addr, int *err_code)
{
tcp_connection_list *tcl = &(a->tcs);
if(tcl->elems) {
size_t i;
for(i=0;i<tcl->sz;++i) {
tcp_connection *otc = tcl->elems[i];
if(otc) {
if(addr_eq(&(otc->peer_addr),peer_addr)) {
*err_code = 446;
return NULL;
}
}
}
}
tcp_connection *tc = (tcp_connection*)turn_malloc(sizeof(tcp_connection));
ns_bzero(tc,sizeof(tcp_connection));
addr_cpy(&(tc->peer_addr),peer_addr);
if(tid)
ns_bcopy(tid,&(tc->tid),sizeof(stun_tid));
tc->owner = a;
int found = 0;
if(a->tcs.elems) {
size_t i;
for(i=0;i<tcl->sz;++i) {
tcp_connection *otc = tcl->elems[i];
if(!otc) {
tcl->elems[i] = tc;
found = 1;
break;
}
}
}
if(!found) {
size_t old_sz_mem = a->tcs.sz * sizeof(tcp_connection*);
a->tcs.elems = (tcp_connection**)turn_realloc(a->tcs.elems,old_sz_mem,old_sz_mem+sizeof(tcp_connection*));
a->tcs.elems[a->tcs.sz] = tc;
a->tcs.sz += 1;
tcl = &(a->tcs);
}
set_new_tc_id(server_id, tc);
return tc;
}
void delete_tcp_connection(tcp_connection *tc)
{
if(tc) {
if(tc->done) {
TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s: check on already closed tcp data connection: 0x%lx\n",__FUNCTION__,(unsigned long)tc);
return;
}
tc->done = 1;
clear_unsent_buffer(&(tc->ub_to_client));
IOA_EVENT_DEL(tc->peer_conn_timeout);
IOA_EVENT_DEL(tc->conn_bind_timeout);
allocation *a = (allocation*)(tc->owner);
if(a) {
ur_map *map = a->tcp_connections;
if(map) {
ur_map_del(map, (ur_map_key_type)(tc->id),NULL);
}
tcp_connection_list *tcl = &(a->tcs);
if(tcl->elems) {
size_t i;
for(i=0;i<tcl->sz;++i) {
if(tcl->elems[i] == tc) {
tcl->elems[i] = NULL;
break;
}
}
}
}
IOA_CLOSE_SOCKET(tc->client_s);
IOA_CLOSE_SOCKET(tc->peer_s);
turn_free(tc,sizeof(tcp_connection));
}
}
tcp_connection *get_and_clean_tcp_connection_by_id(ur_map *map, tcp_connection_id id)
{
if(map) {
ur_map_value_type t = 0;
if (ur_map_get(map, (ur_map_key_type)id, &t) && t) {
ur_map_del(map, (ur_map_key_type)id,NULL);
return (tcp_connection*)t;
}
}
return NULL;
}
tcp_connection *get_tcp_connection_by_id(ur_map *map, tcp_connection_id id)
{
if(map) {
ur_map_value_type t = 0;
if (ur_map_get(map, (ur_map_key_type)id, &t) && t) {
return (tcp_connection*)t;
}
}
return NULL;
}
tcp_connection *get_tcp_connection_by_peer(allocation *a, ioa_addr *peer_addr)
{
if(a && peer_addr) {
tcp_connection_list *tcl = &(a->tcs);
if(tcl->elems) {
size_t i;
size_t sz = tcl->sz;
for(i=0;i<sz;++i) {
tcp_connection *tc = tcl->elems[i];
if(tc) {
if(addr_eq(&(tc->peer_addr),peer_addr)) {
return tc;
}
}
}
}
}
return NULL;
}
int can_accept_tcp_connection_from_peer(allocation *a, ioa_addr *peer_addr, int server_relay)
{
if(server_relay)
return 1;
if(a && peer_addr) {
return (get_from_turn_permission_hashtable(&(a->addr_to_perm), peer_addr) != NULL);
}
return 0;
}
//////////////// Unsent buffers //////////////////////
void clear_unsent_buffer(unsent_buffer *ub)
{
if(ub) {
if(ub->bufs) {
size_t sz;
for(sz = 0; sz<ub->sz; sz++) {
ioa_network_buffer_handle nbh = ub->bufs[sz];
if(nbh) {
ioa_network_buffer_delete(NULL, nbh);
ub->bufs[sz] = NULL;
}
}
turn_free(ub->bufs,sizeof(ioa_network_buffer_handle) * ub->sz);
ub->bufs = NULL;
}
ub->sz = 0;
}
}
void add_unsent_buffer(unsent_buffer *ub, ioa_network_buffer_handle nbh)
{
if(!ub || (ub->sz >= MAX_UNSENT_BUFFER_SIZE)) {
ioa_network_buffer_delete(NULL, nbh);
} else {
ub->bufs = (ioa_network_buffer_handle*)turn_realloc(ub->bufs, sizeof(ioa_network_buffer_handle) * ub->sz, sizeof(ioa_network_buffer_handle) * (ub->sz+1));
ub->bufs[ub->sz] = nbh;
ub->sz +=1;
}
}
ioa_network_buffer_handle top_unsent_buffer(unsent_buffer *ub)
{
ioa_network_buffer_handle ret = NULL;
if(ub && ub->bufs && ub->sz) {
size_t sz;
for(sz=0; sz<ub->sz; ++sz) {
if(ub->bufs[sz]) {
ret = ub->bufs[sz];
break;
}
}
}
return ret;
}
void pop_unsent_buffer(unsent_buffer *ub)
{
if(ub && ub->bufs && ub->sz) {
size_t sz;
for(sz=0; sz<ub->sz; ++sz) {
if(ub->bufs[sz]) {
ub->bufs[sz] = NULL;
break;
}
}
}
}
//////////////////////////////////////////////////////////////////