/* * 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 #ifdef EMBEDDED_UTF8_DECODE #include "utf8_decode.h" #else #include #endif #include "../../events.h" #include "../../receive.h" #include "../../stats.h" #include "../../str.h" #include "../../tcp_conn.h" #include "../../tcp_read.h" #include "../../tcp_server.h" #include "../../lib/kcore/kstats_wrapper.h" #include "../../lib/kmi/tree.h" #include "../../mem/mem.h" #include "ws_conn.h" #include "ws_frame.h" #include "ws_mod.h" #include "ws_handshake.h" #include "config.h" /* 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+ */ typedef struct { unsigned int fin; unsigned int rsv1; unsigned int rsv2; unsigned int rsv3; unsigned int opcode; unsigned int mask; unsigned int payload_len; unsigned char masking_key[4]; char *payload_data; ws_connection_t *wsc; } ws_frame_t; typedef enum { CONN_CLOSE_DO = 0, CONN_CLOSE_DONT } conn_close_t; #define BYTE0_MASK_FIN (0x80) #define BYTE0_MASK_RSV1 (0x40) #define BYTE0_MASK_RSV2 (0x20) #define BYTE0_MASK_RSV3 (0x10) #define BYTE0_MASK_OPCODE (0x0F) #define BYTE1_MASK_MASK (0x80) #define BYTE1_MASK_PAYLOAD_LEN (0x7F) #define OPCODE_CONTINUATION (0x0) #define OPCODE_TEXT_FRAME (0x1) #define OPCODE_BINARY_FRAME (0x2) /* 0x3 - 0x7 are reserved for further non-control frames */ #define OPCODE_CLOSE (0x8) #define OPCODE_PING (0x9) #define OPCODE_PONG (0xa) /* 0xb - 0xf are reserved for further control frames */ int ws_keepalive_mechanism = DEFAULT_KEEPALIVE_MECHANISM; str ws_ping_application_data = STR_NULL; stat_var *ws_failed_connections; stat_var *ws_local_closed_connections; stat_var *ws_received_frames; stat_var *ws_remote_closed_connections; stat_var *ws_transmitted_frames; stat_var *ws_sip_failed_connections; stat_var *ws_sip_local_closed_connections; stat_var *ws_sip_received_frames; stat_var *ws_sip_remote_closed_connections; stat_var *ws_sip_transmitted_frames; stat_var *ws_msrp_failed_connections; stat_var *ws_msrp_local_closed_connections; stat_var *ws_msrp_received_frames; stat_var *ws_msrp_remote_closed_connections; stat_var *ws_msrp_transmitted_frames; /* WebSocket status text */ static str str_status_normal_closure = str_init("Normal closure"); static str str_status_protocol_error = str_init("Protocol error"); static str str_status_unsupported_opcode = str_init("Unsupported opcode"); static str str_status_message_too_big = str_init("Message too big"); /* MI command status text */ static str str_status_empty_param = str_init("Empty connection ID parameter"); static str str_status_too_many_params = str_init("Too many parameters"); static str str_status_bad_param = str_init("Bad connection ID parameter"); static str str_status_error_closing = str_init("Error closing connection"); static str str_status_error_sending = str_init("Error sending frame"); static str str_status_string_error = str_init("Error converting string to int"); static int ws_send_crlf(ws_connection_t *wsc, int opcode); static int encode_and_send_ws_frame(ws_frame_t *frame, conn_close_t conn_close) { int pos = 0, extended_length; unsigned int frame_length; char *send_buf; struct tcp_connection *con; struct dest_info dst; union sockaddr_union *from = NULL; union sockaddr_union local_addr; int sub_proto; LM_DBG("encoding WebSocket frame\n"); if (frame->wsc->state != WS_S_OPEN) { LM_WARN("sending on closing connection\n"); return -1; } wsconn_update(frame->wsc); /* Validate the first byte */ if (!frame->fin) { LM_ERR("WebSocket fragmentation not supported in the sip " "sub-protocol\n"); return -1; } if (frame->rsv1 || frame->rsv2 || frame->rsv3) { LM_ERR("WebSocket reserved fields with non-zero values\n"); return -1; } sub_proto = frame->wsc->sub_protocol; switch(frame->opcode) { case OPCODE_TEXT_FRAME: case OPCODE_BINARY_FRAME: LM_DBG("supported non-control frame: 0x%x\n", (unsigned char) frame->opcode); break; case OPCODE_CLOSE: case OPCODE_PING: case OPCODE_PONG: LM_DBG("supported control frame: 0x%x\n", (unsigned char) frame->opcode); break; default: LM_ERR("unsupported opcode: 0x%x\n", (unsigned char) frame->opcode); return -1; } /* validate the second byte */ if (frame->mask) { LM_ERR("this is a server - all messages sent will be " "unmasked\n"); return -1; } if (frame->payload_len < 126) extended_length = 0; else if (frame->payload_len <= USHRT_MAX ) extended_length = 2; else if (frame->payload_len <= UINT_MAX) extended_length = 4; else { LM_ERR("Kamailio only supports WebSocket frames with payload " "<= %u\n", UINT_MAX); return -1; } /* Allocate send buffer and build frame */ frame_length = frame->payload_len + extended_length + 2; if ((send_buf = pkg_malloc(sizeof(char) * frame_length)) == NULL) { LM_ERR("allocating send buffer from pkg memory\n"); return -1; } memset(send_buf, 0, sizeof(char) * frame_length); send_buf[pos++] = 0x80 | (frame->opcode & 0xff); if (extended_length == 0) send_buf[pos++] = (frame->payload_len & 0xff); else if (extended_length == 2) { send_buf[pos++] = 126; send_buf[pos++] = (frame->payload_len & 0xff00) >> 8; send_buf[pos++] = (frame->payload_len & 0x00ff) >> 0; } else { send_buf[pos++] = 127; send_buf[pos++] = (frame->payload_len & 0xff000000) >> 24; send_buf[pos++] = (frame->payload_len & 0x00ff0000) >> 16; send_buf[pos++] = (frame->payload_len & 0x0000ff00) >> 8; send_buf[pos++] = (frame->payload_len & 0x000000ff) >> 0; } memcpy(&send_buf[pos], frame->payload_data, frame->payload_len); if ((con = tcpconn_get(frame->wsc->id, 0, 0, 0, 0)) == NULL) { LM_WARN("TCP/TLS connection get failed\n"); pkg_free(send_buf); if (wsconn_rm(frame->wsc, WSCONN_EVENTROUTE_YES) < 0) LM_ERR("removing WebSocket connection\n"); return -1; } init_dst_from_rcv(&dst, &con->rcv); if (conn_close == CONN_CLOSE_DO) { dst.send_flags.f |= SND_F_CON_CLOSE; if (wsconn_rm(frame->wsc, WSCONN_EVENTROUTE_YES) < 0) { LM_ERR("removing WebSocket connection\n"); tcpconn_put(con); pkg_free(send_buf); return -1; } } if (dst.proto == PROTO_WS) { if (unlikely(tcp_disable)) { STATS_TX_DROPS; LM_WARN("TCP disabled\n"); pkg_free(send_buf); tcpconn_put(con); return -1; } } #ifdef USE_TLS else if (dst.proto == PROTO_WSS) { if (unlikely(tls_disable)) { STATS_TX_DROPS; LM_WARN("TLS disabled\n"); pkg_free(send_buf); tcpconn_put(con); return -1; } } #endif /* USE_TLS */ if (unlikely((dst.send_flags.f & SND_F_FORCE_SOCKET) && dst.send_sock)) { local_addr = dst.send_sock->su; su_setport(&local_addr, 0); from = &local_addr; } /* Regardless of what has been set before _always_ use existing connections for WebSockets. This is required because a WebSocket server (which Kamailio is) CANNOT create connections. */ dst.send_flags.f |= SND_F_FORCE_CON_REUSE; if (tcp_send(&dst, from, send_buf, frame_length) < 0) { STATS_TX_DROPS; LM_ERR("sending WebSocket frame\n"); pkg_free(send_buf); update_stat(ws_failed_connections, 1); if (sub_proto == SUB_PROTOCOL_SIP) update_stat(ws_sip_failed_connections, 1); else if (sub_proto == SUB_PROTOCOL_MSRP) update_stat(ws_msrp_failed_connections, 1); if (wsconn_rm(frame->wsc, WSCONN_EVENTROUTE_YES) < 0) LM_ERR("removing WebSocket connection\n"); tcpconn_put(con); return -1; } update_stat(ws_transmitted_frames, 1); switch (frame->opcode) { case OPCODE_TEXT_FRAME: case OPCODE_BINARY_FRAME: if (frame->wsc->sub_protocol == SUB_PROTOCOL_SIP) update_stat(ws_sip_transmitted_frames, 1); else if (frame->wsc->sub_protocol == SUB_PROTOCOL_MSRP) update_stat(ws_msrp_transmitted_frames, 1); } pkg_free(send_buf); tcpconn_put(con); return 0; } static int close_connection(ws_connection_t **p_wsc, ws_close_type_t type, short int status, str reason) { char *data; ws_frame_t frame; ws_connection_t * wsc = NULL; int sub_proto = -1; if (!p_wsc || !(*p_wsc)) { LM_ERR("Invalid parameters\n"); return -1; } wsc = *p_wsc; if (wsc->state == WS_S_OPEN) { data = pkg_malloc(sizeof(char) * (reason.len + 2)); if (data == NULL) { LM_ERR("allocating pkg memory\n"); return -1; } data[0] = (status & 0xff00) >> 8; data[1] = (status & 0x00ff) >> 0; memcpy(&data[2], reason.s, reason.len); memset(&frame, 0, sizeof(frame)); frame.fin = 1; frame.opcode = OPCODE_CLOSE; frame.payload_len = reason.len + 2; frame.payload_data = data; frame.wsc = wsc; sub_proto = wsc->sub_protocol; if (encode_and_send_ws_frame(&frame, type == REMOTE_CLOSE ? CONN_CLOSE_DO : CONN_CLOSE_DONT) < 0) { LM_ERR("sending WebSocket close\n"); pkg_free(data); return -1; } pkg_free(data); if (type == LOCAL_CLOSE) { frame.wsc->state = WS_S_CLOSING; update_stat(ws_local_closed_connections, 1); if (frame.wsc->sub_protocol == SUB_PROTOCOL_SIP) update_stat(ws_sip_local_closed_connections, 1); else if (frame.wsc->sub_protocol == SUB_PROTOCOL_MSRP) update_stat(ws_msrp_local_closed_connections, 1); } else { update_stat(ws_remote_closed_connections, 1); if (sub_proto == SUB_PROTOCOL_SIP) update_stat(ws_sip_remote_closed_connections, 1); else if (sub_proto == SUB_PROTOCOL_MSRP) update_stat(ws_msrp_remote_closed_connections, 1); } } else /* if (frame->wsc->state == WS_S_CLOSING) */ { wsconn_close_now(wsc); } return 0; } static int decode_and_validate_ws_frame(ws_frame_t *frame, tcp_event_info_t *tcpinfo, short *err_code, str *err_text) { unsigned int i, len = tcpinfo->len; unsigned int mask_start, j; char *buf = tcpinfo->buf; LM_DBG("decoding WebSocket frame\n"); wsconn_update(frame->wsc); /* Decode and validate first 9 bits */ if (len < 2) { LM_WARN("message is too short\n"); *err_code = 1002; *err_text = str_status_protocol_error; return -1; } frame->fin = (buf[0] & 0xff) & BYTE0_MASK_FIN; frame->rsv1 = (buf[0] & 0xff) & BYTE0_MASK_RSV1; frame->rsv2 = (buf[0] & 0xff) & BYTE0_MASK_RSV2; frame->rsv3 = (buf[0] & 0xff) & BYTE0_MASK_RSV3; frame->opcode = (buf[0] & 0xff) & BYTE0_MASK_OPCODE; frame->mask = (buf[1] & 0xff) & BYTE1_MASK_MASK; if (frame->rsv1 || frame->rsv2 || frame->rsv3) { LM_WARN("WebSocket reserved fields with non-zero values\n"); *err_code = 1002; *err_text = str_status_protocol_error; return -1; } switch(frame->opcode) { case OPCODE_CONTINUATION: LM_DBG("supported continuation frame: 0x%x\n", (unsigned char) frame->opcode); break; case OPCODE_TEXT_FRAME: case OPCODE_BINARY_FRAME: LM_DBG("supported non-control frame: 0x%x\n", (unsigned char) frame->opcode); break; case OPCODE_CLOSE: case OPCODE_PING: case OPCODE_PONG: LM_DBG("supported control frame: 0x%x\n", (unsigned char) frame->opcode); break; default: LM_WARN("unsupported opcode: 0x%x\n", (unsigned char) frame->opcode); *err_code = 1008; *err_text = str_status_unsupported_opcode; return -1; } if (!frame->mask) { LM_WARN("this is a server - all received messages must be " "masked\n"); *err_code = 1002; *err_text = str_status_protocol_error; return -1; } /* Decode and validate length */ frame->payload_len = (buf[1] & 0xff) & BYTE1_MASK_PAYLOAD_LEN; if (frame->payload_len == 126) { if (len < 4) { LM_WARN("message is too short\n"); *err_code = 1002; *err_text = str_status_protocol_error; return -1; } mask_start = 4; frame->payload_len = ((buf[2] & 0xff) << 8) | ((buf[3] & 0xff) << 0); } else if (frame->payload_len == 127) { if (len < 10) { LM_WARN("message is too short\n"); *err_code = 1002; *err_text = str_status_protocol_error; return -1; } mask_start = 10; if ((buf[2] & 0xff) != 0 || (buf[3] & 0xff) != 0 || (buf[4] & 0xff) != 0 || (buf[5] & 0xff) != 0) { LM_WARN("message is too long\n"); *err_code = 1009; *err_text = str_status_message_too_big; return -1; } /* Only decoding the last four bytes of the length... This limits the size of WebSocket messages that can be handled to 2^32 = which should be plenty for SIP! */ frame->payload_len = ((buf[6] & 0xff) << 24) | ((buf[7] & 0xff) << 16) | ((buf[8] & 0xff) << 8) | ((buf[9] & 0xff) << 0); } else mask_start = 2; /* Decode mask */ frame->masking_key[0] = (buf[mask_start + 0] & 0xff); frame->masking_key[1] = (buf[mask_start + 1] & 0xff); frame->masking_key[2] = (buf[mask_start + 2] & 0xff); frame->masking_key[3] = (buf[mask_start + 3] & 0xff); /* Decode and unmask payload */ if ((unsigned long long)len != (unsigned long long)frame->payload_len + mask_start + 4) { LM_WARN("message not complete frame size %u but received %u\n", frame->payload_len + mask_start + 4, len); *err_code = 1002; *err_text = str_status_protocol_error; return -1; } if(frame->payload_len >= BUF_SIZE) { LM_WARN("message is too long for our buffer size (%d / %d)\n", BUF_SIZE, frame->payload_len); *err_code = 1009; *err_text = str_status_message_too_big; return -1; } frame->payload_data = &buf[mask_start + 4]; for (i = 0; i < frame->payload_len; i++) { j = i % 4; frame->payload_data[i] = frame->payload_data[i] ^ frame->masking_key[j]; } LM_DBG("Rx (decoded): %.*s\n", (int) frame->payload_len, frame->payload_data); return frame->opcode; } static int handle_close(ws_frame_t *frame) { unsigned short code = 0; str reason = {0, 0}; if (frame->payload_len >= 2) code = ((frame->payload_data[0] & 0xff) << 8) | ((frame->payload_data[1] & 0xff) << 0); if (frame->payload_len > 2) { reason.s = &frame->payload_data[2]; reason.len = frame->payload_len - 2; } LM_DBG("Rx Close: %hu %.*s\n", code, reason.len, reason.s); if (close_connection(&frame->wsc, frame->wsc->state == WS_S_OPEN ? REMOTE_CLOSE : LOCAL_CLOSE, 1000, str_status_normal_closure) < 0) { LM_ERR("closing connection\n"); return -1; } return 0; } static int handle_ping(ws_frame_t *frame) { LM_DBG("Rx Ping: %.*s\n", frame->payload_len, frame->payload_data); frame->opcode = OPCODE_PONG; frame->mask = 0; if (encode_and_send_ws_frame(frame, CONN_CLOSE_DONT) < 0) { LM_ERR("sending Pong\n"); return -1; } return 0; } static int handle_pong(ws_frame_t *frame) { LM_DBG("Rx Pong: %.*s\n", frame->payload_len, frame->payload_data); if (strncmp(frame->payload_data, ws_ping_application_data.s, ws_ping_application_data.len) == 0) frame->wsc->awaiting_pong = 0; return 0; } int ws_frame_receive(void *data) { ws_frame_t frame; tcp_event_info_t *tcpinfo = (tcp_event_info_t *) data; int opcode = -1; int ret = 0; short err_code = 0; str err_text = {NULL, 0}; update_stat(ws_received_frames, 1); if (tcpinfo == NULL || tcpinfo->buf == NULL || tcpinfo->len <= 0) { LM_WARN("received bad frame\n"); return -1; } /* wsc refcnt++ */ frame.wsc = wsconn_get(tcpinfo->con->id); if (frame.wsc == NULL) { LM_ERR("WebSocket connection not found\n"); return -1; } opcode = decode_and_validate_ws_frame(&frame, tcpinfo, &err_code, &err_text); if (opcode < 0) { if (close_connection(&frame.wsc, LOCAL_CLOSE, err_code, err_text) < 0) LM_ERR("closing connection\n"); wsconn_put(frame.wsc); return -1; } switch(opcode) { case OPCODE_CONTINUATION: if (likely(frame.wsc->sub_protocol == SUB_PROTOCOL_SIP)) { if (frame.wsc->frag_buf.len + frame.payload_len >= BUF_SIZE) { LM_ERR("Buffer overflow assembling websocket fragments %d + %d = %d\n", frame.wsc->frag_buf.len, frame.payload_len, frame.wsc->frag_buf.len + frame.payload_len); wsconn_put(frame.wsc); return -1; } memcpy(frame.wsc->frag_buf.s + frame.wsc->frag_buf.len, frame.payload_data, frame.payload_len); frame.wsc->frag_buf.len += frame.payload_len; frame.wsc->frag_buf.s[frame.wsc->frag_buf.len] = '\0'; if (frame.fin) { ret = receive_msg(frame.wsc->frag_buf.s, frame.wsc->frag_buf.len, tcpinfo->rcv); wsconn_put(frame.wsc); return ret; } wsconn_put(frame.wsc); return 0; } else { LM_ERR("Unsupported fragmented sub-protocol"); wsconn_put(frame.wsc); return -1; } case OPCODE_TEXT_FRAME: case OPCODE_BINARY_FRAME: if (likely(frame.wsc->sub_protocol == SUB_PROTOCOL_SIP)) { LM_DBG("Rx SIP (or text) message:\n%.*s\n", frame.payload_len, frame.payload_data); update_stat(ws_sip_received_frames, 1); if((frame.payload_len==CRLF_LEN && strncmp(frame.payload_data, CRLF, CRLF_LEN)==0) || (frame.payload_len==CRLFCRLF_LEN && strncmp(frame.payload_data, CRLFCRLF, CRLFCRLF_LEN)==0)) { ws_send_crlf(frame.wsc, opcode); wsconn_put(frame.wsc); return 0; } if (frame.fin) { wsconn_put(frame.wsc); return receive_msg(frame.payload_data, frame.payload_len, tcpinfo->rcv); } else { memcpy(frame.wsc->frag_buf.s, frame.payload_data, frame.payload_len); frame.wsc->frag_buf.len = frame.payload_len; frame.wsc->frag_buf.s[frame.wsc->frag_buf.len] = '\0'; wsconn_put(frame.wsc); return 0; } } else if (frame.wsc->sub_protocol == SUB_PROTOCOL_MSRP) { LM_DBG("Rx MSRP frame:\n%.*s\n", frame.payload_len, frame.payload_data); update_stat(ws_msrp_received_frames, 1); if (likely(sr_event_enabled(SREV_TCP_MSRP_FRAME))) { tcp_event_info_t tev; memset(&tev, 0, sizeof(tcp_event_info_t)); tev.type = SREV_TCP_MSRP_FRAME; tev.buf = frame.payload_data; tev.len = frame.payload_len; tev.rcv = tcpinfo->rcv; tev.con = tcpinfo->con; wsconn_put(frame.wsc); return sr_event_exec(SREV_TCP_MSRP_FRAME, (void *) &tev); } else { LM_ERR("no callback registered for MSRP\n"); wsconn_put(frame.wsc); return -1; } } case OPCODE_CLOSE: ret = handle_close(&frame); if (frame.wsc) wsconn_put(frame.wsc); return ret; case OPCODE_PING: ret = handle_ping(&frame); if (frame.wsc) wsconn_put(frame.wsc); return ret; case OPCODE_PONG: ret = handle_pong(&frame); if (frame.wsc) wsconn_put(frame.wsc); return ret; default: LM_WARN("received bad frame\n"); wsconn_put(frame.wsc); return -1; } /* how can we get here ? */ wsconn_put(frame.wsc); return 0; } int ws_frame_transmit(void *data) { ws_event_info_t *wsev = (ws_event_info_t *) data; ws_frame_t frame; memset(&frame, 0, sizeof(frame)); frame.fin = 1; /* Can't be sure whether this message is UTF-8 or not so check to see if it "might" be UTF-8 and send as binary if it definitely isn't */ #ifdef EMBEDDED_UTF8_DECODE frame.opcode = IsUTF8((uint8_t *) wsev->buf, wsev->len) ? OPCODE_TEXT_FRAME : OPCODE_BINARY_FRAME; #else frame.opcode = (u8_check((uint8_t *) wsev->buf, wsev->len) == NULL) ? OPCODE_TEXT_FRAME : OPCODE_BINARY_FRAME; #endif frame.payload_len = wsev->len; frame.payload_data = wsev->buf; frame.wsc = wsconn_get(wsev->id); if (frame.wsc == NULL) { LM_ERR("WebSocket outbound connection not found\n"); return -1; } LM_DBG("Tx message:\n%.*s\n", frame.payload_len, frame.payload_data); if (encode_and_send_ws_frame(&frame, CONN_CLOSE_DONT) < 0) { LM_ERR("sending message\n"); wsconn_put(frame.wsc); return -1; } wsconn_put(frame.wsc); return 0; } static int ping_pong(ws_connection_t *wsc, int opcode) { ws_frame_t frame; memset(&frame, 0, sizeof(frame)); frame.fin = 1; frame.opcode = opcode; frame.payload_len = ws_ping_application_data.len; frame.payload_data = ws_ping_application_data.s; frame.wsc = wsc; if (encode_and_send_ws_frame(&frame, CONN_CLOSE_DONT) < 0) { LM_ERR("sending keepalive\n"); return -1; } if (opcode == OPCODE_PING) wsc->awaiting_pong = 1; return 0; } static int ws_send_crlf(ws_connection_t *wsc, int opcode) { ws_frame_t frame; memset(&frame, 0, sizeof(frame)); frame.fin = 1; frame.opcode = opcode; frame.payload_len = CRLF_LEN; frame.payload_data = CRLF; frame.wsc = wsc; if (encode_and_send_ws_frame(&frame, CONN_CLOSE_DONT) < 0) { LM_ERR("failed sending CRLF\n"); return -1; } return 0; } struct mi_root *ws_mi_close(struct mi_root *cmd, void *param) { unsigned int id; struct mi_node *node = NULL; ws_connection_t *wsc; node = cmd->node.kids; if (node == NULL) { LM_WARN("no connection ID parameter\n"); return init_mi_tree(400, str_status_empty_param.s, str_status_empty_param.len); } if (node->value.s == NULL || node->value.len == 0) { LM_WARN("empty connection ID parameter\n"); return init_mi_tree(400, str_status_empty_param.s, str_status_empty_param.len); } if (str2int(&node->value, &id) < 0) { LM_ERR("converting string to int\n"); return init_mi_tree(400, str_status_string_error.s, str_status_string_error.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); } if ((wsc = wsconn_get(id)) == NULL) { LM_WARN("bad connection ID parameter\n"); return init_mi_tree(400, str_status_bad_param.s, str_status_bad_param.len); } int ret = close_connection(&wsc, LOCAL_CLOSE, 1000, str_status_normal_closure); wsconn_put(wsc); if (ret < 0) { LM_WARN("closing connection\n"); return init_mi_tree(500, str_status_error_closing.s, str_status_error_closing.len); } return init_mi_tree(200, MI_OK_S, MI_OK_LEN); } static struct mi_root *mi_ping_pong(struct mi_root *cmd, void *param, int opcode) { unsigned int id; struct mi_node *node = NULL; ws_connection_t *wsc; node = cmd->node.kids; if (node == NULL) { LM_WARN("no connection ID parameter\n"); return init_mi_tree(400, str_status_empty_param.s, str_status_empty_param.len); } if (node->value.s == NULL || node->value.len == 0) { LM_WARN("empty connection ID parameter\n"); return init_mi_tree(400, str_status_empty_param.s, str_status_empty_param.len); } if (str2int(&node->value, &id) < 0) { LM_ERR("converting string to int\n"); return init_mi_tree(400, str_status_string_error.s, str_status_string_error.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); } if ((wsc = wsconn_get(id)) == NULL) { LM_WARN("bad connection ID parameter\n"); return init_mi_tree(400, str_status_bad_param.s, str_status_bad_param.len); } int ret = ping_pong(wsc, opcode); wsconn_put(wsc); if (ret < 0) { LM_WARN("sending %s\n", OPCODE_PING ? "Ping" : "Pong"); return init_mi_tree(500, str_status_error_sending.s, str_status_error_sending.len); } return init_mi_tree(200, MI_OK_S, MI_OK_LEN); } struct mi_root *ws_mi_ping(struct mi_root *cmd, void *param) { return mi_ping_pong(cmd, param, OPCODE_PING); } struct mi_root *ws_mi_pong(struct mi_root *cmd, void *param) { return mi_ping_pong(cmd, param, OPCODE_PONG); } void ws_keepalive(unsigned int ticks, void *param) { int check_time = (int) time(NULL) - cfg_get(websocket, ws_cfg, keepalive_timeout); ws_connection_t **list = NULL, **list_head = NULL; ws_connection_t *wsc = NULL; /* get an array of pointer to all ws connection */ list_head = wsconn_get_list(); if (!list_head) return; list = list_head; wsc = *list_head; while (wsc && wsc->last_used < check_time) { if (wsc->state == WS_S_CLOSING || wsc->awaiting_pong) { LM_WARN("forcibly closing connection\n"); wsconn_close_now(wsc); } else { int opcode = (ws_keepalive_mechanism == KEEPALIVE_MECHANISM_PING) ? OPCODE_PING : OPCODE_PONG; ping_pong(wsc, opcode); } wsc = *(++list); } wsconn_put_list(list_head); } int ws_close(sip_msg_t *msg) { ws_connection_t *wsc; int ret; if ((wsc = wsconn_get(msg->rcv.proto_reserved1)) == NULL) { LM_ERR("failed to retrieve WebSocket connection\n"); return -1; } ret = (close_connection(&wsc, LOCAL_CLOSE, 1000, str_status_normal_closure) == 0) ? 1: 0; wsconn_put(wsc); return ret; } int ws_close2(sip_msg_t *msg, char *_status, char *_reason) { int status; str reason; ws_connection_t *wsc; int ret; if (get_int_fparam(&status, msg, (fparam_t *) _status) < 0) { LM_ERR("failed to get status code\n"); return -1; } if (get_str_fparam(&reason, msg, (fparam_t *) _reason) < 0) { LM_ERR("failed to get reason string\n"); return -1; } if ((wsc = wsconn_get(msg->rcv.proto_reserved1)) == NULL) { LM_ERR("failed to retrieve WebSocket connection\n"); return -1; } ret = (close_connection(&wsc, LOCAL_CLOSE, status, reason) == 0) ? 1: 0; wsconn_put(wsc); return ret; } int ws_close3(sip_msg_t *msg, char *_status, char *_reason, char *_con) { int status; str reason; int con; ws_connection_t *wsc; int ret; if (get_int_fparam(&status, msg, (fparam_t *) _status) < 0) { LM_ERR("failed to get status code\n"); return -1; } if (get_str_fparam(&reason, msg, (fparam_t *) _reason) < 0) { LM_ERR("failed to get reason string\n"); return -1; } if (get_int_fparam(&con, msg, (fparam_t *) _con) < 0) { LM_ERR("failed to get connection ID\n"); return -1; } if ((wsc = wsconn_get(con)) == NULL) { LM_ERR("failed to retrieve WebSocket connection\n"); return -1; } ret = (close_connection(&wsc, LOCAL_CLOSE, status, reason) == 0) ? 1: 0; wsconn_put(wsc); return ret; }