You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kamailio/modules/websocket/ws_conn.c

710 lines
16 KiB

/*
* Copyright (C) 2012-2013 Crocodile RCS Ltd
*
* This file is part of Kamailio, a free SIP server.
*
* Kamailio is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version
*
* Kamailio is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Exception: permission to copy, modify, propagate, and distribute a work
* formed by combining OpenSSL toolkit software and the code in this file,
* such as linking with software components and libraries released under
* OpenSSL project license.
*
*/
#include "../../locking.h"
#include "../../str.h"
#include "../../tcp_conn.h"
#include "../../lib/kcore/faked_msg.h"
#include "../../lib/kcore/kstats_wrapper.h"
#include "../../lib/kmi/tree.h"
#include "../../mem/mem.h"
#include "ws_conn.h"
#include "ws_mod.h"
/* Maximum number of connections to display when using the ws.dump MI command */
#define MAX_WS_CONNS_DUMP 50
ws_connection_t **wsconn_id_hash = NULL;
#define wsconn_listadd tcpconn_listadd
#define wsconn_listrm tcpconn_listrm
gen_lock_t *wsconn_lock = NULL;
#define WSCONN_LOCK lock_get(wsconn_lock)
#define WSCONN_UNLOCK lock_release(wsconn_lock)
#define wsconn_ref(c) atomic_inc(&((c)->refcnt))
#define wsconn_unref(c) atomic_dec_and_test(&((c)->refcnt))
gen_lock_t *wsstat_lock = NULL;
ws_connection_used_list_t *wsconn_used_list = NULL;
stat_var *ws_current_connections;
stat_var *ws_max_concurrent_connections;
stat_var *ws_sip_current_connections;
stat_var *ws_sip_max_concurrent_connections;
stat_var *ws_msrp_current_connections;
stat_var *ws_msrp_max_concurrent_connections;
char *wsconn_state_str[] =
{
"CONNECTING", /* WS_S_CONNECTING */
"OPEN", /* WS_S_OPEN */
"CLOSING", /* WS_S_CLOSING */
"CLOSED" /* WS_S_CLOSED */
};
/* MI command status text */
static str str_status_empty_param = str_init("Empty display order parameter");
static str str_status_bad_param = str_init("Bad display order parameter");
static str str_status_too_many_params = str_init("Too many parameters");
int wsconn_init(void)
{
wsconn_lock = lock_alloc();
if (wsconn_lock == NULL)
{
LM_ERR("allocating lock\n");
goto error;
}
if (lock_init(wsconn_lock) == 0)
{
LM_ERR("initialising lock\n");
goto error;
}
wsstat_lock = lock_alloc();
if (wsstat_lock == NULL)
{
LM_ERR("allocating lock\n");
goto error;
}
if (lock_init(wsstat_lock) == NULL)
{
LM_ERR("initialising lock\n");
goto error;
}
wsconn_id_hash =
(ws_connection_t **) shm_malloc(TCP_ID_HASH_SIZE *
sizeof(ws_connection_t*));
if (wsconn_id_hash == NULL)
{
LM_ERR("allocating WebSocket hash-table\n");
goto error;
}
memset((void *) wsconn_id_hash, 0,
TCP_ID_HASH_SIZE * sizeof(ws_connection_t *));
wsconn_used_list = (ws_connection_used_list_t *) shm_malloc(
sizeof(ws_connection_used_list_t));
if (wsconn_used_list == NULL)
{
LM_ERR("allocating WebSocket used list\n");
goto error;
}
memset((void *) wsconn_used_list, 0, sizeof(ws_connection_used_list_t));
return 0;
error:
if (wsconn_lock) lock_dealloc((void *) wsconn_lock);
if (wsstat_lock) lock_dealloc((void *) wsstat_lock);
wsconn_lock = wsstat_lock = NULL;
if (wsconn_id_hash) shm_free(wsconn_id_hash);
if (wsconn_used_list) shm_free(wsconn_used_list);
wsconn_id_hash = NULL;
wsconn_used_list = NULL;
return -1;
}
static inline void _wsconn_rm(ws_connection_t *wsc)
{
wsconn_listrm(wsconn_id_hash[wsc->id_hash], wsc, id_next, id_prev);
update_stat(ws_current_connections, -1);
if (wsc->sub_protocol == SUB_PROTOCOL_SIP)
update_stat(ws_sip_current_connections, -1);
else if (wsc->sub_protocol == SUB_PROTOCOL_MSRP)
update_stat(ws_msrp_current_connections, -1);
shm_free(wsc);
}
void wsconn_destroy(void)
{
int h;
if (wsconn_used_list)
{
shm_free(wsconn_used_list);
wsconn_used_list = NULL;
}
if (wsconn_id_hash)
{
WSCONN_UNLOCK;
WSCONN_LOCK;
for (h = 0; h < TCP_ID_HASH_SIZE; h++)
{
ws_connection_t *wsc = wsconn_id_hash[h];
while (wsc)
{
ws_connection_t *next = wsc->id_next;
_wsconn_rm(wsc);
wsc = next;
}
}
WSCONN_UNLOCK;
shm_free(wsconn_id_hash);
wsconn_id_hash = NULL;
}
if (wsconn_lock)
{
lock_destroy(wsconn_lock);
lock_dealloc((void *) wsconn_lock);
wsconn_lock = NULL;
}
if (wsstat_lock)
{
lock_destroy(wsstat_lock);
lock_dealloc((void *) wsstat_lock);
wsstat_lock = NULL;
}
}
int wsconn_add(struct receive_info rcv, unsigned int sub_protocol)
{
int cur_cons, max_cons;
int id = rcv.proto_reserved1;
int id_hash = tcp_id_hash(id);
ws_connection_t *wsc;
LM_DBG("wsconn_add id [%d]\n", id);
/* Allocate and fill in new WebSocket connection */
wsc = shm_malloc(sizeof(ws_connection_t) + BUF_SIZE + 1);
if (wsc == NULL)
{
LM_ERR("allocating shared memory\n");
return -1;
}
memset(wsc, 0, sizeof(ws_connection_t) + BUF_SIZE + 1);
wsc->id = id;
wsc->id_hash = id_hash;
wsc->state = WS_S_OPEN;
wsc->rcv = rcv;
wsc->sub_protocol = sub_protocol;
wsc->run_event = 0;
wsc->frag_buf.s = ((char*)wsc) + sizeof(ws_connection_t);
atomic_set(&wsc->refcnt, 0);
LM_DBG("wsconn_add new wsc => [%p], ref => [%d]\n", wsc, atomic_get(&wsc->refcnt));
WSCONN_LOCK;
/* Add to WebSocket connection table */
wsconn_listadd(wsconn_id_hash[wsc->id_hash], wsc, id_next, id_prev);
/* Add to the end of the WebSocket used list */
wsc->last_used = (int)time(NULL);
if (wsconn_used_list->head == NULL)
wsconn_used_list->head = wsconn_used_list->tail = wsc;
else
{
wsc->used_prev = wsconn_used_list->tail;
wsconn_used_list->tail->used_next = wsc;
wsconn_used_list->tail = wsc;
}
wsconn_ref(wsc);
WSCONN_UNLOCK;
LM_DBG("wsconn_add added to conn_table wsc => [%p], ref => [%d]\n", wsc, atomic_get(&wsc->refcnt));
/* Update connection statistics */
lock_get(wsstat_lock);
update_stat(ws_current_connections, 1);
cur_cons = get_stat_val(ws_current_connections);
max_cons = get_stat_val(ws_max_concurrent_connections);
if (max_cons < cur_cons)
update_stat(ws_max_concurrent_connections, cur_cons - max_cons);
if (wsc->sub_protocol == SUB_PROTOCOL_SIP)
{
update_stat(ws_sip_current_connections, 1);
cur_cons = get_stat_val(ws_sip_current_connections);
max_cons = get_stat_val(ws_sip_max_concurrent_connections);
if (max_cons < cur_cons)
update_stat(ws_sip_max_concurrent_connections,
cur_cons - max_cons);
}
else if (wsc->sub_protocol == SUB_PROTOCOL_MSRP)
{
update_stat(ws_msrp_current_connections, 1);
cur_cons = get_stat_val(ws_msrp_current_connections);
max_cons = get_stat_val(ws_msrp_max_concurrent_connections);
if (max_cons < cur_cons)
update_stat(ws_msrp_max_concurrent_connections,
cur_cons - max_cons);
}
lock_release(wsstat_lock);
return 0;
}
static void wsconn_run_route(ws_connection_t *wsc)
{
int rt, backup_rt;
struct run_act_ctx ctx;
sip_msg_t *fmsg;
LM_DBG("wsconn_run_route event_route[websocket:closed]\n");
rt = route_get(&event_rt, "websocket:closed");
if (rt < 0 || event_rt.rlist[rt] == NULL)
{
LM_DBG("route does not exist");
return;
}
if (faked_msg_init() < 0)
{
LM_ERR("faked_msg_init() failed\n");
return;
}
fmsg = faked_msg_next();
fmsg->rcv = wsc->rcv;
backup_rt = get_route_type();
set_route_type(REQUEST_ROUTE);
init_run_actions_ctx(&ctx);
run_top_route(event_rt.rlist[rt], fmsg, 0);
set_route_type(backup_rt);
}
static void wsconn_dtor(ws_connection_t *wsc)
{
if (!wsc)
return;
LM_DBG("wsconn_dtor for [%p] refcnt [%d]\n", wsc, atomic_get(&wsc->refcnt));
if (wsc->run_event)
wsconn_run_route(wsc);
shm_free(wsc);
LM_DBG("wsconn_dtor for [%p] destroyed\n", wsc);
}
int wsconn_rm(ws_connection_t *wsc, ws_conn_eventroute_t run_event_route)
{
LM_DBG("wsconn_rm for [%p] refcnt [%d]\n", wsc, atomic_get(&wsc->refcnt));
if (run_event_route == WSCONN_EVENTROUTE_YES)
wsc->run_event = 1;
return wsconn_put(wsc);
}
int wsconn_update(ws_connection_t *wsc)
{
if (!wsc)
{
LM_ERR("wsconn_update: null pointer\n");
return -1;
}
WSCONN_LOCK;
wsc->last_used = (int) time(NULL);
if (wsconn_used_list->tail == wsc)
/* Already at the end of the list */
goto end;
if (wsconn_used_list->head == wsc)
wsconn_used_list->head = wsc->used_next;
if (wsc->used_prev)
wsc->used_prev->used_next = wsc->used_next;
if (wsc->used_next)
wsc->used_next->used_prev = wsc->used_prev;
wsc->used_prev = wsconn_used_list->tail;
wsc->used_next = NULL;
wsconn_used_list->tail->used_next = wsc;
wsconn_used_list->tail = wsc;
end:
WSCONN_UNLOCK;
return 0;
}
void wsconn_close_now(ws_connection_t *wsc)
{
struct tcp_connection *con = tcpconn_get(wsc->id, 0, 0, 0, 0);
if (wsconn_rm(wsc, WSCONN_EVENTROUTE_YES) < 0)
LM_ERR("removing WebSocket connection\n");
if (con == NULL)
{
LM_ERR("getting TCP/TLS connection\n");
return;
}
tcpconn_put(con);
con->send_flags.f |= SND_F_CON_CLOSE;
con->state = S_CONN_BAD;
con->timeout = get_ticks_raw();
}
/* must be called with unlocked WSCONN_LOCK */
int wsconn_put(ws_connection_t *wsc)
{
int destroy = 0;
LM_DBG("wsconn_put start for [%p] refcnt [%d]\n", wsc, atomic_get(&wsc->refcnt));
if (!wsc)
return -1;
WSCONN_LOCK;
/* refcnt == 0*/
if (wsconn_unref(wsc))
{
/* Remove from the WebSocket used list */
if (wsconn_used_list->head == wsc)
wsconn_used_list->head = wsc->used_next;
if (wsconn_used_list->tail == wsc)
wsconn_used_list->tail = wsc->used_prev;
if (wsc->used_prev)
wsc->used_prev->used_next = wsc->used_next;
if (wsc->used_next)
wsc->used_next->used_prev = wsc->used_prev;
/* remove from wsconn_id_hash */
wsconn_listrm(wsconn_id_hash[wsc->id_hash], wsc, id_next, id_prev);
/* stat */
update_stat(ws_current_connections, -1);
if (wsc->sub_protocol == SUB_PROTOCOL_SIP)
update_stat(ws_sip_current_connections, -1);
else if (wsc->sub_protocol == SUB_PROTOCOL_MSRP)
update_stat(ws_msrp_current_connections, -1);
destroy = 1;
}
WSCONN_UNLOCK;
LM_DBG("wsconn_put end for [%p] refcnt [%d]\n", wsc, atomic_get(&wsc->refcnt));
/* wsc is removed from all lists and can be destroyed safely */
if (destroy)
wsconn_dtor(wsc);
return 0;
}
ws_connection_t *wsconn_get(int id)
{
int id_hash = tcp_id_hash(id);
ws_connection_t *wsc;
LM_DBG("wsconn_get for id [%d]\n", id);
WSCONN_LOCK;
for (wsc = wsconn_id_hash[id_hash]; wsc; wsc = wsc->id_next)
{
if (wsc->id == id)
{
wsconn_ref(wsc);
LM_DBG("wsconn_get returns wsc [%p] refcnt [%d]\n", wsc, atomic_get(&wsc->refcnt));
WSCONN_UNLOCK;
return wsc;
}
}
WSCONN_UNLOCK;
return NULL;
}
ws_connection_t **wsconn_get_list(void)
{
ws_connection_t **list = NULL;
ws_connection_t *wsc = NULL;
size_t list_size = 0;
size_t list_len = 0;
size_t i = 0;
LM_DBG("wsconn_get_list\n");
WSCONN_LOCK;
/* get the number of used connections */
wsc = wsconn_used_list->head;
while (wsc)
{
LM_DBG("counter wsc [%p] prev => [%p] next => [%p]\n", wsc, wsc->used_prev, wsc->used_next);
list_len++;
wsc = wsc->used_next;
}
if (!list_len)
goto end;
/* allocate a NULL terminated list of wsconn pointers */
list_size = (list_len + 1) * sizeof(ws_connection_t *);
list = pkg_malloc(list_size);
if (!list)
goto end;
memset(list, 0, list_size);
/* copy */
wsc = wsconn_used_list->head;
for(i = 0; i < list_len; i++)
{
if (!wsc) {
LM_ERR("Wrong list length\n");
break;
}
list[i] = wsc;
wsconn_ref(wsc);
LM_DBG("wsc [%p] id [%d] ref++\n", wsc, wsc->id);
wsc = wsc->used_next;
}
list[i] = NULL; /* explicit NULL termination */
end:
WSCONN_UNLOCK;
LM_DBG("wsconn_get_list returns list [%p] with [%d] members\n", list, (int)list_len);
return list;
}
int wsconn_put_list(ws_connection_t **list_head)
{
ws_connection_t **list = NULL;
ws_connection_t *wsc = NULL;
LM_DBG("wsconn_put_list [%p]\n", list_head);
if (!list_head)
return -1;
list = list_head;
wsc = *list_head;
while (wsc)
{
wsconn_put(wsc);
wsc = *(++list);
}
pkg_free(list_head);
return 0;
}
static int add_node(struct mi_root *tree, ws_connection_t *wsc)
{
int interval;
char *src_proto, *dst_proto, *pong, *sub_protocol;
char src_ip[IP6_MAX_STR_SIZE + 1], dst_ip[IP6_MAX_STR_SIZE + 1];
struct tcp_connection *con = tcpconn_get(wsc->id, 0, 0, 0, 0);
if (con)
{
src_proto = (con->rcv.proto== PROTO_WS) ? "ws" : "wss";
memset(src_ip, 0, IP6_MAX_STR_SIZE + 1);
ip_addr2sbuf(&con->rcv.src_ip, src_ip, IP6_MAX_STR_SIZE);
dst_proto = (con->rcv.proto == PROTO_WS) ? "ws" : "wss";
memset(dst_ip, 0, IP6_MAX_STR_SIZE + 1);
ip_addr2sbuf(&con->rcv.dst_ip, dst_ip, IP6_MAX_STR_SIZE);
pong = wsc->awaiting_pong ? "awaiting Pong, " : "";
interval = (int)time(NULL) - wsc->last_used;
if (wsc->sub_protocol == SUB_PROTOCOL_SIP)
sub_protocol = "sip";
else if (wsc->sub_protocol == SUB_PROTOCOL_MSRP)
sub_protocol = "msrp";
else
sub_protocol = "**UNKNOWN**";
if (addf_mi_node_child(&tree->node, 0, 0, 0,
"%d: %s:%s:%hu -> %s:%s:%hu (state: %s"
", %s last used %ds ago"
", sub-protocol: %s)",
wsc->id,
src_proto,
strlen(src_ip) ? src_ip : "*",
con->rcv.src_port,
dst_proto,
strlen(dst_ip) ? dst_ip : "*",
con->rcv.dst_port,
wsconn_state_str[wsc->state],
pong,
interval,
sub_protocol) == 0)
{
tcpconn_put(con);
return -1;
}
tcpconn_put(con);
return 1;
}
else
return 0;
}
struct mi_root *ws_mi_dump(struct mi_root *cmd, void *param)
{
int h, connections = 0, truncated = 0, order = 0, found = 0;
ws_connection_t *wsc;
struct mi_node *node = NULL;
struct mi_root *rpl_tree;
node = cmd->node.kids;
if (node != NULL)
{
if (node->value.s == NULL || node->value.len == 0)
{
LM_WARN("empty display order parameter\n");
return init_mi_tree(400, str_status_empty_param.s,
str_status_empty_param.len);
}
strlower(&node->value);
if (strncmp(node->value.s, "id_hash", 7) == 0)
order = 0;
else if (strncmp(node->value.s, "used_desc", 9) == 0)
order = 1;
else if (strncmp(node->value.s, "used_asc", 8) == 0)
order = 2;
else
{
LM_WARN("bad display order parameter\n");
return init_mi_tree(400, str_status_bad_param.s,
str_status_bad_param.len);
}
if (node->next != NULL)
{
LM_WARN("too many parameters\n");
return init_mi_tree(400, str_status_too_many_params.s,
str_status_too_many_params.len);
}
}
rpl_tree = init_mi_tree(200, MI_OK_S, MI_OK_LEN);
if (rpl_tree == NULL)
return 0;
WSCONN_LOCK;
if (order == 0)
{
for (h = 0; h < TCP_ID_HASH_SIZE; h++)
{
wsc = wsconn_id_hash[h];
while(wsc)
{
if ((found = add_node(rpl_tree, wsc)) < 0)
{
free_mi_tree(rpl_tree);
return 0;
}
connections += found;
if (connections >= MAX_WS_CONNS_DUMP)
{
truncated = 1;
break;
}
wsc = wsc->id_next;
}
if (truncated == 1)
break;
}
}
else if (order == 1)
{
wsc = wsconn_used_list->head;
while (wsc)
{
if ((found = add_node(rpl_tree, wsc)) < 0)
{
free_mi_tree(rpl_tree);
return 0;
}
connections += found;
if (connections >= MAX_WS_CONNS_DUMP)
{
truncated = 1;
break;
}
wsc = wsc->used_next;
}
}
else
{
wsc = wsconn_used_list->tail;
while (wsc)
{
if ((found = add_node(rpl_tree, wsc)) < 0)
{
free_mi_tree(rpl_tree);
return 0;
}
connections += found;
if (connections >= MAX_WS_CONNS_DUMP)
{
truncated = 1;
break;
}
wsc = wsc->used_prev;
}
}
WSCONN_UNLOCK;
if (addf_mi_node_child(&rpl_tree->node, 0, 0, 0,
"%d WebSocket connection%s found%s",
connections, connections == 1 ? "" : "s",
truncated == 1 ? "(truncated)" : "") == 0)
{
free_mi_tree(rpl_tree);
return 0;
}
return rpl_tree;
}