Sat Jul 12 2014 17:18:36

Asterisk developer's documentation


res_http_websocket.c
Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2012, Digium, Inc.
00005  *
00006  * Joshua Colp <jcolp@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief WebSocket support for the Asterisk internal HTTP server
00022  *
00023  * \author Joshua Colp <jcolp@digium.com>
00024  */
00025 
00026 /*** MODULEINFO
00027    <support_level>extended</support_level>
00028  ***/
00029 
00030 #include "asterisk.h"
00031 
00032 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 417310 $")
00033 
00034 #include "asterisk/module.h"
00035 #include "asterisk/http.h"
00036 #include "asterisk/astobj2.h"
00037 #include "asterisk/strings.h"
00038 #include "asterisk/file.h"
00039 #include "asterisk/unaligned.h"
00040 
00041 #define AST_API_MODULE
00042 #include "asterisk/http_websocket.h"
00043 
00044 /*! \brief GUID used to compute the accept key, defined in the specifications */
00045 #define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
00046 
00047 /*! \brief Number of buckets for registered protocols */
00048 #define MAX_PROTOCOL_BUCKETS 7
00049 
00050 /*! \brief Size of the pre-determined buffer for WebSocket frames */
00051 #define MAXIMUM_FRAME_SIZE 8192
00052 
00053 /*! \brief Default reconstruction size for multi-frame payload reconstruction. If exceeded the next frame will start a
00054  *         payload.
00055  */
00056 #define DEFAULT_RECONSTRUCTION_CEILING 16384
00057 
00058 /*! \brief Maximum reconstruction size for multi-frame payload reconstruction. */
00059 #define MAXIMUM_RECONSTRUCTION_CEILING 16384
00060 
00061 /*! \brief Maximum size of a websocket frame header
00062  * 1 byte flags and opcode
00063  * 1 byte mask flag + payload len
00064  * 8 bytes max extended length
00065  * 4 bytes optional masking key
00066  * ... payload follows ...
00067  * */
00068 #define MAX_WS_HDR_SZ 14
00069 #define MIN_WS_HDR_SZ 2
00070 
00071 /*! \brief Structure definition for session */
00072 struct ast_websocket {
00073    FILE *f;                          /*!< Pointer to the file instance used for writing and reading */
00074    int fd;                           /*!< File descriptor for the session, only used for polling */
00075    struct ast_sockaddr address;      /*!< Address of the remote client */
00076    enum ast_websocket_opcode opcode; /*!< Cached opcode for multi-frame messages */
00077    size_t payload_len;               /*!< Length of the payload */
00078    char *payload;                    /*!< Pointer to the payload */
00079    size_t reconstruct;               /*!< Number of bytes before a reconstructed payload will be returned and a new one started */
00080    int timeout;                      /*!< The timeout for operations on the socket */
00081    unsigned int secure:1;            /*!< Bit to indicate that the transport is secure */
00082    unsigned int closing:1;           /*!< Bit to indicate that the session is in the process of being closed */
00083    unsigned int close_sent:1;        /*!< Bit to indicate that the session close opcode has been sent and no further data will be sent */
00084 };
00085 
00086 /*! \brief Structure definition for protocols */
00087 struct websocket_protocol {
00088    char *name;                      /*!< Name of the protocol */
00089    ast_websocket_callback callback; /*!< Callback called when a new session is established */
00090 };
00091 
00092 /*! \brief Container for registered protocols */
00093 static struct ao2_container *protocols;
00094 
00095 /*! \brief Hashing function for protocols */
00096 static int protocol_hash_fn(const void *obj, const int flags)
00097 {
00098    const struct websocket_protocol *protocol = obj;
00099    const char *name = obj;
00100 
00101    return ast_str_case_hash(flags & OBJ_KEY ? name : protocol->name);
00102 }
00103 
00104 /*! \brief Comparison function for protocols */
00105 static int protocol_cmp_fn(void *obj, void *arg, int flags)
00106 {
00107    const struct websocket_protocol *protocol1 = obj, *protocol2 = arg;
00108    const char *protocol = arg;
00109 
00110    return !strcasecmp(protocol1->name, flags & OBJ_KEY ? protocol : protocol2->name) ? CMP_MATCH | CMP_STOP : 0;
00111 }
00112 
00113 /*! \brief Destructor function for protocols */
00114 static void protocol_destroy_fn(void *obj)
00115 {
00116    struct websocket_protocol *protocol = obj;
00117    ast_free(protocol->name);
00118 }
00119 
00120 /*! \brief Destructor function for sessions */
00121 static void session_destroy_fn(void *obj)
00122 {
00123    struct ast_websocket *session = obj;
00124 
00125    ast_websocket_close(session, 0);
00126 
00127    if (session->f) {
00128       fclose(session->f);
00129       ast_verb(2, "WebSocket connection from '%s' closed\n", ast_sockaddr_stringify(&session->address));
00130    }
00131 
00132    ast_free(session->payload);
00133 }
00134 
00135 int AST_OPTIONAL_API_NAME(ast_websocket_add_protocol)(const char *name, ast_websocket_callback callback)
00136 {
00137    struct websocket_protocol *protocol;
00138 
00139    ao2_lock(protocols);
00140 
00141    /* Ensure a second protocol handler is not registered for the same protocol */
00142    if ((protocol = ao2_find(protocols, name, OBJ_KEY | OBJ_NOLOCK))) {
00143       ao2_ref(protocol, -1);
00144       ao2_unlock(protocols);
00145       return -1;
00146    }
00147 
00148    if (!(protocol = ao2_alloc(sizeof(*protocol), protocol_destroy_fn))) {
00149       ao2_unlock(protocols);
00150       return -1;
00151    }
00152 
00153    if (!(protocol->name = ast_strdup(name))) {
00154       ao2_ref(protocol, -1);
00155       ao2_unlock(protocols);
00156       return -1;
00157    }
00158 
00159    protocol->callback = callback;
00160 
00161    ao2_link_flags(protocols, protocol, OBJ_NOLOCK);
00162    ao2_unlock(protocols);
00163    ao2_ref(protocol, -1);
00164 
00165    ast_verb(2, "WebSocket registered sub-protocol '%s'\n", name);
00166 
00167    return 0;
00168 }
00169 
00170 int AST_OPTIONAL_API_NAME(ast_websocket_remove_protocol)(const char *name, ast_websocket_callback callback)
00171 {
00172    struct websocket_protocol *protocol;
00173 
00174    if (!(protocol = ao2_find(protocols, name, OBJ_KEY))) {
00175       return -1;
00176    }
00177 
00178    if (protocol->callback != callback) {
00179       ao2_ref(protocol, -1);
00180       return -1;
00181    }
00182 
00183    ao2_unlink(protocols, protocol);
00184    ao2_ref(protocol, -1);
00185 
00186    ast_verb(2, "WebSocket unregistered sub-protocol '%s'\n", name);
00187 
00188    return 0;
00189 }
00190 
00191 /*! \brief Close function for websocket session */
00192 int AST_OPTIONAL_API_NAME(ast_websocket_close)(struct ast_websocket *session, uint16_t reason)
00193 {
00194    char frame[4] = { 0, }; /* The header is 2 bytes and the reason code takes up another 2 bytes */
00195    int res;
00196 
00197    if (session->close_sent) {
00198       return 0;
00199    }
00200 
00201    frame[0] = AST_WEBSOCKET_OPCODE_CLOSE | 0x80;
00202    frame[1] = 2; /* The reason code is always 2 bytes */
00203 
00204    /* If no reason has been specified assume 1000 which is normal closure */
00205    put_unaligned_uint16(&frame[2], htons(reason ? reason : 1000));
00206 
00207    session->closing = 1;
00208    session->close_sent = 1;
00209 
00210    ao2_lock(session);
00211    res = ast_careful_fwrite(session->f, session->fd, frame, 4, session->timeout);
00212    ao2_unlock(session);
00213 
00214    return res;
00215 }
00216 
00217 
00218 /*! \brief Write function for websocket traffic */
00219 int AST_OPTIONAL_API_NAME(ast_websocket_write)(struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t actual_length)
00220 {
00221    size_t header_size = 2; /* The minimum size of a websocket frame is 2 bytes */
00222    char *frame;
00223    uint64_t length = 0;
00224 
00225    if (actual_length < 126) {
00226       length = actual_length;
00227    } else if (actual_length < (1 << 16)) {
00228       length = 126;
00229       /* We need an additional 2 bytes to store the extended length */
00230       header_size += 2;
00231    } else {
00232       length = 127;
00233       /* We need an additional 8 bytes to store the really really extended length */
00234       header_size += 8;
00235    }
00236 
00237    frame = ast_alloca(header_size);
00238    memset(frame, 0, sizeof(*frame));
00239 
00240    frame[0] = opcode | 0x80;
00241    frame[1] = length;
00242 
00243    /* Use the additional available bytes to store the length */
00244    if (length == 126) {
00245       put_unaligned_uint16(&frame[2], htons(actual_length));
00246    } else if (length == 127) {
00247       put_unaligned_uint64(&frame[2], htonl(actual_length));
00248    }
00249 
00250    ao2_lock(session);
00251    if (session->closing) {
00252       ao2_unlock(session);
00253       return -1;
00254    }
00255 
00256    if (ast_careful_fwrite(session->f, session->fd, frame, header_size, session->timeout)) {
00257       ao2_unlock(session);
00258       return -1;
00259    }
00260 
00261    if (ast_careful_fwrite(session->f, session->fd, payload, actual_length, session->timeout)) {
00262       ao2_unlock(session);
00263       return -1;
00264    }
00265    fflush(session->f);
00266    ao2_unlock(session);
00267 
00268    return 0;
00269 }
00270 
00271 void AST_OPTIONAL_API_NAME(ast_websocket_reconstruct_enable)(struct ast_websocket *session, size_t bytes)
00272 {
00273    session->reconstruct = MIN(bytes, MAXIMUM_RECONSTRUCTION_CEILING);
00274 }
00275 
00276 void AST_OPTIONAL_API_NAME(ast_websocket_reconstruct_disable)(struct ast_websocket *session)
00277 {
00278    session->reconstruct = 0;
00279 }
00280 
00281 void AST_OPTIONAL_API_NAME(ast_websocket_ref)(struct ast_websocket *session)
00282 {
00283    ao2_ref(session, +1);
00284 }
00285 
00286 void AST_OPTIONAL_API_NAME(ast_websocket_unref)(struct ast_websocket *session)
00287 {
00288    ao2_ref(session, -1);
00289 }
00290 
00291 int AST_OPTIONAL_API_NAME(ast_websocket_fd)(struct ast_websocket *session)
00292 {
00293    return session->closing ? -1 : session->fd;
00294 }
00295 
00296 struct ast_sockaddr * AST_OPTIONAL_API_NAME(ast_websocket_remote_address)(struct ast_websocket *session)
00297 {
00298    return &session->address;
00299 }
00300 
00301 int AST_OPTIONAL_API_NAME(ast_websocket_is_secure)(struct ast_websocket *session)
00302 {
00303    return session->secure;
00304 }
00305 
00306 int AST_OPTIONAL_API_NAME(ast_websocket_set_nonblock)(struct ast_websocket *session)
00307 {
00308    int flags;
00309 
00310    if ((flags = fcntl(session->fd, F_GETFL)) == -1) {
00311       return -1;
00312    }
00313 
00314    flags |= O_NONBLOCK;
00315 
00316    if ((flags = fcntl(session->fd, F_SETFL, flags)) == -1) {
00317       return -1;
00318    }
00319 
00320    return 0;
00321 }
00322 
00323 int AST_OPTIONAL_API_NAME(ast_websocket_set_timeout)(struct ast_websocket *session, int timeout)
00324 {
00325    session->timeout = timeout;
00326 
00327    return 0;
00328 }
00329 
00330 /* MAINTENANCE WARNING on ast_websocket_read()!
00331  *
00332  * We have to keep in mind during this function that the fact that session->fd seems ready
00333  * (via poll) does not necessarily mean we have application data ready, because in the case
00334  * of an SSL socket, there is some encryption data overhead that needs to be read from the
00335  * TCP socket, so poll() may say there are bytes to be read, but whether it is just 1 byte
00336  * or N bytes we do not know that, and we do not know how many of those bytes (if any) are
00337  * for application data (for us) and not just for the SSL protocol consumption
00338  *
00339  * There used to be a couple of nasty bugs here that were fixed in last refactoring but I
00340  * want to document them so the constraints are clear and we do not re-introduce them:
00341  *
00342  * - This function would incorrectly assume that fread() would necessarily return more than
00343  *   1 byte of data, just because a websocket frame is always >= 2 bytes, but the thing
00344  *   is we're dealing with a TCP bitstream here, we could read just one byte and that's normal.
00345  *   The problem before was that if just one byte was read, the function bailed out and returned
00346  *   an error, effectively dropping the first byte of a websocket frame header!
00347  *
00348  * - Another subtle bug was that it would just read up to MAX_WS_HDR_SZ (14 bytes) via fread()
00349  *   then assume that executing poll() would tell you if there is more to read, but since
00350  *   we're dealing with a buffered stream (session->f is a FILE*), poll would say there is
00351  *   nothing else to read (in the real tcp socket session->fd) and we would get stuck here
00352  *   without processing the rest of the data in session->f internal buffers until another packet
00353  *   came on the network to unblock us!
00354  *
00355  * Note during the header parsing stage we try to read in small chunks just what we need, this
00356  * is buffered data anyways, no expensive syscall required most of the time ...
00357  */
00358 static inline int ws_safe_read(struct ast_websocket *session, char *buf, int len, enum ast_websocket_opcode *opcode)
00359 {
00360    int sanity;
00361    size_t rlen;
00362    int xlen = len;
00363    char *rbuf = buf;
00364    for (sanity = 10; sanity; sanity--) {
00365       clearerr(session->f);
00366       rlen = fread(rbuf, 1, xlen, session->f);
00367       if (0 == rlen && ferror(session->f) && errno != EAGAIN) {
00368          ast_log(LOG_ERROR, "Error reading from web socket: %s\n", strerror(errno));
00369          (*opcode) = AST_WEBSOCKET_OPCODE_CLOSE;
00370          session->closing = 1;
00371          return -1;
00372       }
00373       xlen = (xlen - rlen);
00374       rbuf = rbuf + rlen;
00375       if (0 == xlen) {
00376          break;
00377       }
00378       if (ast_wait_for_input(session->fd, 1000) < 0) {
00379          ast_log(LOG_ERROR, "ast_wait_for_input returned err: %s\n", strerror(errno));
00380          (*opcode) = AST_WEBSOCKET_OPCODE_CLOSE;
00381          session->closing = 1;
00382          return -1;
00383       }
00384    }
00385    if (!sanity) {
00386       ast_log(LOG_WARNING, "Websocket seems unresponsive, disconnecting ...\n");
00387       (*opcode) = AST_WEBSOCKET_OPCODE_CLOSE;
00388       session->closing = 1;
00389       return -1;
00390    }
00391    return 0;
00392 }
00393 
00394 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)
00395 {
00396    char buf[MAXIMUM_FRAME_SIZE] = "";
00397    int fin = 0;
00398    int mask_present = 0;
00399    char *mask = NULL, *new_payload = NULL;
00400    size_t options_len = 0, frame_size = 0;
00401 
00402    *payload = NULL;
00403    *payload_len = 0;
00404    *fragmented = 0;
00405 
00406    if (ws_safe_read(session, &buf[0], MIN_WS_HDR_SZ, opcode)) {
00407       return 0;
00408    }
00409    frame_size += MIN_WS_HDR_SZ;
00410 
00411    /* 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) */
00412    *opcode = buf[0] & 0xf;
00413    *payload_len = buf[1] & 0x7f;
00414    if (*opcode == AST_WEBSOCKET_OPCODE_TEXT || *opcode == AST_WEBSOCKET_OPCODE_BINARY || *opcode == AST_WEBSOCKET_OPCODE_CONTINUATION ||
00415        *opcode == AST_WEBSOCKET_OPCODE_PING || *opcode == AST_WEBSOCKET_OPCODE_PONG) {
00416       fin = (buf[0] >> 7) & 1;
00417       mask_present = (buf[1] >> 7) & 1;
00418 
00419       /* Based on the mask flag and payload length, determine how much more we need to read before start parsing the rest of the header */
00420       options_len += mask_present ? 4 : 0;
00421       options_len += (*payload_len == 126) ? 2 : (*payload_len == 127) ? 8 : 0;
00422       if (options_len) {
00423          /* read the rest of the header options */
00424          if (ws_safe_read(session, &buf[frame_size], options_len, opcode)) {
00425             return 0;
00426          }
00427          frame_size += options_len;
00428       }
00429 
00430       if (*payload_len == 126) {
00431          /* Grab the 2-byte payload length  */
00432          *payload_len = ntohs(get_unaligned_uint16(&buf[2]));
00433          mask = &buf[4];
00434       } else if (*payload_len == 127) {
00435          /* Grab the 8-byte payload length  */
00436          *payload_len = ntohl(get_unaligned_uint64(&buf[2]));
00437          mask = &buf[10];
00438       } else {
00439          /* Just set the mask after the small 2-byte header */
00440          mask = &buf[2];
00441       }
00442 
00443       /* Now read the rest of the payload */
00444       *payload = &buf[frame_size]; /* payload will start here, at the end of the options, if any */
00445       frame_size = frame_size + (*payload_len); /* final frame size is header + optional headers + payload data */
00446       if (frame_size > MAXIMUM_FRAME_SIZE) {
00447          ast_log(LOG_WARNING, "Cannot fit huge websocket frame of %zu bytes\n", frame_size);
00448          /* The frame won't fit :-( */
00449          ast_websocket_close(session, 1009);
00450          return -1;
00451       }
00452 
00453       if (ws_safe_read(session, (*payload), (*payload_len), opcode)) {
00454          return 0;
00455       }
00456 
00457       /* If a mask is present unmask the payload */
00458       if (mask_present) {
00459          unsigned int pos;
00460          for (pos = 0; pos < *payload_len; pos++) {
00461             (*payload)[pos] ^= mask[pos % 4];
00462          }
00463       }
00464 
00465       if (!(new_payload = ast_realloc(session->payload, (session->payload_len + *payload_len)))) {
00466          ast_log(LOG_WARNING, "Failed allocation: %p, %zu, %"PRIu64"\n",
00467             session->payload, session->payload_len, *payload_len);
00468          *payload_len = 0;
00469          ast_websocket_close(session, 1009);
00470          return 0;
00471       }
00472 
00473       /* Per the RFC for PING we need to send back an opcode with the application data as received */
00474       if ((*opcode == AST_WEBSOCKET_OPCODE_PING) && (ast_websocket_write(session, AST_WEBSOCKET_OPCODE_PONG, *payload, *payload_len))) {
00475          *payload_len = 0;
00476          ast_websocket_close(session, 1009);
00477          return 0;
00478       }
00479 
00480       session->payload = new_payload;
00481       memcpy((session->payload + session->payload_len), (*payload), (*payload_len));
00482       session->payload_len += *payload_len;
00483 
00484       if (!fin && session->reconstruct && (session->payload_len < session->reconstruct)) {
00485          /* If this is not a final message we need to defer returning it until later */
00486          if (*opcode != AST_WEBSOCKET_OPCODE_CONTINUATION) {
00487             session->opcode = *opcode;
00488          }
00489          *opcode = AST_WEBSOCKET_OPCODE_CONTINUATION;
00490          *payload_len = 0;
00491          *payload = NULL;
00492       } else {
00493          if (*opcode == AST_WEBSOCKET_OPCODE_CONTINUATION) {
00494             if (!fin) {
00495                /* If this was not actually the final message tell the user it is fragmented so they can deal with it accordingly */
00496                *fragmented = 1;
00497             } else {
00498                /* Final frame in multi-frame so push up the actual opcode */
00499                *opcode = session->opcode;
00500             }
00501          }
00502          *payload_len = session->payload_len;
00503          *payload = session->payload;
00504          session->payload_len = 0;
00505       }
00506    } else if (*opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
00507       /* Make the payload available so the user can look at the reason code if they so desire */
00508       if ((*payload_len) && (new_payload = ast_realloc(session->payload, *payload_len))) {
00509          if (ws_safe_read(session, &buf[frame_size], (*payload_len), opcode)) {
00510             return 0;
00511          }
00512          session->payload = new_payload;
00513          memcpy(session->payload, &buf[frame_size], *payload_len);
00514          *payload = session->payload;
00515          frame_size += (*payload_len);
00516       }
00517 
00518       session->closing = 1;
00519    } else {
00520       ast_log(LOG_WARNING, "WebSocket unknown opcode %u\n", *opcode);
00521       /* 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
00522        * fit that, I think. */
00523       ast_websocket_close(session, 1003);
00524    }
00525 
00526    return 0;
00527 }
00528 
00529 /*! \brief Callback that is executed everytime an HTTP request is received by this module */
00530 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)
00531 {
00532    struct ast_variable *v;
00533    char *upgrade = NULL, *key = NULL, *key1 = NULL, *key2 = NULL, *protos = NULL, *requested_protocols = NULL, *protocol = NULL;
00534    int version = 0, flags = 1;
00535    struct websocket_protocol *protocol_handler = NULL;
00536    struct ast_websocket *session;
00537 
00538    /* Upgrade requests are only permitted on GET methods */
00539    if (method != AST_HTTP_GET) {
00540       ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
00541       return -1;
00542    }
00543 
00544    /* Get the minimum headers required to satisfy our needs */
00545    for (v = headers; v; v = v->next) {
00546       if (!strcasecmp(v->name, "Upgrade")) {
00547          upgrade = ast_strip(ast_strdupa(v->value));
00548       } else if (!strcasecmp(v->name, "Sec-WebSocket-Key")) {
00549          key = ast_strip(ast_strdupa(v->value));
00550       } else if (!strcasecmp(v->name, "Sec-WebSocket-Key1")) {
00551          key1 = ast_strip(ast_strdupa(v->value));
00552       } else if (!strcasecmp(v->name, "Sec-WebSocket-Key2")) {
00553          key2 = ast_strip(ast_strdupa(v->value));
00554       } else if (!strcasecmp(v->name, "Sec-WebSocket-Protocol")) {
00555          requested_protocols = ast_strip(ast_strdupa(v->value));
00556          protos = ast_strdupa(requested_protocols);
00557       } else if (!strcasecmp(v->name, "Sec-WebSocket-Version")) {
00558          if (sscanf(v->value, "%30d", &version) != 1) {
00559             version = 0;
00560          }
00561       }
00562    }
00563 
00564    /* If this is not a websocket upgrade abort */
00565    if (!upgrade || strcasecmp(upgrade, "websocket")) {
00566       ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - did not request WebSocket\n",
00567          ast_sockaddr_stringify(&ser->remote_address));
00568       ast_http_error(ser, 426, "Upgrade Required", NULL);
00569       return -1;
00570    } else if (ast_strlen_zero(requested_protocols)) {
00571       ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols requested\n",
00572          ast_sockaddr_stringify(&ser->remote_address));
00573       fputs("HTTP/1.1 400 Bad Request\r\n"
00574             "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00575       return -1;
00576    } else if (key1 && key2) {
00577       /* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 and
00578        * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 -- not currently supported*/
00579       ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '00/76' chosen\n",
00580          ast_sockaddr_stringify(&ser->remote_address));
00581       fputs("HTTP/1.1 400 Bad Request\r\n"
00582             "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00583       return 0;
00584    }
00585 
00586    /* Iterate through the requested protocols trying to find one that we have a handler for */
00587    while ((protocol = strsep(&requested_protocols, ","))) {
00588       if ((protocol_handler = ao2_find(protocols, ast_strip(protocol), OBJ_KEY))) {
00589          break;
00590       }
00591    }
00592 
00593    /* If no protocol handler exists bump this back to the requester */
00594    if (!protocol_handler) {
00595       ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols out of '%s' supported\n",
00596          ast_sockaddr_stringify(&ser->remote_address), protos);
00597       fputs("HTTP/1.1 400 Bad Request\r\n"
00598             "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00599       return 0;
00600    }
00601 
00602    /* Determine how to respond depending on the version */
00603    if (version == 7 || version == 8 || version == 13) {
00604       /* Version 7 defined in specification http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07 */
00605       /* Version 8 defined in specification http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 */
00606       /* Version 13 defined in specification http://tools.ietf.org/html/rfc6455 */
00607       char *combined, base64[64];
00608       unsigned combined_length;
00609       uint8_t sha[20];
00610 
00611       combined_length = (key ? strlen(key) : 0) + strlen(WEBSOCKET_GUID) + 1;
00612       if (!key || combined_length > 8192) { /* no stack overflows please */
00613          fputs("HTTP/1.1 400 Bad Request\r\n"
00614                "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00615          ao2_ref(protocol_handler, -1);
00616          return 0;
00617       }
00618 
00619       if (!(session = ao2_alloc(sizeof(*session), session_destroy_fn))) {
00620          ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted\n",
00621             ast_sockaddr_stringify(&ser->remote_address));
00622          fputs("HTTP/1.1 400 Bad Request\r\n"
00623                "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00624          ao2_ref(protocol_handler, -1);
00625          return 0;
00626       }
00627       session->timeout = AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT;
00628 
00629       combined = ast_alloca(combined_length);
00630       snprintf(combined, combined_length, "%s%s", key, WEBSOCKET_GUID);
00631       ast_sha1_hash_uint(sha, combined);
00632       ast_base64encode(base64, (const unsigned char*)sha, 20, sizeof(base64));
00633 
00634       fprintf(ser->f, "HTTP/1.1 101 Switching Protocols\r\n"
00635          "Upgrade: %s\r\n"
00636          "Connection: Upgrade\r\n"
00637          "Sec-WebSocket-Accept: %s\r\n"
00638          "Sec-WebSocket-Protocol: %s\r\n\r\n",
00639          upgrade,
00640          base64,
00641          protocol);
00642       fflush(ser->f);
00643    } else {
00644 
00645       /* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 or completely unknown */
00646       ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '%d' chosen\n",
00647          ast_sockaddr_stringify(&ser->remote_address), version ? version : 75);
00648       fputs("HTTP/1.1 400 Bad Request\r\n"
00649             "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00650       ao2_ref(protocol_handler, -1);
00651       return 0;
00652    }
00653 
00654    /* Enable keepalive on all sessions so the underlying user does not have to */
00655    if (setsockopt(ser->fd, SOL_SOCKET, SO_KEEPALIVE, &flags, sizeof(flags))) {
00656       ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - failed to enable keepalive\n",
00657          ast_sockaddr_stringify(&ser->remote_address));
00658       fputs("HTTP/1.1 400 Bad Request\r\n"
00659             "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00660       ao2_ref(session, -1);
00661       ao2_ref(protocol_handler, -1);
00662       return 0;
00663    }
00664 
00665    ast_verb(2, "WebSocket connection from '%s' for protocol '%s' accepted using version '%d'\n", ast_sockaddr_stringify(&ser->remote_address), protocol, version);
00666 
00667    /* Populate the session with all the needed details */
00668    session->f = ser->f;
00669    session->fd = ser->fd;
00670    ast_sockaddr_copy(&session->address, &ser->remote_address);
00671    session->opcode = -1;
00672    session->reconstruct = DEFAULT_RECONSTRUCTION_CEILING;
00673    session->secure = ser->ssl ? 1 : 0;
00674 
00675    /* Give up ownership of the socket and pass it to the protocol handler */
00676    protocol_handler->callback(session, get_vars, headers);
00677    ao2_ref(protocol_handler, -1);
00678 
00679    /*
00680     * By dropping the FILE* and fd from the session the connection
00681     * won't get closed when the HTTP server cleans up because we
00682     * passed the connection to the protocol handler.
00683     */
00684    ser->f = NULL;
00685    ser->fd = -1;
00686 
00687    return 0;
00688 }
00689 
00690 static struct ast_http_uri websocketuri = {
00691    .callback = websocket_callback,
00692    .description = "Asterisk HTTP WebSocket",
00693    .uri = "ws",
00694    .has_subtree = 0,
00695    .data = NULL,
00696    .key = __FILE__,
00697 };
00698 
00699 /*! \brief Simple echo implementation which echoes received text and binary frames */
00700 static void websocket_echo_callback(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)
00701 {
00702    int flags, res;
00703 
00704    ast_debug(1, "Entering WebSocket echo loop\n");
00705 
00706    if ((flags = fcntl(ast_websocket_fd(session), F_GETFL)) == -1) {
00707       goto end;
00708    }
00709 
00710    flags |= O_NONBLOCK;
00711 
00712    if (fcntl(ast_websocket_fd(session), F_SETFL, flags) == -1) {
00713       goto end;
00714    }
00715 
00716    while ((res = ast_wait_for_input(ast_websocket_fd(session), -1)) > 0) {
00717       char *payload;
00718       uint64_t payload_len;
00719       enum ast_websocket_opcode opcode;
00720       int fragmented;
00721 
00722       if (ast_websocket_read(session, &payload, &payload_len, &opcode, &fragmented)) {
00723          /* We err on the side of caution and terminate the session if any error occurs */
00724          ast_log(LOG_WARNING, "Read failure during WebSocket echo loop\n");
00725          break;
00726       }
00727 
00728       if (opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY) {
00729          ast_websocket_write(session, opcode, payload, payload_len);
00730       } else if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
00731          break;
00732       } else {
00733          ast_debug(1, "Ignored WebSocket opcode %u\n", opcode);
00734       }
00735    }
00736 
00737 end:
00738    ast_debug(1, "Exitting WebSocket echo loop\n");
00739    ast_websocket_unref(session);
00740 }
00741 
00742 static int load_module(void)
00743 {
00744    protocols = ao2_container_alloc(MAX_PROTOCOL_BUCKETS, protocol_hash_fn, protocol_cmp_fn);
00745    ast_http_uri_link(&websocketuri);
00746    ast_websocket_add_protocol("echo", websocket_echo_callback);
00747 
00748    return 0;
00749 }
00750 
00751 static int unload_module(void)
00752 {
00753    ast_websocket_remove_protocol("echo", websocket_echo_callback);
00754    ast_http_uri_unlink(&websocketuri);
00755    ao2_ref(protocols, -1);
00756 
00757    return 0;
00758 }
00759 
00760 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "HTTP WebSocket Support",
00761       .load = load_module,
00762       .unload = unload_module,
00763       .load_pri = AST_MODPRI_CHANNEL_DEPEND,
00764    );