Sat Apr 26 2014 22:03:10

Asterisk developer's documentation


res_http_websocket.c File Reference

WebSocket support for the Asterisk internal HTTP server. More...

#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"
#include "asterisk/http_websocket.h"
Include dependency graph for res_http_websocket.c:

Go to the source code of this file.

Data Structures

struct  ast_websocket
 Structure definition for session. More...
struct  websocket_protocol
 Structure definition for protocols. More...

Defines

#define AST_API_MODULE
#define DEFAULT_RECONSTRUCTION_CEILING   16384
 Default reconstruction size for multi-frame payload reconstruction. If exceeded the next frame will start a payload.
#define MAX_PROTOCOL_BUCKETS   7
 Number of buckets for registered protocols.
#define MAX_WS_HDR_SZ   14
 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 MAXIMUM_FRAME_SIZE   8192
 Size of the pre-determined buffer for WebSocket frames.
#define MAXIMUM_RECONSTRUCTION_CEILING   16384
 Maximum reconstruction size for multi-frame payload reconstruction.
#define MIN_WS_HDR_SZ   2
#define WEBSOCKET_GUID   "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
 GUID used to compute the accept key, defined in the specifications.

Functions

static void __reg_module (void)
static void __unreg_module (void)
int AST_OPTIONAL_API_NAME() ast_websocket_add_protocol (const char *name, ast_websocket_callback callback)
 Add a sub-protocol handler to the server.
int AST_OPTIONAL_API_NAME() ast_websocket_close (struct ast_websocket *session, uint16_t reason)
 Close function for websocket session.
int AST_OPTIONAL_API_NAME() ast_websocket_fd (struct ast_websocket *session)
 Get the file descriptor for a WebSocket session.
int AST_OPTIONAL_API_NAME() ast_websocket_is_secure (struct ast_websocket *session)
 Get whether the WebSocket session is using a secure transport or not.
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)
 Read a WebSocket frame and handle it.
void AST_OPTIONAL_API_NAME() ast_websocket_reconstruct_disable (struct ast_websocket *session)
 Disable multi-frame reconstruction.
void AST_OPTIONAL_API_NAME() ast_websocket_reconstruct_enable (struct ast_websocket *session, size_t bytes)
 Enable multi-frame reconstruction up to a certain number of bytes.
void AST_OPTIONAL_API_NAME() ast_websocket_ref (struct ast_websocket *session)
 Increase the reference count for a WebSocket session.
struct ast_sockaddr
*AST_OPTIONAL_API_NAME() 
ast_websocket_remote_address (struct ast_websocket *session)
 Get the remote address for a WebSocket connected session.
int AST_OPTIONAL_API_NAME() ast_websocket_remove_protocol (const char *name, ast_websocket_callback callback)
 Remove a sub-protocol handler from the server.
int AST_OPTIONAL_API_NAME() ast_websocket_set_nonblock (struct ast_websocket *session)
 Set the socket of a WebSocket session to be non-blocking.
void AST_OPTIONAL_API_NAME() ast_websocket_unref (struct ast_websocket *session)
 Decrease the reference count for a WebSocket session.
int AST_OPTIONAL_API_NAME() ast_websocket_write (struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t actual_length)
 Write function for websocket traffic.
static int load_module (void)
static int protocol_cmp_fn (void *obj, void *arg, int flags)
 Comparison function for protocols.
static void protocol_destroy_fn (void *obj)
 Destructor function for protocols.
static int protocol_hash_fn (const void *obj, const int flags)
 Hashing function for protocols.
static void session_destroy_fn (void *obj)
 Destructor function for sessions.
static int unload_module (void)
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)
 Callback that is executed everytime an HTTP request is received by this module.
static void websocket_echo_callback (struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)
 Simple echo implementation which echoes received text and binary frames.
static int ws_safe_read (struct ast_websocket *session, char *buf, int len, enum ast_websocket_opcode *opcode)

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER , .description = "HTTP WebSocket Support" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_CHANNEL_DEPEND, }
static struct ast_module_infoast_module_info = &__mod_info
static struct ao2_containerprotocols
 Container for registered protocols.
static struct ast_http_uri websocketuri

Detailed Description

WebSocket support for the Asterisk internal HTTP server.

Author:
Joshua Colp <jcolp@digium.com>

Definition in file res_http_websocket.c.


Define Documentation

#define AST_API_MODULE

Definition at line 41 of file res_http_websocket.c.

#define DEFAULT_RECONSTRUCTION_CEILING   16384

Default reconstruction size for multi-frame payload reconstruction. If exceeded the next frame will start a payload.

Definition at line 56 of file res_http_websocket.c.

Referenced by websocket_callback().

#define MAX_PROTOCOL_BUCKETS   7

Number of buckets for registered protocols.

Definition at line 48 of file res_http_websocket.c.

Referenced by load_module().

#define MAX_WS_HDR_SZ   14

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 ...

Definition at line 68 of file res_http_websocket.c.

#define MAXIMUM_FRAME_SIZE   8192

Size of the pre-determined buffer for WebSocket frames.

Definition at line 51 of file res_http_websocket.c.

Referenced by ast_websocket_read().

#define MAXIMUM_RECONSTRUCTION_CEILING   16384

Maximum reconstruction size for multi-frame payload reconstruction.

Definition at line 59 of file res_http_websocket.c.

Referenced by ast_websocket_reconstruct_enable().

#define MIN_WS_HDR_SZ   2

Definition at line 69 of file res_http_websocket.c.

Referenced by ast_websocket_read().

#define WEBSOCKET_GUID   "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

GUID used to compute the accept key, defined in the specifications.

Definition at line 45 of file res_http_websocket.c.

Referenced by websocket_callback().


Function Documentation

static void __reg_module ( void  ) [static]

Definition at line 732 of file res_http_websocket.c.

static void __unreg_module ( void  ) [static]

Definition at line 732 of file res_http_websocket.c.

int AST_OPTIONAL_API_NAME() ast_websocket_add_protocol ( const char *  name,
ast_websocket_callback  callback 
)

Add a sub-protocol handler to the server.

Parameters:
nameName of the sub-protocol to register
callbackCallback called when a new connection requesting the sub-protocol is established
Return values:
0success
-1if sub-protocol handler could not be registered

Definition at line 131 of file res_http_websocket.c.

References ao2_alloc, ao2_find, ao2_link_flags, ao2_lock, ao2_ref, ao2_unlock, ast_strdup, ast_verb, websocket_protocol::callback, websocket_protocol::name, OBJ_KEY, OBJ_NOLOCK, and protocol_destroy_fn().

Referenced by load_module().

{
   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;
}
int AST_OPTIONAL_API_NAME() ast_websocket_close ( struct ast_websocket session,
uint16_t  reason 
)

Close function for websocket session.

Close a WebSocket session by sending a message with the CLOSE opcode and an optional code.

Definition at line 188 of file res_http_websocket.c.

References AST_WEBSOCKET_OPCODE_CLOSE, ast_websocket::closing, ast_websocket::f, and put_unaligned_uint16().

Referenced by ast_websocket_read().

{
   char frame[4] = { 0, }; /* The header is 2 bytes and the reason code takes up another 2 bytes */

   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;

   return (fwrite(frame, 1, 4, session->f) == 4) ? 0 : -1;
}

Get the file descriptor for a WebSocket session.

Return values:
filedescriptor
Note:
You must *not* directly read from or write to this file descriptor. It should only be used for polling.

Definition at line 268 of file res_http_websocket.c.

References ast_websocket::closing, and ast_websocket::fd.

Referenced by sip_prepare_socket(), sip_websocket_callback(), and websocket_echo_callback().

{
   return session->closing ? -1 : session->fd;
}

Get whether the WebSocket session is using a secure transport or not.

Return values:
0if unsecure
1if secure

Definition at line 278 of file res_http_websocket.c.

References ast_websocket::secure.

Referenced by sip_websocket_callback().

{
   return session->secure;
}
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 
)

Read a WebSocket frame and handle it.

Parameters:
sessionPointer to the WebSocket session
payloadPointer to a char* which will be populated with a pointer to the payload if present
payload_lenPointer to a uint64_t which will be populated with the length of the payload if present
opcodePointer to an enum which will be populated with the opcode of the frame
fragmentedPointer to an int which is set to 1 if payload is fragmented and 0 if not
Return values:
-1on error
0on success
Note:
Once an AST_WEBSOCKET_OPCODE_CLOSE opcode is received the socket will be closed

Definition at line 364 of file res_http_websocket.c.

References ast_websocket::address, ast_log(), ast_realloc, ast_sockaddr_stringify(), ast_verb, ast_websocket_close(), AST_WEBSOCKET_OPCODE_BINARY, AST_WEBSOCKET_OPCODE_CLOSE, AST_WEBSOCKET_OPCODE_CONTINUATION, AST_WEBSOCKET_OPCODE_PING, AST_WEBSOCKET_OPCODE_PONG, AST_WEBSOCKET_OPCODE_TEXT, ast_websocket_write(), ast_websocket::closing, ast_websocket::f, frame_size, get_unaligned_uint16(), get_unaligned_uint64(), LOG_WARNING, MAXIMUM_FRAME_SIZE, MIN_WS_HDR_SZ, ast_websocket::opcode, ast_websocket::payload, ast_websocket::payload_len, ast_websocket::reconstruct, and ws_safe_read().

Referenced by sip_websocket_callback(), and websocket_echo_callback().

{
   char buf[MAXIMUM_FRAME_SIZE] = "";
   int fin = 0;
   int mask_present = 0;
   char *mask = NULL, *new_payload = NULL;
   size_t options_len = 0, frame_size = 0;

   *payload = NULL;
   *payload_len = 0;
   *fragmented = 0;

   if (ws_safe_read(session, &buf[0], MIN_WS_HDR_SZ, opcode)) {
      return 0;
   }
   frame_size += MIN_WS_HDR_SZ;

   /* 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) */
   *opcode = buf[0] & 0xf;
   *payload_len = buf[1] & 0x7f;
   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) {
      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)) {
            return 0;
         }
         frame_size += options_len;
      }

      if (*payload_len == 126) {
         /* Grab the 2-byte payload length  */
         *payload_len = ntohs(get_unaligned_uint16(&buf[2]));
         mask = &buf[4];
      } else if (*payload_len == 127) {
         /* Grab the 8-byte payload length  */
         *payload_len = ntohl(get_unaligned_uint64(&buf[2]));
         mask = &buf[10];
      } else {
         /* Just set the mask after the small 2-byte header */
         mask = &buf[2];
      }

      /* 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) {
         ast_log(LOG_WARNING, "Cannot fit huge websocket frame of %zd bytes\n", frame_size);
         /* The frame won't fit :-( */
         ast_websocket_close(session, 1009);
         return -1;
      }

      if (ws_safe_read(session, (*payload), (*payload_len), opcode)) {
         return 0;
      }

      /* 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];
         }
      }

      if (!(new_payload = ast_realloc(session->payload, (session->payload_len + *payload_len)))) {
         ast_log(LOG_WARNING, "Failed allocation: %p, %zd, %"PRIu64"\n",
            session->payload, session->payload_len, *payload_len);
         *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 */
      if (*opcode == AST_WEBSOCKET_OPCODE_PING) {
         ast_websocket_write(session, AST_WEBSOCKET_OPCODE_PONG, *payload, *payload_len);
      }

      session->payload = new_payload;
      memcpy((session->payload + session->payload_len), (*payload), (*payload_len));
      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))) {
         if (ws_safe_read(session, &buf[frame_size], (*payload_len), opcode)) {
            return 0;
         }
         session->payload = new_payload;
         memcpy(session->payload, &buf[frame_size], *payload_len);
         *payload = session->payload;
         frame_size += (*payload_len);
      }

      if (!session->closing) {
         ast_websocket_close(session, 0);
      }

      fclose(session->f);
      session->f = NULL;
      ast_verb(2, "WebSocket connection from '%s' closed\n", ast_sockaddr_stringify(&session->address));
   } else {
      ast_log(LOG_WARNING, "WebSocket unknown opcode %d\n", *opcode);
      /* 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;
}

Disable multi-frame reconstruction.

Parameters:
sessionPointer to the WebSocket session
Note:
If reconstruction is disabled each message that is part of a multi-frame message will be sent up to the user when ast_websocket_read is called.

Definition at line 253 of file res_http_websocket.c.

References ast_websocket::reconstruct.

{
   session->reconstruct = 0;
}
void AST_OPTIONAL_API_NAME() ast_websocket_reconstruct_enable ( struct ast_websocket session,
size_t  bytes 
)

Enable multi-frame reconstruction up to a certain number of bytes.

Parameters:
sessionPointer to the WebSocket session
bytesIf a reconstructed payload exceeds the specified number of bytes the payload will be returned and upon reception of the next multi-frame a new reconstructed payload will begin.

Definition at line 248 of file res_http_websocket.c.

References MAXIMUM_RECONSTRUCTION_CEILING, MIN, and ast_websocket::reconstruct.

Increase the reference count for a WebSocket session.

Parameters:
sessionPointer to the WebSocket session

Definition at line 258 of file res_http_websocket.c.

References ao2_ref.

Referenced by copy_socket_data().

{
   ao2_ref(session, +1);
}

Get the remote address for a WebSocket connected session.

Return values:
ast_sockaddrRemote address

Definition at line 273 of file res_http_websocket.c.

References ast_websocket::address.

Referenced by sip_websocket_callback().

{
   return &session->address;
}
int AST_OPTIONAL_API_NAME() ast_websocket_remove_protocol ( const char *  name,
ast_websocket_callback  callback 
)

Remove a sub-protocol handler from the server.

Parameters:
nameName of the sub-protocol to unregister
callbackCallback that was previously registered with the sub-protocol
Return values:
0success
-1if sub-protocol was not found or if callback did not match

Definition at line 166 of file res_http_websocket.c.

References ao2_find, ao2_ref, ao2_unlink, ast_verb, websocket_protocol::callback, and OBJ_KEY.

Referenced by unload_module().

{
   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;
}

Set the socket of a WebSocket session to be non-blocking.

Return values:
0on success
-1on failure

Definition at line 283 of file res_http_websocket.c.

References ast_websocket::fd.

Referenced by sip_websocket_callback().

{
   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;
}

Decrease the reference count for a WebSocket session.

Parameters:
sessionPointer to the WebSocket session

Definition at line 263 of file res_http_websocket.c.

References ao2_ref.

Referenced by __sip_destroy(), copy_socket_data(), expire_register(), parse_moved_contact(), set_socket_transport(), sip_destroy_peer(), sip_websocket_callback(), and websocket_echo_callback().

{
   ao2_ref(session, -1);
}
int AST_OPTIONAL_API_NAME() ast_websocket_write ( struct ast_websocket session,
enum ast_websocket_opcode  opcode,
char *  payload,
uint64_t  actual_length 
)

Write function for websocket traffic.

Construct and transmit a WebSocket frame.

Definition at line 205 of file res_http_websocket.c.

References ast_alloca, ast_websocket::f, put_unaligned_uint16(), and put_unaligned_uint64().

Referenced by __sip_xmit(), ast_websocket_read(), and websocket_echo_callback().

{
   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;
   }

   frame = ast_alloca(header_size);
   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));
   }

   if (fwrite(frame, 1, header_size, session->f) != header_size) {
      return -1;
   }

   if (fwrite(payload, 1, actual_length, session->f) != actual_length) {
      return -1;
   }
   fflush(session->f);

   return 0;
}
static int protocol_cmp_fn ( void *  obj,
void *  arg,
int  flags 
) [static]

Comparison function for protocols.

Definition at line 103 of file res_http_websocket.c.

References CMP_MATCH, CMP_STOP, websocket_protocol::name, and OBJ_KEY.

Referenced by load_module().

{
   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;
}
static void protocol_destroy_fn ( void *  obj) [static]

Destructor function for protocols.

Definition at line 112 of file res_http_websocket.c.

References ast_free, and websocket_protocol::name.

Referenced by ast_websocket_add_protocol().

{
   struct websocket_protocol *protocol = obj;
   ast_free(protocol->name);
}
static int protocol_hash_fn ( const void *  obj,
const int  flags 
) [static]

Hashing function for protocols.

Definition at line 94 of file res_http_websocket.c.

References ast_str_case_hash(), name, websocket_protocol::name, and OBJ_KEY.

Referenced by load_module().

{
   const struct websocket_protocol *protocol = obj;
   const char *name = obj;

   return ast_str_case_hash(flags & OBJ_KEY ? name : protocol->name);
}
static void session_destroy_fn ( void *  obj) [static]

Destructor function for sessions.

Definition at line 119 of file res_http_websocket.c.

References ast_websocket::address, ast_free, ast_sockaddr_stringify(), ast_verb, ast_websocket::f, and ast_websocket::payload.

Referenced by websocket_callback().

{
   struct ast_websocket *session = obj;

   if (session->f) {
      fclose(session->f);
      ast_verb(2, "WebSocket connection from '%s' closed\n", ast_sockaddr_stringify(&session->address));
   }

   ast_free(session->payload);
}
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 
) [static]

Callback that is executed everytime an HTTP request is received by this module.

Definition at line 504 of file res_http_websocket.c.

References ast_websocket::address, ao2_alloc, ao2_find, ao2_ref, ast_alloca, ast_base64encode(), ast_http_error(), AST_HTTP_GET, ast_log(), ast_sha1_hash_uint(), ast_sockaddr_copy(), ast_sockaddr_stringify(), ast_strip(), ast_strlen_zero(), ast_verb, base64, websocket_protocol::callback, DEFAULT_RECONSTRUCTION_CEILING, ast_websocket::f, ast_tcptls_session_instance::f, ast_websocket::fd, ast_tcptls_session_instance::fd, LOG_WARNING, ast_variable::name, ast_variable::next, OBJ_KEY, ast_websocket::opcode, ast_websocket::reconstruct, ast_tcptls_session_instance::remote_address, ast_websocket::secure, session_destroy_fn(), ast_tcptls_session_instance::ssl, ast_variable::value, version, and WEBSOCKET_GUID.

{
   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")) {
      ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - did not request WebSocket\n",
         ast_sockaddr_stringify(&ser->remote_address));
      ast_http_error(ser, 426, "Upgrade Required", NULL);
      return -1;
   } else if (ast_strlen_zero(requested_protocols)) {
      ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols requested\n",
         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*/
      ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '00/76' chosen\n",
         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 */
      char *combined, base64[64];
      unsigned combined_length;
      uint8_t sha[20];

      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;
      }

      if (!(session = ao2_alloc(sizeof(*session), session_destroy_fn))) {
         ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted\n",
            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;
      }

      combined = ast_alloca(combined_length);
      snprintf(combined, combined_length, "%s%s", key, WEBSOCKET_GUID);
      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);
      fflush(ser->f);
   } else {

      /* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 or completely unknown */
      ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '%d' chosen\n",
         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))) {
      ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - failed to enable keepalive\n",
         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);

   /* By dropping the FILE* from the session it won't get closed when the HTTP server cleans up */
   ser->f = NULL;

   return 0;
}
static void websocket_echo_callback ( struct ast_websocket session,
struct ast_variable parameters,
struct ast_variable headers 
) [static]

Simple echo implementation which echoes received text and binary frames.

Definition at line 668 of file res_http_websocket.c.

References ast_debug, ast_log(), ast_wait_for_input(), ast_websocket_fd(), AST_WEBSOCKET_OPCODE_BINARY, AST_WEBSOCKET_OPCODE_CLOSE, AST_WEBSOCKET_OPCODE_TEXT, ast_websocket_read(), ast_websocket_unref(), ast_websocket_write(), and LOG_WARNING.

Referenced by load_module(), and unload_module().

{
   int flags, res;

   ast_debug(1, "Entering WebSocket echo loop\n");

   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 */
         ast_log(LOG_WARNING, "Read failure during WebSocket echo loop\n");
         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;
      } else {
         ast_debug(1, "Ignored WebSocket opcode %d\n", opcode);
      }
   }

end:
   ast_debug(1, "Exitting WebSocket echo loop\n");
   ast_websocket_unref(session);
}
static int ws_safe_read ( struct ast_websocket session,
char *  buf,
int  len,
enum ast_websocket_opcode opcode 
) [inline, static]

Definition at line 328 of file res_http_websocket.c.

References ast_log(), ast_wait_for_input(), AST_WEBSOCKET_OPCODE_CLOSE, ast_websocket::closing, errno, ast_websocket::f, ast_websocket::fd, len(), LOG_ERROR, and LOG_WARNING.

Referenced by ast_websocket_read().

{
   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;
}

Variable Documentation

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER , .description = "HTTP WebSocket Support" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_CHANNEL_DEPEND, } [static]

Definition at line 732 of file res_http_websocket.c.

Definition at line 732 of file res_http_websocket.c.

struct ao2_container* protocols [static]

Container for registered protocols.

Definition at line 91 of file res_http_websocket.c.

struct ast_http_uri websocketuri [static]

Definition at line 658 of file res_http_websocket.c.