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
<support_level>extended</support_level>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION ( __FILE__ , "$Revision$" )
#include "asterisk/module.h"
#include "asterisk/http.h"
#include "asterisk/astobj2.h"
#include "asterisk/strings.h"
#include "asterisk/file.h"
#include "asterisk/unaligned.h"
2012-09-27 17:05:26 +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"
/*! \brief Number of buckets for registered protocols */
#define MAX_PROTOCOL_BUCKETS 7
/*! \brief Size of the pre-determined buffer for WebSocket frames */
#define MAXIMUM_FRAME_SIZE 8192
/*! \brief Default reconstruction size for multi-frame payload reconstruction. If exceeded the next frame will start a
* payload.
*/
#define DEFAULT_RECONSTRUCTION_CEILING 16384
/*! \brief Maximum reconstruction size for multi-frame payload reconstruction. */
#define MAXIMUM_RECONSTRUCTION_CEILING 16384
2014-03-05 00:25: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 {
FILE * f ; /*!< Pointer to the file instance used for writing and reading */
int fd ; /*!< File descriptor for the session, only used for polling */
struct ast_sockaddr address ; /*!< Address of the remote client */
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 */
2014-06-26 12:06:22 +00:00
int timeout ; /*!< The timeout for operations on the socket */
2012-06-02 21:13:36 +00:00
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 */
2014-04-30 13:04:14 +00:00
unsigned int close_sent : 1 ; /*!< Bit to indicate that the session close opcode has been sent and no further data will be sent */
2012-06-02 21:13:36 +00:00
};
/*! \brief Structure definition for protocols */
struct websocket_protocol {
char * name ; /*!< Name of the protocol */
ast_websocket_callback callback ; /*!< Callback called when a new session is established */
};
/*! \brief Container for registered protocols */
static struct ao2_container * protocols ;
/*! \brief Hashing function for protocols */
static int protocol_hash_fn ( const void * obj , const int flags )
{
const struct websocket_protocol * protocol = obj ;
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 )
{
const struct websocket_protocol * protocol1 = obj , * protocol2 = arg ;
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 )
{
struct websocket_protocol * protocol = obj ;
ast_free ( protocol -> name );
}
/*! \brief Destructor function for sessions */
static void session_destroy_fn ( void * obj )
{
struct ast_websocket * session = obj ;
2014-04-30 13:04:14 +00:00
ast_websocket_close ( session , 0 );
2012-06-02 21:13:36 +00:00
if ( session -> f ) {
fclose ( session -> f );
ast_verb ( 2 , "WebSocket connection from '%s' closed \n " , ast_sockaddr_stringify ( & session -> address ));
}
ast_free ( session -> payload );
}
2012-09-27 17:05:26 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_add_protocol )( const char * name , ast_websocket_callback callback )
2012-06-02 21:13:36 +00:00
{
struct websocket_protocol * protocol ;
ao2_lock ( protocols );
/* Ensure a second protocol handler is not registered for the same protocol */
if (( protocol = ao2_find ( protocols , name , OBJ_KEY | OBJ_NOLOCK ))) {
ao2_ref ( protocol , - 1 );
ao2_unlock ( protocols );
return - 1 ;
}
if ( ! ( protocol = ao2_alloc ( sizeof ( * protocol ), protocol_destroy_fn ))) {
ao2_unlock ( protocols );
return - 1 ;
}
if ( ! ( protocol -> name = ast_strdup ( name ))) {
ao2_ref ( protocol , - 1 );
ao2_unlock ( protocols );
return - 1 ;
}
protocol -> callback = callback ;
ao2_link_flags ( protocols , protocol , OBJ_NOLOCK );
ao2_unlock ( protocols );
ao2_ref ( protocol , - 1 );
ast_verb ( 2 , "WebSocket registered sub-protocol '%s' \n " , name );
return 0 ;
}
2012-09-27 17:05:26 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_remove_protocol )( const char * name , ast_websocket_callback callback )
2012-06-02 21:13:36 +00:00
{
struct websocket_protocol * protocol ;
if ( ! ( protocol = ao2_find ( protocols , name , OBJ_KEY ))) {
return - 1 ;
}
if ( protocol -> callback != callback ) {
ao2_ref ( protocol , - 1 );
return - 1 ;
}
ao2_unlink ( protocols , protocol );
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:05:26 +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
{
char frame [ 4 ] = { 0 , }; /* The header is 2 bytes and the reason code takes up another 2 bytes */
2014-04-30 13:04:14 +00:00
int res ;
if ( session -> close_sent ) {
return 0 ;
}
2012-06-02 21:13:36 +00:00
frame [ 0 ] = AST_WEBSOCKET_OPCODE_CLOSE | 0x80 ;
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:04:14 +00:00
session -> close_sent = 1 ;
2012-06-02 21:13:36 +00:00
2014-04-30 13:04:14 +00:00
ao2_lock ( session );
2014-06-26 12:06:22 +00:00
res = ast_careful_fwrite ( session -> f , session -> fd , frame , 4 , session -> timeout );
2014-04-30 13:04:14 +00:00
ao2_unlock ( session );
2014-06-26 12:06:22 +00:00
2014-04-30 13:04:14 +00:00
return res ;
2012-06-02 21:13:36 +00:00
}
/*! \brief Write function for websocket traffic */
2012-09-27 17:05:26 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_write )( struct ast_websocket * session , enum ast_websocket_opcode opcode , char * payload , uint64_t actual_length )
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 ;
uint64_t length = 0 ;
if ( actual_length < 126 ) {
length = actual_length ;
} else if ( actual_length < ( 1 << 16 )) {
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 ;
}
2012-07-31 20:21:43 +00:00
frame = ast_alloca ( header_size );
2012-06-02 21:13:36 +00:00
memset ( frame , 0 , sizeof ( * frame ));
frame [ 0 ] = opcode | 0x80 ;
frame [ 1 ] = length ;
/* Use the additional available bytes to store the length */
if ( length == 126 ) {
put_unaligned_uint16 ( & frame [ 2 ], htons ( actual_length ));
} else if ( length == 127 ) {
put_unaligned_uint64 ( & frame [ 2 ], htonl ( actual_length ));
}
2014-04-30 13:04:14 +00:00
ao2_lock ( session );
if ( session -> closing ) {
ao2_unlock ( session );
return - 1 ;
}
2014-06-26 12:06:22 +00:00
if ( ast_careful_fwrite ( session -> f , session -> fd , frame , header_size , session -> timeout )) {
2014-04-30 13:04:14 +00:00
ao2_unlock ( session );
2012-06-02 21:13:36 +00:00
return - 1 ;
}
2014-06-26 12:06:22 +00:00
if ( ast_careful_fwrite ( session -> f , session -> fd , payload , actual_length , session -> timeout )) {
2014-04-30 13:04:14 +00:00
ao2_unlock ( session );
2012-06-02 21:13:36 +00:00
return - 1 ;
}
2014-03-05 00:25:44 +00:00
fflush ( session -> f );
2014-04-30 13:04:14 +00:00
ao2_unlock ( session );
2012-06-02 21:13:36 +00:00
return 0 ;
}
2012-09-27 17:05:26 +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:05:26 +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:05:26 +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:05:26 +00:00
void AST_OPTIONAL_API_NAME ( ast_websocket_unref )( struct ast_websocket * session )
2012-06-02 21:13:36 +00:00
{
ao2_ref ( session , - 1 );
}
2012-09-27 17:05:26 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_fd )( struct ast_websocket * session )
2012-06-02 21:13:36 +00:00
{
return session -> closing ? - 1 : session -> fd ;
}
2012-09-27 17:05:26 +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
{
return & session -> address ;
}
2012-09-27 17:05:26 +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:05:26 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_set_nonblock )( struct ast_websocket * session )
2012-07-16 12:35:04 +00:00
{
int flags ;
if (( flags = fcntl ( session -> fd , F_GETFL )) == - 1 ) {
return - 1 ;
}
flags |= O_NONBLOCK ;
if (( flags = fcntl ( session -> fd , F_SETFL , flags )) == - 1 ) {
return - 1 ;
}
return 0 ;
}
2014-06-26 12:06:22 +00:00
int AST_OPTIONAL_API_NAME ( ast_websocket_set_timeout )( struct ast_websocket * session , int timeout )
{
session -> timeout = timeout ;
return 0 ;
}
2014-03-05 00:25: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 ...
*/
static inline int ws_safe_read ( struct ast_websocket * session , char * buf , int len , enum ast_websocket_opcode * opcode )
{
int sanity ;
size_t rlen ;
int xlen = len ;
char * rbuf = buf ;
for ( sanity = 10 ; sanity ; sanity -- ) {
clearerr ( session -> f );
rlen = fread ( rbuf , 1 , xlen , session -> f );
if ( 0 == rlen && ferror ( session -> f ) && errno != EAGAIN ) {
ast_log ( LOG_ERROR , "Error reading from web socket: %s \n " , strerror ( errno ));
( * opcode ) = AST_WEBSOCKET_OPCODE_CLOSE ;
session -> closing = 1 ;
return - 1 ;
}
xlen = ( xlen - rlen );
rbuf = rbuf + rlen ;
if ( 0 == xlen ) {
break ;
}
if ( ast_wait_for_input ( session -> fd , 1000 ) < 0 ) {
ast_log ( LOG_ERROR , "ast_wait_for_input returned err: %s \n " , strerror ( errno ));
( * opcode ) = AST_WEBSOCKET_OPCODE_CLOSE ;
session -> closing = 1 ;
return - 1 ;
}
}
if ( ! sanity ) {
ast_log ( LOG_WARNING , "Websocket seems unresponsive, disconnecting ... \n " );
( * opcode ) = AST_WEBSOCKET_OPCODE_CLOSE ;
session -> closing = 1 ;
return - 1 ;
}
return 0 ;
}
2012-09-27 17:05:26 +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 00:25: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 00:25:44 +00:00
if ( ws_safe_read ( session , & buf [ 0 ], MIN_WS_HDR_SZ , opcode )) {
return 0 ;
2012-06-02 21:13:36 +00:00
}
2014-03-05 00:25:44 +00:00
frame_size += MIN_WS_HDR_SZ ;
2012-06-02 21:13:36 +00:00
2014-03-05 00:25: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 00:25: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 ||
* opcode == AST_WEBSOCKET_OPCODE_PING || * opcode == AST_WEBSOCKET_OPCODE_PONG ) {
2014-03-05 00:25: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 )) {
2012-06-02 21:13:36 +00:00
return 0 ;
}
2014-03-05 00:25:44 +00:00
frame_size += options_len ;
2012-06-02 21:13:36 +00:00
}
if ( * payload_len == 126 ) {
2014-03-05 00:25: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 00:25:44 +00:00
mask = & buf [ 4 ];
2012-06-02 21:13:36 +00:00
} else if ( * payload_len == 127 ) {
2014-03-05 00:25: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 00:25: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 00:25: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:28:40 +00:00
ast_log ( LOG_WARNING , "Cannot fit huge websocket frame of %zu bytes \n " , frame_size );
2014-03-05 00:25:44 +00:00
/* The frame won't fit :-( */
2012-06-02 21:13:36 +00:00
ast_websocket_close ( session , 1009 );
2014-03-05 00:25:44 +00:00
return - 1 ;
2012-06-02 21:13:36 +00:00
}
2014-03-05 00:25:44 +00:00
if ( ws_safe_read ( session , ( * payload ), ( * payload_len ), opcode )) {
return 0 ;
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 ];
}
}
2014-03-05 00:25:44 +00:00
if ( ! ( new_payload = ast_realloc ( session -> payload , ( session -> payload_len + * payload_len )))) {
2014-05-09 22:28:40 +00:00
ast_log ( LOG_WARNING , "Failed allocation: %p, %zu, %" PRIu64 " \n " ,
2014-03-05 00:25:44 +00:00
session -> payload , session -> payload_len , * payload_len );
2012-06-02 21:13:36 +00:00
* payload_len = 0 ;
ast_websocket_close ( session , 1009 );
return 0 ;
}
/* Per the RFC for PING we need to send back an opcode with the application data as received */
2014-06-26 12:06:22 +00:00
if (( * opcode == AST_WEBSOCKET_OPCODE_PING ) && ( ast_websocket_write ( session , AST_WEBSOCKET_OPCODE_PONG , * payload , * payload_len ))) {
* payload_len = 0 ;
ast_websocket_close ( session , 1009 );
return 0 ;
2012-06-02 21:13:36 +00:00
}
session -> payload = new_payload ;
2014-03-05 00:25:44 +00:00
memcpy (( session -> payload + session -> payload_len ), ( * payload ), ( * payload_len ));
2012-06-02 21:13:36 +00:00
session -> payload_len += * payload_len ;
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 if ( * opcode == AST_WEBSOCKET_OPCODE_CLOSE ) {
/* Make the payload available so the user can look at the reason code if they so desire */
if (( * payload_len ) && ( new_payload = ast_realloc ( session -> payload , * payload_len ))) {
2014-03-05 00:25:44 +00:00
if ( ws_safe_read ( session , & buf [ frame_size ], ( * payload_len ), opcode )) {
return 0 ;
}
2012-06-02 21:13:36 +00:00
session -> payload = new_payload ;
2014-03-05 00:25:44 +00:00
memcpy ( session -> payload , & buf [ frame_size ], * payload_len );
2012-06-02 21:13:36 +00:00
* payload = session -> payload ;
2014-03-05 00:25:44 +00:00
frame_size += ( * payload_len );
2012-06-02 21:13:36 +00:00
}
2014-04-30 13:04:14 +00:00
session -> closing = 1 ;
2012-06-02 21:13:36 +00:00
} else {
2014-05-09 22:28:40 +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 ;
}
/*! \brief Callback that is executed everytime an HTTP request is received by this module */
static int websocket_callback ( 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 )
{
struct ast_variable * v ;
char * upgrade = NULL , * key = NULL , * key1 = NULL , * key2 = NULL , * protos = NULL , * requested_protocols = NULL , * protocol = NULL ;
int version = 0 , flags = 1 ;
struct websocket_protocol * protocol_handler = NULL ;
struct ast_websocket * session ;
/* 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" );
return - 1 ;
}
/* Get the minimum headers required to satisfy our needs */
for ( v = headers ; v ; v = v -> next ) {
if ( ! strcasecmp ( v -> name , "Upgrade" )) {
upgrade = ast_strip ( ast_strdupa ( v -> value ));
} else if ( ! strcasecmp ( v -> name , "Sec-WebSocket-Key" )) {
key = ast_strip ( ast_strdupa ( v -> value ));
} else if ( ! strcasecmp ( v -> name , "Sec-WebSocket-Key1" )) {
key1 = ast_strip ( ast_strdupa ( v -> value ));
} else if ( ! strcasecmp ( v -> name , "Sec-WebSocket-Key2" )) {
key2 = ast_strip ( ast_strdupa ( v -> value ));
} else if ( ! strcasecmp ( v -> name , "Sec-WebSocket-Protocol" )) {
requested_protocols = ast_strip ( ast_strdupa ( v -> value ));
protos = ast_strdupa ( requested_protocols );
} 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 21:58:35 +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 );
return - 1 ;
} else if ( ast_strlen_zero ( requested_protocols )) {
2012-11-20 21:58:35 +00:00
ast_log ( LOG_WARNING , "WebSocket connection from '%s' could not be accepted - no protocols requested \n " ,
2012-06-02 21:13:36 +00:00
ast_sockaddr_stringify ( & ser -> remote_address ));
fputs ( "HTTP/1.1 400 Bad Request \r\n "
"Sec-WebSocket-Version: 7, 8, 13 \r\n\r\n " , ser -> f );
return - 1 ;
} 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 21:58:35 +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 ));
fputs ( "HTTP/1.1 400 Bad Request \r\n "
"Sec-WebSocket-Version: 7, 8, 13 \r\n\r\n " , ser -> f );
return 0 ;
}
/* Iterate through the requested protocols trying to find one that we have a handler for */
while (( protocol = strsep ( & requested_protocols , "," ))) {
if (( protocol_handler = ao2_find ( protocols , ast_strip ( protocol ), OBJ_KEY ))) {
break ;
}
}
/* 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 );
fputs ( "HTTP/1.1 400 Bad Request \r\n "
"Sec-WebSocket-Version: 7, 8, 13 \r\n\r\n " , ser -> f );
return 0 ;
}
/* Determine how to respond depending on the version */
if ( version == 7 || version == 8 || version == 13 ) {
/* Version 7 defined in specification http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07 */
/* Version 8 defined in specification http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 */
/* Version 13 defined in specification http://tools.ietf.org/html/rfc6455 */
2013-06-12 21:00:38 +00:00
char * combined , base64 [ 64 ];
unsigned combined_length ;
2012-06-02 21:13:36 +00:00
uint8_t sha [ 20 ];
2013-06-12 21:00:38 +00:00
combined_length = ( key ? strlen ( key ) : 0 ) + strlen ( WEBSOCKET_GUID ) + 1 ;
if ( ! key || combined_length > 8192 ) { /* no stack overflows please */
fputs ( "HTTP/1.1 400 Bad Request \r\n "
"Sec-WebSocket-Version: 7, 8, 13 \r\n\r\n " , ser -> f );
ao2_ref ( protocol_handler , - 1 );
return 0 ;
}
2012-06-02 21:13:36 +00:00
if ( ! ( session = ao2_alloc ( sizeof ( * session ), session_destroy_fn ))) {
2012-11-20 21:58:35 +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 ));
fputs ( "HTTP/1.1 400 Bad Request \r\n "
"Sec-WebSocket-Version: 7, 8, 13 \r\n\r\n " , ser -> f );
ao2_ref ( protocol_handler , - 1 );
return 0 ;
}
2014-06-26 12:06:22 +00:00
session -> timeout = AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT ;
2012-06-02 21:13:36 +00:00
2013-06-12 21:00:38 +00:00
combined = ast_alloca ( combined_length );
snprintf ( combined , combined_length , "%s%s" , key , WEBSOCKET_GUID );
2012-06-02 21:13:36 +00:00
ast_sha1_hash_uint ( sha , combined );
ast_base64encode ( base64 , ( const unsigned char * ) sha , 20 , sizeof ( base64 ));
fprintf ( ser -> f , "HTTP/1.1 101 Switching Protocols \r\n "
"Upgrade: %s \r\n "
"Connection: Upgrade \r\n "
"Sec-WebSocket-Accept: %s \r\n "
"Sec-WebSocket-Protocol: %s \r\n\r\n " ,
upgrade ,
base64 ,
protocol );
2014-03-05 00:25:44 +00:00
fflush ( ser -> f );
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 21:58:35 +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 );
fputs ( "HTTP/1.1 400 Bad Request \r\n "
"Sec-WebSocket-Version: 7, 8, 13 \r\n\r\n " , ser -> f );
ao2_ref ( protocol_handler , - 1 );
return 0 ;
}
/* Enable keepalive on all sessions so the underlying user does not have to */
if ( setsockopt ( ser -> fd , SOL_SOCKET , SO_KEEPALIVE , & flags , sizeof ( flags ))) {
2012-11-20 21:58:35 +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 ));
fputs ( "HTTP/1.1 400 Bad Request \r\n "
"Sec-WebSocket-Version: 7, 8, 13 \r\n\r\n " , ser -> f );
ao2_ref ( session , - 1 );
ao2_ref ( protocol_handler , - 1 );
return 0 ;
}
ast_verb ( 2 , "WebSocket connection from '%s' for protocol '%s' accepted using version '%d' \n " , ast_sockaddr_stringify ( & ser -> remote_address ), protocol , version );
/* Populate the session with all the needed details */
session -> f = ser -> f ;
session -> fd = ser -> fd ;
ast_sockaddr_copy ( & session -> address , & ser -> remote_address );
session -> opcode = - 1 ;
session -> reconstruct = DEFAULT_RECONSTRUCTION_CEILING ;
session -> secure = ser -> ssl ? 1 : 0 ;
/* Give up ownership of the socket and pass it to the protocol handler */
protocol_handler -> callback ( session , get_vars , headers );
ao2_ref ( protocol_handler , - 1 );
2014-06-12 16:22:19 +00:00
/*
* By dropping the FILE* and fd from the session the connection
* won't get closed when the HTTP server cleans up because we
* passed the connection to the protocol handler.
*/
2012-06-02 21:13:36 +00:00
ser -> f = NULL ;
2014-06-12 16:22:19 +00:00
ser -> fd = - 1 ;
2012-06-02 21:13:36 +00:00
return 0 ;
}
static struct ast_http_uri websocketuri = {
. callback = websocket_callback ,
. 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 )
{
int flags , res ;
2014-03-05 00:25:44 +00:00
ast_debug ( 1 , "Entering WebSocket echo loop \n " );
2012-06-02 21:13:36 +00:00
if (( flags = fcntl ( ast_websocket_fd ( session ), F_GETFL )) == - 1 ) {
goto end ;
}
flags |= O_NONBLOCK ;
if ( fcntl ( ast_websocket_fd ( session ), F_SETFL , flags ) == - 1 ) {
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 00:25: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 00:25:44 +00:00
} else {
2014-05-09 22:28:40 +00:00
ast_debug ( 1 , "Ignored WebSocket opcode %u \n " , opcode );
2012-06-02 21:13:36 +00:00
}
}
end :
2014-03-05 00:25:44 +00:00
ast_debug ( 1 , "Exitting WebSocket echo loop \n " );
2012-06-02 21:13:36 +00:00
ast_websocket_unref ( session );
}
static int load_module ( void )
{
protocols = ao2_container_alloc ( MAX_PROTOCOL_BUCKETS , protocol_hash_fn , protocol_cmp_fn );
ast_http_uri_link ( & websocketuri );
ast_websocket_add_protocol ( "echo" , websocket_echo_callback );
return 0 ;
}
static int unload_module ( void )
{
ast_websocket_remove_protocol ( "echo" , websocket_echo_callback );
ast_http_uri_unlink ( & websocketuri );
ao2_ref ( protocols , - 1 );
return 0 ;
}
AST_MODULE_INFO ( ASTERISK_GPL_KEY , AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER , "HTTP WebSocket Support" ,
. load = load_module ,
. unload = unload_module ,
. load_pri = AST_MODPRI_CHANNEL_DEPEND ,
);