2014-11-24 11:01:12 +01:00
|
|
|
/*
|
2022-01-31 22:02:06 +01:00
|
|
|
* Copyright (c) 2014-2022 Joris Vink <joris@coders.se>
|
2014-11-24 11:01:12 +01:00
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/param.h>
|
2019-03-06 09:29:46 +01:00
|
|
|
#include <sys/types.h>
|
2014-11-24 11:01:12 +01:00
|
|
|
|
|
|
|
#include <limits.h>
|
2016-01-22 12:08:13 +01:00
|
|
|
#include <string.h>
|
2014-11-24 11:01:12 +01:00
|
|
|
|
|
|
|
#include "kore.h"
|
|
|
|
#include "http.h"
|
2022-02-17 13:45:28 +01:00
|
|
|
#include "sha1.h"
|
2014-11-24 11:01:12 +01:00
|
|
|
|
|
|
|
#define WEBSOCKET_FRAME_HDR 2
|
|
|
|
#define WEBSOCKET_MASK_LEN 4
|
|
|
|
#define WEBSOCKET_PAYLOAD_SINGLE 125
|
|
|
|
#define WEBSOCKET_PAYLOAD_EXTEND_1 126
|
|
|
|
#define WEBSOCKET_PAYLOAD_EXTEND_2 127
|
|
|
|
#define WEBSOCKET_OPCODE_MASK 0x0f
|
|
|
|
#define WEBSOCKET_FRAME_LENGTH(x) ((x) & ~(1 << 7))
|
|
|
|
#define WEBSOCKET_HAS_MASK(x) ((x) & (1 << 7))
|
|
|
|
#define WEBSOCKET_HAS_FINFLAG(x) ((x) & (1 << 7))
|
|
|
|
#define WEBSOCKET_RSV(x, i) ((x) & (1 << (7 - i)))
|
|
|
|
|
|
|
|
#define WEBSOCKET_SERVER_RESPONSE "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
|
|
|
|
|
|
|
|
|
|
u_int64_t kore_websocket_timeout = 120000;
|
|
|
|
u_int64_t kore_websocket_maxframe = 16384;
|
|
|
|
|
|
|
|
static int websocket_recv_frame(struct netbuf *);
|
|
|
|
static int websocket_recv_opcode(struct netbuf *);
|
|
|
|
static void websocket_disconnect(struct connection *);
|
2015-06-23 18:17:14 +02:00
|
|
|
static void websocket_frame_build(struct kore_buf *, u_int8_t,
|
|
|
|
const void *, size_t);
|
2014-11-24 11:01:12 +01:00
|
|
|
|
|
|
|
void
|
2017-01-29 22:57:34 +01:00
|
|
|
kore_websocket_handshake(struct http_request *req, const char *onconnect,
|
|
|
|
const char *onmessage, const char *ondisconnect)
|
2014-11-24 11:01:12 +01:00
|
|
|
{
|
2022-02-17 13:45:28 +01:00
|
|
|
SHA1_CTX sctx;
|
2014-11-24 11:01:12 +01:00
|
|
|
struct kore_buf *buf;
|
2018-02-14 13:48:49 +01:00
|
|
|
char *base64;
|
|
|
|
const char *key, *version;
|
2022-02-17 13:45:28 +01:00
|
|
|
u_int8_t digest[SHA1_DIGEST_LENGTH];
|
2014-11-24 11:01:12 +01:00
|
|
|
|
|
|
|
if (!http_request_header(req, "sec-websocket-key", &key)) {
|
|
|
|
http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!http_request_header(req, "sec-websocket-version", &version)) {
|
|
|
|
http_response_header(req, "sec-websocket-version", "13");
|
|
|
|
http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strcmp(version, "13")) {
|
|
|
|
http_response_header(req, "sec-websocket-version", "13");
|
|
|
|
http_response(req, HTTP_STATUS_BAD_REQUEST, NULL, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-07-14 12:34:29 +02:00
|
|
|
buf = kore_buf_alloc(128);
|
2014-11-24 11:01:12 +01:00
|
|
|
kore_buf_appendf(buf, "%s%s", key, WEBSOCKET_SERVER_RESPONSE);
|
|
|
|
|
2022-02-17 13:45:28 +01:00
|
|
|
SHA1Init(&sctx);
|
|
|
|
SHA1Update(&sctx, buf->data, buf->offset);
|
|
|
|
SHA1Final(digest, &sctx);
|
2014-11-24 11:01:12 +01:00
|
|
|
|
|
|
|
kore_buf_free(buf);
|
|
|
|
|
|
|
|
if (!kore_base64_encode(digest, sizeof(digest), &base64)) {
|
|
|
|
http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
http_response_header(req, "upgrade", "websocket");
|
|
|
|
http_response_header(req, "connection", "upgrade");
|
|
|
|
http_response_header(req, "sec-websocket-accept", base64);
|
2016-07-12 13:54:14 +02:00
|
|
|
kore_free(base64);
|
2014-11-24 11:01:12 +01:00
|
|
|
|
2015-06-26 05:14:01 +02:00
|
|
|
req->owner->proto = CONN_PROTO_WEBSOCKET;
|
2014-11-24 11:01:12 +01:00
|
|
|
http_response(req, HTTP_STATUS_SWITCHING_PROTOCOLS, NULL, 0);
|
|
|
|
net_recv_reset(req->owner, WEBSOCKET_FRAME_HDR, websocket_recv_opcode);
|
|
|
|
|
|
|
|
req->owner->disconnect = websocket_disconnect;
|
|
|
|
req->owner->rnb->flags &= ~NETBUF_CALL_CB_ALWAYS;
|
|
|
|
|
2019-05-05 14:47:04 +02:00
|
|
|
req->owner->http_timeout = 0;
|
2014-11-25 20:00:33 +01:00
|
|
|
req->owner->idle_timer.start = kore_time_ms();
|
2014-11-24 11:01:12 +01:00
|
|
|
req->owner->idle_timer.length = kore_websocket_timeout;
|
|
|
|
|
2017-01-29 22:57:34 +01:00
|
|
|
if (onconnect != NULL) {
|
|
|
|
req->owner->ws_connect = kore_runtime_getcall(onconnect);
|
|
|
|
if (req->owner->ws_connect == NULL)
|
|
|
|
fatal("no symbol '%s' for ws_connect", onconnect);
|
|
|
|
} else {
|
|
|
|
req->owner->ws_connect = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (onmessage != NULL) {
|
|
|
|
req->owner->ws_message = kore_runtime_getcall(onmessage);
|
|
|
|
if (req->owner->ws_message == NULL)
|
|
|
|
fatal("no symbol '%s' for ws_message", onmessage);
|
|
|
|
} else {
|
|
|
|
req->owner->ws_message = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ondisconnect != NULL) {
|
|
|
|
req->owner->ws_disconnect = kore_runtime_getcall(ondisconnect);
|
|
|
|
if (req->owner->ws_disconnect == NULL)
|
|
|
|
fatal("no symbol '%s' for ws_disconnect", ondisconnect);
|
|
|
|
} else {
|
|
|
|
req->owner->ws_disconnect = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req->owner->ws_connect != NULL)
|
|
|
|
kore_runtime_wsconnect(req->owner->ws_connect, req->owner);
|
2014-11-24 11:01:12 +01:00
|
|
|
}
|
|
|
|
|
2018-04-13 07:40:37 +02:00
|
|
|
int
|
|
|
|
kore_websocket_send_clean(struct netbuf *nb)
|
|
|
|
{
|
2018-04-13 07:41:22 +02:00
|
|
|
kore_free(nb->buf);
|
2018-08-30 09:13:11 +02:00
|
|
|
return (0);
|
2018-04-13 07:40:37 +02:00
|
|
|
}
|
|
|
|
|
2014-11-24 11:01:12 +01:00
|
|
|
void
|
2015-06-23 18:17:14 +02:00
|
|
|
kore_websocket_send(struct connection *c, u_int8_t op, const void *data,
|
|
|
|
size_t len)
|
2014-11-24 11:01:12 +01:00
|
|
|
{
|
2017-12-24 17:28:17 +01:00
|
|
|
struct kore_buf frame;
|
2014-11-24 11:01:12 +01:00
|
|
|
|
2017-12-24 17:28:17 +01:00
|
|
|
kore_buf_init(&frame, len);
|
|
|
|
websocket_frame_build(&frame, op, data, len);
|
2018-04-13 07:41:22 +02:00
|
|
|
net_send_stream(c, frame.data, frame.offset,
|
|
|
|
kore_websocket_send_clean, NULL);
|
2017-12-24 17:28:17 +01:00
|
|
|
|
|
|
|
/* net_send_stream() takes over the buffer data pointer. */
|
|
|
|
frame.data = NULL;
|
|
|
|
kore_buf_cleanup(&frame);
|
2017-01-30 22:21:57 +01:00
|
|
|
|
|
|
|
net_send_flush(c);
|
2015-06-23 18:17:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
kore_websocket_broadcast(struct connection *src, u_int8_t op, const void *data,
|
|
|
|
size_t len, int scope)
|
|
|
|
{
|
|
|
|
struct connection *c;
|
|
|
|
struct kore_buf *frame;
|
|
|
|
|
2016-07-14 12:34:29 +02:00
|
|
|
frame = kore_buf_alloc(len);
|
2015-06-23 18:17:14 +02:00
|
|
|
websocket_frame_build(frame, op, data, len);
|
|
|
|
|
|
|
|
TAILQ_FOREACH(c, &connections, list) {
|
|
|
|
if (c != src && c->proto == CONN_PROTO_WEBSOCKET) {
|
2015-11-27 16:22:50 +01:00
|
|
|
net_send_queue(c, frame->data, frame->offset);
|
2015-06-23 18:17:14 +02:00
|
|
|
net_send_flush(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-06 21:08:36 +02:00
|
|
|
if (scope == WEBSOCKET_BROADCAST_GLOBAL) {
|
|
|
|
kore_msg_send(KORE_MSG_WORKER_ALL,
|
|
|
|
KORE_MSG_WEBSOCKET, frame->data, frame->offset);
|
|
|
|
}
|
2015-06-23 18:17:14 +02:00
|
|
|
|
|
|
|
kore_buf_free(frame);
|
|
|
|
}
|
2014-11-24 11:01:12 +01:00
|
|
|
|
2015-06-23 18:17:14 +02:00
|
|
|
static void
|
|
|
|
websocket_frame_build(struct kore_buf *frame, u_int8_t op, const void *data,
|
|
|
|
size_t len)
|
|
|
|
{
|
|
|
|
u_int8_t len_1;
|
|
|
|
u_int16_t len16;
|
|
|
|
u_int64_t len64;
|
2014-11-24 11:01:12 +01:00
|
|
|
|
|
|
|
if (len > WEBSOCKET_PAYLOAD_SINGLE) {
|
2017-01-30 21:52:22 +01:00
|
|
|
if (len <= USHRT_MAX)
|
2014-11-24 11:01:12 +01:00
|
|
|
len_1 = WEBSOCKET_PAYLOAD_EXTEND_1;
|
|
|
|
else
|
|
|
|
len_1 = WEBSOCKET_PAYLOAD_EXTEND_2;
|
|
|
|
} else {
|
|
|
|
len_1 = len;
|
|
|
|
}
|
|
|
|
|
|
|
|
op |= (1 << 7);
|
|
|
|
kore_buf_append(frame, &op, sizeof(op));
|
|
|
|
|
|
|
|
len_1 &= ~(1 << 7);
|
|
|
|
kore_buf_append(frame, &len_1, sizeof(len_1));
|
|
|
|
|
2015-07-16 06:55:44 +02:00
|
|
|
if (len_1 > WEBSOCKET_PAYLOAD_SINGLE) {
|
2014-11-24 11:01:12 +01:00
|
|
|
switch (len_1) {
|
|
|
|
case WEBSOCKET_PAYLOAD_EXTEND_1:
|
|
|
|
net_write16((u_int8_t *)&len16, len);
|
|
|
|
kore_buf_append(frame, &len16, sizeof(len16));
|
|
|
|
break;
|
|
|
|
case WEBSOCKET_PAYLOAD_EXTEND_2:
|
|
|
|
net_write64((u_int8_t *)&len64, len);
|
|
|
|
kore_buf_append(frame, &len64, sizeof(len64));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-14 13:48:49 +01:00
|
|
|
if (data != NULL && len > 0)
|
|
|
|
kore_buf_append(frame, data, len);
|
2014-11-24 11:01:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
websocket_recv_opcode(struct netbuf *nb)
|
|
|
|
{
|
|
|
|
u_int8_t op, len;
|
|
|
|
struct connection *c = nb->owner;
|
|
|
|
|
2022-08-18 15:20:55 +02:00
|
|
|
if (!WEBSOCKET_HAS_MASK(nb->buf[1]))
|
2014-11-24 11:01:12 +01:00
|
|
|
return (KORE_RESULT_ERROR);
|
|
|
|
|
|
|
|
if (WEBSOCKET_RSV(nb->buf[0], 1) || WEBSOCKET_RSV(nb->buf[0], 2) ||
|
2022-08-18 15:20:55 +02:00
|
|
|
WEBSOCKET_RSV(nb->buf[0], 3))
|
2014-11-24 11:01:12 +01:00
|
|
|
return (KORE_RESULT_ERROR);
|
|
|
|
|
|
|
|
len = WEBSOCKET_FRAME_LENGTH(nb->buf[1]);
|
|
|
|
|
|
|
|
op = nb->buf[0] & WEBSOCKET_OPCODE_MASK;
|
|
|
|
switch (op) {
|
|
|
|
case WEBSOCKET_OP_CONT:
|
|
|
|
case WEBSOCKET_OP_TEXT:
|
|
|
|
case WEBSOCKET_OP_BINARY:
|
|
|
|
break;
|
|
|
|
case WEBSOCKET_OP_CLOSE:
|
|
|
|
case WEBSOCKET_OP_PING:
|
|
|
|
case WEBSOCKET_OP_PONG:
|
|
|
|
if (len > WEBSOCKET_PAYLOAD_SINGLE ||
|
2022-08-18 15:20:55 +02:00
|
|
|
!WEBSOCKET_HAS_FINFLAG(nb->buf[0]))
|
2014-11-24 11:01:12 +01:00
|
|
|
return (KORE_RESULT_ERROR);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return (KORE_RESULT_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (len) {
|
|
|
|
case WEBSOCKET_PAYLOAD_EXTEND_1:
|
|
|
|
len += sizeof(u_int16_t);
|
|
|
|
break;
|
|
|
|
case WEBSOCKET_PAYLOAD_EXTEND_2:
|
|
|
|
len += sizeof(u_int64_t);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
len += WEBSOCKET_MASK_LEN;
|
2015-03-16 16:52:40 +01:00
|
|
|
net_recv_expand(c, len, websocket_recv_frame);
|
2014-11-24 11:01:12 +01:00
|
|
|
|
|
|
|
return (KORE_RESULT_OK);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
websocket_recv_frame(struct netbuf *nb)
|
|
|
|
{
|
|
|
|
struct connection *c;
|
|
|
|
int ret;
|
|
|
|
u_int64_t len, i, total;
|
|
|
|
u_int8_t op, moff, extra;
|
|
|
|
|
|
|
|
c = nb->owner;
|
|
|
|
op = nb->buf[0] & WEBSOCKET_OPCODE_MASK;
|
|
|
|
len = WEBSOCKET_FRAME_LENGTH(nb->buf[1]);
|
|
|
|
|
|
|
|
switch (len) {
|
|
|
|
case WEBSOCKET_PAYLOAD_EXTEND_1:
|
|
|
|
moff = 4;
|
|
|
|
extra = sizeof(u_int16_t);
|
|
|
|
len = net_read16(&nb->buf[2]);
|
|
|
|
break;
|
|
|
|
case WEBSOCKET_PAYLOAD_EXTEND_2:
|
|
|
|
moff = 10;
|
|
|
|
extra = sizeof(u_int64_t);
|
|
|
|
len = net_read64(&nb->buf[2]);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
extra = 0;
|
|
|
|
moff = 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-08-18 15:20:55 +02:00
|
|
|
if (len > kore_websocket_maxframe)
|
2014-11-24 11:01:12 +01:00
|
|
|
return (KORE_RESULT_ERROR);
|
|
|
|
|
|
|
|
extra += WEBSOCKET_FRAME_HDR;
|
|
|
|
total = len + extra + WEBSOCKET_MASK_LEN;
|
|
|
|
if (total > nb->b_len) {
|
|
|
|
total -= nb->b_len;
|
2015-03-16 16:52:40 +01:00
|
|
|
net_recv_expand(c, total, websocket_recv_frame);
|
2014-11-24 11:01:12 +01:00
|
|
|
return (KORE_RESULT_OK);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (total != nb->b_len)
|
|
|
|
return (KORE_RESULT_ERROR);
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
nb->buf[moff + 4 + i] ^= nb->buf[moff + (i % 4)];
|
|
|
|
|
|
|
|
ret = KORE_RESULT_OK;
|
|
|
|
switch (op) {
|
|
|
|
case WEBSOCKET_OP_PONG:
|
2017-01-30 22:21:57 +01:00
|
|
|
break;
|
|
|
|
case WEBSOCKET_OP_CONT:
|
2014-11-24 11:01:12 +01:00
|
|
|
ret = KORE_RESULT_ERROR;
|
2017-02-06 11:39:50 +01:00
|
|
|
kore_log(LOG_ERR,
|
|
|
|
"%p: we do not support op 0x%02x yet", (void *)c, op);
|
2014-11-24 11:01:12 +01:00
|
|
|
break;
|
|
|
|
case WEBSOCKET_OP_TEXT:
|
|
|
|
case WEBSOCKET_OP_BINARY:
|
2017-01-29 22:57:34 +01:00
|
|
|
if (c->ws_message != NULL) {
|
|
|
|
kore_runtime_wsmessage(c->ws_message,
|
|
|
|
c, op, &nb->buf[moff + 4], len);
|
|
|
|
}
|
2014-11-24 11:01:12 +01:00
|
|
|
break;
|
|
|
|
case WEBSOCKET_OP_CLOSE:
|
2018-10-09 19:34:40 +02:00
|
|
|
c->evt.flags &= ~KORE_EVENT_READ;
|
2017-01-30 22:21:57 +01:00
|
|
|
if (!(c->flags & CONN_WS_CLOSE_SENT)) {
|
|
|
|
c->flags |= CONN_WS_CLOSE_SENT;
|
|
|
|
kore_websocket_send(c, WEBSOCKET_OP_CLOSE, NULL, 0);
|
|
|
|
}
|
2014-11-24 11:01:12 +01:00
|
|
|
kore_connection_disconnect(c);
|
|
|
|
break;
|
|
|
|
case WEBSOCKET_OP_PING:
|
|
|
|
kore_websocket_send(c, WEBSOCKET_OP_PONG,
|
|
|
|
&nb->buf[moff + 4], len);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return (KORE_RESULT_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
net_recv_reset(c, WEBSOCKET_FRAME_HDR, websocket_recv_opcode);
|
2017-01-30 22:35:34 +01:00
|
|
|
|
2014-11-24 11:01:12 +01:00
|
|
|
return (ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
websocket_disconnect(struct connection *c)
|
|
|
|
{
|
2017-01-30 22:25:08 +01:00
|
|
|
if (c->ws_disconnect != NULL)
|
|
|
|
kore_runtime_wsdisconnect(c->ws_disconnect, c);
|
|
|
|
|
2017-01-30 22:21:57 +01:00
|
|
|
if (!(c->flags & CONN_WS_CLOSE_SENT)) {
|
|
|
|
c->flags |= CONN_WS_CLOSE_SENT;
|
2018-10-09 19:34:40 +02:00
|
|
|
c->evt.flags &= ~KORE_EVENT_READ;
|
2017-01-30 22:21:57 +01:00
|
|
|
kore_websocket_send(c, WEBSOCKET_OP_CLOSE, NULL, 0);
|
|
|
|
}
|
2014-11-24 11:01:12 +01:00
|
|
|
}
|