2012-06-02 21:13:36 +00:00
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2012, Digium, Inc.
*
* Joshua Colp <jcolp@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief WebSocket support for the Asterisk internal HTTP server
*
* \author Joshua Colp <jcolp@digium.com>
*/
/*** MODULEINFO
2016-04-05 12:21:32 -03:00
<support_level>core</support_level>
2012-06-02 21:13:36 +00:00
***/
#include "asterisk.h"
#include "asterisk/module.h"
#include "asterisk/http.h"
#include "asterisk/astobj2.h"
#include "asterisk/strings.h"
#include "asterisk/file.h"
#include "asterisk/unaligned.h"
2014-06-05 17:22:35 +00:00
#include "asterisk/uri.h"
2015-07-31 11:27:23 -05:00
#include "asterisk/uuid.h"
2012-09-27 17:12:08 +00:00
#define AST_API_MODULE
2012-06-02 21:13:36 +00:00
#include "asterisk/http_websocket.h"
/*! \brief GUID used to compute the accept key, defined in the specifications */
#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
2014-06-05 17:22:35 +00:00
/*! \brief Length of a websocket's client key */
#define CLIENT_KEY_SIZE 16
2012-06-02 21:13:36 +00:00
/*! \brief Number of buckets for registered protocols */
#define MAX_PROTOCOL_BUCKETS 7
2016-11-04 15:42:09 -05:00
#ifdef LOW_MEMORY
2012-06-02 21:13:36 +00:00
/*! \brief Size of the pre-determined buffer for WebSocket frames */
2016-11-04 15:42:09 -05:00
#define MAXIMUM_FRAME_SIZE 8192
2012-06-02 21:13:36 +00:00
/*! \brief Default reconstruction size for multi-frame payload reconstruction. If exceeded the next frame will start a
* payload.
*/
2016-11-04 15:42:09 -05:00
#define DEFAULT_RECONSTRUCTION_CEILING 8192
2012-06-02 21:13:36 +00:00
/*! \brief Maximum reconstruction size for multi-frame payload reconstruction. */
2016-11-04 15:42:09 -05:00
#define MAXIMUM_RECONSTRUCTION_CEILING 8192
#else
/*! \brief Size of the pre-determined buffer for WebSocket frames */
#define MAXIMUM_FRAME_SIZE 32768
/*! \brief Default reconstruction size for multi-frame payload reconstruction. If exceeded the next frame will start a
* payload.
*/
#define DEFAULT_RECONSTRUCTION_CEILING 32768
/*! \brief Maximum reconstruction size for multi-frame payload reconstruction. */
#define MAXIMUM_RECONSTRUCTION_CEILING 32768
#endif
2012-06-02 21:13:36 +00:00
2014-03-05 16:22:44 +00:00
/*! \brief Maximum size of a websocket frame header
* 1 byte flags and opcode
* 1 byte mask flag + payload len
* 8 bytes max extended length
* 4 bytes optional masking key
* ... payload follows ...
* */
#define MAX_WS_HDR_SZ 14
#define MIN_WS_HDR_SZ 2
2012-06-02 21:13:36 +00:00
/*! \brief Structure definition for session */
struct ast_websocket {
2017-11-13 13:14:23 +00:00
struct ast_iostream * stream ; /*!< iostream of the connection */
struct ast_sockaddr remote_address ; /*!< Address of the remote client */
struct ast_sockaddr local_address ; /*!< Our local address */
enum ast_websocket_opcode opcode ; /*!< Cached opcode for multi-frame messages */
size_t payload_len ; /*!< Length of the payload */
char * payload ; /*!< Pointer to the payload */
size_t reconstruct ; /*!< Number of bytes before a reconstructed payload will be returned and a new one started */
int timeout ; /*!< The timeout for operations on the socket */
unsigned int secure : 1 ; /*!< Bit to indicate that the transport is secure */
unsigned int closing : 1 ; /*!< Bit to indicate that the session is in the process of being closed */
unsigned int close_sent : 1 ; /*!< Bit to indicate that the session close opcode has been sent and no further data will be sent */
struct websocket_client * client ; /*!< Client object when connected as a client websocket */
char session_id [ AST_UUID_STR_LEN ]; /*!< The identifier for the websocket session */
2019-01-08 08:38:41 +01:00
uint16_t close_status_code ; /*!< Status code sent in a CLOSE frame upon shutdown */
2012-06-02 21:13:36 +00:00
};
/*! \brief Hashing function for protocols */
static int protocol_hash_fn ( const void * obj , const int flags )
{
2015-05-13 09:55:58 -05:00
const struct ast_websocket_protocol * protocol = obj ;
2012-06-02 21:13:36 +00:00
const char * name = obj ;
return ast_str_case_hash ( flags & OBJ_KEY ? name : protocol -> name );
}
/*! \brief Comparison function for protocols */
static int protocol_cmp_fn ( void * obj , void * arg , int flags )
{
2015-05-13 09:55:58 -05:00
const struct ast_websocket_protocol * protocol1 = obj , * protocol2 = arg ;
2012-06-02 21:13:36 +00:00
const char * protocol = arg ;
return ! strcasecmp ( protocol1 -> name , flags & OBJ_KEY ? protocol : protocol2 -> name ) ? CMP_MATCH | CMP_STOP : 0 ;
}
/*! \brief Destructor function for protocols */
static void protocol_destroy_fn ( void * obj )
{
2015-05-13 09:55:58 -05:00
struct ast_websocket_protocol * protocol = obj ;
2012-06-02 21:13:36 +00:00
ast_free ( protocol -> name );
}
2013-04-18 17:30:28 +00:00
/*! \brief Structure for a WebSocket server */
struct ast_websocket_server {
struct ao2_container * protocols ; /*!< Container for registered protocols */
};
2017-12-29 03:57:17 -05:00
static void websocket_server_dtor ( void * obj )
2013-04-18 17:30:28 +00:00
{
struct ast_websocket_server * server = obj ;
ao2_cleanup ( server -> protocols );
server -> protocols = NULL ;
}
2017-12-29 03:57:17 -05:00
static struct ast_websocket_server * websocket_server_create_impl ( void )
2013-04-18 17:30:28 +00:00
{
RAII_VAR ( struct ast_websocket_server * , server , NULL , ao2_cleanup );
2017-12-29 03:57:17 -05:00
server = ao2_alloc ( sizeof ( * server ), websocket_server_dtor );
2013-04-18 17:30:28 +00:00
if ( ! server ) {
return NULL ;
}
2018-11-19 15:10:02 -05:00
server -> protocols = ao2_container_alloc_hash ( AO2_ALLOC_OPT_LOCK_MUTEX , 0 ,
MAX_PROTOCOL_BUCKETS , protocol_hash_fn , NULL , protocol_cmp_fn );
2013-04-18 17:30:28 +00:00
if ( ! server -> protocols ) {
return NULL ;
}
ao2_ref ( server , + 1 );
return server ;
}
2013-08-30 13:40:27 +00:00
static struct ast_websocket_server * websocket_server_internal_create ( void )
{
2017-12-29 03:57:17 -05:00
return websocket_server_create_impl ();
2013-08-30 13:40:27 +00:00
}
struct ast_websocket_server * AST_OPTIONAL_API_NAME ( ast_websocket_server_create )( void )
{
2017-12-29 03:57:17 -05:00
return websocket_server_create_impl ();
2013-08-30 13:40:27 +00:00
}
2012-06-02 21:13:36 +00:00
/*! \brief Destructor function for sessions */
static void session_destroy_fn ( void * obj )
{
struct ast_websocket * session = obj ;
2016-06-02 22:10:06 +03:00
if ( session -> stream ) {
2019-01-08 08:38:41 +01:00
ast_websocket_close ( session , session -> close_status_code );
2016-06-02 22:10:06 +03:00
if ( session -> stream ) {
ast_iostream_close ( session -> stream );
session -> stream = NULL ;
2015-08-13 07:26:51 -03:00
ast_verb ( 2 , "WebSocket connection %s '%s' closed \n " , session -> client ? "to" : "from" ,
2017-11-13 13:14:23 +00:00
ast_sockaddr_stringify ( & session -> remote_address ));
2015-08-13 07:26:51 -03:00
}
2012-06-02 21:13:36 +00:00
}
2014-06-05 17:22:35 +00:00
ao2_cleanup ( session -> client );
2012-06-02 21:13:36 +00:00
ast_free ( session -> payload );
}
2015-05-13 09:55:58 -05:00
struct ast_websocket_protocol * AST_OPTIONAL_API_NAME ( ast_websocket_sub_protocol_alloc )( const char * name )
{
struct ast_websocket_protocol * protocol ;
protocol = ao2_alloc ( sizeof ( * protocol ), protocol_destroy_fn );
if ( ! protocol ) {
return NULL ;
}
protocol -> name = ast_strdup ( name );
if ( ! protocol -> name ) {
ao2_ref ( protocol , - 1 );
return NULL ;
}
protocol -> version = AST_WEBSOCKET_PROTOCOL_VERSION ;
return protocol ;
}
2013-04-18 17:30:28 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_server_add_protocol )( struct ast_websocket_server * server , const char * name , ast_websocket_callback callback )
2012-06-02 21:13:36 +00:00
{
2015-05-13 09:55:58 -05:00
struct ast_websocket_protocol * protocol ;
2012-06-02 21:13:36 +00:00
2013-04-18 17:30:28 +00:00
if ( ! server -> protocols ) {
2013-04-08 15:38:34 +00:00
return - 1 ;
}
2015-05-13 09:55:58 -05:00
protocol = ast_websocket_sub_protocol_alloc ( name );
if ( ! protocol ) {
return - 1 ;
}
protocol -> session_established = callback ;
2012-06-02 21:13:36 +00:00
2015-05-13 09:55:58 -05:00
if ( ast_websocket_server_add_protocol2 ( server , protocol )) {
2012-06-02 21:13:36 +00:00
ao2_ref ( protocol , - 1 );
return - 1 ;
}
2015-05-13 09:55:58 -05:00
return 0 ;
}
int AST_OPTIONAL_API_NAME ( ast_websocket_server_add_protocol2 )( struct ast_websocket_server * server , struct ast_websocket_protocol * protocol )
{
struct ast_websocket_protocol * existing ;
if ( ! server -> protocols ) {
2012-06-02 21:13:36 +00:00
return - 1 ;
}
2015-05-13 09:55:58 -05:00
if ( protocol -> version != AST_WEBSOCKET_PROTOCOL_VERSION ) {
ast_log ( LOG_WARNING , "WebSocket could not register sub-protocol '%s': "
"expected version '%u', got version '%u' \n " ,
protocol -> name , AST_WEBSOCKET_PROTOCOL_VERSION , protocol -> version );
2012-06-02 21:13:36 +00:00
return - 1 ;
}
2015-05-13 09:55:58 -05:00
ao2_lock ( server -> protocols );
/* Ensure a second protocol handler is not registered for the same protocol */
existing = ao2_find ( server -> protocols , protocol -> name , OBJ_KEY | OBJ_NOLOCK );
if ( existing ) {
ao2_ref ( existing , - 1 );
ao2_unlock ( server -> protocols );
return - 1 ;
}
2012-06-02 21:13:36 +00:00
2013-04-18 17:30:28 +00:00
ao2_link_flags ( server -> protocols , protocol , OBJ_NOLOCK );
ao2_unlock ( server -> protocols );
2012-06-02 21:13:36 +00:00
2015-05-13 09:55:58 -05:00
ast_verb ( 2 , "WebSocket registered sub-protocol '%s' \n " , protocol -> name );
ao2_ref ( protocol , - 1 );
2012-06-02 21:13:36 +00:00
return 0 ;
}
2013-04-18 17:30:28 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_server_remove_protocol )( struct ast_websocket_server * server , const char * name , ast_websocket_callback callback )
2012-06-02 21:13:36 +00:00
{
2015-05-13 09:55:58 -05:00
struct ast_websocket_protocol * protocol ;
2012-06-02 21:13:36 +00:00
2013-04-18 17:30:28 +00:00
if ( ! ( protocol = ao2_find ( server -> protocols , name , OBJ_KEY ))) {
2012-06-02 21:13:36 +00:00
return - 1 ;
}
2015-05-13 09:55:58 -05:00
if ( protocol -> session_established != callback ) {
2012-06-02 21:13:36 +00:00
ao2_ref ( protocol , - 1 );
return - 1 ;
}
2013-04-18 17:30:28 +00:00
ao2_unlink ( server -> protocols , protocol );
2012-06-02 21:13:36 +00:00
ao2_ref ( protocol , - 1 );
ast_verb ( 2 , "WebSocket unregistered sub-protocol '%s' \n " , name );
return 0 ;
}
/*! \brief Close function for websocket session */
2012-09-27 17:12:08 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_close )( struct ast_websocket * session , uint16_t reason )
2012-06-02 21:13:36 +00:00
{
2018-01-06 08:25:14 +01:00
enum ast_websocket_opcode opcode = AST_WEBSOCKET_OPCODE_CLOSE ;
2012-06-02 21:13:36 +00:00
char frame [ 4 ] = { 0 , }; /* The header is 2 bytes and the reason code takes up another 2 bytes */
2014-04-30 13:08:07 +00:00
int res ;
if ( session -> close_sent ) {
return 0 ;
}
2012-06-02 21:13:36 +00:00
2018-01-06 08:25:14 +01:00
frame [ 0 ] = opcode | 0x80 ;
2012-06-02 21:13:36 +00:00
frame [ 1 ] = 2 ; /* The reason code is always 2 bytes */
/* If no reason has been specified assume 1000 which is normal closure */
put_unaligned_uint16 ( & frame [ 2 ], htons ( reason ? reason : 1000 ));
session -> closing = 1 ;
2014-04-30 13:08:07 +00:00
session -> close_sent = 1 ;
2012-06-02 21:13:36 +00:00
2014-04-30 13:08:07 +00:00
ao2_lock ( session );
2016-06-02 22:10:06 +03:00
ast_iostream_set_timeout_inactivity ( session -> stream , session -> timeout );
res = ast_iostream_write ( session -> stream , frame , sizeof ( frame ));
ast_iostream_set_timeout_disable ( session -> stream );
2015-08-11 07:24:30 -03:00
/* If an error occurred when trying to close this connection explicitly terminate it now.
* Doing so will cause the thread polling on it to wake up and terminate.
*/
2016-06-02 22:10:06 +03:00
if ( res != sizeof ( frame )) {
ast_iostream_close ( session -> stream );
session -> stream = NULL ;
2015-08-11 07:24:30 -03:00
ast_verb ( 2 , "WebSocket connection %s '%s' forcefully closed due to fatal write error \n " ,
2017-11-13 13:14:23 +00:00
session -> client ? "to" : "from" , ast_sockaddr_stringify ( & session -> remote_address ));
2015-08-11 07:24:30 -03:00
}
2014-04-30 13:08:07 +00:00
ao2_unlock ( session );
2016-06-02 22:10:06 +03:00
return res == sizeof ( frame );
2012-06-02 21:13:36 +00:00
}
2015-08-04 09:47:34 -05:00
static const char * opcode_map [] = {
[ AST_WEBSOCKET_OPCODE_CONTINUATION ] = "continuation" ,
[ AST_WEBSOCKET_OPCODE_TEXT ] = "text" ,
[ AST_WEBSOCKET_OPCODE_BINARY ] = "binary" ,
[ AST_WEBSOCKET_OPCODE_CLOSE ] = "close" ,
[ AST_WEBSOCKET_OPCODE_PING ] = "ping" ,
[ AST_WEBSOCKET_OPCODE_PONG ] = "pong" ,
};
static const char * websocket_opcode2str ( enum ast_websocket_opcode opcode )
{
if ( opcode < AST_WEBSOCKET_OPCODE_CONTINUATION ||
opcode > AST_WEBSOCKET_OPCODE_PONG ) {
return "<unknown>" ;
} else {
return opcode_map [ opcode ];
}
}
2012-06-02 21:13:36 +00:00
/*! \brief Write function for websocket traffic */
2015-12-24 20:19:59 -08:00
int AST_OPTIONAL_API_NAME ( ast_websocket_write )( struct ast_websocket * session , enum ast_websocket_opcode opcode , char * payload , uint64_t payload_size )
2012-06-02 21:13:36 +00:00
{
size_t header_size = 2 ; /* The minimum size of a websocket frame is 2 bytes */
char * frame ;
2014-12-19 20:56:12 +00:00
uint64_t length ;
2015-12-24 20:19:59 -08:00
uint64_t frame_size ;
2012-06-02 21:13:36 +00:00
2015-08-04 09:47:34 -05:00
ast_debug ( 3 , "Writing websocket %s frame, length %" PRIu64 " \n " ,
2015-12-24 20:19:59 -08:00
websocket_opcode2str ( opcode ), payload_size );
2015-08-04 09:47:34 -05:00
2015-12-24 20:19:59 -08:00
if ( payload_size < 126 ) {
length = payload_size ;
} else if ( payload_size < ( 1 << 16 )) {
2012-06-02 21:13:36 +00:00
length = 126 ;
/* We need an additional 2 bytes to store the extended length */
header_size += 2 ;
} else {
length = 127 ;
/* We need an additional 8 bytes to store the really really extended length */
header_size += 8 ;
}
2015-12-24 20:19:59 -08:00
frame_size = header_size + payload_size ;
frame = ast_alloca ( frame_size + 1 );
memset ( frame , 0 , frame_size + 1 );
2012-06-02 21:13:36 +00:00
frame [ 0 ] = opcode | 0x80 ;
frame [ 1 ] = length ;
/* Use the additional available bytes to store the length */
if ( length == 126 ) {
2015-12-24 20:19:59 -08:00
put_unaligned_uint16 ( & frame [ 2 ], htons ( payload_size ));
2012-06-02 21:13:36 +00:00
} else if ( length == 127 ) {
2015-12-24 20:19:59 -08:00
put_unaligned_uint64 ( & frame [ 2 ], htonll ( payload_size ));
2012-06-02 21:13:36 +00:00
}
2015-12-24 20:19:59 -08:00
memcpy ( & frame [ header_size ], payload , payload_size );
2014-04-30 13:08:07 +00:00
ao2_lock ( session );
if ( session -> closing ) {
ao2_unlock ( session );
return - 1 ;
}
2012-06-02 21:13:36 +00:00
2016-06-02 22:10:06 +03:00
ast_iostream_set_timeout_sequence ( session -> stream , ast_tvnow (), session -> timeout );
if ( ast_iostream_write ( session -> stream , frame , frame_size ) != frame_size ) {
2014-04-30 13:08:07 +00:00
ao2_unlock ( session );
2015-02-11 16:52:55 +00:00
/* 1011 - server terminating connection due to not being able to fulfill the request */
2015-12-24 20:19:59 -08:00
ast_debug ( 1 , "Closing WS with 1011 because we can't fulfill a write request \n " );
2015-02-11 16:52:55 +00:00
ast_websocket_close ( session , 1011 );
2012-06-02 21:13:36 +00:00
return - 1 ;
}
2015-12-24 20:19:59 -08:00
2016-06-02 22:10:06 +03:00
ast_iostream_set_timeout_disable ( session -> stream );
2014-04-30 13:08:07 +00:00
ao2_unlock ( session );
2012-06-02 21:13:36 +00:00
return 0 ;
}
2012-09-27 17:12:08 +00:00
void AST_OPTIONAL_API_NAME ( ast_websocket_reconstruct_enable )( struct ast_websocket * session , size_t bytes )
2012-06-02 21:13:36 +00:00
{
session -> reconstruct = MIN ( bytes , MAXIMUM_RECONSTRUCTION_CEILING );
}
2012-09-27 17:12:08 +00:00
void AST_OPTIONAL_API_NAME ( ast_websocket_reconstruct_disable )( struct ast_websocket * session )
2012-06-02 21:13:36 +00:00
{
session -> reconstruct = 0 ;
}
2012-09-27 17:12:08 +00:00
void AST_OPTIONAL_API_NAME ( ast_websocket_ref )( struct ast_websocket * session )
2012-06-02 21:13:36 +00:00
{
ao2_ref ( session , + 1 );
}
2012-09-27 17:12:08 +00:00
void AST_OPTIONAL_API_NAME ( ast_websocket_unref )( struct ast_websocket * session )
2012-06-02 21:13:36 +00:00
{
2013-07-03 16:32:00 +00:00
ao2_cleanup ( session );
2012-06-02 21:13:36 +00:00
}
2012-09-27 17:12:08 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_fd )( struct ast_websocket * session )
2012-06-02 21:13:36 +00:00
{
2016-06-02 22:10:06 +03:00
return session -> closing ? - 1 : ast_iostream_get_fd ( session -> stream );
2012-06-02 21:13:36 +00:00
}
2012-09-27 17:12:08 +00:00
struct ast_sockaddr * AST_OPTIONAL_API_NAME ( ast_websocket_remote_address )( struct ast_websocket * session )
2012-06-02 21:13:36 +00:00
{
2017-11-13 13:14:23 +00:00
return & session -> remote_address ;
}
struct ast_sockaddr * AST_OPTIONAL_API_NAME ( ast_websocket_local_address )( struct ast_websocket * session )
{
return & session -> local_address ;
2012-06-02 21:13:36 +00:00
}
2012-09-27 17:12:08 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_is_secure )( struct ast_websocket * session )
2012-06-02 21:13:36 +00:00
{
return session -> secure ;
}
2012-09-27 17:12:08 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_set_nonblock )( struct ast_websocket * session )
2012-07-16 12:35:04 +00:00
{
2016-06-02 22:10:06 +03:00
ast_iostream_nonblock ( session -> stream );
ast_iostream_set_exclusive_input ( session -> stream , 0 );
2012-07-16 12:35:04 +00:00
return 0 ;
}
2014-06-26 12:21:14 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_set_timeout )( struct ast_websocket * session , int timeout )
{
session -> timeout = timeout ;
return 0 ;
}
2015-07-31 11:27:23 -05:00
const char * AST_OPTIONAL_API_NAME ( ast_websocket_session_id )( struct ast_websocket * session )
{
return session -> session_id ;
}
2014-03-05 16:22:44 +00:00
/* MAINTENANCE WARNING on ast_websocket_read()!
*
* We have to keep in mind during this function that the fact that session->fd seems ready
* (via poll) does not necessarily mean we have application data ready, because in the case
* of an SSL socket, there is some encryption data overhead that needs to be read from the
* TCP socket, so poll() may say there are bytes to be read, but whether it is just 1 byte
* or N bytes we do not know that, and we do not know how many of those bytes (if any) are
* for application data (for us) and not just for the SSL protocol consumption
*
* There used to be a couple of nasty bugs here that were fixed in last refactoring but I
* want to document them so the constraints are clear and we do not re-introduce them:
*
* - This function would incorrectly assume that fread() would necessarily return more than
* 1 byte of data, just because a websocket frame is always >= 2 bytes, but the thing
* is we're dealing with a TCP bitstream here, we could read just one byte and that's normal.
* The problem before was that if just one byte was read, the function bailed out and returned
* an error, effectively dropping the first byte of a websocket frame header!
*
* - Another subtle bug was that it would just read up to MAX_WS_HDR_SZ (14 bytes) via fread()
* then assume that executing poll() would tell you if there is more to read, but since
* we're dealing with a buffered stream (session->f is a FILE*), poll would say there is
* nothing else to read (in the real tcp socket session->fd) and we would get stuck here
* without processing the rest of the data in session->f internal buffers until another packet
* came on the network to unblock us!
*
* Note during the header parsing stage we try to read in small chunks just what we need, this
* is buffered data anyways, no expensive syscall required most of the time ...
*/
2018-02-05 17:46:26 -05:00
static inline int ws_safe_read ( struct ast_websocket * session , char * buf , size_t len , enum ast_websocket_opcode * opcode )
2014-03-05 16:22:44 +00:00
{
2016-12-08 18:34:28 +00:00
ssize_t rlen ;
2014-03-05 16:22:44 +00:00
int xlen = len ;
char * rbuf = buf ;
2015-02-25 20:47:39 +00:00
int sanity = 10 ;
2018-02-05 17:46:26 -05:00
ast_assert ( len > 0 );
if ( ! len ) {
errno = EINVAL ;
return - 1 ;
}
2015-08-11 07:24:30 -03:00
ao2_lock ( session );
2016-06-02 22:10:06 +03:00
if ( ! session -> stream ) {
2015-08-11 07:24:30 -03:00
ao2_unlock ( session );
errno = ECONNABORTED ;
return - 1 ;
}
2015-02-25 20:47:39 +00:00
for (;;) {
2016-06-02 22:10:06 +03:00
rlen = ast_iostream_read ( session -> stream , rbuf , xlen );
if ( rlen != xlen ) {
if ( rlen == 0 ) {
2015-02-25 20:47:39 +00:00
ast_log ( LOG_WARNING , "Web socket closed abruptly \n " );
* opcode = AST_WEBSOCKET_OPCODE_CLOSE ;
session -> closing = 1 ;
2015-08-11 07:24:30 -03:00
ao2_unlock ( session );
2015-02-25 20:47:39 +00:00
return - 1 ;
}
2016-06-02 22:10:06 +03:00
if ( rlen < 0 && errno != EAGAIN ) {
2015-02-25 20:47:39 +00:00
ast_log ( LOG_ERROR , "Error reading from web socket: %s \n " , strerror ( errno ));
* opcode = AST_WEBSOCKET_OPCODE_CLOSE ;
session -> closing = 1 ;
2015-08-11 07:24:30 -03:00
ao2_unlock ( session );
2015-02-25 20:47:39 +00:00
return - 1 ;
}
if ( !-- sanity ) {
ast_log ( LOG_WARNING , "Websocket seems unresponsive, disconnecting ... \n " );
* opcode = AST_WEBSOCKET_OPCODE_CLOSE ;
session -> closing = 1 ;
2015-08-11 07:24:30 -03:00
ao2_unlock ( session );
2015-02-25 20:47:39 +00:00
return - 1 ;
}
2014-03-05 16:22:44 +00:00
}
2017-03-07 13:38:17 -06:00
if ( rlen > 0 ) {
xlen = xlen - rlen ;
rbuf = rbuf + rlen ;
if ( ! xlen ) {
break ;
}
2014-03-05 16:22:44 +00:00
}
2016-06-02 22:10:06 +03:00
if ( ast_wait_for_input ( ast_iostream_get_fd ( session -> stream ), 1000 ) < 0 ) {
2014-03-05 16:22:44 +00:00
ast_log ( LOG_ERROR , "ast_wait_for_input returned err: %s \n " , strerror ( errno ));
2014-10-27 02:47:03 +00:00
* opcode = AST_WEBSOCKET_OPCODE_CLOSE ;
2014-03-05 16:22:44 +00:00
session -> closing = 1 ;
2015-08-11 07:24:30 -03:00
ao2_unlock ( session );
2014-03-05 16:22:44 +00:00
return - 1 ;
}
}
2015-08-11 07:24:30 -03:00
ao2_unlock ( session );
2014-03-05 16:22:44 +00:00
return 0 ;
}
2012-09-27 17:12:08 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_read )( struct ast_websocket * session , char ** payload , uint64_t * payload_len , enum ast_websocket_opcode * opcode , int * fragmented )
2012-06-02 21:13:36 +00:00
{
char buf [ MAXIMUM_FRAME_SIZE ] = "" ;
2014-03-05 16:22:44 +00:00
int fin = 0 ;
int mask_present = 0 ;
char * mask = NULL , * new_payload = NULL ;
size_t options_len = 0 , frame_size = 0 ;
2012-06-02 21:13:36 +00:00
* payload = NULL ;
* payload_len = 0 ;
* fragmented = 0 ;
2014-03-05 16:22:44 +00:00
if ( ws_safe_read ( session , & buf [ 0 ], MIN_WS_HDR_SZ , opcode )) {
2015-08-11 07:24:30 -03:00
return - 1 ;
2012-06-02 21:13:36 +00:00
}
2014-03-05 16:22:44 +00:00
frame_size += MIN_WS_HDR_SZ ;
2012-06-02 21:13:36 +00:00
2014-03-05 16:22:44 +00:00
/* ok, now we have the first 2 bytes, so we know some flags, opcode and payload length (or whether payload length extension will be required) */
2012-06-02 21:13:36 +00:00
* opcode = buf [ 0 ] & 0xf ;
2014-03-05 16:22:44 +00:00
* payload_len = buf [ 1 ] & 0x7f ;
2012-06-02 21:13:36 +00:00
if ( * opcode == AST_WEBSOCKET_OPCODE_TEXT || * opcode == AST_WEBSOCKET_OPCODE_BINARY || * opcode == AST_WEBSOCKET_OPCODE_CONTINUATION ||
2019-01-08 08:38:41 +01:00
* opcode == AST_WEBSOCKET_OPCODE_PING || * opcode == AST_WEBSOCKET_OPCODE_PONG || * opcode == AST_WEBSOCKET_OPCODE_CLOSE ) {
2014-03-05 16:22:44 +00:00
fin = ( buf [ 0 ] >> 7 ) & 1 ;
mask_present = ( buf [ 1 ] >> 7 ) & 1 ;
/* Based on the mask flag and payload length, determine how much more we need to read before start parsing the rest of the header */
options_len += mask_present ? 4 : 0 ;
options_len += ( * payload_len == 126 ) ? 2 : ( * payload_len == 127 ) ? 8 : 0 ;
if ( options_len ) {
/* read the rest of the header options */
if ( ws_safe_read ( session , & buf [ frame_size ], options_len , opcode )) {
2015-08-11 07:24:30 -03:00
return - 1 ;
2012-06-02 21:13:36 +00:00
}
2014-03-05 16:22:44 +00:00
frame_size += options_len ;
2012-06-02 21:13:36 +00:00
}
if ( * payload_len == 126 ) {
2014-03-05 16:22:44 +00:00
/* Grab the 2-byte payload length */
2012-06-02 21:13:36 +00:00
* payload_len = ntohs ( get_unaligned_uint16 ( & buf [ 2 ]));
2014-03-05 16:22:44 +00:00
mask = & buf [ 4 ];
2012-06-02 21:13:36 +00:00
} else if ( * payload_len == 127 ) {
2014-03-05 16:22:44 +00:00
/* Grab the 8-byte payload length */
2012-06-02 21:13:36 +00:00
* payload_len = ntohl ( get_unaligned_uint64 ( & buf [ 2 ]));
2014-03-05 16:22:44 +00:00
mask = & buf [ 10 ];
} else {
/* Just set the mask after the small 2-byte header */
mask = & buf [ 2 ];
2012-06-02 21:13:36 +00:00
}
2014-03-05 16:22:44 +00:00
/* Now read the rest of the payload */
* payload = & buf [ frame_size ]; /* payload will start here, at the end of the options, if any */
frame_size = frame_size + ( * payload_len ); /* final frame size is header + optional headers + payload data */
if ( frame_size > MAXIMUM_FRAME_SIZE ) {
2014-05-09 22:49:26 +00:00
ast_log ( LOG_WARNING , "Cannot fit huge websocket frame of %zu bytes \n " , frame_size );
2014-03-05 16:22:44 +00:00
/* The frame won't fit :-( */
2012-06-02 21:13:36 +00:00
ast_websocket_close ( session , 1009 );
2014-03-05 16:22:44 +00:00
return - 1 ;
2012-06-02 21:13:36 +00:00
}
2018-02-05 17:46:26 -05:00
if ( * payload_len ) {
if ( ws_safe_read ( session , * payload , * payload_len , opcode )) {
return - 1 ;
}
2012-06-02 21:13:36 +00:00
}
2018-02-05 17:46:26 -05:00
2012-06-02 21:13:36 +00:00
/* If a mask is present unmask the payload */
if ( mask_present ) {
unsigned int pos ;
for ( pos = 0 ; pos < * payload_len ; pos ++ ) {
( * payload )[ pos ] ^= mask [ pos % 4 ];
}
}
/* Per the RFC for PING we need to send back an opcode with the application data as received */
2019-01-23 11:45:56 +01:00
if ( * opcode == AST_WEBSOCKET_OPCODE_PING ) {
if ( ast_websocket_write ( session , AST_WEBSOCKET_OPCODE_PONG , * payload , * payload_len )) {
ast_websocket_close ( session , 1009 );
}
* payload_len = 0 ;
return 0 ;
}
/* Stop PONG processing here */
if ( * opcode == AST_WEBSOCKET_OPCODE_PONG ) {
2014-06-26 12:21:14 +00:00
* payload_len = 0 ;
return 0 ;
2012-06-02 21:13:36 +00:00
}
2019-01-08 08:38:41 +01:00
/* Save the CLOSE status code which will be sent in our own CLOSE in the destructor */
if ( * opcode == AST_WEBSOCKET_OPCODE_CLOSE ) {
session -> closing = 1 ;
if ( * payload_len >= 2 ) {
session -> close_status_code = ntohs ( get_unaligned_uint16 ( * payload ));
}
* payload_len = 0 ;
return 0 ;
}
2019-01-23 11:45:56 +01:00
/* Below this point we are handling TEXT, BINARY or CONTINUATION opcodes */
2014-12-10 13:35:52 +00:00
if ( * payload_len ) {
if ( ! ( new_payload = ast_realloc ( session -> payload , ( session -> payload_len + * payload_len )))) {
ast_log ( LOG_WARNING , "Failed allocation: %p, %zu, %" PRIu64 " \n " ,
session -> payload , session -> payload_len , * payload_len );
* payload_len = 0 ;
ast_websocket_close ( session , 1009 );
2015-08-11 07:24:30 -03:00
return - 1 ;
2014-12-10 13:35:52 +00:00
}
session -> payload = new_payload ;
memcpy (( session -> payload + session -> payload_len ), ( * payload ), ( * payload_len ));
session -> payload_len += * payload_len ;
} else if ( ! session -> payload_len && session -> payload ) {
ast_free ( session -> payload );
session -> payload = NULL ;
}
2012-06-02 21:13:36 +00:00
if ( ! fin && session -> reconstruct && ( session -> payload_len < session -> reconstruct )) {
/* If this is not a final message we need to defer returning it until later */
if ( * opcode != AST_WEBSOCKET_OPCODE_CONTINUATION ) {
session -> opcode = * opcode ;
}
* opcode = AST_WEBSOCKET_OPCODE_CONTINUATION ;
* payload_len = 0 ;
* payload = NULL ;
} else {
if ( * opcode == AST_WEBSOCKET_OPCODE_CONTINUATION ) {
if ( ! fin ) {
/* If this was not actually the final message tell the user it is fragmented so they can deal with it accordingly */
* fragmented = 1 ;
} else {
/* Final frame in multi-frame so push up the actual opcode */
* opcode = session -> opcode ;
}
}
* payload_len = session -> payload_len ;
* payload = session -> payload ;
session -> payload_len = 0 ;
}
} else {
2014-05-09 22:49:26 +00:00
ast_log ( LOG_WARNING , "WebSocket unknown opcode %u \n " , * opcode );
2012-06-02 21:13:36 +00:00
/* We received an opcode that we don't understand, the RFC states that 1003 is for a type of data that can't be accepted... opcodes
* fit that, I think. */
ast_websocket_close ( session , 1003 );
}
return 0 ;
}
2013-07-03 16:32:00 +00:00
/*!
* \brief If the server has exactly one configured protocol, return it.
*/
2015-05-13 09:55:58 -05:00
static struct ast_websocket_protocol * one_protocol (
2013-07-03 16:32:00 +00:00
struct ast_websocket_server * server )
{
SCOPED_AO2LOCK ( lock , server -> protocols );
if ( ao2_container_count ( server -> protocols ) != 1 ) {
return NULL ;
}
return ao2_callback ( server -> protocols , OBJ_NOLOCK , NULL , NULL );
}
2014-06-05 17:22:35 +00:00
static char * websocket_combine_key ( const char * key , char * res , int res_size )
{
char * combined ;
unsigned combined_length = strlen ( key ) + strlen ( WEBSOCKET_GUID ) + 1 ;
uint8_t sha [ 20 ];
combined = ast_alloca ( combined_length );
snprintf ( combined , combined_length , "%s%s" , key , WEBSOCKET_GUID );
ast_sha1_hash_uint ( sha , combined );
ast_base64encode ( res , ( const unsigned char * ) sha , 20 , res_size );
return res ;
}
2014-07-03 17:16:55 +00:00
static void websocket_bad_request ( struct ast_tcptls_session_instance * ser )
{
struct ast_str * http_header = ast_str_create ( 64 );
if ( ! http_header ) {
ast_http_request_close_on_completion ( ser );
ast_http_error ( ser , 500 , "Server Error" , "Out of memory" );
return ;
}
ast_str_set ( & http_header , 0 , "Sec-WebSocket-Version: 7, 8, 13 \r\n " );
ast_http_send ( ser , AST_HTTP_UNKNOWN , 400 , "Bad Request" , http_header , NULL , 0 , 0 );
}
2013-08-30 13:40:27 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_uri_cb )( struct ast_tcptls_session_instance * ser , const struct ast_http_uri * urih , const char * uri , enum ast_http_method method , struct ast_variable * get_vars , struct ast_variable * headers )
2012-06-02 21:13:36 +00:00
{
struct ast_variable * v ;
2018-08-16 11:45:53 -04:00
const char * upgrade = NULL , * key = NULL , * key1 = NULL , * key2 = NULL , * protos = NULL ;
char * requested_protocols = NULL , * protocol = NULL ;
2012-06-02 21:13:36 +00:00
int version = 0 , flags = 1 ;
2015-05-13 09:55:58 -05:00
struct ast_websocket_protocol * protocol_handler = NULL ;
2012-06-02 21:13:36 +00:00
struct ast_websocket * session ;
2013-04-18 17:30:28 +00:00
struct ast_websocket_server * server ;
2012-06-02 21:13:36 +00:00
2013-08-30 13:40:27 +00:00
SCOPED_MODULE_USE ( ast_module_info -> self );
2012-06-02 21:13:36 +00:00
/* Upgrade requests are only permitted on GET methods */
if ( method != AST_HTTP_GET ) {
ast_http_error ( ser , 501 , "Not Implemented" , "Attempt to use unimplemented / unsupported method" );
2014-07-03 17:16:55 +00:00
return 0 ;
2012-06-02 21:13:36 +00:00
}
2013-04-18 17:30:28 +00:00
server = urih -> data ;
2012-06-02 21:13:36 +00:00
/* Get the minimum headers required to satisfy our needs */
for ( v = headers ; v ; v = v -> next ) {
if ( ! strcasecmp ( v -> name , "Upgrade" )) {
2018-08-16 11:45:53 -04:00
upgrade = v -> value ;
2012-06-02 21:13:36 +00:00
} else if ( ! strcasecmp ( v -> name , "Sec-WebSocket-Key" )) {
2018-08-16 11:45:53 -04:00
key = v -> value ;
2012-06-02 21:13:36 +00:00
} else if ( ! strcasecmp ( v -> name , "Sec-WebSocket-Key1" )) {
2018-08-16 11:45:53 -04:00
key1 = v -> value ;
2012-06-02 21:13:36 +00:00
} else if ( ! strcasecmp ( v -> name , "Sec-WebSocket-Key2" )) {
2018-08-16 11:45:53 -04:00
key2 = v -> value ;
2012-06-02 21:13:36 +00:00
} else if ( ! strcasecmp ( v -> name , "Sec-WebSocket-Protocol" )) {
2018-08-16 11:45:53 -04:00
protos = v -> value ;
2012-06-02 21:13:36 +00:00
} else if ( ! strcasecmp ( v -> name , "Sec-WebSocket-Version" )) {
if ( sscanf ( v -> value , "%30d" , & version ) != 1 ) {
version = 0 ;
}
}
}
/* If this is not a websocket upgrade abort */
if ( ! upgrade || strcasecmp ( upgrade , "websocket" )) {
2012-11-20 22:06:05 +00:00
ast_log ( LOG_WARNING , "WebSocket connection from '%s' could not be accepted - did not request WebSocket \n " ,
2012-06-02 21:13:36 +00:00
ast_sockaddr_stringify ( & ser -> remote_address ));
ast_http_error ( ser , 426 , "Upgrade Required" , NULL );
2014-07-03 17:16:55 +00:00
return 0 ;
2018-08-16 11:45:53 -04:00
} else if ( ast_strlen_zero ( protos )) {
2013-07-03 16:32:00 +00:00
/* If there's only a single protocol registered, and the
* client doesn't specify what protocol it's using, go ahead
* and accept the connection */
protocol_handler = one_protocol ( server );
if ( ! protocol_handler ) {
/* Multiple registered subprotocols; client must specify */
ast_log ( LOG_WARNING , "WebSocket connection from '%s' could not be accepted - no protocols requested \n " ,
ast_sockaddr_stringify ( & ser -> remote_address ));
2014-07-03 17:16:55 +00:00
websocket_bad_request ( ser );
return 0 ;
2013-07-03 16:32:00 +00:00
}
2012-06-02 21:13:36 +00:00
} else if ( key1 && key2 ) {
/* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 and
* http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 -- not currently supported*/
2012-11-20 22:06:05 +00:00
ast_log ( LOG_WARNING , "WebSocket connection from '%s' could not be accepted - unsupported version '00/76' chosen \n " ,
2012-06-02 21:13:36 +00:00
ast_sockaddr_stringify ( & ser -> remote_address ));
2014-07-03 17:16:55 +00:00
websocket_bad_request ( ser );
2012-06-02 21:13:36 +00:00
return 0 ;
}
2018-08-16 11:45:53 -04:00
if ( ! protocol_handler && protos ) {
requested_protocols = ast_strdupa ( protos );
/* Iterate through the requested protocols trying to find one that we have a handler for */
while ( ! protocol_handler && ( protocol = strsep ( & requested_protocols , "," ))) {
protocol_handler = ao2_find ( server -> protocols , ast_strip ( protocol ), OBJ_KEY );
}
2012-06-02 21:13:36 +00:00
}
/* If no protocol handler exists bump this back to the requester */
if ( ! protocol_handler ) {
ast_log ( LOG_WARNING , "WebSocket connection from '%s' could not be accepted - no protocols out of '%s' supported \n " ,
ast_sockaddr_stringify ( & ser -> remote_address ), protos );
2014-07-03 17:16:55 +00:00
websocket_bad_request ( ser );
2012-06-02 21:13:36 +00:00
return 0 ;
}
/* Determine how to respond depending on the version */
if ( version == 7 || version == 8 || version == 13 ) {
2014-06-05 17:22:35 +00:00
char base64 [ 64 ];
if ( ! key || strlen ( key ) + strlen ( WEBSOCKET_GUID ) + 1 > 8192 ) { /* no stack overflows please */
2014-07-03 17:16:55 +00:00
websocket_bad_request ( ser );
ao2_ref ( protocol_handler , - 1 );
return 0 ;
}
if ( ast_http_body_discard ( ser )) {
websocket_bad_request ( ser );
2013-06-12 21:08:40 +00:00
ao2_ref ( protocol_handler , - 1 );
return 0 ;
}
2015-07-31 11:27:23 -05:00
if ( ! ( session = ao2_alloc ( sizeof ( * session ) + AST_UUID_STR_LEN + 1 , session_destroy_fn ))) {
2012-11-20 22:06:05 +00:00
ast_log ( LOG_WARNING , "WebSocket connection from '%s' could not be accepted \n " ,
2012-06-02 21:13:36 +00:00
ast_sockaddr_stringify ( & ser -> remote_address ));
2014-07-03 17:16:55 +00:00
websocket_bad_request ( ser );
2012-06-02 21:13:36 +00:00
ao2_ref ( protocol_handler , - 1 );
return 0 ;
}
2016-06-02 22:10:06 +03:00
session -> timeout = AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT ;
2012-06-02 21:13:36 +00:00
2015-07-31 11:27:23 -05:00
/* Generate the session id */
if ( ! ast_uuid_generate_str ( session -> session_id , sizeof ( session -> session_id ))) {
ast_log ( LOG_WARNING , "WebSocket connection from '%s' could not be accepted - failed to generate a session id \n " ,
ast_sockaddr_stringify ( & ser -> remote_address ));
ast_http_error ( ser , 500 , "Internal Server Error" , "Allocation failed" );
2015-08-18 14:24:17 -05:00
ao2_ref ( protocol_handler , - 1 );
2015-07-31 11:27:23 -05:00
return 0 ;
}
2015-05-13 09:55:58 -05:00
if ( protocol_handler -> session_attempted
2015-07-31 11:27:23 -05:00
&& protocol_handler -> session_attempted ( ser , get_vars , headers , session -> session_id )) {
2015-05-13 09:55:58 -05:00
ast_debug ( 3 , "WebSocket connection from '%s' rejected by protocol handler '%s' \n " ,
ast_sockaddr_stringify ( & ser -> remote_address ), protocol_handler -> name );
2015-08-18 16:06:54 -05:00
websocket_bad_request ( ser );
2015-05-13 09:55:58 -05:00
ao2_ref ( protocol_handler , - 1 );
return 0 ;
}
2013-09-13 14:19:19 +00:00
/* RFC 6455, Section 4.1:
*
* 6. If the response includes a |Sec-WebSocket-Protocol| header
* field and this header field indicates the use of a
* subprotocol that was not present in the client's handshake
* (the server has indicated a subprotocol not requested by
* the client), the client MUST _Fail the WebSocket
* Connection_.
*/
if ( protocol ) {
2016-06-02 22:10:06 +03:00
ast_iostream_printf ( ser -> stream ,
"HTTP/1.1 101 Switching Protocols \r\n "
2015-07-04 20:22:01 -03:00
"Upgrade: %s \r\n "
"Connection: Upgrade \r\n "
"Sec-WebSocket-Accept: %s \r\n "
"Sec-WebSocket-Protocol: %s \r\n\r\n " ,
upgrade ,
websocket_combine_key ( key , base64 , sizeof ( base64 )),
2013-09-13 14:19:19 +00:00
protocol );
2015-07-04 20:22:01 -03:00
} else {
2016-06-02 22:10:06 +03:00
ast_iostream_printf ( ser -> stream ,
"HTTP/1.1 101 Switching Protocols \r\n "
2015-07-04 20:22:01 -03:00
"Upgrade: %s \r\n "
"Connection: Upgrade \r\n "
"Sec-WebSocket-Accept: %s \r\n\r\n " ,
upgrade ,
websocket_combine_key ( key , base64 , sizeof ( base64 )));
2013-09-13 14:19:19 +00:00
}
2012-06-02 21:13:36 +00:00
} else {
/* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 or completely unknown */
2012-11-20 22:06:05 +00:00
ast_log ( LOG_WARNING , "WebSocket connection from '%s' could not be accepted - unsupported version '%d' chosen \n " ,
2012-06-02 21:13:36 +00:00
ast_sockaddr_stringify ( & ser -> remote_address ), version ? version : 75 );
2014-07-03 17:16:55 +00:00
websocket_bad_request ( ser );
2012-06-02 21:13:36 +00:00
ao2_ref ( protocol_handler , - 1 );
return 0 ;
}
/* Enable keepalive on all sessions so the underlying user does not have to */
2016-06-02 22:10:06 +03:00
if ( setsockopt ( ast_iostream_get_fd ( ser -> stream ), SOL_SOCKET , SO_KEEPALIVE , & flags , sizeof ( flags ))) {
2012-11-20 22:06:05 +00:00
ast_log ( LOG_WARNING , "WebSocket connection from '%s' could not be accepted - failed to enable keepalive \n " ,
2012-06-02 21:13:36 +00:00
ast_sockaddr_stringify ( & ser -> remote_address ));
2014-07-03 17:16:55 +00:00
websocket_bad_request ( ser );
2012-06-02 21:13:36 +00:00
ao2_ref ( session , - 1 );
ao2_ref ( protocol_handler , - 1 );
return 0 ;
}
2017-11-13 13:14:23 +00:00
/* Get our local address for the connected socket */
if ( ast_getsockname ( ast_iostream_get_fd ( ser -> stream ), & session -> local_address )) {
ast_log ( LOG_WARNING , "WebSocket connection from '%s' could not be accepted - failed to get local address \n " ,
ast_sockaddr_stringify ( & ser -> remote_address ));
websocket_bad_request ( ser );
ao2_ref ( session , - 1 );
ao2_ref ( protocol_handler , - 1 );
return 0 ;
}
2013-07-16 15:30:09 +00:00
ast_verb ( 2 , "WebSocket connection from '%s' for protocol '%s' accepted using version '%d' \n " , ast_sockaddr_stringify ( & ser -> remote_address ), protocol ? : "" , version );
2012-06-02 21:13:36 +00:00
/* Populate the session with all the needed details */
2016-06-02 22:10:06 +03:00
session -> stream = ser -> stream ;
2017-11-13 13:14:23 +00:00
ast_sockaddr_copy ( & session -> remote_address , & ser -> remote_address );
2012-06-02 21:13:36 +00:00
session -> opcode = - 1 ;
session -> reconstruct = DEFAULT_RECONSTRUCTION_CEILING ;
2016-06-02 22:10:06 +03:00
session -> secure = ast_iostream_get_ssl ( ser -> stream ) ? 1 : 0 ;
2012-06-02 21:13:36 +00:00
/* Give up ownership of the socket and pass it to the protocol handler */
2016-06-02 22:10:06 +03:00
ast_iostream_set_exclusive_input ( session -> stream , 0 );
2015-05-13 09:55:58 -05:00
protocol_handler -> session_established ( session , get_vars , headers );
2012-06-02 21:13:36 +00:00
ao2_ref ( protocol_handler , - 1 );
2014-06-12 17:00:08 +00:00
/*
2016-06-02 22:10:06 +03:00
* By dropping the stream from the session the connection
2014-06-12 17:00:08 +00:00
* won't get closed when the HTTP server cleans up because we
* passed the connection to the protocol handler.
*/
2016-06-02 22:10:06 +03:00
ser -> stream = NULL ;
2012-06-02 21:13:36 +00:00
return 0 ;
}
static struct ast_http_uri websocketuri = {
2013-08-30 13:40:27 +00:00
. callback = AST_OPTIONAL_API_NAME ( ast_websocket_uri_cb ),
2012-06-02 21:13:36 +00:00
. description = "Asterisk HTTP WebSocket" ,
. uri = "ws" ,
. has_subtree = 0 ,
. data = NULL ,
. key = __FILE__ ,
};
/*! \brief Simple echo implementation which echoes received text and binary frames */
static void websocket_echo_callback ( struct ast_websocket * session , struct ast_variable * parameters , struct ast_variable * headers )
{
2017-12-07 10:52:39 -05:00
int res ;
2012-06-02 21:13:36 +00:00
2014-03-05 16:22:44 +00:00
ast_debug ( 1 , "Entering WebSocket echo loop \n " );
2017-12-07 10:52:39 -05:00
if ( ast_fd_set_flags ( ast_websocket_fd ( session ), O_NONBLOCK )) {
2012-06-02 21:13:36 +00:00
goto end ;
}
while (( res = ast_wait_for_input ( ast_websocket_fd ( session ), - 1 )) > 0 ) {
char * payload ;
uint64_t payload_len ;
enum ast_websocket_opcode opcode ;
int fragmented ;
if ( ast_websocket_read ( session , & payload , & payload_len , & opcode , & fragmented )) {
/* We err on the side of caution and terminate the session if any error occurs */
2014-03-05 16:22:44 +00:00
ast_log ( LOG_WARNING , "Read failure during WebSocket echo loop \n " );
2012-06-02 21:13:36 +00:00
break ;
}
if ( opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY ) {
ast_websocket_write ( session , opcode , payload , payload_len );
} else if ( opcode == AST_WEBSOCKET_OPCODE_CLOSE ) {
break ;
2014-03-05 16:22:44 +00:00
} else {
2014-05-09 22:49:26 +00:00
ast_debug ( 1 , "Ignored WebSocket opcode %u \n " , opcode );
2012-06-02 21:13:36 +00:00
}
}
end :
2014-10-27 02:47:03 +00:00
ast_debug ( 1 , "Exiting WebSocket echo loop \n " );
2012-06-02 21:13:36 +00:00
ast_websocket_unref ( session );
}
2013-08-30 13:40:27 +00:00
static int websocket_add_protocol_internal ( const char * name , ast_websocket_callback callback )
2013-04-18 17:30:28 +00:00
{
struct ast_websocket_server * ws_server = websocketuri . data ;
if ( ! ws_server ) {
return - 1 ;
}
return ast_websocket_server_add_protocol ( ws_server , name , callback );
}
2013-08-30 13:40:27 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_add_protocol )( const char * name , ast_websocket_callback callback )
{
2017-12-29 03:57:17 -05:00
return websocket_add_protocol_internal ( name , callback );
2013-08-30 13:40:27 +00:00
}
2015-05-13 09:55:58 -05:00
int AST_OPTIONAL_API_NAME ( ast_websocket_add_protocol2 )( struct ast_websocket_protocol * protocol )
{
struct ast_websocket_server * ws_server = websocketuri . data ;
if ( ! ws_server ) {
return - 1 ;
}
if ( ast_websocket_server_add_protocol2 ( ws_server , protocol )) {
return - 1 ;
}
return 0 ;
}
2013-08-30 13:40:27 +00:00
static int websocket_remove_protocol_internal ( const char * name , ast_websocket_callback callback )
2013-04-18 17:30:28 +00:00
{
struct ast_websocket_server * ws_server = websocketuri . data ;
if ( ! ws_server ) {
return - 1 ;
}
return ast_websocket_server_remove_protocol ( ws_server , name , callback );
}
2013-08-30 13:40:27 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_remove_protocol )( const char * name , ast_websocket_callback callback )
{
2017-12-29 03:57:17 -05:00
return websocket_remove_protocol_internal ( name , callback );
2013-08-30 13:40:27 +00:00
}
2014-06-05 17:22:35 +00:00
/*! \brief Parse the given uri into a path and remote address.
*
* Expected uri form: [ws[s]]://<host>[:port][/<path>]
*
* The returned host will contain the address and optional port while
* path will contain everything after the address/port if included.
*/
2014-08-17 16:11:27 +00:00
static int websocket_client_parse_uri ( const char * uri , char ** host , struct ast_str ** path )
2014-06-05 17:22:35 +00:00
{
struct ast_uri * parsed_uri = ast_uri_parse_websocket ( uri );
if ( ! parsed_uri ) {
return - 1 ;
}
* host = ast_uri_make_host_with_port ( parsed_uri );
2014-08-17 16:11:27 +00:00
if ( ast_uri_path ( parsed_uri ) || ast_uri_query ( parsed_uri )) {
* path = ast_str_create ( 64 );
if ( !* path ) {
ao2_ref ( parsed_uri , - 1 );
return - 1 ;
}
if ( ast_uri_path ( parsed_uri )) {
ast_str_set ( path , 0 , "%s" , ast_uri_path ( parsed_uri ));
}
if ( ast_uri_query ( parsed_uri )) {
ast_str_append ( path , 0 , "?%s" , ast_uri_query ( parsed_uri ));
}
2014-06-05 17:22:35 +00:00
}
ao2_ref ( parsed_uri , - 1 );
return 0 ;
}
static void websocket_client_args_destroy ( void * obj )
{
struct ast_tcptls_session_args * args = obj ;
if ( args -> tls_cfg ) {
ast_free ( args -> tls_cfg -> certfile );
ast_free ( args -> tls_cfg -> pvtfile );
ast_free ( args -> tls_cfg -> cipher );
ast_free ( args -> tls_cfg -> cafile );
ast_free ( args -> tls_cfg -> capath );
ast_ssl_teardown ( args -> tls_cfg );
}
ast_free ( args -> tls_cfg );
}
static struct ast_tcptls_session_args * websocket_client_args_create (
const char * host , struct ast_tls_config * tls_cfg ,
enum ast_websocket_result * result )
{
struct ast_sockaddr * addr ;
struct ast_tcptls_session_args * args = ao2_alloc (
sizeof ( * args ), websocket_client_args_destroy );
if ( ! args ) {
* result = WS_ALLOCATE_ERROR ;
return NULL ;
}
args -> accept_fd = - 1 ;
args -> tls_cfg = tls_cfg ;
args -> name = "websocket client" ;
if ( ! ast_sockaddr_resolve ( & addr , host , 0 , 0 )) {
ast_log ( LOG_ERROR , "Unable to resolve address %s \n " ,
host );
ao2_ref ( args , - 1 );
* result = WS_URI_RESOLVE_ERROR ;
return NULL ;
}
ast_sockaddr_copy ( & args -> remote_address , addr );
ast_free ( addr );
return args ;
}
static char * websocket_client_create_key ( void )
{
static int encoded_size = CLIENT_KEY_SIZE * 2 * sizeof ( char ) + 1 ;
/* key is randomly selected 16-byte base64 encoded value */
unsigned char key [ CLIENT_KEY_SIZE + sizeof ( long ) - 1 ];
char * encoded = ast_malloc ( encoded_size );
long i = 0 ;
if ( ! encoded ) {
ast_log ( LOG_ERROR , "Unable to allocate client websocket key \n " );
return NULL ;
}
while ( i < CLIENT_KEY_SIZE ) {
long num = ast_random ();
memcpy ( key + i , & num , sizeof ( long ));
i += sizeof ( long );
}
ast_base64encode ( encoded , key , CLIENT_KEY_SIZE , encoded_size );
return encoded ;
}
struct websocket_client {
/*! host portion of client uri */
char * host ;
/*! path for logical websocket connection */
2014-08-17 16:11:27 +00:00
struct ast_str * resource_name ;
2014-06-05 17:22:35 +00:00
/*! unique key used during server handshaking */
char * key ;
/*! container for registered protocols */
char * protocols ;
/*! the protocol accepted by the server */
char * accept_protocol ;
/*! websocket protocol version */
int version ;
/*! tcptls connection arguments */
struct ast_tcptls_session_args * args ;
/*! tcptls connection instance */
struct ast_tcptls_session_instance * ser ;
};
static void websocket_client_destroy ( void * obj )
{
struct websocket_client * client = obj ;
ao2_cleanup ( client -> ser );
ao2_cleanup ( client -> args );
ast_free ( client -> accept_protocol );
ast_free ( client -> protocols );
ast_free ( client -> key );
ast_free ( client -> resource_name );
ast_free ( client -> host );
}
static struct ast_websocket * websocket_client_create (
const char * uri , const char * protocols , struct ast_tls_config * tls_cfg ,
enum ast_websocket_result * result )
{
struct ast_websocket * ws = ao2_alloc ( sizeof ( * ws ), session_destroy_fn );
if ( ! ws ) {
ast_log ( LOG_ERROR , "Unable to allocate websocket \n " );
* result = WS_ALLOCATE_ERROR ;
return NULL ;
}
if ( ! ( ws -> client = ao2_alloc (
sizeof ( * ws -> client ), websocket_client_destroy ))) {
ast_log ( LOG_ERROR , "Unable to allocate websocket client \n " );
* result = WS_ALLOCATE_ERROR ;
return NULL ;
}
if ( ! ( ws -> client -> key = websocket_client_create_key ())) {
ao2_ref ( ws , - 1 );
* result = WS_KEY_ERROR ;
return NULL ;
}
if ( websocket_client_parse_uri (
uri , & ws -> client -> host , & ws -> client -> resource_name )) {
ao2_ref ( ws , - 1 );
* result = WS_URI_PARSE_ERROR ;
return NULL ;
}
if ( ! ( ws -> client -> args = websocket_client_args_create (
ws -> client -> host , tls_cfg , result ))) {
ao2_ref ( ws , - 1 );
return NULL ;
}
ws -> client -> protocols = ast_strdup ( protocols );
ws -> client -> version = 13 ;
ws -> opcode = - 1 ;
ws -> reconstruct = DEFAULT_RECONSTRUCTION_CEILING ;
return ws ;
}
const char * AST_OPTIONAL_API_NAME (
ast_websocket_client_accept_protocol )( struct ast_websocket * ws )
{
return ws -> client -> accept_protocol ;
}
static enum ast_websocket_result websocket_client_handle_response_code (
struct websocket_client * client , int response_code )
{
if ( response_code <= 0 ) {
return WS_INVALID_RESPONSE ;
}
switch ( response_code ) {
case 101 :
return 0 ;
case 400 :
ast_log ( LOG_ERROR , "Received response 400 - Bad Request "
"- from %s \n " , client -> host );
return WS_BAD_REQUEST ;
case 404 :
ast_log ( LOG_ERROR , "Received response 404 - Request URL not "
"found - from %s \n " , client -> host );
return WS_URL_NOT_FOUND ;
}
ast_log ( LOG_ERROR , "Invalid HTTP response code %d from %s \n " ,
response_code , client -> host );
return WS_INVALID_RESPONSE ;
}
static enum ast_websocket_result websocket_client_handshake_get_response (
struct websocket_client * client )
{
enum ast_websocket_result res ;
char buf [ 4096 ];
char base64 [ 64 ];
int has_upgrade = 0 ;
int has_connection = 0 ;
int has_accept = 0 ;
int has_protocol = 0 ;
2016-06-02 22:10:06 +03:00
if ( ast_iostream_gets ( client -> ser -> stream , buf , sizeof ( buf )) <= 0 ) {
2014-06-05 17:22:35 +00:00
ast_log ( LOG_ERROR , "Unable to retrieve HTTP status line." );
return WS_BAD_STATUS ;
}
if (( res = websocket_client_handle_response_code ( client ,
ast_http_response_status_line (
buf , "HTTP/1.1" , 101 ))) != WS_OK ) {
return res ;
}
/* Ignoring line folding - assuming header field values are contained
within a single line */
2016-06-02 22:10:06 +03:00
while ( ast_iostream_gets ( client -> ser -> stream , buf , sizeof ( buf )) > 0 ) {
2014-06-05 17:22:35 +00:00
char * name , * value ;
int parsed = ast_http_header_parse ( buf , & name , & value );
if ( parsed < 0 ) {
break ;
}
if ( parsed > 0 ) {
continue ;
}
if ( ! has_upgrade &&
( has_upgrade = ast_http_header_match (
name , "upgrade" , value , "websocket" )) < 0 ) {
return WS_HEADER_MISMATCH ;
} else if ( ! has_connection &&
( has_connection = ast_http_header_match (
name , "connection" , value , "upgrade" )) < 0 ) {
return WS_HEADER_MISMATCH ;
} else if ( ! has_accept &&
( has_accept = ast_http_header_match (
name , "sec-websocket-accept" , value ,
websocket_combine_key (
client -> key , base64 , sizeof ( base64 )))) < 0 ) {
return WS_HEADER_MISMATCH ;
} else if ( ! has_protocol &&
( has_protocol = ast_http_header_match_in (
name , "sec-websocket-protocol" , value , client -> protocols ))) {
if ( has_protocol < 0 ) {
return WS_HEADER_MISMATCH ;
}
client -> accept_protocol = ast_strdup ( value );
} else if ( ! strcasecmp ( name , "sec-websocket-extensions" )) {
ast_log ( LOG_ERROR , "Extensions received, but not "
"supported by client \n " );
return WS_NOT_SUPPORTED ;
}
}
return has_upgrade && has_connection && has_accept ?
WS_OK : WS_HEADER_MISSING ;
}
static enum ast_websocket_result websocket_client_handshake (
struct websocket_client * client )
{
char protocols [ 100 ] = "" ;
if ( ! ast_strlen_zero ( client -> protocols )) {
sprintf ( protocols , "Sec-WebSocket-Protocol: %s \r\n " ,
client -> protocols );
}
2016-06-02 22:10:06 +03:00
if ( ast_iostream_printf ( client -> ser -> stream ,
"GET /%s HTTP/1.1 \r\n "
"Sec-WebSocket-Version: %d \r\n "
"Upgrade: websocket \r\n "
"Connection: Upgrade \r\n "
"Host: %s \r\n "
"Sec-WebSocket-Key: %s \r\n "
"%s \r\n " ,
client -> resource_name ? ast_str_buffer ( client -> resource_name ) : "" ,
client -> version ,
client -> host ,
client -> key ,
protocols ) < 0 ) {
2014-06-05 17:22:35 +00:00
ast_log ( LOG_ERROR , "Failed to send handshake. \n " );
return WS_WRITE_ERROR ;
}
/* wait for a response before doing anything else */
return websocket_client_handshake_get_response ( client );
}
static enum ast_websocket_result websocket_client_connect ( struct ast_websocket * ws )
{
enum ast_websocket_result res ;
/* create and connect the client - note client_start
releases the session instance on failure */
if ( ! ( ws -> client -> ser = ast_tcptls_client_start (
ast_tcptls_client_create ( ws -> client -> args )))) {
return WS_CLIENT_START_ERROR ;
}
if (( res = websocket_client_handshake ( ws -> client )) != WS_OK ) {
ao2_ref ( ws -> client -> ser , - 1 );
ws -> client -> ser = NULL ;
return res ;
}
2016-06-02 22:10:06 +03:00
ws -> stream = ws -> client -> ser -> stream ;
ws -> secure = ast_iostream_get_ssl ( ws -> stream ) ? 1 : 0 ;
ws -> client -> ser -> stream = NULL ;
2017-11-13 13:14:23 +00:00
ast_sockaddr_copy ( & ws -> remote_address , & ws -> client -> ser -> remote_address );
2014-06-05 17:22:35 +00:00
return WS_OK ;
}
struct ast_websocket * AST_OPTIONAL_API_NAME ( ast_websocket_client_create )
( const char * uri , const char * protocols , struct ast_tls_config * tls_cfg ,
enum ast_websocket_result * result )
{
struct ast_websocket * ws = websocket_client_create (
uri , protocols , tls_cfg , result );
if ( ! ws ) {
return NULL ;
}
if (( * result = websocket_client_connect ( ws )) != WS_OK ) {
ao2_ref ( ws , - 1 );
return NULL ;
}
return ws ;
}
int AST_OPTIONAL_API_NAME ( ast_websocket_read_string )
2014-06-16 16:22:33 +00:00
( struct ast_websocket * ws , char ** buf )
2014-06-05 17:22:35 +00:00
{
char * payload ;
uint64_t payload_len ;
enum ast_websocket_opcode opcode ;
int fragmented = 1 ;
while ( fragmented ) {
if ( ast_websocket_read ( ws , & payload , & payload_len ,
& opcode , & fragmented )) {
ast_log ( LOG_ERROR , "Client WebSocket string read - "
"error reading string data \n " );
return - 1 ;
}
2014-06-16 16:22:33 +00:00
if ( opcode == AST_WEBSOCKET_OPCODE_CONTINUATION ) {
continue ;
}
2014-06-05 17:22:35 +00:00
if ( opcode == AST_WEBSOCKET_OPCODE_CLOSE ) {
return - 1 ;
}
if ( opcode != AST_WEBSOCKET_OPCODE_TEXT ) {
ast_log ( LOG_ERROR , "Client WebSocket string read - "
"non string data received \n " );
return - 1 ;
}
2014-06-16 16:22:33 +00:00
}
2014-06-05 17:22:35 +00:00
2014-06-16 16:22:33 +00:00
if ( ! ( * buf = ast_malloc ( payload_len + 1 ))) {
return - 1 ;
2014-06-05 17:22:35 +00:00
}
2014-06-16 16:22:33 +00:00
ast_copy_string ( * buf , payload , payload_len + 1 );
return payload_len + 1 ;
2014-06-05 17:22:35 +00:00
}
int AST_OPTIONAL_API_NAME ( ast_websocket_write_string )
2014-06-16 16:22:33 +00:00
( struct ast_websocket * ws , const char * buf )
2014-06-05 17:22:35 +00:00
{
2015-08-03 11:06:07 -05:00
uint64_t len = strlen ( buf );
2015-08-04 09:47:34 -05:00
ast_debug ( 3 , "Writing websocket string of length %" PRIu64 " \n " , len );
2015-08-03 11:06:07 -05:00
/* We do not pass strlen(buf) to ast_websocket_write() directly because the
* size_t returned by strlen() may not require the same storage size
* as the uint64_t that ast_websocket_write() uses. This normally
* would not cause a problem, but since ast_websocket_write() uses
* the optional API, this function call goes through a series of macros
* that may cause a 32-bit to 64-bit conversion to go awry.
*/
2014-06-05 17:22:35 +00:00
return ast_websocket_write ( ws , AST_WEBSOCKET_OPCODE_TEXT ,
2015-08-03 11:06:07 -05:00
( char * ) buf , len );
2014-06-05 17:22:35 +00:00
}
2012-06-02 21:13:36 +00:00
static int load_module ( void )
{
2013-08-30 13:40:27 +00:00
websocketuri . data = websocket_server_internal_create ();
2013-04-18 17:30:28 +00:00
if ( ! websocketuri . data ) {
2017-04-11 10:07:39 -06:00
return AST_MODULE_LOAD_DECLINE ;
2013-04-18 17:30:28 +00:00
}
2012-06-02 21:13:36 +00:00
ast_http_uri_link ( & websocketuri );
2013-08-30 13:40:27 +00:00
websocket_add_protocol_internal ( "echo" , websocket_echo_callback );
2012-06-02 21:13:36 +00:00
return 0 ;
}
static int unload_module ( void )
{
2014-11-04 19:33:21 +00:00
websocket_remove_protocol_internal ( "echo" , websocket_echo_callback );
2012-06-02 21:13:36 +00:00
ast_http_uri_unlink ( & websocketuri );
2013-04-18 17:30:28 +00:00
ao2_ref ( websocketuri . data , - 1 );
websocketuri . data = NULL ;
2012-06-02 21:13:36 +00:00
return 0 ;
}
AST_MODULE_INFO ( ASTERISK_GPL_KEY , AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER , "HTTP WebSocket Support" ,
2015-05-05 20:49:04 -04:00
. support_level = AST_MODULE_SUPPORT_EXTENDED ,
. load = load_module ,
. unload = unload_module ,
. load_pri = AST_MODPRI_CHANNEL_DEPEND ,
2018-02-16 22:11:42 -05:00
. requires = "http" ,
2015-05-05 20:49:04 -04:00
);