Sat Apr 26 2014 22:01:40

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