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"
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_info * | ast_module_info = &__mod_info |
| static struct ao2_container * | protocols |
| Container for registered protocols. | |
| static struct ast_http_uri | websocketuri |
WebSocket support for the Asterisk internal HTTP server.
Definition in file res_http_websocket.c.
| #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().
| 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.
| name | Name of the sub-protocol to register |
| callback | Callback called when a new connection requesting the sub-protocol is established |
| 0 | success |
| -1 | if 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;
}
| int AST_OPTIONAL_API_NAME() ast_websocket_fd | ( | struct ast_websocket * | session | ) |
Get the file descriptor for a WebSocket session.
| file | descriptor |
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().
| 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.
| 0 | if unsecure |
| 1 | if 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.
| session | Pointer to the WebSocket session |
| payload | Pointer to a char* which will be populated with a pointer to the payload if present |
| payload_len | Pointer to a uint64_t which will be populated with the length of the payload if present |
| opcode | Pointer to an enum which will be populated with the opcode of the frame |
| fragmented | Pointer to an int which is set to 1 if payload is fragmented and 0 if not |
| -1 | on error |
| 0 | on success |
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;
}
| void AST_OPTIONAL_API_NAME() ast_websocket_reconstruct_disable | ( | struct ast_websocket * | session | ) |
Disable multi-frame reconstruction.
| session | Pointer to the WebSocket session |
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.
| session | Pointer to the WebSocket session |
| bytes | If 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.
{
session->reconstruct = MIN(bytes, MAXIMUM_RECONSTRUCTION_CEILING);
}
| void AST_OPTIONAL_API_NAME() ast_websocket_ref | ( | struct ast_websocket * | session | ) |
Increase the reference count for a WebSocket session.
| session | Pointer 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);
}
| struct ast_sockaddr* AST_OPTIONAL_API_NAME() ast_websocket_remote_address | ( | struct ast_websocket * | session | ) | [read] |
Get the remote address for a WebSocket connected session.
| ast_sockaddr | Remote 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.
| name | Name of the sub-protocol to unregister |
| callback | Callback that was previously registered with the sub-protocol |
| 0 | success |
| -1 | if 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;
}
| int AST_OPTIONAL_API_NAME() ast_websocket_set_nonblock | ( | struct ast_websocket * | session | ) |
Set the socket of a WebSocket session to be non-blocking.
| 0 | on success |
| -1 | on failure |
Definition at line 283 of file res_http_websocket.c.
References ast_websocket::fd.
Referenced by sip_websocket_callback().
| void AST_OPTIONAL_API_NAME() ast_websocket_unref | ( | struct ast_websocket * | session | ) |
Decrease the reference count for a WebSocket session.
| session | Pointer 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 load_module | ( | void | ) | [static] |
Definition at line 710 of file res_http_websocket.c.
References ao2_container_alloc, ast_http_uri_link(), ast_websocket_add_protocol(), MAX_PROTOCOL_BUCKETS, protocol_cmp_fn(), protocol_hash_fn(), and websocket_echo_callback().
{
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 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 unload_module | ( | void | ) | [static] |
Definition at line 719 of file res_http_websocket.c.
References ao2_ref, ast_http_uri_unlink(), ast_websocket_remove_protocol(), and websocket_echo_callback().
{
ast_websocket_remove_protocol("echo", websocket_echo_callback);
ast_http_uri_unlink(&websocketuri);
ao2_ref(protocols, -1);
return 0;
}
| 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;
}
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.
struct ast_module_info* ast_module_info = &__mod_info [static] |
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.