Asterisk - The Open Source Telephony Project GIT-master-6144b6b
Loading...
Searching...
No Matches
res_http_websocket.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2012, Digium, Inc.
5 *
6 * Joshua Colp <jcolp@digium.com>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19/*! \file
20 *
21 * \brief WebSocket support for the Asterisk internal HTTP server
22 *
23 * \author Joshua Colp <jcolp@digium.com>
24 */
25
26/*** MODULEINFO
27 <support_level>core</support_level>
28 ***/
29
30#include "asterisk.h"
31
32#include "asterisk/module.h"
33#include "asterisk/http.h"
34#include "asterisk/astobj2.h"
35#include "asterisk/strings.h"
36#include "asterisk/file.h"
37#include "asterisk/sched.h"
38#include "asterisk/unaligned.h"
39#include "asterisk/uri.h"
40#include "asterisk/uuid.h"
41
42#define AST_API_MODULE
44
45/*! \brief GUID used to compute the accept key, defined in the specifications */
46#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
47
48/*! \brief Length of a websocket's client key */
49#define CLIENT_KEY_SIZE 16
50
51/*! \brief Number of buckets for registered protocols */
52#define MAX_PROTOCOL_BUCKETS 7
53
54#ifdef LOW_MEMORY
55/*! \brief Default reconstruction size for multi-frame payload reconstruction. If exceeded the next frame will start a
56 * payload.
57 */
58#define DEFAULT_RECONSTRUCTION_CEILING AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE
59
60/*! \brief Maximum reconstruction size for multi-frame payload reconstruction. */
61#define MAXIMUM_RECONSTRUCTION_CEILING AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE
62#else
63/*! \brief Default reconstruction size for multi-frame payload reconstruction. If exceeded the next frame will start a
64 * payload.
65 */
66#define DEFAULT_RECONSTRUCTION_CEILING AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE
67
68/*! \brief Maximum reconstruction size for multi-frame payload reconstruction. */
69#define MAXIMUM_RECONSTRUCTION_CEILING AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE
70#endif
71
72/*! \brief Maximum size of a websocket frame header
73 * 1 byte flags and opcode
74 * 1 byte mask flag + payload len
75 * 8 bytes max extended length
76 * 4 bytes optional masking key
77 * ... payload follows ...
78 * */
79#define MAX_WS_HDR_SZ 14
80#define MIN_WS_HDR_SZ 2
81
82/*! \brief WS_PING_PAYLOAD
83 * It's possible that a user of this API could be sending their own PINGs
84 * and expecting to see PONGs so we use the PING_PAYLOAD in the PINGs we
85 * send so we can detect that any PONGs we receive are from our own PINGs.
86 */
87#define WS_PING_PAYLOAD "WS_CLIENT_PING"
88#define WS_PING_PAYLOAD_LEN 14
89
91
97
99 /*! Options used to create the client */
101 /*! host portion of client uri */
102 char *host;
103 /*! path for logical websocket connection */
105 /*! unique key used during server handshaking */
106 char *key;
107 /*! container for registered protocols */
109 /*! the protocol accepted by the server */
111 /*! websocket protocol version */
113 /*! tcptls connection arguments */
115 /*! tcptls connection instance */
117 /*! Authentication userid:password */
118 char *userinfo;
119 /*! Suppress connection log messages */
121 /*! Proxy-Authentication userid:password */
123 /*! The ping scheduler timer id */
125 /*! How many missed pong responses currently */
127};
128
129/*! \brief Structure definition for session */
131 struct ast_iostream *stream; /*!< iostream of the connection */
132 struct ast_sockaddr remote_address; /*!< Address of the remote client */
133 struct ast_sockaddr local_address; /*!< Our local address */
134 enum ast_websocket_opcode opcode; /*!< Cached opcode for multi-frame messages */
135 size_t payload_len; /*!< Length of the payload */
136 char *payload; /*!< Pointer to the payload */
137 size_t reconstruct; /*!< Number of bytes before a reconstructed payload will be returned and a new one started */
138 int timeout; /*!< The timeout for operations on the socket */
139 unsigned int secure:1; /*!< Bit to indicate that the transport is secure */
140 unsigned int closing:1; /*!< Bit to indicate that the session is in the process of being closed */
141 unsigned int close_sent:1; /*!< Bit to indicate that the session close opcode has been sent and no further data will be sent */
142 unsigned int non_blocking:1; /*!< Bit to indicate that the socket is non-blocking */
143 struct websocket_client *client; /*!< Client object when connected as a client websocket */
144 char session_id[AST_UUID_STR_LEN]; /*!< The identifier for the websocket session */
145 uint16_t close_status_code; /*!< Status code sent in a CLOSE frame upon shutdown */
146 enum ws_closed_by closed_by; /*!< Who's closing the websocket? */
147 char buf[AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE]; /*!< Fixed buffer for reading data into */
148};
149
150#define WS_SESSION_REMOTE(_session) (_session ? (_session->client ? _session->client->options->uri : ast_sockaddr_stringify(&_session->remote_address)) : "NULL")
151#define ARE_PINGPONGS_ENABLED(_session) (_session && _session->client && _session->client->options->pingpongs && _session->client->ping_sched_timer >= 0)
152
153static const char *closed_by_str[] = {
154 [WS_NOT_CLOSED] = "not closed",
155 [WS_CLOSED_BY_REMOTE] = "remote",
156 [WS_CLOSED_BY_US] = "local"
157};
158
159static const char *closed_by_to_str(enum ws_closed_by closed_by)
160{
161 if (!ARRAY_IN_BOUNDS(closed_by, closed_by_str)) {
162 return "unknown";
163 }
164 return closed_by_str[closed_by];
165}
166
168{
169 switch (type) {
171 return "persistent";
173 return "per_call";
175 return "per_call_config";
177 return "client";
179 return "inbound";
181 return "server";
182 case AST_WS_TYPE_ANY:
183 return "any";
184 default:
185 return "unknown";
186 }
187}
188
189/*! \brief Hashing function for protocols */
190static int protocol_hash_fn(const void *obj, const int flags)
191{
192 const struct ast_websocket_protocol *protocol = obj;
193 const char *name = obj;
194
195 return ast_str_case_hash(flags & OBJ_KEY ? name : protocol->name);
196}
197
198/*! \brief Comparison function for protocols */
199static int protocol_cmp_fn(void *obj, void *arg, int flags)
200{
201 const struct ast_websocket_protocol *protocol1 = obj, *protocol2 = arg;
202 const char *protocol = arg;
203
204 return !strcasecmp(protocol1->name, flags & OBJ_KEY ? protocol : protocol2->name) ? CMP_MATCH | CMP_STOP : 0;
205}
206
207/*! \brief Destructor function for protocols */
208static void protocol_destroy_fn(void *obj)
209{
210 struct ast_websocket_protocol *protocol = obj;
211 ast_free(protocol->name);
212}
213
214/*! \brief Structure for a WebSocket server */
216 struct ao2_container *protocols; /*!< Container for registered protocols */
217};
218
219static void websocket_server_dtor(void *obj)
220{
221 struct ast_websocket_server *server = obj;
222 ao2_cleanup(server->protocols);
223 server->protocols = NULL;
224}
225
227{
228 RAII_VAR(struct ast_websocket_server *, server, NULL, ao2_cleanup);
229
230 server = ao2_alloc(sizeof(*server), websocket_server_dtor);
231 if (!server) {
232 return NULL;
233 }
234
237 if (!server->protocols) {
238 return NULL;
239 }
240
241 ao2_ref(server, +1);
242 return server;
243}
244
249
254
255/*! \brief Destructor function for sessions */
256static void session_destroy_fn(void *obj)
257{
258 struct ast_websocket *session = obj;
260 SCOPE_ENTER(2, "%s: Session %p destructor\n", id, obj);
261
262 if (session->stream) {
263 ast_websocket_close(session, session->close_status_code);
264 if (session->stream) {
266 session->stream = NULL;
267 ast_trace(-1, "%s: WebSocket connection closed\n", WS_SESSION_REMOTE(session));
268 }
269 }
270
271 ao2_cleanup(session->client);
272 ast_free(session->payload);
273 SCOPE_EXIT_RTN("%s; Session %p destructor complete\n", id, obj);
274}
275
277{
278 struct ast_websocket_protocol *protocol;
279
280 protocol = ao2_alloc(sizeof(*protocol), protocol_destroy_fn);
281 if (!protocol) {
282 return NULL;
283 }
284
285 protocol->name = ast_strdup(name);
286 if (!protocol->name) {
287 ao2_ref(protocol, -1);
288 return NULL;
289 }
291
292 return protocol;
293}
294
296{
297 struct ast_websocket_protocol *protocol;
298
299 if (!server->protocols) {
300 return -1;
301 }
302
304 if (!protocol) {
305 return -1;
306 }
307 protocol->session_established = callback;
308
309 if (ast_websocket_server_add_protocol2(server, protocol)) {
310 ao2_ref(protocol, -1);
311 return -1;
312 }
313
314 return 0;
315}
316
318{
319 struct ast_websocket_protocol *existing;
320
321 if (!server->protocols) {
322 return -1;
323 }
324
325 if (protocol->version != AST_WEBSOCKET_PROTOCOL_VERSION) {
326 ast_log(LOG_WARNING, "WebSocket could not register sub-protocol '%s': "
327 "expected version '%u', got version '%u'\n",
328 protocol->name, AST_WEBSOCKET_PROTOCOL_VERSION, protocol->version);
329 return -1;
330 }
331
332 ao2_lock(server->protocols);
333
334 /* Ensure a second protocol handler is not registered for the same protocol */
335 existing = ao2_find(server->protocols, protocol->name, OBJ_KEY | OBJ_NOLOCK);
336 if (existing) {
337 ao2_ref(existing, -1);
338 ao2_unlock(server->protocols);
339 return -1;
340 }
341
342 ao2_link_flags(server->protocols, protocol, OBJ_NOLOCK);
343 ao2_unlock(server->protocols);
344
345 ast_debug(1, "WebSocket registered sub-protocol '%s'\n", protocol->name);
346 ao2_ref(protocol, -1);
347
348 return 0;
349}
350
352{
353 struct ast_websocket_protocol *protocol;
354
355 if (!(protocol = ao2_find(server->protocols, name, OBJ_KEY))) {
356 return -1;
357 }
358
359 if (protocol->session_established != callback) {
360 ao2_ref(protocol, -1);
361 return -1;
362 }
363
364 ao2_unlink(server->protocols, protocol);
365 ao2_ref(protocol, -1);
366
367 ast_debug(1, "WebSocket unregistered sub-protocol '%s'\n", name);
368
369 return 0;
370}
371
372/*! \brief Perform payload masking for client sessions */
373static void websocket_mask_payload(struct ast_websocket *session, char *frame, char *payload, uint64_t payload_size)
374{
375 /* RFC 6455 5.1 - clients MUST mask frame data */
376 if (session->client) {
377 uint64_t i;
378 uint8_t mask_key_idx;
379 uint32_t mask_key = ast_random();
380 uint8_t length = frame[1] & 0x7f;
381 frame[1] |= 0x80; /* set mask bit to 1 */
382 /* The mask key octet position depends on the length */
383 mask_key_idx = length == 126 ? 4 : length == 127 ? 10 : 2;
384 put_unaligned_uint32(&frame[mask_key_idx], mask_key);
385 for (i = 0; i < payload_size; i++) {
386 payload[i] ^= ((char *)&mask_key)[i % 4];
387 }
388 }
389}
390
391static const char *opcode_map[] = {
392 [AST_WEBSOCKET_OPCODE_CONTINUATION] = "continuation",
393 [AST_WEBSOCKET_OPCODE_TEXT] = "text",
394 [AST_WEBSOCKET_OPCODE_BINARY] = "binary",
395 [AST_WEBSOCKET_OPCODE_CLOSE] = "close",
396 [AST_WEBSOCKET_OPCODE_PING] = "ping",
397 [AST_WEBSOCKET_OPCODE_PONG] = "pong",
398};
399
400static const char *websocket_opcode2str(enum ast_websocket_opcode opcode)
401{
402 if (!ARRAY_IN_BOUNDS(opcode, opcode_map)) {
403 return "<unknown>";
404 }
405 return opcode_map[opcode];
406}
407
409{
411 SCOPE_ENTER(2, "%s: Cancelling PING/PONG keepalives\n", WS_SESSION_REMOTE(session));
412
413 if (!enabled) {
414 SCOPE_EXIT_RTN("%s: Not enabled, cancel not needed\n", WS_SESSION_REMOTE(session));
415 }
416 AST_SCHED_DEL(ping_scheduler, session->client->ping_sched_timer);
417 ao2_ref(session, -1);
418 SCOPE_EXIT_RTN("%s: Cancelled PING/PONG keepalives\n", WS_SESSION_REMOTE(session));
419}
420
421static int websocket_close(struct ast_websocket *session, uint16_t reason, int force)
422{
424 /* The header is either 2 or 6 bytes and the
425 * reason code takes up another 2 bytes */
426 char frame[8] = { 0, };
427 int header_size, fsize, res = 0;
428 int fd = session->stream ? ast_iostream_get_fd(session->stream) : -1;
429 SCOPE_ENTER(2, "%s: Close requested. Reason: %s (%d) Closed by: %s Force: %s\n", WS_SESSION_REMOTE(session),
430 ast_websocket_status_to_str(reason), reason, closed_by_to_str(session->closed_by), AST_YESNO(force));
431
433
435 if (session->closing) {
437 SCOPE_EXIT_RTN_VALUE(0, "%s: Close already sent\n", WS_SESSION_REMOTE(session));
438 }
439
440 session->closing = 1;
441
442 if (!session->stream) {
444 SCOPE_EXIT_RTN_VALUE(-1, "%s: WebSocket stream already closed\n", WS_SESSION_REMOTE(session));
445 }
446
447 if (force) {
448 ast_trace(-1, "%s: Forcing close. Handle: %p FD: %d\n", WS_SESSION_REMOTE(session),
449 session->stream, fd);
451 session->stream = NULL;
453 SCOPE_EXIT_RTN_VALUE(-1, "%s: Forced close\n", WS_SESSION_REMOTE(session));
454 }
455
456 /* clients need space for an additional 4 byte masking key */
457 header_size = session->client ? 6 : 2;
458 fsize = header_size + 2;
459 session->close_sent = 1;
460
461 frame[0] = opcode | 0x80;
462 frame[1] = 2; /* The reason code is always 2 bytes */
463
464 /*
465 * If the remote initiated the close we should respond with the same
466 * reason code they sent.
467 */
468 if (session->closed_by == WS_CLOSED_BY_REMOTE) {
469 reason = session->close_status_code;
470 }
471
472 /* If no reason has been specified assume 1000 which is normal closure */
473 put_unaligned_uint16(&frame[header_size], htons(reason ? reason : 1000));
474
475 websocket_mask_payload(session, frame, &frame[header_size], 2);
476 ast_trace(-1, "%s: Writing %sCLOSE frame with reason %s (%d). fd: %d\n", WS_SESSION_REMOTE(session),
477 session->closed_by == WS_CLOSED_BY_REMOTE ? "reply " : "", ast_websocket_status_to_str(reason), reason, fd);
478
480 res = ast_iostream_write(session->stream, frame, fsize);
482
483 /*
484 * If the remote initiated the close or we failed to send,
485 * we can close the socket. If we just sent a CLOSE of our own, we need to wait
486 * until we get the close reply from the remote.
487 */
488 if (session->closed_by == WS_CLOSED_BY_REMOTE || res != fsize) {
489 ast_trace(-1, "%s: %s Closing socket. fd: %d\n",
490 session->closed_by == WS_CLOSED_BY_REMOTE ? "Wrote CLOSE reply." : "Writing CLOSE failed.",
492
494 session->stream = NULL;
496 SCOPE_EXIT_RTN_VALUE(0, "%s: Socket closed after %s\n", WS_SESSION_REMOTE(session),
497 session->closed_by == WS_CLOSED_BY_REMOTE ? "reply" : "write failure");
498 }
499
501 SCOPE_EXIT_RTN_VALUE(res == sizeof(frame), "%s: Close done\n", WS_SESSION_REMOTE(session));
502}
503
504static int websocket_handled_pong_or_close(struct ast_websocket *session, char *payload, uint64_t payload_len,
505 enum ast_websocket_opcode opcode)
506{
507 SCOPE_ENTER(4, "%s: Opcode: %s\n", WS_SESSION_REMOTE(session), websocket_opcode2str(opcode));
508
509 if (opcode == AST_WEBSOCKET_OPCODE_PONG) {
510 /*
511 * If it's from our own PING, reset the missed count.
512 */
513 if (session->client->missed_pong_count
514 && payload_len == WS_PING_PAYLOAD_LEN
515 && strncmp(payload, WS_PING_PAYLOAD, payload_len) == 0) {
516 int mpc = session->client->missed_pong_count;
517
518 session->client->missed_pong_count = 0;
519 SCOPE_EXIT_RTN_VALUE(1, "%s: Received PONG from our own PING. Missed count was: %d. Cleared.\n",
521 } else {
522 SCOPE_EXIT_RTN_VALUE(0, "%s: Received PONG. Passing up to client.\n", WS_SESSION_REMOTE(session));
523
524 }
525 }
526
527 if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
528 if (payload_len >= 2) {
529 session->close_status_code = ntohs(get_unaligned_uint16(payload));
530 }
531 if (session->closed_by == WS_NOT_CLOSED) {
532 session->closed_by = WS_CLOSED_BY_REMOTE;
533 SCOPE_EXIT_RTN_VALUE(1, "%s: Handled CLOSE request by remote with reason %s (%d)\n", WS_SESSION_REMOTE(session),
534 ast_websocket_status_to_str(session->close_status_code), session->close_status_code);
535 }
536
537 ast_trace(-1, "%s: Received CLOSE response from remote with reason: %s (%d)\n",
539 session->close_status_code);
540 /*
541 * We got the close response so we can now clean up the socket.
542 */
543 websocket_close(session, session->close_status_code, 1);
544
545
546 SCOPE_EXIT_RTN_VALUE(1, "%s: Handled CLOSE\n", WS_SESSION_REMOTE(session));
547 }
548
549 SCOPE_EXIT_RTN_VALUE(0, "%s: Unhandled %s opcode\n", WS_SESSION_REMOTE(session), websocket_opcode2str(opcode));
550}
551
552/*! \brief Close function for websocket session */
554{
555 if (session->closed_by == WS_NOT_CLOSED) {
556 session->closed_by = WS_CLOSED_BY_US;
557 }
558
559 return websocket_close(session, reason, 0);
560}
561
562/*! \brief Write function for websocket traffic */
564 char *payload, uint64_t payload_size)
565{
566 size_t header_size = 2; /* The minimum size of a websocket frame is 2 bytes */
567 char *frame;
568 uint64_t length;
569 uint64_t frame_size;
570 SCOPE_ENTER(4, "%s: Opcode: %s Length: %"PRIu64"\n",
572
573 if (payload_size < 126) {
574 length = payload_size;
575 } else if (payload_size < (1 << 16)) {
576 length = 126;
577 /* We need an additional 2 bytes to store the extended length */
578 header_size += 2;
579 } else {
580 length = 127;
581 /* We need an additional 8 bytes to store the really really extended length */
582 header_size += 8;
583 }
584
585 if (session->client) {
586 /* Additional 4 bytes for the client masking key */
587 header_size += 4;
588 }
589
590 frame_size = header_size + payload_size;
591
592 frame = ast_alloca(frame_size + 1);
593 memset(frame, 0, frame_size + 1);
594
595 frame[0] = opcode | 0x80;
596 frame[1] = length;
597
598 /* Use the additional available bytes to store the length */
599 if (length == 126) {
600 put_unaligned_uint16(&frame[2], htons(payload_size));
601 } else if (length == 127) {
602 put_unaligned_uint64(&frame[2], htonll(payload_size));
603 }
604
605 memcpy(&frame[header_size], payload, payload_size);
606
607 websocket_mask_payload(session, frame, &frame[header_size], payload_size);
608
610 if (session->closing) {
612 SCOPE_EXIT_RTN_VALUE(-1, "%s: Websocket already closing\n", WS_SESSION_REMOTE(session));
613 }
614
616 if (ast_iostream_write(session->stream, frame, frame_size) != frame_size) {
618 /* 1011 - server terminating connection due to not being able to fulfill the request */
619 ast_trace(-1, "%s: Closing WS with 1011 because we can't fulfill a write request\n",
621 websocket_close(session, 1011, 1);
622 SCOPE_EXIT_RTN_VALUE(-1, "%s: Closed WS with 1011 because we couldn't fulfill a write request\n",
624 }
625
628
629 SCOPE_EXIT_RTN_VALUE(0, "%s: Wrote opcode: %s length: %"PRIu64"\n",
631}
632
637
642
644{
646 int refcount = session ? ao2_ref(session, 0) : 0;
647 SCOPE_ENTER(2, "%s: Reffing. Refcount: %d\n", id, refcount);
648 ao2_ref(session, +1);
649 SCOPE_EXIT("%s: Reffed. Refcount: %d\n", id, session ? refcount - 1 : 0);
650}
651
653{
655 int refcount = session ? ao2_ref(session, 0) : 0;
656 SCOPE_ENTER(2, "%s: Unreffing. Refcount: %d\n", id, refcount);
657
659 SCOPE_EXIT("%s: Unreffed. Refcount: %d\n", id, session ? refcount - 1 : 0);
660}
661
663{
664 return session->closing ? -1 : ast_iostream_get_fd(session->stream);
665}
666
671
673{
674 return &session->remote_address;
675}
676
678{
679 return &session->local_address;
680}
681
686
693
695{
696 session->timeout = timeout;
697
698 return 0;
699}
700
702{
703 return session->session_id;
704}
705
706
707/* MAINTENANCE WARNING on ast_websocket_read()!
708 *
709 * We have to keep in mind during this function that the fact that session->fd seems ready
710 * (via poll) does not necessarily mean we have application data ready, because in the case
711 * of an SSL socket, there is some encryption data overhead that needs to be read from the
712 * TCP socket, so poll() may say there are bytes to be read, but whether it is just 1 byte
713 * or N bytes we do not know that, and we do not know how many of those bytes (if any) are
714 * for application data (for us) and not just for the SSL protocol consumption
715 *
716 * There used to be a couple of nasty bugs here that were fixed in last refactoring but I
717 * want to document them so the constraints are clear and we do not re-introduce them:
718 *
719 * - This function would incorrectly assume that fread() would necessarily return more than
720 * 1 byte of data, just because a websocket frame is always >= 2 bytes, but the thing
721 * is we're dealing with a TCP bitstream here, we could read just one byte and that's normal.
722 * The problem before was that if just one byte was read, the function bailed out and returned
723 * an error, effectively dropping the first byte of a websocket frame header!
724 *
725 * - Another subtle bug was that it would just read up to MAX_WS_HDR_SZ (14 bytes) via fread()
726 * then assume that executing poll() would tell you if there is more to read, but since
727 * we're dealing with a buffered stream (session->f is a FILE*), poll would say there is
728 * nothing else to read (in the real tcp socket session->fd) and we would get stuck here
729 * without processing the rest of the data in session->f internal buffers until another packet
730 * came on the network to unblock us!
731 *
732 * Note during the header parsing stage we try to read in small chunks just what we need, this
733 * is buffered data anyways, no expensive syscall required most of the time ...
734 */
735static inline int ws_safe_read(struct ast_websocket *session, char *buf, size_t len, enum ast_websocket_opcode *opcode)
736{
737 ssize_t rlen;
738 int xlen = len;
739 char *rbuf = buf;
740 int sanity = 10;
741
742 ast_assert(len > 0);
743
744 if (!len) {
745 errno = EINVAL;
746 return -1;
747 }
748
750 if (!session->stream) {
752 errno = ECONNABORTED;
753 return -1;
754 }
755
756 for (;;) {
757 rlen = ast_iostream_read(session->stream, rbuf, xlen);
758 if (rlen != xlen) {
759 if (rlen == 0) {
760 ast_log(LOG_WARNING, "Web socket closed abruptly\n");
762 session->closing = 1;
764 return -1;
765 }
766
767 if (rlen < 0 && errno != EAGAIN) {
768 ast_log(LOG_ERROR, "Error reading from web socket: %s\n", strerror(errno));
770 session->closing = 1;
772 return -1;
773 }
774
775 if (!--sanity) {
776 ast_log(LOG_WARNING, "Websocket seems unresponsive, disconnecting ...\n");
778 session->closing = 1;
780 return -1;
781 }
782 }
783 if (rlen > 0) {
784 xlen = xlen - rlen;
785 rbuf = rbuf + rlen;
786 if (!xlen) {
787 break;
788 }
789 }
790 if (ast_iostream_wait_for_input(session->stream, 1000) < 0) {
791 ast_log(LOG_ERROR, "ast_iostream_wait_for_input returned err: %s\n", strerror(errno));
793 session->closing = 1;
795 return -1;
796 }
797 }
798
800 return 0;
801}
802
804{
805 int fin = 0;
806 int mask_present = 0;
807 char *mask = NULL, *new_payload = NULL;
808 size_t options_len = 0, frame_size = 0;
809 SCOPE_ENTER(4, "%s: Reading\n", WS_SESSION_REMOTE(session));
810
811 *payload = NULL;
812 *payload_len = 0;
813 *fragmented = 0;
814
816 SCOPE_EXIT_RTN_VALUE(-1, "%s: Initial ws_safe_read failed\n", WS_SESSION_REMOTE(session));
817 }
819
820 /* 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) */
821 *opcode = session->buf[0] & 0xf;
822 *payload_len = session->buf[1] & 0x7f;
825 fin = (session->buf[0] >> 7) & 1;
826 mask_present = (session->buf[1] >> 7) & 1;
827
828 /* Based on the mask flag and payload length, determine how much more we need to read before start parsing the rest of the header */
829 options_len += mask_present ? 4 : 0;
830 options_len += (*payload_len == 126) ? 2 : (*payload_len == 127) ? 8 : 0;
831 if (options_len) {
832 /* read the rest of the header options */
833 if (ws_safe_read(session, &session->buf[frame_size], options_len, opcode)) {
834 SCOPE_EXIT_RTN_VALUE(-1, "%s: ws_safe_read of options failed\n", WS_SESSION_REMOTE(session));
835 }
836 frame_size += options_len;
837 }
838
839 if (*payload_len == 126) {
840 /* Grab the 2-byte payload length */
841 *payload_len = ntohs(get_unaligned_uint16(&session->buf[2]));
842 mask = &session->buf[4];
843 } else if (*payload_len == 127) {
844 /* Grab the 8-byte payload length */
846 mask = &session->buf[10];
847 } else {
848 /* Just set the mask after the small 2-byte header */
849 mask = &session->buf[2];
850 }
851
852 /* Now read the rest of the payload */
853 *payload = &session->buf[frame_size]; /* payload will start here, at the end of the options, if any */
854 frame_size = frame_size + (*payload_len); /* final frame size is header + optional headers + payload data */
856 /* The frame won't fit :-( */
858 SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Cannot fit huge websocket frame of %zu bytes\n",
860 }
861
862 if (*payload_len) {
864 SCOPE_EXIT_RTN_VALUE(-1, "%s: ws_safe_read of payload failed\n", WS_SESSION_REMOTE(session));
865 }
866 }
867
868 /* If a mask is present unmask the payload */
869 if (mask_present) {
870 unsigned int pos;
871 for (pos = 0; pos < *payload_len; pos++) {
872 (*payload)[pos] ^= mask[pos % 4];
873 }
874 }
875
876 /* Per the RFC for PING we need to send back an opcode with the application data as received */
880 }
881 *payload_len = 0;
882 SCOPE_EXIT_RTN_VALUE(0, "%s: PING received. Sent PONG\n", WS_SESSION_REMOTE(session));
883 }
884
886 SCOPE_EXIT_RTN_VALUE(0, "%s: Handled PONG or CLOSE\n", WS_SESSION_REMOTE(session));
887 }
888
889 /* Below this point we are handling TEXT, BINARY or CONTINUATION opcodes */
890 if (*payload_len) {
891 if (!(new_payload = ast_realloc(session->payload, (session->payload_len + *payload_len)))) {
892 *payload_len = 0;
894 SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Failed allocation: %p, %zu, %"PRIu64"\n",
895 WS_SESSION_REMOTE(session), session->payload, session->payload_len, *payload_len);
896 }
897
898 session->payload = new_payload;
899 memcpy((session->payload + session->payload_len), (*payload), (*payload_len));
900 session->payload_len += *payload_len;
901 } else if (!session->payload_len && session->payload) {
902 ast_free(session->payload);
903 session->payload = NULL;
904 }
905
906 if (!fin && session->reconstruct && (session->payload_len < session->reconstruct)) {
907 /* If this is not a final message we need to defer returning it until later */
909 session->opcode = *opcode;
910 }
912 *payload_len = 0;
913 *payload = NULL;
914 } else {
916 if (!fin) {
917 /* If this was not actually the final message tell the user it is fragmented so they can deal with it accordingly */
918 *fragmented = 1;
919 } else {
920 /* Final frame in multi-frame so push up the actual opcode */
921 *opcode = session->opcode;
922 }
923 }
924 *payload_len = session->payload_len;
925 *payload = session->payload;
926 session->payload_len = 0;
927 }
928 } else {
929 ast_log(LOG_WARNING, "WebSocket unknown opcode %u\n", *opcode);
930 /* 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
931 * fit that, I think. */
933 }
934
935 SCOPE_EXIT_RTN_VALUE(0, "%s: Read complete. Opcode: %s Length: %"PRIu64"\n",
937}
938
939/*!
940 * \brief If the server has exactly one configured protocol, return it.
941 */
943 struct ast_websocket_server *server)
944{
945 SCOPED_AO2LOCK(lock, server->protocols);
946
947 if (ao2_container_count(server->protocols) != 1) {
948 return NULL;
949 }
950
951 return ao2_callback(server->protocols, OBJ_NOLOCK, NULL, NULL);
952}
953
954static char *websocket_combine_key(const char *key, char *res, int res_size)
955{
956 char *combined;
957 unsigned combined_length = strlen(key) + strlen(WEBSOCKET_GUID) + 1;
958 uint8_t sha[20];
959
960 combined = ast_alloca(combined_length);
961 snprintf(combined, combined_length, "%s%s", key, WEBSOCKET_GUID);
962 ast_sha1_hash_uint(sha, combined);
963 ast_base64encode(res, (const unsigned char*)sha, 20, res_size);
964 return res;
965}
966
968{
969 struct ast_str *http_header = ast_str_create(64);
970
971 if (!http_header) {
973 ast_http_error(ser, 500, "Server Error", "Out of memory");
974 return;
975 }
976 ast_str_set(&http_header, 0, "Sec-WebSocket-Version: 7, 8, 13\r\n");
977 ast_http_send(ser, AST_HTTP_UNKNOWN, 400, "Bad Request", http_header, NULL, 0, 0);
978}
979
980int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(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)
981{
982 struct ast_variable *v;
983 const char *upgrade = NULL, *key = NULL, *key1 = NULL, *key2 = NULL, *protos = NULL;
984 char *requested_protocols = NULL, *protocol = NULL;
985 int version = 0, flags = 1;
986 struct ast_websocket_protocol *protocol_handler = NULL;
987 struct ast_websocket *session;
988 struct ast_websocket_server *server;
989
991
992 /* Upgrade requests are only permitted on GET methods */
993 if (method != AST_HTTP_GET) {
994 ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
995 return 0;
996 }
997
998 server = urih->data;
999
1000 /* Get the minimum headers required to satisfy our needs */
1001 for (v = headers; v; v = v->next) {
1002 if (!strcasecmp(v->name, "Upgrade")) {
1003 upgrade = v->value;
1004 } else if (!strcasecmp(v->name, "Sec-WebSocket-Key")) {
1005 key = v->value;
1006 } else if (!strcasecmp(v->name, "Sec-WebSocket-Key1")) {
1007 key1 = v->value;
1008 } else if (!strcasecmp(v->name, "Sec-WebSocket-Key2")) {
1009 key2 = v->value;
1010 } else if (!strcasecmp(v->name, "Sec-WebSocket-Protocol")) {
1011 protos = v->value;
1012 } else if (!strcasecmp(v->name, "Sec-WebSocket-Version")) {
1013 if (sscanf(v->value, "%30d", &version) != 1) {
1014 version = 0;
1015 }
1016 }
1017 }
1018
1019 /* If this is not a websocket upgrade abort */
1020 if (!upgrade || strcasecmp(upgrade, "websocket")) {
1021 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - did not request WebSocket\n",
1023 ast_http_error(ser, 426, "Upgrade Required", NULL);
1024 return 0;
1025 } else if (ast_strlen_zero(protos)) {
1026 /* If there's only a single protocol registered, and the
1027 * client doesn't specify what protocol it's using, go ahead
1028 * and accept the connection */
1029 protocol_handler = one_protocol(server);
1030 if (!protocol_handler) {
1031 /* Multiple registered subprotocols; client must specify */
1032 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols requested\n",
1035 return 0;
1036 }
1037 } else if (key1 && key2) {
1038 /* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 and
1039 * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 -- not currently supported*/
1040 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '00/76' chosen\n",
1043 return 0;
1044 }
1045
1046 if (!protocol_handler && protos) {
1047 requested_protocols = ast_strdupa(protos);
1048 /* Iterate through the requested protocols trying to find one that we have a handler for */
1049 while (!protocol_handler && (protocol = strsep(&requested_protocols, ","))) {
1050 protocol_handler = ao2_find(server->protocols, ast_strip(protocol), OBJ_KEY);
1051 }
1052 }
1053
1054 /* If no protocol handler exists bump this back to the requester */
1055 if (!protocol_handler) {
1056 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols out of '%s' supported\n",
1057 ast_sockaddr_stringify(&ser->remote_address), protos);
1059 return 0;
1060 }
1061
1062 /* Determine how to respond depending on the version */
1063 if (version == 7 || version == 8 || version == 13) {
1064 char base64[64];
1065
1066 if (!key || strlen(key) + strlen(WEBSOCKET_GUID) + 1 > 8192) { /* no stack overflows please */
1068 ao2_ref(protocol_handler, -1);
1069 return 0;
1070 }
1071
1072 if (ast_http_body_discard(ser)) {
1074 ao2_ref(protocol_handler, -1);
1075 return 0;
1076 }
1077
1078 if (!(session = ao2_alloc(sizeof(*session) + AST_UUID_STR_LEN + 1, session_destroy_fn))) {
1079 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted\n",
1082 ao2_ref(protocol_handler, -1);
1083 return 0;
1084 }
1086
1087 /* Generate the session id */
1088 if (!ast_uuid_generate_str(session->session_id, sizeof(session->session_id))) {
1089 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - failed to generate a session id\n",
1091 ast_http_error(ser, 500, "Internal Server Error", "Allocation failed");
1092 ao2_ref(protocol_handler, -1);
1093 return 0;
1094 }
1095
1096 if (protocol_handler->session_attempted
1097 && protocol_handler->session_attempted(ser, get_vars, headers, session->session_id)) {
1098 ast_debug(3, "WebSocket connection from '%s' rejected by protocol handler '%s'\n",
1099 ast_sockaddr_stringify(&ser->remote_address), protocol_handler->name);
1101 ao2_ref(protocol_handler, -1);
1102 return 0;
1103 }
1104
1105 /* RFC 6455, Section 4.1:
1106 *
1107 * 6. If the response includes a |Sec-WebSocket-Protocol| header
1108 * field and this header field indicates the use of a
1109 * subprotocol that was not present in the client's handshake
1110 * (the server has indicated a subprotocol not requested by
1111 * the client), the client MUST _Fail the WebSocket
1112 * Connection_.
1113 */
1114 if (protocol) {
1116 "HTTP/1.1 101 Switching Protocols\r\n"
1117 "Upgrade: %s\r\n"
1118 "Connection: Upgrade\r\n"
1119 "Sec-WebSocket-Accept: %s\r\n"
1120 "Sec-WebSocket-Protocol: %s\r\n\r\n",
1121 upgrade,
1122 websocket_combine_key(key, base64, sizeof(base64)),
1123 protocol);
1124 } else {
1126 "HTTP/1.1 101 Switching Protocols\r\n"
1127 "Upgrade: %s\r\n"
1128 "Connection: Upgrade\r\n"
1129 "Sec-WebSocket-Accept: %s\r\n\r\n",
1130 upgrade,
1131 websocket_combine_key(key, base64, sizeof(base64)));
1132 }
1133 } else {
1134
1135 /* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 or completely unknown */
1136 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '%d' chosen\n",
1139 ao2_ref(protocol_handler, -1);
1140 return 0;
1141 }
1142
1143 /* Enable keepalive on all sessions so the underlying user does not have to */
1144 if (setsockopt(ast_iostream_get_fd(ser->stream), SOL_SOCKET, SO_KEEPALIVE, &flags, sizeof(flags))) {
1145 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - failed to enable keepalive\n",
1148 ao2_ref(session, -1);
1149 ao2_ref(protocol_handler, -1);
1150 return 0;
1151 }
1152
1153 /* Get our local address for the connected socket */
1154 if (ast_getsockname(ast_iostream_get_fd(ser->stream), &session->local_address)) {
1155 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - failed to get local address\n",
1158 ao2_ref(session, -1);
1159 ao2_ref(protocol_handler, -1);
1160 return 0;
1161 }
1162
1163 ast_debug(3, "WebSocket connection from '%s' for protocol '%s' accepted using version '%d'\n", ast_sockaddr_stringify(&ser->remote_address), protocol ? : "", version);
1164
1165 /* Populate the session with all the needed details */
1166 session->stream = ser->stream;
1167 ast_sockaddr_copy(&session->remote_address, &ser->remote_address);
1168 session->opcode = -1;
1170 session->secure = ast_iostream_get_ssl(ser->stream) ? 1 : 0;
1171
1172 /* Give up ownership of the socket and pass it to the protocol handler */
1174 protocol_handler->session_established(session, get_vars, headers);
1175 ao2_ref(protocol_handler, -1);
1176
1177 /*
1178 * By dropping the stream from the session the connection
1179 * won't get closed when the HTTP server cleans up because we
1180 * passed the connection to the protocol handler.
1181 */
1182 ser->stream = NULL;
1183
1184 return 0;
1185}
1186
1189 .description = "Asterisk HTTP WebSocket",
1190 .uri = "ws",
1191 .has_subtree = 0,
1192 .data = NULL,
1193 .key = __FILE__,
1194};
1195
1196/*! \brief Simple echo implementation which echoes received text and binary frames */
1197static void websocket_echo_callback(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)
1198{
1199 int res;
1200
1201 ast_debug(1, "Entering WebSocket echo loop\n");
1202
1203 if (ast_fd_set_flags(ast_websocket_fd(session), O_NONBLOCK)) {
1204 goto end;
1205 }
1206
1207 while ((res = ast_websocket_wait_for_input(session, -1)) > 0) {
1208 char *payload;
1209 uint64_t payload_len;
1210 enum ast_websocket_opcode opcode;
1211 int fragmented;
1212
1213 if (ast_websocket_read(session, &payload, &payload_len, &opcode, &fragmented)) {
1214 /* We err on the side of caution and terminate the session if any error occurs */
1215 ast_log(LOG_WARNING, "Read failure during WebSocket echo loop\n");
1216 break;
1217 }
1218
1219 if (opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY) {
1220 ast_websocket_write(session, opcode, payload, payload_len);
1221 } else if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
1222 break;
1223 } else {
1224 ast_debug(1, "Ignored WebSocket opcode %u\n", opcode);
1225 }
1226 }
1227
1228end:
1229 ast_debug(1, "Exiting WebSocket echo loop\n");
1231}
1232
1234{
1235 struct ast_websocket_server *ws_server = websocketuri.data;
1236 if (!ws_server) {
1237 return -1;
1238 }
1240}
1241
1246
1248{
1249 struct ast_websocket_server *ws_server = websocketuri.data;
1250
1251 if (!ws_server) {
1252 return -1;
1253 }
1254
1255 if (ast_websocket_server_add_protocol2(ws_server, protocol)) {
1256 return -1;
1257 }
1258
1259 return 0;
1260}
1261
1263{
1264 struct ast_websocket_server *ws_server = websocketuri.data;
1265 if (!ws_server) {
1266 return -1;
1267 }
1269}
1270
1275
1276/*! \brief Parse the given uri into a path and remote address.
1277 *
1278 * Expected uri form:
1279 * \verbatim [ws[s]]://<host>[:port][/<path>] \endverbatim
1280 *
1281 * The returned host will contain the address and optional port while
1282 * path will contain everything after the address/port if included.
1283 */
1284static int websocket_client_parse_uri(const char *uri, char **host,
1285 struct ast_str **path, char **userinfo, int proxy)
1286{
1287 struct ast_uri *parsed_uri = proxy ? ast_uri_parse_http(uri) : ast_uri_parse_websocket(uri);
1288
1289 if (!parsed_uri) {
1290 return -1;
1291 }
1292
1293 *host = ast_uri_make_host_with_port(parsed_uri);
1294 *userinfo = ast_strdup(ast_uri_user_info(parsed_uri));
1295 if (ast_uri_path(parsed_uri) || ast_uri_query(parsed_uri)) {
1296 *path = ast_str_create(64);
1297 if (!*path) {
1298 ao2_ref(parsed_uri, -1);
1299 return -1;
1300 }
1301
1302 if (ast_uri_path(parsed_uri)) {
1303 ast_str_set(path, 0, "%s", ast_uri_path(parsed_uri));
1304 }
1305
1306 if (ast_uri_query(parsed_uri)) {
1307 ast_str_append(path, 0, "?%s", ast_uri_query(parsed_uri));
1308 }
1309 }
1310
1311 ao2_ref(parsed_uri, -1);
1312 return 0;
1313}
1314
1315static void websocket_client_args_destroy(void *obj)
1316{
1317 struct ast_tcptls_session_args *args = obj;
1318
1319 if (args->tls_cfg) {
1320 ast_free(args->tls_cfg->certfile);
1321 ast_free(args->tls_cfg->pvtfile);
1322 ast_free(args->tls_cfg->cipher);
1323 ast_free(args->tls_cfg->cafile);
1324 ast_free(args->tls_cfg->capath);
1325
1326 ast_ssl_teardown(args->tls_cfg);
1327 }
1328 ast_free(args->tls_cfg);
1329}
1330
1333{
1334 struct ast_sockaddr *addr;
1337 const char *resolve_host = NULL;
1338
1339 if (!args) {
1341 return NULL;
1342 }
1343
1344 args->accept_fd = -1;
1345 args->tls_cfg = options->tls_cfg;
1346 args->name = "websocket client";
1347
1349 resolve_host = ws->client->options->proxy_host;
1350 } else {
1351 resolve_host = ws->client->host;
1352 }
1353 if (!ast_sockaddr_resolve(&addr, resolve_host, 0, 0)) {
1354 ast_log(LOG_ERROR, "Unable to resolve address %s\n",
1355 resolve_host);
1356 ao2_ref(args, -1);
1358 return NULL;
1359 }
1360 ast_sockaddr_copy(&args->remote_address, addr);
1361 ast_free(addr);
1362
1363 /* We need to save off the hostname but it may contain a port spec */
1364 snprintf(args->hostname, sizeof(args->hostname),
1365 "%.*s",
1366 (int) strcspn(ws->client->host, ":"), ws->client->host);
1367
1368 return args;
1369}
1370
1372{
1373 static int encoded_size = CLIENT_KEY_SIZE * 2 * sizeof(char) + 1;
1374 /* key is randomly selected 16-byte base64 encoded value */
1375 unsigned char key[CLIENT_KEY_SIZE + sizeof(long) - 1];
1376 char *encoded = ast_malloc(encoded_size);
1377 long i = 0;
1378
1379 if (!encoded) {
1380 ast_log(LOG_ERROR, "Unable to allocate client websocket key\n");
1381 return NULL;
1382 }
1383
1384 while (i < CLIENT_KEY_SIZE) {
1385 long num = ast_random();
1386 memcpy(key + i, &num, sizeof(long));
1387 i += sizeof(long);
1388 }
1389
1390 ast_base64encode(encoded, key, CLIENT_KEY_SIZE, encoded_size);
1391 return encoded;
1392}
1393
1394static void websocket_client_destroy(void *obj)
1395{
1396 struct websocket_client *client = obj;
1397 char *id = ast_strdupa(client->options->uri);
1398 SCOPE_ENTER(2, "%s: Client destructor %p\n", id, obj);
1399
1400 ao2_cleanup(client->options);
1401 ao2_cleanup(client->ser);
1402 ao2_cleanup(client->args);
1403
1404 ast_free(client->accept_protocol);
1405 ast_free(client->protocols);
1406 ast_free(client->key);
1407 ast_free(client->resource_name);
1408 ast_free(client->host);
1409 ast_free(client->userinfo);
1410 ast_free(client->proxy_userinfo);
1411
1412 SCOPE_EXIT_RTN("%s: Client destructor complete\n", id);
1413}
1414
1415static void client_options_destroy(void *obj)
1416{
1417 struct ast_websocket_client_options *clone = obj;
1418 ast_free((char *)clone->uri);
1419 ast_free((char *)clone->protocols);
1420 ast_free((char *)clone->username);
1421 ast_free((char *)clone->password);
1422 ast_free((char *)clone->proxy_host);
1423 ast_free((char *)clone->proxy_username);
1424 ast_free((char *)clone->proxy_password);
1425 ast_free(clone->tls_cfg);
1426}
1427
1428#define SAFE_STRDUP_WITH_ERROR_RTN(_clone, _str) \
1429({ \
1430 char *_duped = NULL; \
1431 if (_str) { \
1432 _duped = ast_strdup(_str); \
1433 if (!_duped) { \
1434 ao2_cleanup(_clone); \
1435 return NULL; \
1436 } \
1437 } \
1438 _duped; \
1439})
1440
1443{
1444 struct ast_websocket_client_options *clone = NULL;
1445
1446 clone = ao2_alloc(sizeof(*clone), client_options_destroy);
1447 if (!clone) {
1448 ast_log(LOG_ERROR, "Unable to clone client options\n");
1449 return NULL;
1450 }
1451
1452 memcpy(clone, options, sizeof(*options));
1453 clone->uri = SAFE_STRDUP_WITH_ERROR_RTN(clone, options->uri);
1454 clone->protocols = SAFE_STRDUP_WITH_ERROR_RTN(clone, options->protocols);
1455 clone->username = SAFE_STRDUP_WITH_ERROR_RTN(clone, options->username);
1456 clone->password = SAFE_STRDUP_WITH_ERROR_RTN(clone, options->password);
1457 clone->proxy_host = SAFE_STRDUP_WITH_ERROR_RTN(clone, options->proxy_host);
1458 clone->proxy_username = SAFE_STRDUP_WITH_ERROR_RTN(clone, options->proxy_username);
1459 clone->proxy_password = SAFE_STRDUP_WITH_ERROR_RTN(clone, options->proxy_password);
1460 if (options->tls_cfg) {
1461 clone->tls_cfg = ast_calloc(1, sizeof(*options->tls_cfg));
1462 if (!clone->tls_cfg) {
1463 ao2_cleanup(clone);
1464 return NULL;
1465 }
1466 memcpy(clone->tls_cfg, options->tls_cfg, sizeof(*options->tls_cfg));
1467 }
1468
1469 return clone;
1470}
1471
1474{
1475 struct ast_websocket *ws = NULL;
1476
1477 ast_debug(2, "%s: Creating client\n", options->uri);
1478
1479 ws = ao2_alloc(sizeof(*ws), session_destroy_fn);
1480 if (!ws) {
1481 ast_log(LOG_ERROR, "Unable to allocate websocket\n");
1483 return NULL;
1484 }
1485
1486 if (!ast_uuid_generate_str(ws->session_id, sizeof(ws->session_id))) {
1487 ast_log(LOG_ERROR, "Unable to allocate websocket session_id\n");
1488 ao2_ref(ws, -1);
1490 return NULL;
1491 }
1492
1493 if (!(ws->client = ao2_alloc(
1494 sizeof(*ws->client), websocket_client_destroy))) {
1495 ast_log(LOG_ERROR, "Unable to allocate websocket client\n");
1496 ao2_ref(ws, -1);
1498 return NULL;
1499 }
1500 ws->client->ping_sched_timer = -1;
1501
1503 if (!ws->client->options) {
1504 ast_log(LOG_ERROR, "Unable to clone client options\n");
1505 ao2_ref(ws, -1);
1507 return NULL;
1508 }
1509
1510 if (!(ws->client->key = websocket_client_create_key())) {
1511 ao2_ref(ws, -1);
1513 return NULL;
1514 }
1515
1517 options->uri, &ws->client->host, &ws->client->resource_name,
1518 &ws->client->userinfo, 0)) {
1519 ao2_ref(ws, -1);
1521 return NULL;
1522 }
1523 ast_debug(2, "%s: host: %s resource: %s userinfo: %s\n", options->uri, ws->client->host,
1525 ws->client->userinfo);
1526
1528 && !ast_strlen_zero(options->username)
1529 && !ast_strlen_zero(options->password)) {
1530 ast_asprintf(&ws->client->userinfo, "%s:%s", options->username,
1531 options->password);
1532 }
1533
1534 if (!ast_strlen_zero(options->proxy_host)) {
1535 ast_debug(2, "%s: Proxy host: %s userinfo: %s\n", options->uri, ws->client->options->proxy_host,
1536 ws->client->proxy_userinfo);
1537
1539 && !ast_strlen_zero(options->proxy_username)
1540 && !ast_strlen_zero(options->proxy_password)) {
1541 ast_asprintf(&ws->client->proxy_userinfo, "%s:%s", options->proxy_username,
1542 options->proxy_password);
1543 }
1544
1545 }
1546
1548 ao2_ref(ws, -1);
1549 return NULL;
1550 }
1551
1552 ws->client->suppress_connection_msgs = options->suppress_connection_msgs;
1553 ws->client->args->suppress_connection_msgs = options->suppress_connection_msgs;
1554 ws->client->protocols = ast_strdup(options->protocols);
1555 ws->client->version = 13;
1556 ws->opcode = -1;
1558 ws->timeout = options->timeout;
1559
1560 return ws;
1561}
1562
1565{
1566 return ws->client->accept_protocol;
1567}
1568
1570 struct websocket_client *client, int response_code, int proxy)
1571{
1572 if (response_code <= 0) {
1573 return WS_INVALID_RESPONSE;
1574 }
1575
1576 switch (response_code) {
1577 case 101:
1578 if (!proxy) {
1579 return WS_OK;
1580 }
1581 break;
1582 case 200:
1583 if (proxy) {
1584 return WS_OK;
1585 }
1586 break;
1587 case 400:
1589 ast_log(LOG_ERROR, "Received response 400 - Bad Request "
1590 "- from %s\n", client->host);
1591 }
1592 return WS_BAD_REQUEST;
1593 case 401:
1595 ast_log(LOG_ERROR, "Received response 401 - Unauthorized "
1596 "- from %s\n", client->host);
1597 }
1598 return WS_UNAUTHORIZED;
1599 case 404:
1601 ast_log(LOG_ERROR, "Received response 404 - Request URL not "
1602 "found - from %s\n", client->host);
1603 }
1604 return WS_URL_NOT_FOUND;
1605 }
1606
1608 ast_log(LOG_ERROR, "Invalid HTTP response code %d from %s\n",
1609 response_code, proxy ? client->options->proxy_host : client->host);
1610 }
1611 return WS_INVALID_RESPONSE;
1612}
1613
1615 struct websocket_client *client, int proxy)
1616{
1617 enum ast_websocket_result res;
1618 char buf[4096];
1619 char base64[64];
1620 int has_upgrade = 0;
1621 int has_connection = 0;
1622 int has_accept = 0;
1623 int has_protocol = 0;
1624 int status_code = 0;
1625 SCOPE_ENTER(2, "%s: Proxy? %s Proxy host: %s\n", client->options->uri, AST_YESNO(proxy),
1626 S_OR(client->options->proxy_host, "N/A"));
1627
1628 while (ast_iostream_gets(client->ser->stream, buf, sizeof(buf)) <= 0) {
1630 LOG_ERROR, "%s: %s waiting for HTTP status line", client->options->uri,
1631 (errno == EINTR || errno == EAGAIN) ? "Timeout" : "Error");
1632 }
1633
1634 status_code = ast_http_response_status_line(buf, "HTTP/1.1", 101);
1635 ast_trace(-1, "%s: Status code: %d\n", client->options->uri, status_code);
1636
1637 res = websocket_client_handle_response_code(client, status_code, proxy);
1638 if (res != WS_OK) {
1639 SCOPE_EXIT_RTN_VALUE(res, "%s: HTTP status line result: %d/%s", client->options->uri,
1640 res, ast_websocket_result_to_str(res));
1641 }
1642
1643 /* Ignoring line folding - assuming header field values are contained
1644 within a single line */
1645 while (1) {
1646 ssize_t len = ast_iostream_gets(client->ser->stream, buf, sizeof(buf));
1647 char *name, *value;
1648 int parsed;
1649
1650 if (len <= 0) {
1651 if (errno == EINTR || errno == EAGAIN) {
1653 LOG_ERROR, "%s: %s waiting for HTTP header", client->options->uri,
1654 (errno == EINTR || errno == EAGAIN) ? "Timeout" : "Error");
1655 } else {
1656 ast_trace(-1, "%s: Blank line received\n", client->options->uri);
1657 }
1658 break;
1659 }
1660
1661 parsed = ast_http_header_parse(buf, &name, &value);
1662 if (parsed < 0) {
1663 break;
1664 }
1665
1666 if (proxy || parsed > 0) {
1667 continue;
1668 }
1669
1670 if (!has_upgrade &&
1671 (has_upgrade = ast_http_header_match(
1672 name, "upgrade", value, "websocket")) < 0) {
1674 } else if (!has_connection &&
1675 (has_connection = ast_http_header_match(
1676 name, "connection", value, "upgrade")) < 0) {
1678 } else if (!has_accept &&
1679 (has_accept = ast_http_header_match(
1680 name, "sec-websocket-accept", value,
1682 client->key, base64, sizeof(base64)))) < 0) {
1684 } else if (!has_protocol &&
1685 (has_protocol = ast_http_header_match_in(
1686 name, "sec-websocket-protocol", value, client->protocols))) {
1687 if (has_protocol < 0) {
1689 }
1691 } else if (!strcasecmp(name, "sec-websocket-extensions")) {
1692 ast_log(LOG_ERROR, "Extensions received, but not "
1693 "supported by client\n");
1695 }
1696 }
1697
1698 if (status_code == 408) {
1700 } else {
1701 if (proxy) {
1702 res = WS_OK;
1703 } else {
1704 res = has_upgrade && has_connection && has_accept ?
1706 }
1707 }
1708
1709 SCOPE_EXIT_RTN_VALUE(res, "%s: Status code: %d\n", client->options->uri, status_code);
1710}
1711
1713{
1714 /*
1715 * ast_iostream_gets (called above in websocket_client_handshake_get_response) is
1716 * a blocking call which means that if the TCP/TLS connection succeeds but the remote doesn't
1717 * actually respond to the proxy CONNECT (if proxy is configured) or GET requests, the process
1718 * can hang indefinitely and escalate to a deadlock. To get ast_iostream_gets to timeout,
1719 * we need to make the following 3 calls on the iostream.
1720 *
1721 * Since the write of the CONNECT and/or GET request is included in the timeout, we'll
1722 * double the timeout set in the websocket client's "connect_timeout" parameter to give
1723 * the server enough time to respond.
1724 */
1728}
1729
1731{
1732 /*
1733 * Once the handshake is complete, we need to undo what we did in
1734 * websocket_client_start_handshake_timer.
1735 */
1739}
1740
1741#define optional_header_spec "%s%s%s"
1742#define print_optional_header(test, name, value) \
1743 test ? name : "", \
1744 test ? value : "", \
1745 test ? "\r\n" : ""
1746
1748 struct websocket_client *client)
1749{
1750 struct ast_variable *auth_header = NULL;
1751 enum ast_websocket_result res = WS_OK;
1752 size_t bytes_written = 0;
1753 SCOPE_ENTER(2, "%s: Handshaking with proxy %s\n", client->options->uri, client->options->proxy_host);
1754
1755 if (!ast_strlen_zero(client->proxy_userinfo)) {
1757 if (!auth_header) {
1758 SCOPE_EXIT_LOG_RTN_VALUE(WS_ALLOCATE_ERROR, LOG_ERROR, "Unable to allocate client websocket userinfo\n");
1759 }
1760 }
1761
1763
1764 bytes_written = ast_iostream_printf(client->ser->stream,
1765 "CONNECT %s HTTP/1.1\r\n"
1766 "Host: %s\r\n"
1768 "Proxy-Connection: Keep-Alive\r\n"
1769 "\r\n",
1770 client->host,
1771 client->host,
1772 print_optional_header(auth_header, "Proxy-Authorization: ", auth_header->value)
1773 );
1774
1775 ast_variables_destroy(auth_header);
1776 if (bytes_written < 0) {
1778 SCOPE_EXIT_LOG_RTN_VALUE(WS_WRITE_ERROR, LOG_ERROR, "Failed to send handshake\n");
1779 }
1780
1781 /* wait for a response before doing anything else */
1784
1786}
1787
1789 struct websocket_client *client)
1790{
1791 size_t protocols_len = 0;
1792 struct ast_variable *auth_header = NULL;
1793 size_t res;
1794 SCOPE_ENTER(2, "%s: Handshaking with server\n", client->options->uri);
1795
1796 if (!ast_strlen_zero(client->userinfo)) {
1797 auth_header = ast_http_create_basic_auth_header(client->userinfo, NULL);
1798 if (!auth_header) {
1799 SCOPE_EXIT_LOG_RTN_VALUE(WS_ALLOCATE_ERROR, LOG_ERROR, "Unable to allocate client websocket userinfo\n");
1800 }
1801 }
1802
1803 protocols_len = client->protocols ? strlen(client->protocols) : 0;
1804
1806
1807 res = ast_iostream_printf(client->ser->stream,
1808 "GET /%s HTTP/1.1\r\n"
1809 "Sec-WebSocket-Version: %d\r\n"
1810 "Upgrade: websocket\r\n"
1811 "Connection: Upgrade\r\n"
1812 "Host: %s\r\n"
1815 "Sec-WebSocket-Key: %s\r\n"
1816 "\r\n",
1817 client->resource_name ? ast_str_buffer(client->resource_name) : "",
1818 client->version,
1819 client->host,
1820 print_optional_header(auth_header, "Authorization: ", auth_header->value),
1821 print_optional_header(protocols_len, "Sec-WebSocket-Protocol: ", client->protocols),
1822 client->key
1823 );
1824
1825 ast_variables_destroy(auth_header);
1826 if (res < 0) {
1828 SCOPE_EXIT_LOG_RTN_VALUE(WS_WRITE_ERROR, LOG_ERROR, "Failed to send handshake\n");
1829 }
1830
1831 /* wait for a response before doing anything else */
1834
1836}
1837
1840{
1841 enum ast_websocket_result res;
1842 int original_tls_enabled = ws->client->args->tls_cfg ? ws->client->args->tls_cfg->enabled : 0;
1843 int proxy = !ast_strlen_zero(ws->client->options->proxy_host);
1844 SCOPE_ENTER(2, "%s: proxy: %s tls_enabled: %s\n", options->uri, S_OR(ws->client->options->proxy_host, "N/A"),
1845 AST_YESNO(original_tls_enabled));
1846
1847
1848 /* create and connect the client - note client_start
1849 releases the session instance on failure */
1850
1851 if (proxy && original_tls_enabled ) {
1852 ast_trace(-1, "%s: Disabling TLS while handshaking with proxy\n", options->uri);
1853 if (ws->client->args->tls_cfg) {
1854 ws->client->args->tls_cfg->enabled = 0;
1855 }
1856 }
1857
1858 ast_trace(-1, "%s: Creating tcptls client\n", options->uri);
1860 if (!ws->client->ser) {
1861 SCOPE_EXIT_LOG_RTN_VALUE(WS_CLIENT_START_ERROR, LOG_ERROR, "%s: Unable to create tcptls client\n", options->uri);
1862 }
1863
1864 ast_trace(-1, "%s: Connecting%s%s\n", options->uri, proxy ? " via proxy " : "", S_OR(ws->client->options->proxy_host, ""));
1865 if (!ast_tcptls_client_start_timeout(ws->client->ser, options->timeout)) {
1866 ws->client->ser = NULL;
1867 SCOPE_EXIT_LOG_RTN_VALUE(WS_CLIENT_START_ERROR, LOG_ERROR, "%s: Unable to connect%s%s\n", options->uri,
1868 proxy ? " via proxy " : "", S_OR(ws->client->options->proxy_host, ""));
1869 }
1870
1871 ast_trace(-1, "%s: Connected%s%s\n", options->uri, proxy ? " via proxy " : "", S_OR(ws->client->options->proxy_host, ""));
1872 if (proxy) {
1874 if (res != WS_OK) {
1875 ao2_ref(ws->client->ser, -1);
1876 ws->client->ser = NULL;
1877 SCOPE_EXIT_LOG_RTN_VALUE(res, LOG_ERROR, "%s: Unable to perform proxy handshake with %s\n", options->uri,
1878 ws->client->options->proxy_host);
1879 }
1880 }
1881
1882 if (proxy && original_tls_enabled && !ws->client->args->tls_cfg->enabled) {
1883 int rc = 0;
1884 ast_trace(-1, "%s: Re-enabling TLS after handshaking with proxy %s\n", options->uri, ws->client->options->proxy_host);
1885 ws->client->args->tls_cfg->enabled = 1;
1887 if (rc != 1) {
1888 ao2_cleanup(ws->client->ser);
1889 ws->client->ser = NULL;
1890 SCOPE_EXIT_LOG_RTN_VALUE(WS_TLS_ERROR, LOG_ERROR, "%s: TLS context setup failed after handshake with %s\n",
1891 options->uri, ws->client->options->proxy_host);
1892 }
1893 if (!ast_tcptls_start_tls(ws->client->ser)) {
1894 ws->client->ser = NULL;
1895 SCOPE_EXIT_LOG_RTN_VALUE(WS_TLS_ERROR, LOG_ERROR, "%s: TLS with websocket server failed after handshake with %s\n",
1896 options->uri, ws->client->options->proxy_host);
1897 }
1898 }
1899
1900 if ((res = websocket_client_handshake(ws->client)) != WS_OK) {
1901 ao2_ref(ws->client->ser, -1);
1902 ws->client->ser = NULL;
1903 SCOPE_EXIT_LOG_RTN_VALUE(res, LOG_ERROR, "%s: Unable to perform websocket handshake\n", options->uri);
1904 }
1905
1906 ws->stream = ws->client->ser->stream;
1907 ws->secure = ast_iostream_get_ssl(ws->stream) ? 1 : 0;
1908 ws->client->ser->stream = NULL;
1910
1911 SCOPE_EXIT_RTN_VALUE(WS_OK, "%s: Handshake complete\n", options->uri);
1912}
1913
1915 (const char *uri, const char *protocols, struct ast_tls_config *tls_cfg,
1917{
1919 .uri = uri,
1920 .protocols = protocols,
1921 .timeout = -1,
1922 .tls_cfg = tls_cfg,
1923 };
1924
1926}
1927
1928static int ping_scheduler_callback(const void *obj)
1929{
1930 struct ast_websocket *session = (struct ast_websocket *)obj;
1931
1932 if (!session->client) {
1933 /*
1934 * We should never get here because we can only start pingpongs from a client
1935 * but just in case...
1936 */
1937 return 0;
1938 }
1939
1941 if (session->closing) {
1943 return 0;
1944 }
1945
1946 if (session->client->missed_pong_count > 1) {
1947 ast_debug(2, "%s: Missed PONG count is now %d\n", WS_SESSION_REMOTE(session),
1948 session->client->missed_pong_count);
1949 }
1950
1951 if (session->client->missed_pong_count >= session->client->options->pingpong_probes) {
1953 ast_log(LOG_WARNING, "%s: %d missed PONGs. Closing connection.\n",
1954 WS_SESSION_REMOTE(session), session->client->missed_pong_count);
1955 session->client->ping_sched_timer = -1;
1957 return 0;
1958 }
1959
1961 session->client->missed_pong_count++;
1962
1964 return session->client->options->pingpong_interval * 1000;
1965}
1966
1967/*
1968 * Notes on client session lifecycle:
1969 *
1970 * Historically, the lifecycle of a client session was fairly simple. Ownership was
1971 * transferred to the caller with the return of the create and when the caller released
1972 * their last reference, the session was closed by the destructor. There was one issue
1973 * with this however, if the remote end sent us a CLOSE opcode, we were destroying
1974 * everything without sending the required CLOSE reply. This could cause issues on
1975 * the remote end. The addition of WebSocket PING/PONG capability also doesn't work well
1976 * with that pattern because it adds a scheduler and callback which means that a reference
1977 * must be held by this module as long as the scheduler is active. This means that a caller
1978 * can't just unref the websocket and expect it to be automatically closed.
1979 *
1980 * So now...
1981 *
1982 * Once the websocket is created and connected, the usual pattern is for the higher
1983 * level client to be in a loop that blocks waiting on a websocket frame to be
1984 * available. The client owns a reference to the websocket while the loop is
1985 * active and if we're sending PINGs, the scheduled task also holds a reference.
1986 *
1987 * When the higher level client wants to close the websocket, it calls
1988 * ast_websocket_close() from another thread. We mark the session as CLOSED_BY_US,
1989 * stop the scheduled PING task and release its reference, send a CLOSE opcode to the
1990 * remote, then return to the caller. The client's read thread is still blocked
1991 * at this point. When we receive the CLOSE reply from the remote, we use the
1992 * CLOSED_BY_US flag to indicate that we're done and can close the socket and stream.
1993 * This causes the client's read thread to unblock with a CLOSE opcode which then calls
1994 * ast_websocket_close() (which is a NoOp because we already did the close) and
1995 * ast_websocket_unref() which triggers the session destructor.
1996 *
1997 * When the remote sends us a CLOSE opcode, we mark the session as CLOSED_BY_REMOTE
1998 * and return the CLOSE opcode in the ast_websocket_read that the caller should have
1999 * been blocked on. The caller must then call ast_websocket_close() just as above.
2000 * In this case however, it's not a NoOp. We stop the scheduled PING task and
2001 * release its reference, send a CLOSE reply to the remote and since the transaction
2002 * is done, we close the socket and stream and return to the caller. The caller then
2003 * calls ast_websocket_unref() which triggers the session destructor.
2004 */
2005
2008{
2009 struct ast_websocket *ws = NULL;
2010 SCOPE_ENTER(2, "%s: Creating client\n", options->uri);
2011
2013 if (!ws) {
2014 SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Failed to create: %s\n", options->uri, ast_websocket_result_to_str(*result));
2015 }
2016
2017 ast_trace(-1, "%s: Connecting\n", ws->client->options->uri);
2018
2019 if ((*result = websocket_client_connect(ws, options)) != WS_OK) {
2020 ao2_ref(ws, -1);
2021 SCOPE_EXIT_RTN_VALUE(NULL, "%s: Failed to connect: %s\n", options->uri, ast_websocket_result_to_str(*result));
2022 }
2023 ast_trace(-1, "%s: Connected\n", ws->client->options->uri);
2024
2025 if (ws->client->options->tcp_keepalives) {
2026 int sockfd = ast_iostream_get_fd(ws->stream);
2027 int enabled = 1;
2028
2029 setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled));
2030 setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &options->tcp_keepalive_time, sizeof(options->tcp_keepalive_time));
2031 setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &options->tcp_keepalive_interval, sizeof(&options->tcp_keepalive_interval));
2032 setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &options->tcp_keepalive_probes, sizeof(&options->tcp_keepalive_probes));
2033 ast_trace(-1, "%s: Enabled TCP keepalives\n", ws->client->options->uri);
2034 }
2035
2036 if (ws->client->options->pingpongs) {
2039 if (ws->client->ping_sched_timer < 0) {
2040 ast_log(LOG_WARNING, "%s: Unable to schedule PING/PONG keepalives\n", ws->client->options->uri);
2041 ao2_ref(ws, -1);
2042 } else {
2043 ast_trace(-1, "%s: Enabled PING/PONG keepalives\n", ws->client->options->uri);
2044 }
2045 }
2046 SCOPE_EXIT_RTN_VALUE(ws, "%s: Client created and connected %p %p\n", options->uri, ws, ws->client);
2047}
2048
2050 (struct ast_websocket *ws, char **buf)
2051{
2052 char *payload;
2053 uint64_t payload_len;
2055 int fragmented = 1;
2056
2057 while (fragmented) {
2059 &opcode, &fragmented)) {
2060 ast_log(LOG_ERROR, "Client WebSocket string read - "
2061 "error reading string data\n");
2062 return -1;
2063 }
2064
2066 /* Try read again, we have sent pong already */
2067 fragmented = 1;
2068 continue;
2069 }
2070
2072 continue;
2073 }
2074
2076 return -1;
2077 }
2078
2080 ast_log(LOG_ERROR, "Client WebSocket string read - "
2081 "non string data received\n");
2082 return -1;
2083 }
2084 }
2085
2086 if (!(*buf = ast_strndup(payload, payload_len))) {
2087 return -1;
2088 }
2089
2090 return payload_len + 1;
2091}
2092
2094 (struct ast_websocket *ws, const char *buf)
2095{
2096 uint64_t len = strlen(buf);
2097
2098 ast_debug(3, "Writing websocket string of length %" PRIu64 "\n", len);
2099
2100 /* We do not pass strlen(buf) to ast_websocket_write() directly because the
2101 * size_t returned by strlen() may not require the same storage size
2102 * as the uint64_t that ast_websocket_write() uses. This normally
2103 * would not cause a problem, but since ast_websocket_write() uses
2104 * the optional API, this function call goes through a series of macros
2105 * that may cause a 32-bit to 64-bit conversion to go awry.
2106 */
2108 (char *)buf, len);
2109}
2110
2112 [WS_OK] = "OK",
2113 [WS_ALLOCATE_ERROR] = "Allocation error",
2114 [WS_KEY_ERROR] = "Key error",
2115 [WS_URI_PARSE_ERROR] = "URI parse error",
2116 [WS_URI_RESOLVE_ERROR] = "URI resolve error",
2117 [WS_BAD_STATUS] = "Bad status line",
2118 [WS_INVALID_RESPONSE] = "Invalid response code",
2119 [WS_BAD_REQUEST] = "Bad request",
2120 [WS_URL_NOT_FOUND] = "URL not found",
2121 [WS_HEADER_MISMATCH] = "Header mismatch",
2122 [WS_HEADER_MISSING] = "Header missing",
2123 [WS_NOT_SUPPORTED] = "Not supported",
2124 [WS_WRITE_ERROR] = "Write error",
2125 [WS_CLIENT_START_ERROR] = "Client start error",
2126 [WS_UNAUTHORIZED] = "Unauthorized"
2127};
2128
2137
2140 const char *desc;
2141};
2142
2143static const struct status_map websocket_status_map[] = {
2144 { AST_WEBSOCKET_STATUS_NORMAL, "Normal" },
2145 { AST_WEBSOCKET_STATUS_GOING_AWAY, "Going away" },
2146 { AST_WEBSOCKET_STATUS_PROTOCOL_ERROR, "Protocol error" },
2147 { AST_WEBSOCKET_STATUS_UNSUPPORTED_DATA, "Unsupported data" },
2148 { AST_WEBSOCKET_STATUS_RESERVED_1004, "reserved 1004" },
2149 { AST_WEBSOCKET_STATUS_RESERVED_1005, "reserved 1005" },
2150 { AST_WEBSOCKET_STATUS_RESERVED_1006, "reserved 1006" },
2151 { AST_WEBSOCKET_STATUS_INVALID_FRAME, "Invalid frame" },
2152 { AST_WEBSOCKET_STATUS_POLICY_VIOLATION, "Policy violation" },
2153 { AST_WEBSOCKET_STATUS_TOO_BIG, "Data too big" },
2154 { AST_WEBSOCKET_STATUS_MANDATORY_EXT, "Mandatory extension" },
2155 { AST_WEBSOCKET_STATUS_INTERNAL_ERROR, "Internal error" },
2156 { AST_WEBSOCKET_STATUS_RESERVED_1012, "reserved 1012" },
2157 { AST_WEBSOCKET_STATUS_RESERVED_1013, "reserved 1013" },
2158 { AST_WEBSOCKET_STATUS_BAD_GATEWAY, "Bad gateway" },
2159 { AST_WEBSOCKET_STATUS_RESERVED_1015, "reserved 1015" },
2160};
2161
2164{
2165 int i;
2166
2167 for (i = 0; i < ARRAY_LEN(websocket_status_map); i++) {
2168 if (websocket_status_map[i].code == code)
2169 return websocket_status_map[i].desc;
2170 }
2171
2172 return "Unknown";
2173}
2174
2189
2190static int load_module(void)
2191{
2193 if (!websocketuri.data) {
2195 }
2198
2200 if (!ping_scheduler) {
2201 unload_module();
2203 }
2204
2206 unload_module();
2208 }
2209
2210 return 0;
2211}
2212
2214 .support_level = AST_MODULE_SUPPORT_CORE,
2215 .load = load_module,
2216 .unload = unload_module,
2217 .load_pri = AST_MODPRI_CHANNEL_DEPEND,
2218 .requires = "http",
ast_mutex_t lock
Definition app_sla.c:337
uint64_t ntohll(uint64_t net64)
Definition strcompat.c:365
char * strsep(char **str, const char *delims)
uint64_t htonll(uint64_t host64)
Definition strcompat.c:391
Asterisk main include file. File version handling, generic pbx functions.
static struct ast_mansession session
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition astmm.h:288
#define ast_free(a)
Definition astmm.h:180
#define ast_strndup(str, len)
A wrapper for strndup()
Definition astmm.h:256
#define ast_realloc(p, len)
A wrapper for realloc()
Definition astmm.h:226
#define ast_strdup(str)
A wrapper for strdup()
Definition astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition astmm.h:298
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition astmm.h:267
#define ast_calloc(num, len)
A wrapper for calloc()
Definition astmm.h:202
#define ast_malloc(len)
A wrapper for malloc()
Definition astmm.h:191
#define ast_log
Definition astobj2.c:42
@ CMP_MATCH
Definition astobj2.h:1027
@ CMP_STOP
Definition astobj2.h:1028
#define OBJ_KEY
Definition astobj2.h:1151
@ AO2_ALLOC_OPT_LOCK_MUTEX
Definition astobj2.h:363
#define ao2_callback(c, flags, cb_fn, arg)
ao2_callback() is a generic function that applies cb_fn() to all objects in a container,...
Definition astobj2.h:1693
int ao2_container_count(struct ao2_container *c)
Returns the number of elements in a container.
#define ao2_cleanup(obj)
Definition astobj2.h:1934
#define ao2_unlink(container, obj)
Remove an object from a container.
Definition astobj2.h:1578
#define ao2_link_flags(container, obj, flags)
Add an object to a container.
Definition astobj2.h:1554
#define ao2_find(container, arg, flags)
Definition astobj2.h:1736
#define ao2_unlock(a)
Definition astobj2.h:729
#define ao2_lock(a)
Definition astobj2.h:717
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition astobj2.h:459
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition astobj2.h:480
@ OBJ_NOLOCK
Assume that the ao2_container is already locked.
Definition astobj2.h:1063
#define ao2_alloc(data_size, destructor_fn)
Definition astobj2.h:409
#define ao2_container_alloc_hash(ao2_options, container_options, n_buckets, hash_fn, sort_fn, cmp_fn)
Allocate and initialize a hash container with the desired number of buckets.
Definition astobj2.h:1303
static PGresult * result
Definition cel_pgsql.c:84
static const char type[]
static char version[AST_MAX_EXTENSION]
static struct ast_channel * callback(struct ast_channelstorage_instance *driver, ao2_callback_data_fn *cb_fn, void *arg, void *data, int ao2_flags, int rdlock)
static int enabled
Definition dnsmgr.c:91
char * end
Definition eagi_proxy.c:73
char buf[BUFSIZE]
Definition eagi_proxy.c:66
Generic File Format Support. Should be included by clients of the file handling routines....
static int frame_size[4]
Definition format_g726.c:52
static const char name[]
Definition format_mp3.c:68
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
#define SCOPE_EXIT_RTN(...)
#define SCOPE_EXIT_RTN_VALUE(__return_value,...)
#define SCOPE_EXIT_LOG_RTN_VALUE(__value, __log_level,...)
#define SCOPE_ENTER(level,...)
#define SCOPE_EXIT(...)
#define ast_trace(level,...)
Support for Private Asterisk HTTP Servers.
void ast_http_send(struct ast_tcptls_session_instance *ser, enum ast_http_method method, int status_code, const char *status_title, struct ast_str *http_header, struct ast_str *out, int fd, unsigned int static_content)
Generic function for sending HTTP/1.1 response.
Definition http.c:522
ast_http_method
HTTP Request methods known by Asterisk.
Definition http.h:58
@ AST_HTTP_GET
Definition http.h:60
@ AST_HTTP_UNKNOWN
Definition http.h:59
int ast_http_header_parse(char *buf, char **name, char **value)
Parse a header into the given name/value strings.
Definition http.c:1822
int ast_http_header_match_in(const char *name, const char *expected_name, const char *value, const char *expected_value)
Check if the header name matches the expected header name. If so, then check to see if the value can ...
Definition http.c:1860
void ast_http_uri_unlink(struct ast_http_uri *urihandler)
Unregister a URI handler.
Definition http.c:771
int ast_http_body_discard(struct ast_tcptls_session_instance *ser)
Read and discard any unread HTTP request body.
Definition http.c:1185
struct ast_variable * ast_http_create_basic_auth_header(const char *userid, const char *password)
Create an HTTP authorization header.
Definition http.c:1726
int ast_http_header_match(const char *name, const char *expected_name, const char *value, const char *expected_value)
Check if the header and value match (case insensitive) their associated expected values.
Definition http.c:1844
int ast_http_response_status_line(const char *buf, const char *version, int code)
Parse the http response status line.
Definition http.c:1770
void ast_http_request_close_on_completion(struct ast_tcptls_session_instance *ser)
Request the HTTP connection be closed after this HTTP request.
Definition http.c:903
void ast_http_error(struct ast_tcptls_session_instance *ser, int status, const char *title, const char *text)
Send HTTP error message and close socket.
Definition http.c:714
int ast_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
Definition http.c:739
Support for WebSocket connections within the Asterisk HTTP server and client WebSocket connections to...
ast_websocket_status_code
Websocket Status Codes from RFC-6455.
@ AST_WEBSOCKET_STATUS_RESERVED_1015
@ AST_WEBSOCKET_STATUS_BAD_GATEWAY
@ AST_WEBSOCKET_STATUS_UNSUPPORTED_DATA
@ AST_WEBSOCKET_STATUS_PROTOCOL_ERROR
@ AST_WEBSOCKET_STATUS_NORMAL
@ AST_WEBSOCKET_STATUS_MANDATORY_EXT
@ AST_WEBSOCKET_STATUS_TOO_BIG
@ AST_WEBSOCKET_STATUS_RESERVED_1006
@ AST_WEBSOCKET_STATUS_RESERVED_1004
@ AST_WEBSOCKET_STATUS_RESERVED_1013
@ AST_WEBSOCKET_STATUS_RESERVED_1005
@ AST_WEBSOCKET_STATUS_POLICY_VIOLATION
@ AST_WEBSOCKET_STATUS_GOING_AWAY
@ AST_WEBSOCKET_STATUS_INTERNAL_ERROR
@ AST_WEBSOCKET_STATUS_RESERVED_1012
@ AST_WEBSOCKET_STATUS_INVALID_FRAME
#define AST_WEBSOCKET_PROTOCOL_VERSION
Protocol version. This prevents dynamically loadable modules from registering if this struct is chang...
ast_websocket_result
Result code for a websocket client.
@ WS_BAD_REQUEST
@ WS_TLS_ERROR
@ WS_URL_NOT_FOUND
@ WS_CLIENT_START_ERROR
@ WS_KEY_ERROR
@ WS_NOT_SUPPORTED
@ WS_ALLOCATE_ERROR
@ WS_HEADER_MISMATCH
@ WS_WRITE_ERROR
@ WS_UNAUTHORIZED
@ WS_URI_RESOLVE_ERROR
@ WS_OK
@ WS_INVALID_RESPONSE
@ WS_BAD_STATUS
@ WS_URI_PARSE_ERROR
@ WS_HEADER_MISSING
#define AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE
Size of the pre-determined buffer for WebSocket frames.
#define AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT
Default websocket write timeout, in ms.
ast_websocket_opcode
WebSocket operation codes.
@ AST_WEBSOCKET_OPCODE_PING
@ AST_WEBSOCKET_OPCODE_PONG
@ AST_WEBSOCKET_OPCODE_CONTINUATION
@ AST_WEBSOCKET_OPCODE_BINARY
@ AST_WEBSOCKET_OPCODE_CLOSE
@ AST_WEBSOCKET_OPCODE_TEXT
ast_websocket_type
WebSocket connection/configuration types.
@ AST_WS_TYPE_ANY
@ AST_WS_TYPE_CLIENT
@ AST_WS_TYPE_INBOUND
@ AST_WS_TYPE_CLIENT_PER_CALL_CONFIG
@ AST_WS_TYPE_SERVER
@ AST_WS_TYPE_CLIENT_PERSISTENT
@ AST_WS_TYPE_CLIENT_PER_CALL
void(* ast_websocket_callback)(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)
Callback for when a new connection for a sub-protocol is established.
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition extconf.c:1260
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_WARNING
void ast_iostream_blocking(struct ast_iostream *stream)
Make an iostream blocking.
Definition iostream.c:109
ssize_t ast_iostream_printf(struct ast_iostream *stream, const char *format,...)
Write a formatted string to an iostream.
Definition iostream.c:507
ssize_t ast_iostream_gets(struct ast_iostream *stream, char *buffer, size_t size)
Read a LF-terminated string from an iostream.
Definition iostream.c:316
void ast_iostream_set_timeout_inactivity(struct ast_iostream *stream, int timeout)
Set the iostream inactivity timeout timer.
Definition iostream.c:127
SSL * ast_iostream_get_ssl(struct ast_iostream *stream)
Get a pointer to an iostream's OpenSSL SSL structure.
Definition iostream.c:114
ssize_t ast_iostream_write(struct ast_iostream *stream, const void *buffer, size_t count)
Write data to an iostream.
Definition iostream.c:390
int ast_iostream_get_fd(struct ast_iostream *stream)
Get an iostream's file descriptor.
Definition iostream.c:85
void ast_iostream_set_exclusive_input(struct ast_iostream *stream, int exclusive_input)
Set the iostream if it can exclusively depend upon the set timeouts.
Definition iostream.c:154
ssize_t ast_iostream_read(struct ast_iostream *stream, void *buffer, size_t count)
Read data from an iostream.
Definition iostream.c:289
void ast_iostream_set_timeout_sequence(struct ast_iostream *stream, struct timeval start, int timeout)
Set the iostream I/O sequence timeout timer.
Definition iostream.c:145
void ast_iostream_nonblock(struct ast_iostream *stream)
Make an iostream non-blocking.
Definition iostream.c:104
int ast_iostream_close(struct ast_iostream *stream)
Close an iostream.
Definition iostream.c:544
void ast_iostream_set_timeout_disable(struct ast_iostream *stream)
Disable the iostream timeout timer.
Definition iostream.c:119
int ast_iostream_wait_for_input(struct ast_iostream *stream, int timeout)
Wait for input on the iostream's file descriptor.
Definition iostream.c:90
#define SCOPED_AO2LOCK(varname, obj)
scoped lock specialization for ao2 mutexes.
Definition lock.h:611
int errno
Asterisk module definitions.
#define SCOPED_MODULE_USE(module)
Definition module.h:679
@ AST_MODFLAG_LOAD_ORDER
Definition module.h:331
@ AST_MODFLAG_GLOBAL_SYMBOLS
Definition module.h:330
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition module.h:557
@ AST_MODPRI_CHANNEL_DEPEND
Definition module.h:340
@ AST_MODULE_SUPPORT_CORE
Definition module.h:121
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition module.h:46
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition module.h:78
int ast_getsockname(int sockfd, struct ast_sockaddr *addr)
Wrapper around getsockname(2) that uses struct ast_sockaddr.
Definition netsock2.c:600
static char * ast_sockaddr_stringify(const struct ast_sockaddr *addr)
Wrapper around ast_sockaddr_stringify_fmt() with default format.
Definition netsock2.h:256
static void ast_sockaddr_copy(struct ast_sockaddr *dst, const struct ast_sockaddr *src)
Copies the data from one ast_sockaddr to another.
Definition netsock2.h:167
int ast_sockaddr_resolve(struct ast_sockaddr **addrs, const char *str, int flags, int family)
Parses a string with an IPv4 or IPv6 address and place results into an array.
Definition netsock2.c:280
#define AST_OPTIONAL_API_NAME(name)
Expands to the name of the implementation function.
void AST_OPTIONAL_API_NAME() ast_websocket_ref(struct ast_websocket *session)
Increase the reference count for a WebSocket session.
int AST_OPTIONAL_API_NAME() ast_websocket_add_protocol(const char *name, ast_websocket_callback callback)
Add a sub-protocol handler to the default /ws server.
static int websocket_close(struct ast_websocket *session, uint16_t reason, int force)
const char *AST_OPTIONAL_API_NAME() ast_websocket_session_id(struct ast_websocket *session)
Get the session ID for a WebSocket session.
static void websocket_client_destroy(void *obj)
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 enum ast_websocket_result websocket_client_handle_response_code(struct websocket_client *client, int response_code, int proxy)
int AST_OPTIONAL_API_NAME() ast_websocket_write(struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t payload_size)
Write function for websocket traffic.
static int ping_scheduler_callback(const void *obj)
static void websocket_bad_request(struct ast_tcptls_session_instance *ser)
static int websocket_client_parse_uri(const char *uri, char **host, struct ast_str **path, char **userinfo, int proxy)
Parse the given uri into a path and remote address.
static struct ast_http_uri websocketuri
int AST_OPTIONAL_API_NAME() ast_websocket_server_add_protocol2(struct ast_websocket_server *server, struct ast_websocket_protocol *protocol)
Add a sub-protocol handler to the given server.
int AST_OPTIONAL_API_NAME() ast_websocket_write_string(struct ast_websocket *ws, const char *buf)
Construct and transmit a WebSocket frame containing string data.
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.
#define MIN_WS_HDR_SZ
static struct ast_websocket_protocol * one_protocol(struct ast_websocket_server *server)
If the server has exactly one configured protocol, return it.
static void websocket_client_args_destroy(void *obj)
static int ws_safe_read(struct ast_websocket *session, char *buf, size_t len, enum ast_websocket_opcode *opcode)
struct ast_sockaddr *AST_OPTIONAL_API_NAME() ast_websocket_local_address(struct ast_websocket *session)
Get the local address for a WebSocket connection session.
static int websocket_remove_protocol_internal(const char *name, ast_websocket_callback callback)
static struct ast_websocket * websocket_client_create(struct ast_websocket_client_options *options, enum ast_websocket_result *result)
static struct ast_sched_context * ping_scheduler
#define SAFE_STRDUP_WITH_ERROR_RTN(_clone, _str)
#define optional_header_spec
#define MAX_PROTOCOL_BUCKETS
Number of buckets for registered protocols.
struct ast_websocket *AST_OPTIONAL_API_NAME() ast_websocket_client_create(const char *uri, const char *protocols, struct ast_tls_config *tls_cfg, enum ast_websocket_result *result)
Create, and connect, a websocket client.
struct ast_sockaddr *AST_OPTIONAL_API_NAME() ast_websocket_remote_address(struct ast_websocket *session)
Get the remote address for a WebSocket connected session.
static struct ast_websocket_server * websocket_server_internal_create(void)
int AST_OPTIONAL_API_NAME() ast_websocket_uri_cb(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 suitable for use with a ast_http_uri.
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.
static void websocket_server_dtor(void *obj)
static enum ast_websocket_result websocket_client_handshake_get_response(struct websocket_client *client, int proxy)
static enum ast_websocket_result websocket_client_handshake(struct websocket_client *client)
static void websocket_mask_payload(struct ast_websocket *session, char *frame, char *payload, uint64_t payload_size)
Perform payload masking for client sessions.
void AST_OPTIONAL_API_NAME() ast_websocket_reconstruct_disable(struct ast_websocket *session)
Disable multi-frame reconstruction.
@ WS_NOT_CLOSED
@ WS_CLOSED_BY_REMOTE
@ WS_CLOSED_BY_US
static struct ast_tcptls_session_args * websocket_client_args_create(struct ast_websocket *ws, struct ast_websocket_client_options *options, enum ast_websocket_result *result)
static int protocol_cmp_fn(void *obj, void *arg, int flags)
Comparison function for protocols.
#define DEFAULT_RECONSTRUCTION_CEILING
Default reconstruction size for multi-frame payload reconstruction. If exceeded the next frame will s...
struct ast_websocket *AST_OPTIONAL_API_NAME() ast_websocket_client_create_with_options(struct ast_websocket_client_options *options, enum ast_websocket_result *result)
Create, and connect, a websocket client using given options.
static char * websocket_client_create_key(void)
int AST_OPTIONAL_API_NAME() ast_websocket_wait_for_input(struct ast_websocket *session, int timeout)
Wait for the WebSocket session to be ready to be read.
static const char * closed_by_str[]
const char * ast_websocket_type_to_str(enum ast_websocket_type type)
int AST_OPTIONAL_API_NAME() ast_websocket_fd(struct ast_websocket *session)
Get the file descriptor for a WebSocket session.
static void protocol_destroy_fn(void *obj)
Destructor function for protocols.
static char * websocket_combine_key(const char *key, char *res, int res_size)
#define print_optional_header(test, name, value)
static const char * closed_by_to_str(enum ws_closed_by closed_by)
#define WS_PING_PAYLOAD
WS_PING_PAYLOAD It's possible that a user of this API could be sending their own PINGs and expecting ...
static struct ast_websocket_server * websocket_server_create_impl(void)
static int websocket_handled_pong_or_close(struct ast_websocket *session, char *payload, uint64_t payload_len, enum ast_websocket_opcode opcode)
const char *AST_OPTIONAL_API_NAME() ast_websocket_client_accept_protocol(struct ast_websocket *ws)
Retrieve the server accepted sub-protocol on the client.
static struct ast_websocket_client_options * client_options_clone(struct ast_websocket_client_options *options)
static int protocol_hash_fn(const void *obj, const int flags)
Hashing function for protocols.
int AST_OPTIONAL_API_NAME() ast_websocket_set_nonblock(struct ast_websocket *session)
Set the socket of a WebSocket session to be non-blocking.
#define WS_SESSION_REMOTE(_session)
int AST_OPTIONAL_API_NAME() ast_websocket_server_add_protocol(struct ast_websocket_server *server, const char *name, ast_websocket_callback callback)
Add a sub-protocol handler to the given server.
static int websocket_add_protocol_internal(const char *name, ast_websocket_callback callback)
static enum ast_websocket_result websocket_proxy_handshake(struct websocket_client *client)
#define CLIENT_KEY_SIZE
Length of a websocket's client key.
static void ping_scheduler_cancel(struct ast_websocket *session)
const char *AST_OPTIONAL_API_NAME() ast_websocket_status_to_str(enum ast_websocket_status_code code)
Convert a websocket status code to a string.
static int load_module(void)
static void client_options_destroy(void *obj)
#define ARE_PINGPONGS_ENABLED(_session)
int AST_OPTIONAL_API_NAME() ast_websocket_server_remove_protocol(struct ast_websocket_server *server, const char *name, ast_websocket_callback callback)
Remove a sub-protocol handler from the given server.
static const struct status_map websocket_status_map[]
static int unload_module(void)
#define WEBSOCKET_GUID
GUID used to compute the accept key, defined in the specifications.
const char * websocket_result_string_map[]
#define WS_PING_PAYLOAD_LEN
struct ast_websocket_protocol *AST_OPTIONAL_API_NAME() ast_websocket_sub_protocol_alloc(const char *name)
Allocate a websocket sub-protocol instance.
int AST_OPTIONAL_API_NAME() ast_websocket_set_timeout(struct ast_websocket *session, int timeout)
Set the timeout on a non-blocking WebSocket session.
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.
static enum ast_websocket_result websocket_client_connect(struct ast_websocket *ws, struct ast_websocket_client_options *options)
int AST_OPTIONAL_API_NAME() ast_websocket_close(struct ast_websocket *session, uint16_t reason)
Close function for websocket session.
struct ast_websocket_server *AST_OPTIONAL_API_NAME() ast_websocket_server_create(void)
Creates a ast_websocket_server.
static void websocket_client_start_handshake_timer(struct websocket_client *client)
int AST_OPTIONAL_API_NAME() ast_websocket_remove_protocol(const char *name, ast_websocket_callback callback)
Remove a sub-protocol handler from the default /ws server.
static void session_destroy_fn(void *obj)
Destructor function for sessions.
void AST_OPTIONAL_API_NAME() ast_websocket_unref(struct ast_websocket *session)
Decrease the reference count for a WebSocket session.
static const char * websocket_opcode2str(enum ast_websocket_opcode opcode)
static const char * opcode_map[]
int AST_OPTIONAL_API_NAME() ast_websocket_read_string(struct ast_websocket *ws, char **buf)
Read a WebSocket frame containing string data.
int AST_OPTIONAL_API_NAME() ast_websocket_add_protocol2(struct ast_websocket_protocol *protocol)
Add a sub-protocol handler to the default /ws server.
static void websocket_client_stop_handshake_timer(struct websocket_client *client)
#define MAXIMUM_RECONSTRUCTION_CEILING
Maximum reconstruction size for multi-frame payload reconstruction.
const char *AST_OPTIONAL_API_NAME() ast_websocket_result_to_str(enum ast_websocket_result result)
Convert a websocket result code to a string.
const char * method
Definition res_pjsip.c:1273
static struct @522 args
#define NULL
Definition resample.c:96
Scheduler Routines (derived from cheops)
#define AST_SCHED_DEL(sched, id)
Remove a scheduler entry.
Definition sched.h:46
int ast_sched_add(struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data) attribute_warn_unused_result
Adds a scheduled event.
Definition sched.c:567
void ast_sched_context_destroy(struct ast_sched_context *c)
destroys a schedule context
Definition sched.c:271
int ast_sched_start_thread(struct ast_sched_context *con)
Start a thread for processing scheduler entries.
Definition sched.c:197
struct ast_sched_context * ast_sched_context_create(void)
Create a scheduler context.
Definition sched.c:238
String manipulation functions.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition strings.h:1139
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition strings.h:80
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition strings.h:65
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition strings.h:659
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition strings.h:1113
static force_inline int attribute_pure ast_str_case_hash(const char *str)
Compute a hash value on a case-insensitive string.
Definition strings.h:1303
#define AST_YESNO(x)
return Yes or No depending on the argument.
Definition strings.h:143
char *attribute_pure ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition strings.h:761
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition strings.h:223
Generic container type.
Definition of a URI handler.
Definition http.h:102
ast_http_callback callback
Definition http.h:107
const char * uri
Definition http.h:105
void * data
Definition http.h:116
struct ast_module * self
Definition module.h:356
Socket address structure.
Definition netsock2.h:97
Support for dynamic strings.
Definition strings.h:623
arguments for the accepting thread
Definition tcptls.h:130
struct ast_tls_config * tls_cfg
Definition tcptls.h:135
describes a server instance
Definition tcptls.h:151
struct ast_iostream * stream
Definition tcptls.h:162
struct ast_sockaddr remote_address
Definition tcptls.h:153
Stores parsed uri information.
Definition uri.c:30
char * host
Definition uri.c:36
char * path
Definition uri.c:40
char uri[0]
Definition uri.c:44
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
Options used for a websocket client.
struct ast_tls_config * tls_cfg
A websocket protocol implementation.
ast_websocket_callback session_established
Callback called when a new session is established. Mandatory.
unsigned int version
Protocol version. Should be set to /ref AST_WEBSOCKET_PROTOCOL_VERSION.
char * name
Name of the protocol.
ast_websocket_pre_callback session_attempted
Callback called when a new session is attempted. Optional.
Structure for a WebSocket server.
struct ao2_container * protocols
Structure definition for session.
unsigned int close_sent
struct ast_iostream * stream
enum ws_closed_by closed_by
struct websocket_client * client
struct ast_sockaddr local_address
struct ast_sockaddr remote_address
unsigned int closing
char session_id[AST_UUID_STR_LEN]
unsigned int secure
enum ast_websocket_opcode opcode
char buf[AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE]
unsigned int non_blocking
enum ast_websocket_status_code code
const char * desc
struct ast_websocket_client_options * options
struct ast_tcptls_session_args * args
struct ast_tcptls_session_instance * ser
struct ast_str * resource_name
int value
Definition syslog.c:37
int ast_ssl_setup_client(struct ast_tls_config *cfg)
Set up an SSL client.
Definition tcptls.c:592
struct ast_tcptls_session_instance * ast_tcptls_start_tls(struct ast_tcptls_session_instance *tcptls_session)
Start TLS negotiation on an existing unsecured connection.
Definition tcptls.c:133
void ast_ssl_teardown(struct ast_tls_config *cfg)
free resources used by an SSL server
Definition tcptls.c:597
struct ast_tcptls_session_instance * ast_tcptls_client_create(struct ast_tcptls_session_args *desc)
Creates a client connection's ast_tcptls_session_instance.
Definition tcptls.c:701
struct ast_tcptls_session_instance * ast_tcptls_client_start_timeout(struct ast_tcptls_session_instance *tcptls_session, int timeout)
Attempt to connect and start a tcptls session within the given timeout.
Definition tcptls.c:667
static struct test_options options
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition time.h:159
Handle unaligned data access.
static void put_unaligned_uint16(void *p, unsigned short datum)
Definition unaligned.h:65
static unsigned short get_unaligned_uint16(const void *p)
Definition unaligned.h:44
static void put_unaligned_uint64(void *p, uint64_t datum)
Definition unaligned.h:51
static void put_unaligned_uint32(void *p, unsigned int datum)
Definition unaligned.h:58
static uint64_t get_unaligned_uint64(const void *p)
Definition unaligned.h:32
const char * ast_uri_path(const struct ast_uri *uri)
Retrieve the uri path.
Definition uri.c:135
struct ast_uri * ast_uri_parse_http(const char *uri)
Parse the given http uri into a structure.
Definition uri.c:290
struct ast_uri * ast_uri_parse_websocket(const char *uri)
Parse the given websocket uri into a structure.
Definition uri.c:295
const char * ast_uri_query(const struct ast_uri *uri)
Retrieve the uri query parameters.
Definition uri.c:140
const char * ast_uri_user_info(const struct ast_uri *uri)
Retrieve the uri user information.
Definition uri.c:120
char * ast_uri_make_host_with_port(const struct ast_uri *uri)
Retrieve a string of the host and port.
Definition uri.c:300
static char base64[64]
Definition utils.c:80
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition utils.h:981
#define ast_assert(a)
Definition utils.h:779
#define MIN(a, b)
Definition utils.h:252
int ast_base64encode(char *dst, const unsigned char *src, int srclen, int max)
Encode data in base64.
Definition utils.c:404
long int ast_random(void)
Definition utils.c:2346
#define ast_fd_set_flags(fd, flags)
Set flags on the given file descriptor.
Definition utils.h:1079
#define ARRAY_IN_BOUNDS(v, a)
Checks to see if value is within the bounds of the given array.
Definition utils.h:727
#define ARRAY_LEN(a)
Definition utils.h:706
void ast_sha1_hash_uint(uint8_t *digest, const char *input)
Produces SHA1 hash based on input string, stored in uint8_t array.
Definition utils.c:284
Universally unique identifier support.
#define AST_UUID_STR_LEN
Definition uuid.h:27
char * ast_uuid_generate_str(char *buf, size_t size)
Generate a UUID string.
Definition uuid.c:141