keepalive: early start of OPTIONS checking
[sip-router] / src / modules / websocket / ws_conn.c
1 /*
2  * Copyright (C) 2012-2013 Crocodile RCS Ltd
3  *
4  * This file is part of Kamailio, a free SIP server.
5  *
6  * Kamailio is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version
10  *
11  * Kamailio is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * Exception: permission to copy, modify, propagate, and distribute a work
21  * formed by combining OpenSSL toolkit software and the code in this file,
22  * such as linking with software components and libraries released under
23  * OpenSSL project license.
24  *
25  */
26
27 #include "../../core/locking.h"
28 #include "../../core/str.h"
29 #include "../../core/tcp_conn.h"
30 #include "../../core/fmsg.h"
31 #include "../../core/counters.h"
32 #include "../../core/kemi.h"
33 #include "../../core/mem/mem.h"
34 #include "ws_conn.h"
35 #include "websocket.h"
36
37 /* Maximum number of connections to display when using the ws.dump command */
38 #define MAX_WS_CONNS_DUMP 50
39
40 extern int ws_verbose_list;
41 extern str ws_event_callback;
42 extern int ws_keepalive_processes;
43 extern int ws_rm_delay_interval;
44
45 ws_connection_t **wsconn_id_hash = NULL;
46 #define wsconn_listadd tcpconn_listadd
47 #define wsconn_listrm tcpconn_listrm
48
49 gen_lock_t *wsconn_lock = NULL;
50 #define WSCONN_LOCK lock_get(wsconn_lock)
51 #define WSCONN_UNLOCK lock_release(wsconn_lock)
52
53 #define wsconn_ref(c) atomic_inc(&((c)->refcnt))
54 #define wsconn_unref(c) atomic_dec_and_test(&((c)->refcnt))
55
56 gen_lock_t *wsstat_lock = NULL;
57
58 ws_connection_list_t *wsconn_used_list = NULL;
59
60 stat_var *ws_current_connections;
61 stat_var *ws_max_concurrent_connections;
62 stat_var *ws_sip_current_connections;
63 stat_var *ws_sip_max_concurrent_connections;
64 stat_var *ws_msrp_current_connections;
65 stat_var *ws_msrp_max_concurrent_connections;
66
67 char *wsconn_state_str[] = {
68                 "CONNECTING", /* WS_S_CONNECTING */
69                 "OPEN",           /* WS_S_OPEN */
70                 "CLOSING",      /* WS_S_CLOSING */
71                 "CLOSED"          /* WS_S_CLOSED */
72 };
73
74 /* RPC command status text */
75 static str str_status_bad_param = str_init("Bad display order parameter");
76
77 int wsconn_init(void)
78 {
79         wsconn_lock = lock_alloc();
80         if(wsconn_lock == NULL) {
81                 LM_ERR("allocating lock\n");
82                 goto error;
83         }
84         if(lock_init(wsconn_lock) == 0) {
85                 LM_ERR("initialising lock\n");
86                 goto error;
87         }
88
89         wsstat_lock = lock_alloc();
90         if(wsstat_lock == NULL) {
91                 LM_ERR("allocating lock\n");
92                 goto error;
93         }
94         if(lock_init(wsstat_lock) == NULL) {
95                 LM_ERR("initialising lock\n");
96                 goto error;
97         }
98
99         wsconn_id_hash = (ws_connection_t **)shm_malloc(
100                         TCP_ID_HASH_SIZE * sizeof(ws_connection_t *));
101         if(wsconn_id_hash == NULL) {
102                 LM_ERR("allocating WebSocket hash-table\n");
103                 goto error;
104         }
105         memset((void *)wsconn_id_hash, 0,
106                         TCP_ID_HASH_SIZE * sizeof(ws_connection_t *));
107
108         wsconn_used_list = (ws_connection_list_t *)shm_malloc(
109                         sizeof(ws_connection_list_t));
110         if(wsconn_used_list == NULL) {
111                 LM_ERR("allocating WebSocket used list\n");
112                 goto error;
113         }
114         memset((void *)wsconn_used_list, 0, sizeof(ws_connection_list_t));
115
116         return 0;
117
118 error:
119         if(wsconn_lock)
120                 lock_dealloc((void *)wsconn_lock);
121         if(wsstat_lock)
122                 lock_dealloc((void *)wsstat_lock);
123         wsconn_lock = wsstat_lock = NULL;
124
125         if(wsconn_id_hash)
126                 shm_free(wsconn_id_hash);
127         if(wsconn_used_list)
128                 shm_free(wsconn_used_list);
129         wsconn_id_hash = NULL;
130         wsconn_used_list = NULL;
131
132         return -1;
133 }
134
135 static inline void _wsconn_rm(ws_connection_t *wsc)
136 {
137         wsconn_listrm(wsconn_id_hash[wsc->id_hash], wsc, id_next, id_prev);
138
139         update_stat(ws_current_connections, -1);
140         if(wsc->sub_protocol == SUB_PROTOCOL_SIP)
141                 update_stat(ws_sip_current_connections, -1);
142         else if(wsc->sub_protocol == SUB_PROTOCOL_MSRP)
143                 update_stat(ws_msrp_current_connections, -1);
144
145         shm_free(wsc);
146 }
147
148 void wsconn_destroy(void)
149 {
150         int h;
151
152         if(wsconn_used_list) {
153                 shm_free(wsconn_used_list);
154                 wsconn_used_list = NULL;
155         }
156
157         if(wsconn_id_hash) {
158                 WSCONN_UNLOCK;
159                 WSCONN_LOCK;
160                 for(h = 0; h < TCP_ID_HASH_SIZE; h++) {
161                         ws_connection_t *wsc = wsconn_id_hash[h];
162                         while(wsc) {
163                                 ws_connection_t *next = wsc->id_next;
164                                 _wsconn_rm(wsc);
165                                 wsc = next;
166                         }
167                 }
168                 WSCONN_UNLOCK;
169
170                 shm_free(wsconn_id_hash);
171                 wsconn_id_hash = NULL;
172         }
173
174         if(wsconn_lock) {
175                 lock_destroy(wsconn_lock);
176                 lock_dealloc((void *)wsconn_lock);
177                 wsconn_lock = NULL;
178         }
179
180         if(wsstat_lock) {
181                 lock_destroy(wsstat_lock);
182                 lock_dealloc((void *)wsstat_lock);
183                 wsstat_lock = NULL;
184         }
185 }
186
187 int wsconn_add(struct receive_info rcv, unsigned int sub_protocol)
188 {
189         int cur_cons, max_cons;
190         int id = rcv.proto_reserved1;
191         int id_hash = tcp_id_hash(id);
192         ws_connection_t *wsc;
193
194         LM_DBG("wsconn_add id [%d]\n", id);
195
196         /* Allocate and fill in new WebSocket connection */
197         wsc = shm_malloc(sizeof(ws_connection_t) + BUF_SIZE + 1);
198         if(wsc == NULL) {
199                 LM_ERR("allocating shared memory\n");
200                 return -1;
201         }
202         memset(wsc, 0, sizeof(ws_connection_t) + BUF_SIZE + 1);
203         wsc->id = id;
204         wsc->id_hash = id_hash;
205         wsc->state = WS_S_OPEN;
206         wsc->rcv = rcv;
207         wsc->sub_protocol = sub_protocol;
208         wsc->run_event = 0;
209         wsc->frag_buf.s = ((char *)wsc) + sizeof(ws_connection_t);
210         atomic_set(&wsc->refcnt, 0);
211
212         LM_DBG("wsconn_add new wsc => [%p], ref => [%d]\n", wsc,
213                         atomic_get(&wsc->refcnt));
214
215         WSCONN_LOCK;
216         /* Add to WebSocket connection table */
217         wsconn_listadd(wsconn_id_hash[wsc->id_hash], wsc, id_next, id_prev);
218
219         /* Add to the end of the WebSocket used list */
220         wsc->last_used = (int)time(NULL);
221         if(wsconn_used_list->head == NULL)
222                 wsconn_used_list->head = wsconn_used_list->tail = wsc;
223         else {
224                 wsc->used_prev = wsconn_used_list->tail;
225                 wsconn_used_list->tail->used_next = wsc;
226                 wsconn_used_list->tail = wsc;
227         }
228         wsconn_ref(wsc);
229
230         WSCONN_UNLOCK;
231
232         LM_DBG("wsconn_add added to conn_table wsc => [%p], ref => [%d]\n", wsc,
233                         atomic_get(&wsc->refcnt));
234
235         /* Update connection statistics */
236         lock_get(wsstat_lock);
237
238         update_stat(ws_current_connections, 1);
239         cur_cons = get_stat_val(ws_current_connections);
240         max_cons = get_stat_val(ws_max_concurrent_connections);
241         if(max_cons < cur_cons)
242                 update_stat(ws_max_concurrent_connections, cur_cons - max_cons);
243
244         if(wsc->sub_protocol == SUB_PROTOCOL_SIP) {
245                 update_stat(ws_sip_current_connections, 1);
246                 cur_cons = get_stat_val(ws_sip_current_connections);
247                 max_cons = get_stat_val(ws_sip_max_concurrent_connections);
248                 if(max_cons < cur_cons)
249                         update_stat(ws_sip_max_concurrent_connections, cur_cons - max_cons);
250         } else if(wsc->sub_protocol == SUB_PROTOCOL_MSRP) {
251                 update_stat(ws_msrp_current_connections, 1);
252                 cur_cons = get_stat_val(ws_msrp_current_connections);
253                 max_cons = get_stat_val(ws_msrp_max_concurrent_connections);
254                 if(max_cons < cur_cons)
255                         update_stat(
256                                         ws_msrp_max_concurrent_connections, cur_cons - max_cons);
257         }
258
259         lock_release(wsstat_lock);
260
261         return 0;
262 }
263
264 static void wsconn_run_route(ws_connection_t *wsc)
265 {
266         int rt, backup_rt;
267         struct run_act_ctx ctx;
268         sip_msg_t *fmsg;
269         sr_kemi_eng_t *keng = NULL;
270         str evrtname = str_init("websocket:closed");
271
272         LM_DBG("wsconn_run_route event_route[websocket:closed]\n");
273
274         rt = route_lookup(&event_rt, evrtname.s);
275         if(rt < 0 || event_rt.rlist[rt] == NULL) {
276                 if(ws_event_callback.len <= 0 || ws_event_callback.s == NULL) {
277                         LM_DBG("event route does not exist");
278                         return;
279                 }
280                 keng = sr_kemi_eng_get();
281                 if(keng == NULL) {
282                         LM_DBG("event route callback engine does not exist");
283                         return;
284                 } else {
285                         rt = -1;
286                 }
287         }
288
289         if(faked_msg_init() < 0) {
290                 LM_ERR("faked_msg_init() failed\n");
291                 return;
292         }
293
294         fmsg = faked_msg_next();
295         wsc->rcv.proto_reserved1 = wsc->id;
296         fmsg->rcv = wsc->rcv;
297
298         backup_rt = get_route_type();
299         set_route_type(EVENT_ROUTE);
300         init_run_actions_ctx(&ctx);
301         if(rt < 0) {
302                 /* kemi script event route callback */
303                 if(keng && sr_kemi_route(keng,fmsg, EVENT_ROUTE, &ws_event_callback,
304                                         &evrtname) < 0) {
305                         LM_ERR("error running event route kemi callback\n");
306                 }
307         } else {
308                 /* native cfg event route */
309                 run_top_route(event_rt.rlist[rt], fmsg, 0);
310         }
311         set_route_type(backup_rt);
312 }
313
314 static void wsconn_dtor(ws_connection_t *wsc)
315 {
316         if(!wsc)
317                 return;
318
319         LM_DBG("wsconn_dtor for [%p] refcnt [%d]\n", wsc, atomic_get(&wsc->refcnt));
320
321         if(wsc->run_event)
322                 wsconn_run_route(wsc);
323
324         shm_free(wsc);
325
326         LM_DBG("wsconn_dtor for [%p] destroyed\n", wsc);
327 }
328
329 int wsconn_rm(ws_connection_t *wsc, ws_conn_eventroute_t run_event_route)
330 {
331         LM_DBG("wsconn_rm for [%p] refcnt [%d]\n", wsc, atomic_get(&wsc->refcnt));
332
333         if(run_event_route == WSCONN_EVENTROUTE_YES)
334                 wsc->run_event = 1;
335
336         return wsconn_put(wsc);
337 }
338
339 int wsconn_update(ws_connection_t *wsc)
340 {
341         if(!wsc) {
342                 LM_ERR("wsconn_update: null pointer\n");
343                 return -1;
344         }
345
346         WSCONN_LOCK;
347         wsc->last_used = (int)time(NULL);
348         if(wsconn_used_list->tail == wsc)
349                 /* Already at the end of the list */
350                 goto end;
351         if(wsconn_used_list->head == wsc)
352                 wsconn_used_list->head = wsc->used_next;
353         if(wsc->used_prev)
354                 wsc->used_prev->used_next = wsc->used_next;
355         if(wsc->used_next)
356                 wsc->used_next->used_prev = wsc->used_prev;
357         wsc->used_prev = wsconn_used_list->tail;
358         wsc->used_next = NULL;
359         wsconn_used_list->tail->used_next = wsc;
360         wsconn_used_list->tail = wsc;
361
362 end:
363         WSCONN_UNLOCK;
364         return 0;
365 }
366
367 void wsconn_close_now(ws_connection_t *wsc)
368 {
369         struct tcp_connection *con = tcpconn_get(wsc->id, 0, 0, 0, 0);
370
371         if(wsconn_rm(wsc, WSCONN_EVENTROUTE_YES) < 0)
372                 LM_ERR("removing WebSocket connection\n");
373
374         if(con == NULL) {
375                 LM_ERR("getting TCP/TLS connection\n");
376                 return;
377         }
378
379         tcpconn_put(con);
380         con->send_flags.f |= SND_F_CON_CLOSE;
381         con->state = S_CONN_BAD;
382         con->timeout = get_ticks_raw();
383 }
384
385 void wsconn_detach_connection(ws_connection_t *wsc)
386 {
387         /* Remove from the WebSocket used list */
388         if(wsconn_used_list->head == wsc)
389                 wsconn_used_list->head = wsc->used_next;
390         if(wsconn_used_list->tail == wsc)
391                 wsconn_used_list->tail = wsc->used_prev;
392         if(wsc->used_prev)
393                 wsc->used_prev->used_next = wsc->used_next;
394         if(wsc->used_next)
395                 wsc->used_next->used_prev = wsc->used_prev;
396
397         /* remove from wsconn_id_hash */
398         wsconn_listrm(wsconn_id_hash[wsc->id_hash], wsc, id_next, id_prev);
399
400         /* stat */
401         update_stat(ws_current_connections, -1);
402         if(wsc->sub_protocol == SUB_PROTOCOL_SIP)
403                 update_stat(ws_sip_current_connections, -1);
404         else if(wsc->sub_protocol == SUB_PROTOCOL_MSRP)
405                 update_stat(ws_msrp_current_connections, -1);
406 }
407
408 /* mode controls if lock needs to be aquired */
409 int wsconn_put_mode(ws_connection_t *wsc, int mode)
410 {
411         if(!wsc)
412                 return -1;
413
414         LM_DBG("wsconn_put start for [%p] refcnt [%d]\n", wsc,
415                         atomic_get(&wsc->refcnt));
416
417         if(mode) {
418                 WSCONN_LOCK;
419         }
420         if(wsc->state == WS_S_REMOVING) {
421                 goto done;
422         }
423         /* refcnt == 0*/
424         if(wsconn_unref(wsc)) {
425                 wsc->state = WS_S_REMOVING;
426                 wsc->rmticks = get_ticks();
427         }
428         LM_DBG("wsconn_put end for [%p] refcnt [%d]\n", wsc,
429                         atomic_get(&wsc->refcnt));
430
431 done:
432         if(mode) {
433                 WSCONN_UNLOCK;
434         }
435
436         return 0;
437 }
438
439 /* must be called with unlocked WSCONN_LOCK */
440 int wsconn_put(ws_connection_t *wsc)
441 {
442         return wsconn_put_mode(wsc, 1);
443 }
444
445 ws_connection_t *wsconn_get(int id)
446 {
447         int id_hash = tcp_id_hash(id);
448         ws_connection_t *wsc;
449
450         LM_DBG("wsconn_get for id [%d]\n", id);
451
452         WSCONN_LOCK;
453         for(wsc = wsconn_id_hash[id_hash]; wsc; wsc = wsc->id_next) {
454                 if(wsc->id == id) {
455                         wsconn_ref(wsc);
456                         LM_DBG("wsconn_get returns wsc [%p] refcnt [%d]\n", wsc,
457                                         atomic_get(&wsc->refcnt));
458
459                         WSCONN_UNLOCK;
460
461                         return wsc;
462                 }
463         }
464         WSCONN_UNLOCK;
465
466         return NULL;
467 }
468
469 int wsconn_put_id(int id)
470 {
471         int id_hash = tcp_id_hash(id);
472         ws_connection_t *wsc;
473
474         LM_DBG("wsconn put id [%d]\n", id);
475
476         WSCONN_LOCK;
477         for(wsc = wsconn_id_hash[id_hash]; wsc; wsc = wsc->id_next) {
478                 if(wsc->id == id) {
479                         LM_DBG("wsc [%p] refcnt [%d]\n", wsc,
480                                         atomic_get(&wsc->refcnt));
481                         wsconn_put_mode(wsc, 0);
482
483                         WSCONN_UNLOCK;
484
485                         return 1;
486                 }
487         }
488         WSCONN_UNLOCK;
489
490         return 0;
491 }
492
493 ws_connection_t **wsconn_get_list(void)
494 {
495         ws_connection_t **list = NULL;
496         ws_connection_t *wsc = NULL;
497         size_t list_size = 0;
498         size_t list_len = 0;
499         size_t i = 0;
500
501         if(ws_verbose_list)
502                 LM_DBG("wsconn get list - starting\n");
503
504         WSCONN_LOCK;
505
506         /* get the number of used connections */
507         wsc = wsconn_used_list->head;
508         while(wsc) {
509                 if(ws_verbose_list)
510                         LM_DBG("counter wsc [%p] prev => [%p] next => [%p]\n", wsc,
511                                         wsc->used_prev, wsc->used_next);
512                 list_len++;
513                 wsc = wsc->used_next;
514         }
515
516         if(!list_len)
517                 goto end;
518
519         /* allocate a NULL terminated list of wsconn pointers */
520         list_size = (list_len + 1) * sizeof(ws_connection_t *);
521         list = pkg_malloc(list_size);
522         if(!list)
523                 goto end;
524
525         memset(list, 0, list_size);
526
527         /* copy */
528         wsc = wsconn_used_list->head;
529         for(i = 0; i < list_len; i++) {
530                 if(!wsc) {
531                         LM_ERR("Wrong list length\n");
532                         break;
533                 }
534
535                 list[i] = wsc;
536                 wsconn_ref(wsc);
537                 if(ws_verbose_list)
538                         LM_DBG("wsc [%p] id [%d] ref++\n", wsc, wsc->id);
539
540                 wsc = wsc->used_next;
541         }
542         list[i] = NULL; /* explicit NULL termination */
543
544 end:
545         WSCONN_UNLOCK;
546
547         if(ws_verbose_list)
548                 LM_DBG("wsconn_get_list returns list [%p]"
549                            " with [%d] members\n",
550                                 list, (int)list_len);
551
552         return list;
553 }
554
555 int wsconn_put_list(ws_connection_t **list_head)
556 {
557         ws_connection_t **list = NULL;
558         ws_connection_t *wsc = NULL;
559
560         LM_DBG("wsconn_put_list [%p]\n", list_head);
561
562         if(!list_head)
563                 return -1;
564
565         list = list_head;
566         wsc = *list_head;
567         while(wsc) {
568                 wsconn_put(wsc);
569                 wsc = *(++list);
570         }
571
572         pkg_free(list_head);
573
574         return 0;
575 }
576
577
578 ws_connection_id_t *wsconn_get_list_ids(int idx)
579 {
580         ws_connection_id_t *list = NULL;
581         ws_connection_t *wsc = NULL;
582         size_t list_size = 0;
583         size_t list_len = 0;
584         size_t i = 0;
585
586         if(ws_verbose_list)
587                 LM_DBG("wsconn get list ids - starting\n");
588
589         WSCONN_LOCK;
590
591         /* get the number of used connections */
592         wsc = wsconn_used_list->head;
593         while(wsc) {
594                 if(wsc->id % ws_keepalive_processes == idx) {
595                         if(ws_verbose_list) {
596                                 LM_DBG("counter wsc [%p] prev => [%p] next => [%p] (%d/%d)\n",
597                                                 wsc, wsc->used_prev, wsc->used_next, wsc->id, idx);
598                         }
599                         list_len++;
600                 }
601                 wsc = wsc->used_next;
602         }
603
604         if(!list_len)
605                 goto end;
606
607         /* allocate a NULL terminated list of wsconn pointers */
608         list_size = (list_len + 1) * sizeof(ws_connection_id_t);
609         list = pkg_malloc(list_size);
610         if(!list)
611                 goto end;
612
613         memset(list, 0, list_size);
614
615         /* copy */
616         wsc = wsconn_used_list->head;
617         for(i = 0; i < list_len; i++) {
618                 if(!wsc) {
619                         LM_ERR("Wrong list length\n");
620                         break;
621                 }
622
623                 if(wsc->id % ws_keepalive_processes == idx) {
624                         list[i].id = wsc->id;
625                         wsconn_ref(wsc);
626                         if(ws_verbose_list) {
627                                 LM_DBG("wsc [%p] id [%d] (%d) - ref++\n", wsc, wsc->id, idx);
628                         }
629                 }
630                 wsc = wsc->used_next;
631         }
632         list[i].id = -1; /* explicit -1 termination */
633
634 end:
635         WSCONN_UNLOCK;
636
637         if(ws_verbose_list) {
638                 LM_DBG("wsconn get list id returns list [%p]"
639                            " with [%d] members (%d)\n",
640                                 list, (int)list_len, idx);
641         }
642
643         return list;
644 }
645
646 int wsconn_put_list_ids(ws_connection_id_t *list_head)
647 {
648         ws_connection_id_t *list = NULL;
649         int i;
650
651         LM_DBG("wsconn put list id [%p]\n", list_head);
652
653         if(!list_head)
654                 return -1;
655
656         list = list_head;
657         for(i=0; list[i].id!=-1; i++) {
658                 wsconn_put_id(list[i].id);
659         }
660
661         pkg_free(list_head);
662
663         return 0;
664 }
665
666 void ws_timer(unsigned int ticks, void *param)
667 {
668         ws_connection_list_t rmlist;
669         ws_connection_t *wsc;
670         ws_connection_t *next;
671         ticks_t nticks;
672         int h;
673
674         rmlist.head = NULL;
675         nticks = get_ticks();
676
677         WSCONN_LOCK;
678         for(h = 0; h < TCP_ID_HASH_SIZE; h++) {
679                 wsc = wsconn_id_hash[h];
680                 while(wsc) {
681                         next = wsc->id_next;
682                         if(wsc->state == WS_S_REMOVING
683                                         && wsc->rmticks <= nticks - ws_rm_delay_interval) {
684                                 wsconn_detach_connection(wsc);
685                                 wsc->id_next = rmlist.head;
686                                 rmlist.head = wsc;
687                         }
688                         wsc = next;
689                 }
690         }
691         WSCONN_UNLOCK;
692
693         for(wsc = rmlist.head; wsc; ) {
694                 next = wsc->id_next;
695                 wsconn_dtor(wsc);
696                 wsc = next;
697         }
698 }
699
700 static int ws_rpc_add_node(
701                 rpc_t *rpc, void *ctx, void *ih, ws_connection_t *wsc)
702 {
703         int interval;
704         char *src_proto, *dst_proto, *pong, *sub_protocol;
705         char src_ip[IP6_MAX_STR_SIZE + 1], dst_ip[IP6_MAX_STR_SIZE + 1];
706         struct tcp_connection *con = tcpconn_get(wsc->id, 0, 0, 0, 0);
707         char rplbuf[512];
708
709         if(con) {
710                 src_proto = (con->rcv.proto == PROTO_WS) ? "ws" : "wss";
711                 memset(src_ip, 0, IP6_MAX_STR_SIZE + 1);
712                 ip_addr2sbuf(&con->rcv.src_ip, src_ip, IP6_MAX_STR_SIZE);
713
714                 dst_proto = (con->rcv.proto == PROTO_WS) ? "ws" : "wss";
715                 memset(dst_ip, 0, IP6_MAX_STR_SIZE + 1);
716                 ip_addr2sbuf(&con->rcv.dst_ip, dst_ip, IP6_MAX_STR_SIZE);
717
718                 pong = wsc->awaiting_pong ? "awaiting Pong, " : "";
719
720                 interval = (int)time(NULL) - wsc->last_used;
721                 if(wsc->sub_protocol == SUB_PROTOCOL_SIP)
722                         sub_protocol = "sip";
723                 else if(wsc->sub_protocol == SUB_PROTOCOL_MSRP)
724                         sub_protocol = "msrp";
725                 else
726                         sub_protocol = "**UNKNOWN**";
727
728                 if(snprintf(rplbuf, 512, "%d: %s:%s:%hu -> %s:%s:%hu (state: %s"
729                                                                  ", %s last used %ds ago"
730                                                                  ", sub-protocol: %s)",
731                                    wsc->id, src_proto, strlen(src_ip) ? src_ip : "*",
732                                    con->rcv.src_port, dst_proto, strlen(dst_ip) ? dst_ip : "*",
733                                    con->rcv.dst_port, wsconn_state_str[wsc->state], pong,
734                                    interval, sub_protocol)
735                                 < 0) {
736                         tcpconn_put(con);
737                         rpc->fault(ctx, 500, "Failed to print connection details");
738                         return -1;
739                 }
740                 if(rpc->array_add(ih, "s", rplbuf) < 0) {
741                         tcpconn_put(con);
742                         rpc->fault(ctx, 500, "Failed to add to response");
743                         return -1;
744                 }
745
746                 tcpconn_put(con);
747                 return 1;
748         } else
749                 return 0;
750 }
751
752 void ws_rpc_dump(rpc_t *rpc, void *ctx)
753 {
754         int h, connections = 0, truncated = 0, order = 0, found = 0;
755         ws_connection_t *wsc;
756         str sorder = {0};
757         void *th;
758         void *ih;
759         void *dh;
760
761         if(rpc->scan(ctx, "*S", &sorder) == 1) {
762                 if(sorder.len == 7 && strncasecmp(sorder.s, "id_hash", 7) == 0) {
763                         order = 0;
764                 } else if(sorder.len == 9
765                                   && strncasecmp(sorder.s, "used_desc", 9) == 0) {
766                         order = 1;
767                 } else if(sorder.len == 8
768                                   && strncasecmp(sorder.s, "used_asc", 8) == 0) {
769                         order = 2;
770                 } else {
771                         LM_WARN("bad display order parameter\n");
772                         rpc->fault(ctx, 400, str_status_bad_param.s);
773                         return;
774                 }
775         }
776
777         /* add root node */
778         if(rpc->add(ctx, "{", &th) < 0) {
779                 rpc->fault(ctx, 500, "Internal error root reply");
780                 return;
781         }
782         if(rpc->struct_add(th, "[", "connections", &ih) < 0) {
783                 rpc->fault(ctx, 500, "Internal error connections structure");
784                 return;
785         }
786
787         WSCONN_LOCK;
788         if(order == 0) {
789                 for(h = 0; h < TCP_ID_HASH_SIZE; h++) {
790                         wsc = wsconn_id_hash[h];
791                         while(wsc) {
792                                 if((found = ws_rpc_add_node(rpc, ctx, ih, wsc)) < 0) {
793                                         WSCONN_UNLOCK;
794                                         return;
795                                 }
796
797
798                                 connections += found;
799                                 if(connections >= MAX_WS_CONNS_DUMP) {
800                                         truncated = 1;
801                                         break;
802                                 }
803
804                                 wsc = wsc->id_next;
805                         }
806
807                         if(truncated == 1)
808                                 break;
809                 }
810         } else if(order == 1) {
811                 wsc = wsconn_used_list->head;
812                 while(wsc) {
813                         if((found = ws_rpc_add_node(rpc, ctx, ih, wsc)) < 0) {
814                                 WSCONN_UNLOCK;
815                                 return;
816                         }
817
818                         connections += found;
819                         if(connections >= MAX_WS_CONNS_DUMP) {
820                                 truncated = 1;
821                                 break;
822                         }
823
824                         wsc = wsc->used_next;
825                 }
826         } else {
827                 wsc = wsconn_used_list->tail;
828                 while(wsc) {
829                         if((found = ws_rpc_add_node(rpc, ctx, ih, wsc)) < 0) {
830                                 WSCONN_UNLOCK;
831                                 return;
832                         }
833
834                         connections += found;
835                         if(connections >= MAX_WS_CONNS_DUMP) {
836                                 truncated = 1;
837                                 break;
838                         }
839
840                         wsc = wsc->used_prev;
841                 }
842         }
843         WSCONN_UNLOCK;
844
845         if(rpc->struct_add(th, "{", "info", &dh) < 0) {
846                 rpc->fault(ctx, 500, "Internal error info structure");
847                 return;
848         }
849         if(rpc->struct_add(dh, "ds", "wscounter", connections, "truncated",
850                            (truncated == 1) ? "yes" : "no")
851                         < 0) {
852                 rpc->fault(ctx, 500, "Internal error adding info structure");
853                 return;
854         }
855
856         return;
857 }