[Spice-commits] 23 commits - configure.ac .gitlab-ci.yml server/Makefile.am server/meson.build server/reds.c server/red-stream.c server/red-stream.h server/tests server/websocket.c server/websocket.h
GitLab Mirror
gitlab-mirror at kemper.freedesktop.org
Thu Jun 27 09:37:55 UTC 2019
.gitlab-ci.yml | 20
configure.ac | 9
server/Makefile.am | 2
server/meson.build | 2
server/red-stream.c | 56 ++
server/red-stream.h | 2
server/reds.c | 15
server/tests/.gitignore | 1
server/tests/Makefile.am | 6
server/tests/autobahn-check-report | 18
server/tests/fuzzingclient.json | 11
server/tests/meson.build | 1
server/tests/test-glib-compat.h | 2
server/tests/test-websocket.c | 299 +++++++++++++
server/websocket.c | 796 +++++++++++++++++++++++++++++++++++++
server/websocket.h | 43 +
16 files changed, 1282 insertions(+), 1 deletion(-)
New commits:
commit a68fd56b418aa891242c589bee7fcac172ba28fe
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Thu Jun 20 12:39:31 2019 +0100
ci: Add test for websockets
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6e5204af..b303d465 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -136,3 +136,23 @@ makecheck-windows:
- mingw64-configure --disable-celt051
- mingw64-make
- mingw64-make LOG_COMPILE=wine -C server check || (cat server/tests/test-suite.log && exit 1)
+
+websocket-autobahn:
+ before_script:
+ - apt-get update || true
+ - apt-get install -y python-six python-pyparsing libopus-dev libssl-dev libglib2.0-dev
+ - git clone ${CI_REPOSITORY_URL/spice.git/spice-protocol.git}
+ - (cd spice-protocol && ./autogen.sh --prefix=/usr && make install)
+ image: crossbario/autobahn-testsuite
+ script:
+ - ./autogen.sh
+ - make -j4
+ - ./server/tests/test-websocket & pid=$!
+ - wstest -m fuzzingclient -s server/tests/fuzzingclient.json
+ - kill $pid
+ - server/tests/autobahn-check-report reports/servers/index.json
+ - rm -rf reports/servers
+ - ./server/tests/test-websocket -n & pid=$!
+ - wstest -m fuzzingclient -s server/tests/fuzzingclient.json
+ - kill $pid
+ - server/tests/autobahn-check-report reports/servers/index.json
diff --git a/server/tests/autobahn-check-report b/server/tests/autobahn-check-report
new file mode 100755
index 00000000..9e29128c
--- /dev/null
+++ b/server/tests/autobahn-check-report
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+import sys
+import json
+
+num_tests = 0
+for server in json.load(open(sys.argv[1])).values():
+ for test, result in server.items():
+ is_test = 0
+ for k, v in result.items():
+ if k[:8].lower() != 'behavior':
+ continue
+ is_test = 1
+ if v != 'OK' and v != 'INFORMATIONAL':
+ raise Exception('Invalid %s %s for test %s' % (k, v, test))
+ num_tests += is_test
+if num_tests < 100:
+ raise Exception('Too few tests done %s' % num_tests)
+print('Output report is fine')
diff --git a/server/tests/fuzzingclient.json b/server/tests/fuzzingclient.json
new file mode 100644
index 00000000..8bc1b718
--- /dev/null
+++ b/server/tests/fuzzingclient.json
@@ -0,0 +1,11 @@
+{
+ "outdir": "./reports/servers",
+ "servers": [
+ {
+ "url": "ws://127.0.0.1:7777"
+ }
+ ],
+ "cases": ["*"],
+ "exclude-cases": ["6.*","12.*","13.*"],
+ "exclude-agent-cases": {}
+}
commit c1c5d8bf5abb78054c52093c144931a8d9aa1c80
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Fri Feb 3 19:05:22 2017 +0000
websocket: Handle continuation and 0-size frames
The WebSocket protocol allows 0-size frames so a returned lenth of
0 does not only mean an issue but it's perfectly expected.
This is also required by WebSocket specification.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/tests/test-websocket.c b/server/tests/test-websocket.c
index 120a522f..dc7b7d34 100644
--- a/server/tests/test-websocket.c
+++ b/server/tests/test-websocket.c
@@ -219,21 +219,28 @@ handle_client(int new_sock)
assert(ws);
char buffer[4096];
+ bool got_message = false;
size_t to_send = 0;
unsigned ws_flags = WEBSOCKET_BINARY_FINAL;
while (!got_term) {
int events = 0;
- if (sizeof(buffer) > to_send) {
+ if (sizeof(buffer) > to_send &&
+ (!got_message || (ws_flags & WEBSOCKET_FINAL) == 0)) {
events |= POLLIN;
}
- if (to_send) {
+ assert(!to_send || got_message);
+ if (got_message) {
events |= POLLOUT;
}
events = wait_for(new_sock, events);
if (events & POLLIN) {
assert(sizeof(buffer) > to_send);
+ unsigned flags;
int size = websocket_read(ws, (void *) (buffer + to_send), sizeof(buffer) - to_send,
- &ws_flags);
+ &flags);
+ if (flags) {
+ ws_flags = flags;
+ }
if (size < 0) {
if (errno == EIO) {
@@ -245,14 +252,15 @@ handle_client(int new_sock)
err(1, "recv");
}
- if (size == 0) {
+ if (size == 0 && flags == 0) {
break;
}
if (debug) {
- printf("received %d bytes of data\n", size);
+ printf("received %d bytes of data flags %x\n", size, flags);
}
to_send += size;
+ got_message = true;
}
if (events & POLLOUT) {
@@ -275,12 +283,11 @@ handle_client(int new_sock)
printf("sent %d bytes of data\n", size);
}
- if (size == 0) {
- errx(1, "Unexpected short write\n");
- }
-
to_send -= size;
memmove(buffer, buffer + size, to_send);
+ if (!to_send) {
+ got_message = false;
+ }
}
}
diff --git a/server/websocket.c b/server/websocket.c
index e076645d..f5df63f8 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -83,7 +83,7 @@ typedef struct {
} WebSocketControl;
typedef struct {
- uint8_t type;
+ uint8_t type, fin, unfinished;
uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
int header_pos;
bool frame_ready:1;
@@ -100,6 +100,7 @@ struct RedsWebSocket {
uint64_t write_remainder;
uint8_t write_header[WEBSOCKET_MAX_HEADER_SIZE];
uint8_t write_header_pos, write_header_len;
+ bool send_unfinished;
bool close_pending;
WebSocketControl pong;
WebSocketControl pending_pong;
@@ -247,7 +248,9 @@ static char *generate_reply_key(char *buf)
static void websocket_clear_frame(websocket_frame_t *frame)
{
+ uint8_t unfinished = frame->unfinished;
memset(frame, 0, sizeof(*frame));
+ frame->unfinished = unfinished;
}
/* Extract a frame header of data from a set of data transmitted by
@@ -261,7 +264,7 @@ static bool websocket_get_frame_header(websocket_frame_t *frame)
return true;
}
- fin = frame->header[0] & FIN_FLAG;
+ fin = frame->fin = frame->header[0] & FIN_FLAG;
frame->type = frame->header[0] & TYPE_MASK;
used++;
@@ -282,8 +285,16 @@ static bool websocket_get_frame_header(websocket_frame_t *frame)
/* This is a Spice specific optimization. We don't really
care about assembling frames fully, so we treat
a frame in process as a finished frame and pass it along. */
- if (!fin && frame->type == CONTINUATION_FRAME) {
- frame->type = BINARY_FRAME;
+ if ((frame->type & CONTROL_FRAME_MASK) == 0) {
+ if (frame->type == CONTINUATION_FRAME) {
+ if (!frame->unfinished) {
+ return false;
+ }
+ frame->type = frame->unfinished;
+ } else if (frame->unfinished) {
+ return false;
+ }
+ frame->unfinished = fin ? 0 : frame->type;
}
frame->expected_len = extract_length(frame->header + used, &used);
@@ -358,18 +369,21 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size, unsigned *flags
send_pending_data(ws);
return 0;
} else if (frame->type == BINARY_FRAME || frame->type == TEXT_FRAME) {
- rc = ws->raw_read(ws->raw_stream, buf,
- MIN(size, frame->expected_len - frame->relayed));
- if (rc <= 0) {
- goto read_error;
+ rc = 0;
+ if (frame->expected_len > frame->relayed) {
+ rc = ws->raw_read(ws->raw_stream, buf,
+ MIN(size, frame->expected_len - frame->relayed));
+ if (rc <= 0) {
+ goto read_error;
+ }
+
+ relay_data(buf, rc, frame);
+ n += rc;
+ buf += rc;
+ size -= rc;
}
*flags = frame->type;
-
- relay_data(buf, rc, frame);
- n += rc;
- buf += rc;
- size -= rc;
} else if (frame->type == PING_FRAME) {
spice_assert(ws->pong.data_len == frame->expected_len);
rc = 0;
@@ -409,8 +423,11 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size, unsigned *flags
}
frame->relayed += rc;
if (frame->relayed >= frame->expected_len) {
+ if (*flags) {
+ *flags |= frame->fin;
+ }
websocket_clear_frame(frame);
- if (n) {
+ if (*flags) {
break;
}
}
@@ -433,8 +450,8 @@ static int fill_header(uint8_t *header, uint64_t len, uint8_t type)
int used = 0;
int i;
- type &= TYPE_MASK;
- header[0] = FIN_FLAG | (type ? type : BINARY_FRAME);
+ type &= FIN_FLAG|TYPE_MASK;
+ header[0] = type;
used++;
header[1] = 0;
@@ -512,7 +529,11 @@ static int send_data_header(RedsWebSocket *ws, uint64_t len, uint8_t type)
/* fill a new header */
ws->write_header_pos = 0;
+ if (ws->send_unfinished) {
+ type &= FIN_FLAG;
+ }
ws->write_header_len = fill_header(ws->write_header, len, type);
+ ws->send_unfinished = (type & FIN_FLAG) == 0;
return send_data_header_left(ws);
}
commit 9d6a438aae15826943756e45cb5be7a2c705f1a0
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Sun Jun 23 09:03:54 2019 +0100
websocket: Handle text data
Allows to specify and get frame type.
Type and flags are returned calling websocket_read and returned
calling websocket_write or websocket_writev.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/red-stream.c b/server/red-stream.c
index 1e0103b0..04be3af3 100644
--- a/server/red-stream.c
+++ b/server/red-stream.c
@@ -1163,17 +1163,23 @@ error:
static ssize_t stream_websocket_read(RedStream *s, void *buf, size_t size)
{
- return websocket_read(s->priv->ws, buf, size);
+ unsigned flags;
+ int len;
+
+ do {
+ len = websocket_read(s->priv->ws, buf, size, &flags);
+ } while (len == 0 && flags != 0);
+ return len;
}
static ssize_t stream_websocket_write(RedStream *s, const void *buf, size_t size)
{
- return websocket_write(s->priv->ws, buf, size);
+ return websocket_write(s->priv->ws, buf, size, WEBSOCKET_BINARY_FINAL);
}
static ssize_t stream_websocket_writev(RedStream *s, const struct iovec *iov, int iovcnt)
{
- return websocket_writev(s->priv->ws, (struct iovec *) iov, iovcnt);
+ return websocket_writev(s->priv->ws, (struct iovec *) iov, iovcnt, WEBSOCKET_BINARY_FINAL);
}
/*
diff --git a/server/tests/test-websocket.c b/server/tests/test-websocket.c
index 6596c27e..120a522f 100644
--- a/server/tests/test-websocket.c
+++ b/server/tests/test-websocket.c
@@ -220,6 +220,7 @@ handle_client(int new_sock)
char buffer[4096];
size_t to_send = 0;
+ unsigned ws_flags = WEBSOCKET_BINARY_FINAL;
while (!got_term) {
int events = 0;
if (sizeof(buffer) > to_send) {
@@ -231,7 +232,8 @@ handle_client(int new_sock)
events = wait_for(new_sock, events);
if (events & POLLIN) {
assert(sizeof(buffer) > to_send);
- int size = websocket_read(ws, (void *) (buffer + to_send), sizeof(buffer) - to_send);
+ int size = websocket_read(ws, (void *) (buffer + to_send), sizeof(buffer) - to_send,
+ &ws_flags);
if (size < 0) {
if (errno == EIO) {
@@ -254,7 +256,7 @@ handle_client(int new_sock)
}
if (events & POLLOUT) {
- int size = websocket_write(ws, buffer, to_send);
+ int size = websocket_write(ws, buffer, to_send, ws_flags);
if (size < 0) {
switch (errno) {
diff --git a/server/websocket.c b/server/websocket.c
index 514e72c9..e076645d 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -313,12 +313,14 @@ static void relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
}
}
-int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size, unsigned *flags)
{
int n = 0;
int rc;
websocket_frame_t *frame = &ws->read_frame;
+ *flags = 0;
+
if (ws->closed || ws->close_pending) {
/* this avoids infinite loop in the case connection is still open and we have
* pending data */
@@ -355,13 +357,15 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
websocket_clear_frame(frame);
send_pending_data(ws);
return 0;
- } else if (frame->type == BINARY_FRAME) {
+ } else if (frame->type == BINARY_FRAME || frame->type == TEXT_FRAME) {
rc = ws->raw_read(ws->raw_stream, buf,
MIN(size, frame->expected_len - frame->relayed));
if (rc <= 0) {
goto read_error;
}
+ *flags = frame->type;
+
relay_data(buf, rc, frame);
n += rc;
buf += rc;
@@ -406,6 +410,9 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
frame->relayed += rc;
if (frame->relayed >= frame->expected_len) {
websocket_clear_frame(frame);
+ if (n) {
+ break;
+ }
}
}
@@ -421,12 +428,13 @@ read_error:
return rc;
}
-static int fill_header(uint8_t *header, uint64_t len)
+static int fill_header(uint8_t *header, uint64_t len, uint8_t type)
{
int used = 0;
int i;
- header[0] = FIN_FLAG | BINARY_FRAME;
+ type &= TYPE_MASK;
+ header[0] = FIN_FLAG | (type ? type : BINARY_FRAME);
used++;
header[1] = 0;
@@ -497,14 +505,14 @@ static int send_data_header_left(RedsWebSocket *ws)
return -1;
}
-static int send_data_header(RedsWebSocket *ws, uint64_t len)
+static int send_data_header(RedsWebSocket *ws, uint64_t len, uint8_t type)
{
spice_assert(ws->write_header_pos >= ws->write_header_len);
spice_assert(ws->write_remainder == 0);
/* fill a new header */
ws->write_header_pos = 0;
- ws->write_header_len = fill_header(ws->write_header, len);
+ ws->write_header_len = fill_header(ws->write_header, len, type);
return send_data_header_left(ws);
}
@@ -557,7 +565,7 @@ static int send_pending_data(RedsWebSocket *ws)
}
/* Write a WebSocket frame with the enclosed data out. */
-int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
+int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt, unsigned flags)
{
uint64_t len;
int rc;
@@ -595,7 +603,7 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
}
ws->write_header_pos = 0;
- ws->write_header_len = fill_header(ws->write_header, len);
+ ws->write_header_len = fill_header(ws->write_header, len, flags);
iov_out[0].iov_len = ws->write_header_len;
iov_out[0].iov_base = ws->write_header;
rc = ws->raw_writev(ws->raw_stream, iov_out, iov_out_cnt);
@@ -622,7 +630,7 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
return rc;
}
-int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
+int websocket_write(RedsWebSocket *ws, const void *buf, size_t len, unsigned flags)
{
int rc;
@@ -636,7 +644,7 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
return rc;
}
if (ws->write_remainder == 0) {
- rc = send_data_header(ws, len);
+ rc = send_data_header(ws, len, flags);
if (rc <= 0) {
return rc;
}
diff --git a/server/websocket.h b/server/websocket.h
index b586e303..22120d93 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -21,9 +21,23 @@ typedef ssize_t (*websocket_writev_cb_t)(void *opaque, struct iovec *iov, int io
typedef struct RedsWebSocket RedsWebSocket;
+enum {
+ WEBSOCKET_TEXT = 1,
+ WEBSOCKET_BINARY= 2,
+ WEBSOCKET_FINAL = 0x80,
+ WEBSOCKET_TEXT_FINAL = WEBSOCKET_TEXT | WEBSOCKET_FINAL,
+ WEBSOCKET_BINARY_FINAL = WEBSOCKET_BINARY | WEBSOCKET_FINAL,
+};
+
RedsWebSocket *websocket_new(const void *buf, size_t len, void *stream, websocket_read_cb_t read_cb,
websocket_write_cb_t write_cb, websocket_writev_cb_t writev_cb);
void websocket_free(RedsWebSocket *ws);
-int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len);
-int websocket_write(RedsWebSocket *ws, const void *buf, size_t len);
-int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt);
+
+/**
+ * Read data from websocket.
+ * Can return 0 in case client sent an empty text/binary data, check
+ * flags to detect this.
+ */
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len, unsigned *flags);
+int websocket_write(RedsWebSocket *ws, const void *buf, size_t len, unsigned flags);
+int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt, unsigned flags);
commit 0c7f91511d979e94e0daa978a760dc98e4917bec
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Sat Jun 22 18:14:56 2019 +0100
websocket: Do not require "Sec-WebSocket-Protocol" header
Not strictly needed, client can work even without specifying
that.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index cb222c69..514e72c9 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -668,37 +668,41 @@ static int websocket_ack_close(RedsWebSocket *ws)
return rc;
}
-static bool websocket_is_start(char *buf)
+static bool websocket_is_start(char *buf, bool *p_has_prototol)
{
- const char *protocol = find_str(buf, "\nSec-WebSocket-Protocol:");
+ *p_has_prototol = false;
const char *key = find_str(buf, "\nSec-WebSocket-Key:");
if (strncmp(buf, "GET ", 4) != 0 ||
- protocol == NULL || key == NULL ||
+ key == NULL ||
!g_str_has_suffix(buf, "\r\n\r\n")) {
return false;
}
- /* check protocol value ignoring spaces before and after */
- int binary_pos = -1;
- sscanf(protocol, " binary %n", &binary_pos);
- if (binary_pos <= 0) {
- return false;
+ const char *protocol = find_str(buf, "\nSec-WebSocket-Protocol:");
+ if (protocol) {
+ *p_has_prototol = true;
+ /* check protocol value ignoring spaces before and after */
+ int binary_pos = -1;
+ sscanf(protocol, " binary %n", &binary_pos);
+ if (binary_pos <= 0) {
+ return false;
+ }
}
return true;
}
-static void websocket_create_reply(char *buf, char *outbuf)
+static void websocket_create_reply(char *buf, char *outbuf, bool has_protocol)
{
char *key;
key = generate_reply_key(buf);
sprintf(outbuf, "HTTP/1.1 101 Switching Protocols\r\n"
- "Upgrade: websocket\r\n"
+ "Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
- "Sec-WebSocket-Accept: %s\r\n"
- "Sec-WebSocket-Protocol: binary\r\n\r\n", key);
+ "Sec-WebSocket-Accept: %s\r\n%s\r\n", key,
+ has_protocol ? "Sec-WebSocket-Protocol: binary\r\n": "");
g_free(key);
}
@@ -731,13 +735,14 @@ RedsWebSocket *websocket_new(const void *buf, size_t len, void *stream, websocke
so it seems wisest to live with this theoretical flaw.
*/
- if (!websocket_is_start(rbuf)) {
+ bool has_protocol;
+ if (!websocket_is_start(rbuf, &has_protocol)) {
return NULL;
}
char outbuf[1024];
- websocket_create_reply(rbuf, outbuf);
+ websocket_create_reply(rbuf, outbuf, has_protocol);
rc = write_cb(stream, outbuf, strlen(outbuf));
if (rc != strlen(outbuf)) {
return NULL;
commit 975d444f2101b28de95e27498b3707c5fb6ef533
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Fri Jan 27 18:45:40 2017 +0000
test-websocket: Write a test helper to make possible to run Autobahn testsuite
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/tests/.gitignore b/server/tests/.gitignore
index 81b604bc..36e978d4 100644
--- a/server/tests/.gitignore
+++ b/server/tests/.gitignore
@@ -28,5 +28,6 @@ test-gst
test-leaks
test-sasl
test-record
+test-websocket
/test-*.log
/test-*.trs
diff --git a/server/tests/Makefile.am b/server/tests/Makefile.am
index c50826e6..26aadd5f 100644
--- a/server/tests/Makefile.am
+++ b/server/tests/Makefile.am
@@ -83,6 +83,12 @@ noinst_PROGRAMS = \
$(check_PROGRAMS) \
$(NULL)
+if !OS_WIN32
+noinst_PROGRAMS += \
+ test-websocket \
+ $(NULL)
+endif
+
TESTS = $(check_PROGRAMS) \
$(NULL)
diff --git a/server/tests/meson.build b/server/tests/meson.build
index b4269c58..b6cf8989 100644
--- a/server/tests/meson.build
+++ b/server/tests/meson.build
@@ -67,6 +67,7 @@ if host_machine.system() != 'windows'
tests += [
['test-stream', true],
['test-stat-file', true],
+ ['test-websocket', false],
]
endif
diff --git a/server/tests/test-websocket.c b/server/tests/test-websocket.c
new file mode 100644
index 00000000..6596c27e
--- /dev/null
+++ b/server/tests/test-websocket.c
@@ -0,0 +1,290 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2017-2019 Red Hat, Inc.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+/* Utility to allow checking our websocket implementaion using Autobahn
+ * Test Suite.
+ * This suite require a WebSocket server implementation echoing
+ * data sent to it
+ */
+#undef NDEBUG
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <unistd.h>
+#include <err.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+#include <glib.h>
+#include <signal.h>
+
+#include "websocket.h"
+
+/*
+on data arrived on socket:
+ try to read data, read again till error, handle error, on EAGAIN polling again
+ queue readed data for echo
+
+on data writable (if we have data to write):
+ write data
+
+question... pings are handled ??
+
+if data size == 0 when we receive we must send it
+
+*/
+
+static int port = 7777;
+static gboolean non_blocking = false;
+static gboolean debug = false;
+static volatile bool got_term = false;
+static unsigned int num_connections = 0;
+
+static GOptionEntry cmd_entries[] = {
+ {"port", 'p', 0, G_OPTION_ARG_INT, &port,
+ "Local port to bind to", NULL},
+ {"non-blocking", 'n', 0, G_OPTION_ARG_NONE, &non_blocking,
+ "Enable non-blocking i/o", NULL},
+ {"debug", 0, 0, G_OPTION_ARG_NONE, &debug,
+ "Enable debug output", NULL},
+ {NULL}
+};
+
+static void handle_client(int new_sock);
+
+static void
+set_nonblocking(int sock)
+{
+ unsigned int ioctl_nonblocking = 1;
+
+ if (ioctl(sock, FIONBIO, &ioctl_nonblocking) < 0) {
+ err(1, "ioctl");
+ }
+}
+
+static int
+wait_for(int sock, short events)
+{
+ struct pollfd fds[1] = { { sock, events, 0 } };
+ for (;;) {
+ switch (poll(fds, 1, -1)) {
+ case -1:
+ if (errno == EINTR) {
+ if (got_term) {
+ printf("handled %u connections\n", num_connections);
+ exit(0);
+ }
+ break;
+ }
+ err(1, "poll");
+ break;
+ case 1:
+ if ((fds->revents & events) != 0) {
+ return fds->revents & events;
+ }
+ break;
+ case 0:
+ assert(0);
+ }
+ }
+}
+
+static ssize_t
+ws_read(void *opaque, void *buf, size_t nbyte)
+{
+ int sock = GPOINTER_TO_INT(opaque);
+ return recv(sock, buf, nbyte, MSG_NOSIGNAL);
+}
+
+static ssize_t
+ws_write(void *opaque, const void *buf, size_t nbyte)
+{
+ int sock = GPOINTER_TO_INT(opaque);
+ return send(sock, buf, nbyte, MSG_NOSIGNAL);
+}
+
+static ssize_t
+ws_writev(void *opaque, struct iovec *iov, int iovcnt)
+{
+ int sock = GPOINTER_TO_INT(opaque);
+ return writev(sock, iov, iovcnt);
+}
+
+static void
+go_out(int sig)
+{
+ got_term = true;
+}
+
+int
+main(int argc, char **argv)
+{
+ GError *error = NULL;
+ GOptionContext *context;
+
+ context = g_option_context_new(" - Websocket test");
+ g_option_context_add_main_entries(context, cmd_entries, NULL);
+ if (!g_option_context_parse(context, &argc, &argv, &error)) {
+ errx(1, "%s: %s\n", argv[0], error->message);
+ }
+
+ int sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock < 0) {
+ err(1, "socket");
+ }
+
+ int enable = 1;
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
+
+ if (non_blocking) {
+ set_nonblocking(sock);
+ }
+
+ struct sockaddr_in sin;
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons((short) port);
+ sin.sin_family = AF_INET;
+
+ if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
+ err(1, "bind");
+ }
+
+ if (listen(sock, 5) < 0) {
+ err(1, "listen");
+ }
+
+ signal(SIGTERM, go_out);
+ signal(SIGINT, go_out);
+
+ while (!got_term) {
+ wait_for(sock, POLLIN);
+
+ socklen_t sock_len = sizeof(sin);
+ int new_sock = accept(sock, (struct sockaddr *) &sin, &sock_len);
+ if (got_term) {
+ break;
+ }
+ if (new_sock < 0) {
+ err(1, "accept");
+ }
+
+ ++num_connections;
+ handle_client(new_sock);
+
+ close(new_sock);
+ }
+
+ close(sock);
+ printf("handled %u connections\n", num_connections);
+ return 0;
+}
+
+static void
+handle_client(int new_sock)
+{
+ if (non_blocking) {
+ set_nonblocking(new_sock);
+ }
+
+ int enable = 1;
+ setsockopt(new_sock, SOL_TCP, TCP_NODELAY, (const void *) &enable, sizeof(enable));
+
+ // wait header
+ wait_for(new_sock, POLLIN);
+
+ RedsWebSocket *ws = websocket_new("", 0, GINT_TO_POINTER(new_sock),
+ ws_read, ws_write, ws_writev);
+ assert(ws);
+
+ char buffer[4096];
+ size_t to_send = 0;
+ while (!got_term) {
+ int events = 0;
+ if (sizeof(buffer) > to_send) {
+ events |= POLLIN;
+ }
+ if (to_send) {
+ events |= POLLOUT;
+ }
+ events = wait_for(new_sock, events);
+ if (events & POLLIN) {
+ assert(sizeof(buffer) > to_send);
+ int size = websocket_read(ws, (void *) (buffer + to_send), sizeof(buffer) - to_send);
+
+ if (size < 0) {
+ if (errno == EIO) {
+ break;
+ }
+ if (errno == EAGAIN) {
+ continue;
+ }
+ err(1, "recv");
+ }
+
+ if (size == 0) {
+ break;
+ }
+
+ if (debug) {
+ printf("received %d bytes of data\n", size);
+ }
+ to_send += size;
+ }
+
+ if (events & POLLOUT) {
+ int size = websocket_write(ws, buffer, to_send);
+
+ if (size < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ continue;
+ case ECONNRESET:
+ break;
+ default:
+ err(1, "send");
+ }
+ break;
+ }
+
+ if (debug) {
+ printf("sent %d bytes of data\n", size);
+ }
+
+ if (size == 0) {
+ errx(1, "Unexpected short write\n");
+ }
+
+ to_send -= size;
+ memmove(buffer, buffer + size, to_send);
+ }
+ }
+
+ websocket_free(ws);
+
+ if (debug) {
+ printf("connection closed\n");
+ }
+}
commit 62582df789c581708bff5a63ba3c1b95813dbfda
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Wed Feb 1 17:20:07 2017 +0000
websocket: Handle PING and PONG frames
Websocket implementations are required to implement such messages.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index 72a4b064..cb222c69 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -67,6 +67,21 @@
#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
+#define MAX_CONTROL_DATA 125
+#define CONTROL_HDR_LEN 2
+
+typedef struct {
+ uint8_t raw_pos;
+ union {
+ uint8_t raw_data[MAX_CONTROL_DATA + CONTROL_HDR_LEN];
+ struct {
+ uint8_t type;
+ uint8_t data_len;
+ uint8_t data[MAX_CONTROL_DATA];
+ };
+ };
+} WebSocketControl;
+
typedef struct {
uint8_t type;
uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
@@ -86,6 +101,8 @@ struct RedsWebSocket {
uint8_t write_header[WEBSOCKET_MAX_HEADER_SIZE];
uint8_t write_header_pos, write_header_len;
bool close_pending;
+ WebSocketControl pong;
+ WebSocketControl pending_pong;
void *raw_stream;
websocket_read_cb_t raw_read;
@@ -96,6 +113,28 @@ struct RedsWebSocket {
static int websocket_ack_close(RedsWebSocket *ws);
static int send_pending_data(RedsWebSocket *ws);
+static inline int get_control_raw_len(const WebSocketControl *control)
+{
+ return control->data_len + CONTROL_HDR_LEN;
+}
+
+static inline void control_init(WebSocketControl *control, uint8_t type)
+{
+ control->raw_pos = CONTROL_HDR_LEN;
+ control->type = type;
+ control->data_len = 0;
+}
+
+static inline bool control_sent(const WebSocketControl *control)
+{
+ return control->raw_pos >= get_control_raw_len(control);
+}
+
+static inline void pong_init(WebSocketControl *pong)
+{
+ control_init(pong, FIN_FLAG | PONG_FRAME);
+}
+
/* Perform a case insensitive search for needle in haystack.
If found, return a pointer to the byte after the end of needle.
Otherwise, return NULL */
@@ -264,18 +303,14 @@ static bool websocket_get_frame_header(websocket_frame_t *frame)
return true;
}
-static int relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
+static void relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
{
- int i;
- int n = MIN(size, frame->expected_len - frame->relayed);
-
if (frame->masked) {
- for (i = 0; i < n; i++, frame->relayed++) {
- *buf++ ^= frame->mask[frame->relayed % 4];
+ size_t i;
+ for (i = 0; i < size; i++) {
+ *buf++ ^= frame->mask[(frame->relayed + i) % 4];
}
}
-
- return n;
}
int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
@@ -307,7 +342,15 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
errno = EIO;
return -1;
}
- } else if (frame->type == CLOSE_FRAME) {
+ /* discard a pending ping, replace with current one */
+ if (frame->frame_ready && frame->type == PING_FRAME) {
+ pong_init(&ws->pong);
+ ws->pong.data_len = frame->expected_len;
+ }
+ continue;
+ }
+
+ if (frame->type == CLOSE_FRAME) {
ws->close_pending = true;
websocket_clear_frame(frame);
send_pending_data(ws);
@@ -319,18 +362,50 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
goto read_error;
}
- rc = relay_data(buf, rc, frame);
+ relay_data(buf, rc, frame);
n += rc;
buf += rc;
size -= rc;
- if (frame->relayed >= frame->expected_len) {
- websocket_clear_frame(frame);
+ } else if (frame->type == PING_FRAME) {
+ spice_assert(ws->pong.data_len == frame->expected_len);
+ rc = 0;
+ if (ws->pong.data_len > (ws->pong.raw_pos - CONTROL_HDR_LEN)) {
+ rc = ws->raw_read(ws->raw_stream, ws->pong.raw_data + ws->pong.raw_pos,
+ ws->pong.data_len - (ws->pong.raw_pos - CONTROL_HDR_LEN));
+ if (rc <= 0) {
+ goto read_error;
+ }
+ relay_data(ws->pong.raw_data + ws->pong.raw_pos, rc, frame);
+ ws->pong.raw_pos += rc;
+ }
+ if (ws->pong.raw_pos - CONTROL_HDR_LEN >= ws->pong.data_len) {
+ ws->pong.raw_pos = 0;
+ if (control_sent(&ws->pending_pong)) {
+ ws->pending_pong = ws->pong;
+ pong_init(&ws->pong);
+ }
+ send_pending_data(ws);
}
} else {
- /* TODO - We don't handle PING at this point */
- spice_warning("Unexpected WebSocket frame.type %d. Failure now likely.", frame->type);
+ /* client could sent a PONG just as heartbeat */
+ if (frame->type != PONG_FRAME) {
+ spice_warning("Unexpected WebSocket frame.type %d. Failure now likely.", frame->type);
+ }
+
+ // discard this data
+ uint8_t discard[128];
+ rc = 0;
+ if (frame->expected_len > frame->relayed) {
+ rc = ws->raw_read(ws->raw_stream, discard,
+ MIN(sizeof(discard), frame->expected_len - frame->relayed));
+ if (rc <= 0) {
+ goto read_error;
+ }
+ }
+ }
+ frame->relayed += rc;
+ if (frame->relayed >= frame->expected_len) {
websocket_clear_frame(frame);
- continue;
}
}
@@ -459,6 +534,25 @@ static int send_pending_data(RedsWebSocket *ws)
return rc;
}
}
+
+ WebSocketControl *pong = &ws->pending_pong;
+ if (!control_sent(pong)) {
+ rc = ws->raw_write(ws->raw_stream, pong->raw_data + pong->raw_pos,
+ get_control_raw_len(pong) - pong->raw_pos);
+ if (rc <= 0) {
+ return rc;
+ }
+ pong->raw_pos += rc;
+ if (!control_sent(pong)) {
+ errno = EAGAIN;
+ return -1;
+ }
+ /* already another pending */
+ if (ws->pong.raw_pos == 0) {
+ ws->pending_pong = ws->pong;
+ pong_init(&ws->pong);
+ }
+ }
return 1;
}
@@ -656,6 +750,9 @@ RedsWebSocket *websocket_new(const void *buf, size_t len, void *stream, websocke
ws->raw_write = write_cb;
ws->raw_writev = writev_cb;
+ pong_init(&ws->pong);
+ pong_init(&ws->pending_pong);
+
return ws;
}
commit 5a352c182304faa9aed42b491489bc13c9318bc8
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Mon Nov 21 12:42:43 2016 +0000
websocket: Avoids to write close frame in the middle of data
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index 9fd5fde5..72a4b064 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -85,6 +85,7 @@ struct RedsWebSocket {
uint64_t write_remainder;
uint8_t write_header[WEBSOCKET_MAX_HEADER_SIZE];
uint8_t write_header_pos, write_header_len;
+ bool close_pending;
void *raw_stream;
websocket_read_cb_t raw_read;
@@ -92,7 +93,8 @@ struct RedsWebSocket {
websocket_writev_cb_t raw_writev;
};
-static void websocket_ack_close(void *stream, websocket_write_cb_t write_cb);
+static int websocket_ack_close(RedsWebSocket *ws);
+static int send_pending_data(RedsWebSocket *ws);
/* Perform a case insensitive search for needle in haystack.
If found, return a pointer to the byte after the end of needle.
@@ -282,7 +284,11 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
int rc;
websocket_frame_t *frame = &ws->read_frame;
- if (ws->closed) {
+ if (ws->closed || ws->close_pending) {
+ /* this avoids infinite loop in the case connection is still open and we have
+ * pending data */
+ uint8_t discard[128];
+ ws->raw_read(ws->raw_stream, discard, sizeof(discard));
return 0;
}
@@ -302,9 +308,9 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
return -1;
}
} else if (frame->type == CLOSE_FRAME) {
- websocket_ack_close(ws->raw_stream, ws->raw_write);
+ ws->close_pending = true;
websocket_clear_frame(frame);
- ws->closed = true;
+ send_pending_data(ws);
return 0;
} else if (frame->type == BINARY_FRAME) {
rc = ws->raw_read(ws->raw_stream, buf,
@@ -418,15 +424,44 @@ static int send_data_header_left(RedsWebSocket *ws)
static int send_data_header(RedsWebSocket *ws, uint64_t len)
{
- /* if we don't have a pending header fill it */
- if (ws->write_header_pos >= ws->write_header_len) {
- ws->write_header_pos = 0;
- ws->write_header_len = fill_header(ws->write_header, len);
- }
+ spice_assert(ws->write_header_pos >= ws->write_header_len);
+ spice_assert(ws->write_remainder == 0);
+
+ /* fill a new header */
+ ws->write_header_pos = 0;
+ ws->write_header_len = fill_header(ws->write_header, len);
return send_data_header_left(ws);
}
+static int send_pending_data(RedsWebSocket *ws)
+{
+ int rc;
+
+ /* don't send while we are sending a data frame */
+ if (ws->write_remainder) {
+ return 1;
+ }
+
+ /* write pending data frame header not send completely */
+ if (ws->write_header_pos < ws->write_header_len) {
+ rc = send_data_header_left(ws);
+ if (rc <= 0) {
+ return rc;
+ }
+ return 1;
+ }
+
+ /* write close frame */
+ if (ws->close_pending) {
+ rc = websocket_ack_close(ws);
+ if (rc <= 0) {
+ return rc;
+ }
+ }
+ return 1;
+}
+
/* Write a WebSocket frame with the enclosed data out. */
int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
{
@@ -440,11 +475,9 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
errno = EPIPE;
return -1;
}
- if (ws->write_header_pos < ws->write_header_len) {
- rc = send_data_header_left(ws);
- if (rc <= 0) {
- return rc;
- }
+ rc = send_pending_data(ws);
+ if (rc <= 0) {
+ return rc;
}
if (ws->write_remainder > 0) {
constrain_iov((struct iovec *) iov, iovcnt, &iov_out, &iov_out_cnt, ws->write_remainder);
@@ -504,6 +537,10 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
return -1;
}
+ rc = send_pending_data(ws);
+ if (rc <= 0) {
+ return rc;
+ }
if (ws->write_remainder == 0) {
rc = send_data_header(ws, len);
if (rc <= 0) {
@@ -521,14 +558,20 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
return rc;
}
-static void websocket_ack_close(void *stream, websocket_write_cb_t write_cb)
+static int websocket_ack_close(RedsWebSocket *ws)
{
unsigned char header[2];
+ int rc;
header[0] = FIN_FLAG | CLOSE_FRAME;
header[1] = 0;
- write_cb(stream, header, sizeof(header));
+ rc = ws->raw_write(ws->raw_stream, header, sizeof(header));
+ if (rc == sizeof(header)) {
+ ws->close_pending = false;
+ ws->closed = true;
+ }
+ return rc;
}
static bool websocket_is_start(char *buf)
commit b5a622df76af06155bb3152b336cb013f2ec97f8
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Mon Nov 21 12:42:09 2016 +0000
websocket: Handle case if server cannot write the header entirely
Quite rare case, can only happen with congestion, buffers very low
and some space left in the former packet.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index 96c6fce1..9fd5fde5 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -83,6 +83,8 @@ struct RedsWebSocket {
websocket_frame_t read_frame;
uint64_t write_remainder;
+ uint8_t write_header[WEBSOCKET_MAX_HEADER_SIZE];
+ uint8_t write_header_pos, write_header_len;
void *raw_stream;
websocket_read_cb_t raw_read;
@@ -391,22 +393,59 @@ static void constrain_iov(struct iovec *iov, int iovcnt,
*iov_out = iov;
}
+static int send_data_header_left(RedsWebSocket *ws)
+{
+ /* send the pending header */
+ /* this can be tested capping the length with MIN with a small size like 3 */
+ int rc = ws->raw_write(ws->raw_stream, ws->write_header + ws->write_header_pos,
+ ws->write_header_len - ws->write_header_pos);
+ if (rc <= 0) {
+ return rc;
+ }
+ ws->write_header_pos += rc;
+
+ /* if header was sent now we can send data */
+ if (ws->write_header_pos >= ws->write_header_len) {
+ int used = 1;
+ ws->write_remainder = extract_length(ws->write_header + used, &used);
+ return ws->write_header_len;
+ }
+
+ /* otherwise try to send the rest later */
+ errno = EAGAIN;
+ return -1;
+}
+
+static int send_data_header(RedsWebSocket *ws, uint64_t len)
+{
+ /* if we don't have a pending header fill it */
+ if (ws->write_header_pos >= ws->write_header_len) {
+ ws->write_header_pos = 0;
+ ws->write_header_len = fill_header(ws->write_header, len);
+ }
+
+ return send_data_header_left(ws);
+}
/* Write a WebSocket frame with the enclosed data out. */
int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
{
- uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
uint64_t len;
- int rc = -1;
+ int rc;
struct iovec *iov_out;
int iov_out_cnt;
int i;
- int header_len;
if (ws->closed) {
errno = EPIPE;
return -1;
}
+ if (ws->write_header_pos < ws->write_header_len) {
+ rc = send_data_header_left(ws);
+ if (rc <= 0) {
+ return rc;
+ }
+ }
if (ws->write_remainder > 0) {
constrain_iov((struct iovec *) iov, iovcnt, &iov_out, &iov_out_cnt, ws->write_remainder);
rc = ws->raw_writev(ws->raw_stream, iov_out, iov_out_cnt);
@@ -428,23 +467,25 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
iov_out[i + 1] = iov[i];
}
- memset(header, 0, sizeof(header));
- header_len = fill_header(header, len);
- iov_out[0].iov_len = header_len;
- iov_out[0].iov_base = header;
+ ws->write_header_pos = 0;
+ ws->write_header_len = fill_header(ws->write_header, len);
+ iov_out[0].iov_len = ws->write_header_len;
+ iov_out[0].iov_base = ws->write_header;
rc = ws->raw_writev(ws->raw_stream, iov_out, iov_out_cnt);
g_free(iov_out);
if (rc <= 0) {
+ ws->write_header_len = 0;
return rc;
}
- rc -= header_len;
- /* TODO this in theory can happen if we can't write the header */
- if (SPICE_UNLIKELY(rc < 0)) {
- ws->closed = true;
- errno = EPIPE;
+ /* this can happen if we can't write the header */
+ if (SPICE_UNLIKELY(rc < ws->write_header_len)) {
+ ws->write_header_pos = ws->write_header_len - rc;
+ errno = EAGAIN;
return -1;
}
+ ws->write_header_pos = ws->write_header_len;
+ rc -= ws->write_header_len;
/* Key point: if we did not write out all the data, remember how
much more data the client is expecting, and write that data without
@@ -456,9 +497,7 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
{
- uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
int rc;
- int header_len;
if (ws->closed) {
errno = EPIPE;
@@ -466,18 +505,11 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
}
if (ws->write_remainder == 0) {
- header_len = fill_header(header, len);
- rc = ws->raw_write(ws->raw_stream, header, header_len);
+ rc = send_data_header(ws, len);
if (rc <= 0) {
return rc;
}
- if (rc != header_len) {
- /* TODO - In theory, we can handle this case. In practice,
- it does not occur, and does not seem to be worth
- the code complexity */
- errno = EPIPE;
- return -1;
- }
+ len = ws->write_remainder;
} else {
len = MIN(ws->write_remainder, len);
}
commit 2ba66599c1404a16e998de83d0c88d867c69838d
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Mon Nov 21 12:41:56 2016 +0000
websocket: Support correctly protocol values
Ignore spaces before "binary" value.
HTTP allows space before and after the value although usually
browsers implementation start the value with a single ASCII space.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index 6f1581f9..96c6fce1 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -501,15 +501,23 @@ static void websocket_ack_close(void *stream, websocket_write_cb_t write_cb)
static bool websocket_is_start(char *buf)
{
- if (strncmp(buf, "GET ", 4) == 0 &&
- // TODO strip, do not assume a single space
- find_str(buf, "\nSec-WebSocket-Protocol: binary") &&
- find_str(buf, "\nSec-WebSocket-Key:") &&
- g_str_has_suffix(buf, "\r\n\r\n")) {
- return true;
+ const char *protocol = find_str(buf, "\nSec-WebSocket-Protocol:");
+ const char *key = find_str(buf, "\nSec-WebSocket-Key:");
+
+ if (strncmp(buf, "GET ", 4) != 0 ||
+ protocol == NULL || key == NULL ||
+ !g_str_has_suffix(buf, "\r\n\r\n")) {
+ return false;
}
- return false;
+ /* check protocol value ignoring spaces before and after */
+ int binary_pos = -1;
+ sscanf(protocol, " binary %n", &binary_pos);
+ if (binary_pos <= 0) {
+ return false;
+ }
+
+ return true;
}
static void websocket_create_reply(char *buf, char *outbuf)
commit 39a791a964a7b4553f7cb0235380c55af13c3f8a
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Mon Nov 21 12:41:52 2016 +0000
websocket: Avoid possible server crash using websockets
Currently code don't handle if system can't sent the
header in a single write command.
Don't cause abort but just close the connection.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index dda71f76..6f1581f9 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -439,7 +439,12 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
}
rc -= header_len;
- spice_assert(rc >= 0);
+ /* TODO this in theory can happen if we can't write the header */
+ if (SPICE_UNLIKELY(rc < 0)) {
+ ws->closed = true;
+ errno = EPIPE;
+ return -1;
+ }
/* Key point: if we did not write out all the data, remember how
much more data the client is expecting, and write that data without
commit 0c2f386bebdf894ed4fc2ea46f9a9c0055e6d63b
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Sat Jun 22 08:45:45 2019 +0100
websocket: Fix updating remaining bytes to write in websocket_write
"len" is not always the full remainder (consider the case when
we are writing a partial frame).
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index afb7502a..dda71f76 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -478,10 +478,8 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
}
rc = ws->raw_write(ws->raw_stream, buf, len);
- if (rc <= 0) {
- ws->write_remainder = len;
- } else {
- ws->write_remainder = len - rc;
+ if (rc > 0) {
+ ws->write_remainder -= rc;
}
return rc;
}
commit 9f1417c11fed4c3811bbf861fcf2d6552dc81429
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Mon Nov 21 12:41:33 2016 +0000
websocket: Propagate some variable
These were introduced moving code around.
No more reason to copy, just use directly structure fields.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index 38496fb5..afb7502a 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -90,7 +90,7 @@ struct RedsWebSocket {
websocket_writev_cb_t raw_writev;
};
-static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
+static void websocket_ack_close(void *stream, websocket_write_cb_t write_cb);
/* Perform a case insensitive search for needle in haystack.
If found, return a pointer to the byte after the end of needle.
@@ -279,9 +279,6 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
int n = 0;
int rc;
websocket_frame_t *frame = &ws->read_frame;
- void *opaque = ws->raw_stream;
- websocket_read_cb_t read_cb = (websocket_read_cb_t) ws->raw_read;
- websocket_write_cb_t write_cb = (websocket_write_cb_t) ws->raw_write;
if (ws->closed) {
return 0;
@@ -290,7 +287,8 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
while (size > 0) {
// make sure we have a proper frame ready
if (!frame->frame_ready) {
- rc = read_cb(ws->raw_stream, frame->header + frame->header_pos, frame_bytes_needed(frame));
+ rc = ws->raw_read(ws->raw_stream, frame->header + frame->header_pos,
+ frame_bytes_needed(frame));
if (rc <= 0) {
goto read_error;
}
@@ -302,12 +300,13 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
return -1;
}
} else if (frame->type == CLOSE_FRAME) {
- websocket_ack_close(opaque, write_cb);
+ websocket_ack_close(ws->raw_stream, ws->raw_write);
websocket_clear_frame(frame);
ws->closed = true;
return 0;
} else if (frame->type == BINARY_FRAME) {
- rc = read_cb(opaque, buf, MIN(size, frame->expected_len - frame->relayed));
+ rc = ws->raw_read(ws->raw_stream, buf,
+ MIN(size, frame->expected_len - frame->relayed));
if (rc <= 0) {
goto read_error;
}
@@ -403,24 +402,21 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
int iov_out_cnt;
int i;
int header_len;
- void *opaque = ws->raw_stream;
- websocket_writev_cb_t writev_cb = (websocket_writev_cb_t) ws->raw_writev;
- uint64_t *remainder = &ws->write_remainder;
if (ws->closed) {
errno = EPIPE;
return -1;
}
- if (*remainder > 0) {
- constrain_iov((struct iovec *) iov, iovcnt, &iov_out, &iov_out_cnt, *remainder);
- rc = writev_cb(opaque, iov_out, iov_out_cnt);
+ if (ws->write_remainder > 0) {
+ constrain_iov((struct iovec *) iov, iovcnt, &iov_out, &iov_out_cnt, ws->write_remainder);
+ rc = ws->raw_writev(ws->raw_stream, iov_out, iov_out_cnt);
if (iov_out != iov) {
g_free(iov_out);
}
if (rc <= 0) {
return rc;
}
- *remainder -= rc;
+ ws->write_remainder -= rc;
return rc;
}
@@ -436,7 +432,7 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
header_len = fill_header(header, len);
iov_out[0].iov_len = header_len;
iov_out[0].iov_base = header;
- rc = writev_cb(opaque, iov_out, iov_out_cnt);
+ rc = ws->raw_writev(ws->raw_stream, iov_out, iov_out_cnt);
g_free(iov_out);
if (rc <= 0) {
return rc;
@@ -448,7 +444,7 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
/* Key point: if we did not write out all the data, remember how
much more data the client is expecting, and write that data without
a header of any kind the next time around */
- *remainder = len - rc;
+ ws->write_remainder = len - rc;
return rc;
}
@@ -458,18 +454,15 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
int rc;
int header_len;
- void *opaque = ws->raw_stream;
- websocket_write_cb_t write_cb = (websocket_write_cb_t) ws->raw_write;
- uint64_t *remainder = &ws->write_remainder;
if (ws->closed) {
errno = EPIPE;
return -1;
}
- if (*remainder == 0) {
+ if (ws->write_remainder == 0) {
header_len = fill_header(header, len);
- rc = write_cb(opaque, header, header_len);
+ rc = ws->raw_write(ws->raw_stream, header, header_len);
if (rc <= 0) {
return rc;
}
@@ -481,26 +474,26 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
return -1;
}
} else {
- len = MIN(*remainder, len);
+ len = MIN(ws->write_remainder, len);
}
- rc = write_cb(opaque, buf, len);
+ rc = ws->raw_write(ws->raw_stream, buf, len);
if (rc <= 0) {
- *remainder = len;
+ ws->write_remainder = len;
} else {
- *remainder = len - rc;
+ ws->write_remainder = len - rc;
}
return rc;
}
-static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb)
+static void websocket_ack_close(void *stream, websocket_write_cb_t write_cb)
{
unsigned char header[2];
header[0] = FIN_FLAG | CLOSE_FRAME;
header[1] = 0;
- write_cb(opaque, header, sizeof(header));
+ write_cb(stream, header, sizeof(header));
}
static bool websocket_is_start(char *buf)
commit 6b41f3b5421d603a5b586c9f57673c160414afe8
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Mon Nov 21 12:41:12 2016 +0000
websocket: Better variable types
"type" is just 8 bit.
"frame_ready" and "masked" as booleans.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index 9a940037..38496fb5 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -68,11 +68,11 @@
#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
typedef struct {
- int type;
- int masked;
+ uint8_t type;
uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
int header_pos;
- int frame_ready:1;
+ bool frame_ready:1;
+ bool masked:1;
uint8_t mask[4];
uint64_t relayed;
uint64_t expected_len;
commit 007e009bc3b9354d4f693e1abf6b43faf83939da
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Wed Feb 1 17:19:57 2017 +0000
websocket: Detect and handle some header error
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index 01bcef82..9a940037 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -46,7 +46,9 @@
/* Constants / masks all from RFC 6455 */
#define FIN_FLAG 0x80
+#define RSV_MASK 0x70
#define TYPE_MASK 0x0F
+#define CONTROL_FRAME_MASK 0x8
#define CONTINUATION_FRAME 0x0
#define TEXT_FRAME 0x1
@@ -206,21 +208,33 @@ static void websocket_clear_frame(websocket_frame_t *frame)
}
/* Extract a frame header of data from a set of data transmitted by
- a WebSocket client. */
-static void websocket_get_frame_header(websocket_frame_t *frame)
+ a WebSocket client. Returns success or error */
+static bool websocket_get_frame_header(websocket_frame_t *frame)
{
int fin;
int used = 0;
if (frame_bytes_needed(frame) > 0) {
- return;
+ return true;
}
fin = frame->header[0] & FIN_FLAG;
frame->type = frame->header[0] & TYPE_MASK;
used++;
- frame->masked = frame->header[1] & MASK_FLAG;
+ // reserved bits are not expected
+ if (frame->header[0] & RSV_MASK) {
+ return false;
+ }
+ // control commands cannot be split
+ if (!fin && (frame->type & CONTROL_FRAME_MASK) != 0) {
+ return false;
+ }
+ if ((frame->type & ~CONTROL_FRAME_MASK) >= 3) {
+ return false;
+ }
+
+ frame->masked = !!(frame->header[1] & MASK_FLAG);
/* This is a Spice specific optimization. We don't really
care about assembling frames fully, so we treat
@@ -235,8 +249,15 @@ static void websocket_get_frame_header(websocket_frame_t *frame)
memcpy(frame->mask, frame->header + used, 4);
}
+ /* control frames cannot have more than 125 bytes of data */
+ if ((frame->type & CONTROL_FRAME_MASK) != 0 &&
+ frame->expected_len >= LENGTH_16BIT) {
+ return false;
+ }
+
frame->relayed = 0;
- frame->frame_ready = 1;
+ frame->frame_ready = true;
+ return true;
}
static int relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
@@ -275,7 +296,11 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
}
frame->header_pos += rc;
- websocket_get_frame_header(frame);
+ if (!websocket_get_frame_header(frame)) {
+ ws->closed = true;
+ errno = EIO;
+ return -1;
+ }
} else if (frame->type == CLOSE_FRAME) {
websocket_ack_close(opaque, write_cb);
websocket_clear_frame(frame);
commit 55df5fa8417fd4c0e25188802be31838d4829c08
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Mon Nov 21 12:40:31 2016 +0000
websocket: Better encapsulation
Move websocket structure declarations to C file.
Make some functions static as now not used externally.
Introduce a websocket_free function for symmetry.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/red-stream.c b/server/red-stream.c
index fd3fdced..1e0103b0 100644
--- a/server/red-stream.c
+++ b/server/red-stream.c
@@ -436,7 +436,7 @@ void red_stream_free(RedStream *s)
SSL_free(s->priv->ssl);
}
- g_free(s->priv->ws);
+ websocket_free(s->priv->ws);
red_stream_remove_watch(s);
socket_close(s->socket);
@@ -1184,38 +1184,11 @@ static ssize_t stream_websocket_writev(RedStream *s, const struct iovec *iov, in
*/
bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len)
{
- char rbuf[4096];
- int rc;
-
if (stream->priv->ws) {
return false;
}
- memcpy(rbuf, buf, len);
- rc = stream->priv->read(stream, rbuf + len, sizeof(rbuf) - len - 1);
- if (rc <= 0) {
- return false;
- }
- len += rc;
- rbuf[len] = 0;
-
- /* TODO: this has a theoretical flaw around packet buffering
- that is not likely to occur in practice. That is,
- to be fully correct, we should repeatedly read bytes until
- either we get the end of the GET header (\r\n\r\n), or until
- an amount of time has passed. Instead, we just read for
- 16 bytes, and then read up to the sizeof rbuf. So if the
- GET request is only partially complete at this point we
- will fail.
-
- A typical GET request is 520 bytes, and it's difficult to
- imagine a real world case where that will come in fragmented
- such that we trigger this failure. Further, the spice reds
- code has no real mechanism to do variable length/time based reads,
- so it seems wisest to live with this theoretical flaw.
- */
-
- stream->priv->ws = websocket_new(rbuf, stream,
+ stream->priv->ws = websocket_new(buf, len, stream,
(websocket_read_cb_t) stream->priv->read,
(websocket_write_cb_t) stream->priv->write,
(websocket_writev_cb_t) stream->priv->writev);
diff --git a/server/websocket.c b/server/websocket.c
index 03d93102..01bcef82 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -63,6 +63,31 @@
#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
+
+typedef struct {
+ int type;
+ int masked;
+ uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
+ int header_pos;
+ int frame_ready:1;
+ uint8_t mask[4];
+ uint64_t relayed;
+ uint64_t expected_len;
+} websocket_frame_t;
+
+struct RedsWebSocket {
+ bool closed;
+
+ websocket_frame_t read_frame;
+ uint64_t write_remainder;
+
+ void *raw_stream;
+ websocket_read_cb_t raw_read;
+ websocket_write_cb_t raw_write;
+ websocket_writev_cb_t raw_writev;
+};
+
static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
/* Perform a case insensitive search for needle in haystack.
@@ -453,7 +478,7 @@ static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb)
write_cb(opaque, header, sizeof(header));
}
-bool websocket_is_start(char *buf)
+static bool websocket_is_start(char *buf)
{
if (strncmp(buf, "GET ", 4) == 0 &&
// TODO strip, do not assume a single space
@@ -466,7 +491,7 @@ bool websocket_is_start(char *buf)
return false;
}
-void websocket_create_reply(char *buf, char *outbuf)
+static void websocket_create_reply(char *buf, char *outbuf)
{
char *key;
@@ -479,9 +504,35 @@ void websocket_create_reply(char *buf, char *outbuf)
g_free(key);
}
-RedsWebSocket *websocket_new(char *rbuf, struct RedStream *stream, websocket_read_cb_t read_cb,
+RedsWebSocket *websocket_new(const void *buf, size_t len, void *stream, websocket_read_cb_t read_cb,
websocket_write_cb_t write_cb, websocket_writev_cb_t writev_cb)
{
+ char rbuf[4096];
+
+ memcpy(rbuf, buf, len);
+ int rc = read_cb(stream, rbuf + len, sizeof(rbuf) - len - 1);
+ if (rc <= 0) {
+ return NULL;
+ }
+ len += rc;
+ rbuf[len] = 0;
+
+ /* TODO: this has a theoretical flaw around packet buffering
+ that is not likely to occur in practice. That is,
+ to be fully correct, we should repeatedly read bytes until
+ either we get the end of the GET header (\r\n\r\n), or until
+ an amount of time has passed. Instead, we just read for
+ 16 bytes, and then read up to the sizeof rbuf. So if the
+ GET request is only partially complete at this point we
+ will fail.
+
+ A typical GET request is 520 bytes, and it's difficult to
+ imagine a real world case where that will come in fragmented
+ such that we trigger this failure. Further, the spice reds
+ code has no real mechanism to do variable length/time based reads,
+ so it seems wisest to live with this theoretical flaw.
+ */
+
if (!websocket_is_start(rbuf)) {
return NULL;
}
@@ -489,7 +540,7 @@ RedsWebSocket *websocket_new(char *rbuf, struct RedStream *stream, websocket_rea
char outbuf[1024];
websocket_create_reply(rbuf, outbuf);
- int rc = write_cb(stream, outbuf, strlen(outbuf));
+ rc = write_cb(stream, outbuf, strlen(outbuf));
if (rc != strlen(outbuf)) {
return NULL;
}
@@ -503,3 +554,8 @@ RedsWebSocket *websocket_new(char *rbuf, struct RedStream *stream, websocket_rea
return ws;
}
+
+void websocket_free(RedsWebSocket *ws)
+{
+ g_free(ws);
+}
diff --git a/server/websocket.h b/server/websocket.h
index ab9eb812..b586e303 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -15,41 +15,15 @@
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
-#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
-
-struct RedStream;
-
-typedef struct {
- int type;
- int masked;
- uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
- int header_pos;
- int frame_ready:1;
- uint8_t mask[4];
- uint64_t relayed;
- uint64_t expected_len;
-} websocket_frame_t;
-
typedef ssize_t (*websocket_read_cb_t)(void *opaque, void *buf, size_t nbyte);
typedef ssize_t (*websocket_write_cb_t)(void *opaque, const void *buf, size_t nbyte);
typedef ssize_t (*websocket_writev_cb_t)(void *opaque, struct iovec *iov, int iovcnt);
-typedef struct {
- int closed;
-
- websocket_frame_t read_frame;
- uint64_t write_remainder;
-
- struct RedStream *raw_stream;
- websocket_read_cb_t raw_read;
- websocket_write_cb_t raw_write;
- websocket_writev_cb_t raw_writev;
-} RedsWebSocket;
+typedef struct RedsWebSocket RedsWebSocket;
-RedsWebSocket *websocket_new(char *buf, struct RedStream *s, websocket_read_cb_t read_cb,
+RedsWebSocket *websocket_new(const void *buf, size_t len, void *stream, websocket_read_cb_t read_cb,
websocket_write_cb_t write_cb, websocket_writev_cb_t writev_cb);
-bool websocket_is_start(char *buf);
-void websocket_create_reply(char *buf, char *outbuf);
+void websocket_free(RedsWebSocket *ws);
int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len);
int websocket_write(RedsWebSocket *ws, const void *buf, size_t len);
int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt);
commit a5c05ad845a47739dd4daef8c31148dfc3a5438c
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Mon Nov 21 12:40:07 2016 +0000
websocket: New interface to create RedsWebSocket
Less coupling. This is a preparation for next patch.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/red-stream.c b/server/red-stream.c
index d6a00dd9..fd3fdced 100644
--- a/server/red-stream.c
+++ b/server/red-stream.c
@@ -1215,28 +1215,19 @@ bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len)
so it seems wisest to live with this theoretical flaw.
*/
- if (websocket_is_start(rbuf)) {
- char outbuf[1024];
-
- websocket_create_reply(rbuf, outbuf);
- rc = stream->priv->write(stream, outbuf, strlen(outbuf));
- if (rc == strlen(outbuf)) {
- stream->priv->ws = g_malloc0(sizeof(*stream->priv->ws));
-
- stream->priv->ws->raw_stream = stream;
- stream->priv->ws->raw_read = stream->priv->read;
- stream->priv->ws->raw_write = stream->priv->write;
-
- stream->priv->read = stream_websocket_read;
- stream->priv->write = stream_websocket_write;
-
- if (stream->priv->writev) {
- stream->priv->ws->raw_writev = stream->priv->writev;
- stream->priv->writev = stream_websocket_writev;
- }
+ stream->priv->ws = websocket_new(rbuf, stream,
+ (websocket_read_cb_t) stream->priv->read,
+ (websocket_write_cb_t) stream->priv->write,
+ (websocket_writev_cb_t) stream->priv->writev);
+ if (stream->priv->ws) {
+ stream->priv->read = stream_websocket_read;
+ stream->priv->write = stream_websocket_write;
- return true;
+ if (stream->priv->writev) {
+ stream->priv->writev = stream_websocket_writev;
}
+
+ return true;
}
return false;
diff --git a/server/websocket.c b/server/websocket.c
index 1b18aa61..03d93102 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -478,3 +478,28 @@ void websocket_create_reply(char *buf, char *outbuf)
"Sec-WebSocket-Protocol: binary\r\n\r\n", key);
g_free(key);
}
+
+RedsWebSocket *websocket_new(char *rbuf, struct RedStream *stream, websocket_read_cb_t read_cb,
+ websocket_write_cb_t write_cb, websocket_writev_cb_t writev_cb)
+{
+ if (!websocket_is_start(rbuf)) {
+ return NULL;
+ }
+
+ char outbuf[1024];
+
+ websocket_create_reply(rbuf, outbuf);
+ int rc = write_cb(stream, outbuf, strlen(outbuf));
+ if (rc != strlen(outbuf)) {
+ return NULL;
+ }
+
+ RedsWebSocket *ws = g_new0(RedsWebSocket, 1);
+
+ ws->raw_stream = stream;
+ ws->raw_read = read_cb;
+ ws->raw_write = write_cb;
+ ws->raw_writev = writev_cb;
+
+ return ws;
+}
diff --git a/server/websocket.h b/server/websocket.h
index 17310905..ab9eb812 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -41,11 +41,13 @@ typedef struct {
uint64_t write_remainder;
struct RedStream *raw_stream;
- ssize_t (*raw_read)(struct RedStream *s, void *buf, size_t nbyte);
- ssize_t (*raw_write)(struct RedStream *s, const void *buf, size_t nbyte);
- ssize_t (*raw_writev)(struct RedStream *s, const struct iovec *iov, int iovcnt);
+ websocket_read_cb_t raw_read;
+ websocket_write_cb_t raw_write;
+ websocket_writev_cb_t raw_writev;
} RedsWebSocket;
+RedsWebSocket *websocket_new(char *buf, struct RedStream *s, websocket_read_cb_t read_cb,
+ websocket_write_cb_t write_cb, websocket_writev_cb_t writev_cb);
bool websocket_is_start(char *buf);
void websocket_create_reply(char *buf, char *outbuf);
int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len);
commit b5f3ed1502ac048b2ccb441ee5c7011768fa76fe
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Sat Jun 22 17:54:30 2019 +0100
websocket: Make websocket_ack_close static
It's used only in websocket.c.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index b0ea867e..1b18aa61 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -63,6 +63,8 @@
#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
+
/* Perform a case insensitive search for needle in haystack.
If found, return a pointer to the byte after the end of needle.
Otherwise, return NULL */
@@ -441,7 +443,7 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
return rc;
}
-void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb)
+static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb)
{
unsigned char header[2];
diff --git a/server/websocket.h b/server/websocket.h
index e3a61000..17310905 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -48,7 +48,6 @@ typedef struct {
bool websocket_is_start(char *buf);
void websocket_create_reply(char *buf, char *outbuf);
-void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len);
int websocket_write(RedsWebSocket *ws, const void *buf, size_t len);
int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt);
commit bbfb472154288ec69efed380fecb352356793fcc
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Mon Nov 21 12:39:54 2016 +0000
websocket: Make websocket function more ABI compatibles with RedStream
Use same argument types as red_stream_* functions.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index 145d829f..b0ea867e 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -226,7 +226,7 @@ static int relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
return n;
}
-int websocket_read(RedsWebSocket *ws, uint8_t *buf, int size)
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
{
int n = 0;
int rc;
@@ -342,7 +342,7 @@ static void constrain_iov(struct iovec *iov, int iovcnt,
/* Write a WebSocket frame with the enclosed data out. */
-int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt)
+int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
{
uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
uint64_t len;
@@ -360,7 +360,7 @@ int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt)
return -1;
}
if (*remainder > 0) {
- constrain_iov(iov, iovcnt, &iov_out, &iov_out_cnt, *remainder);
+ constrain_iov((struct iovec *) iov, iovcnt, &iov_out, &iov_out_cnt, *remainder);
rc = writev_cb(opaque, iov_out, iov_out_cnt);
if (iov_out != iov) {
g_free(iov_out);
@@ -401,7 +401,7 @@ int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt)
return rc;
}
-int websocket_write(RedsWebSocket *ws, const uint8_t *buf, int len)
+int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
{
uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
int rc;
diff --git a/server/websocket.h b/server/websocket.h
index f65b4e3d..e3a61000 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -48,7 +48,7 @@ typedef struct {
bool websocket_is_start(char *buf);
void websocket_create_reply(char *buf, char *outbuf);
-int websocket_read(RedsWebSocket *ws, uint8_t *buf, int len);
-int websocket_write(RedsWebSocket *ws, const uint8_t *buf, int len);
-int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt);
void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len);
+int websocket_write(RedsWebSocket *ws, const void *buf, size_t len);
+int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt);
commit 98c183a6f6907e679749acb899454779e6f7b8a7
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Mon Nov 21 12:39:46 2016 +0000
websocket: Move RedsWebSocket to header
Intention is to make private in websockets.c and reduce
changes in red-stream.c
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/red-stream.c b/server/red-stream.c
index 4b899241..d6a00dd9 100644
--- a/server/red-stream.c
+++ b/server/red-stream.c
@@ -78,17 +78,6 @@ typedef struct RedSASL {
} RedSASL;
#endif
-typedef struct {
- int closed;
-
- websocket_frame_t read_frame;
- uint64_t write_remainder;
-
- ssize_t (*raw_read)(RedStream *s, void *buf, size_t nbyte);
- ssize_t (*raw_write)(RedStream *s, const void *buf, size_t nbyte);
- ssize_t (*raw_writev)(RedStream *s, const struct iovec *iov, int iovcnt);
-} RedsWebSocket;
-
struct RedStreamPrivate {
SSL *ssl;
@@ -1174,39 +1163,17 @@ error:
static ssize_t stream_websocket_read(RedStream *s, void *buf, size_t size)
{
- int rc;
-
- if (s->priv->ws->closed)
- return 0;
-
- rc = websocket_read((void *)s, buf, size, &s->priv->ws->read_frame,
- (websocket_read_cb_t) s->priv->ws->raw_read,
- (websocket_write_cb_t) s->priv->ws->raw_write);
-
- if (rc == 0)
- s->priv->ws->closed = 1;
-
- return rc;
+ return websocket_read(s->priv->ws, buf, size);
}
static ssize_t stream_websocket_write(RedStream *s, const void *buf, size_t size)
{
- if (s->priv->ws->closed) {
- errno = EPIPE;
- return -1;
- }
- return websocket_write((void *)s, buf, size, &s->priv->ws->write_remainder,
- (websocket_write_cb_t) s->priv->ws->raw_write);
+ return websocket_write(s->priv->ws, buf, size);
}
static ssize_t stream_websocket_writev(RedStream *s, const struct iovec *iov, int iovcnt)
{
- if (s->priv->ws->closed) {
- errno = EPIPE;
- return -1;
- }
- return websocket_writev((void *)s, (struct iovec *) iov, iovcnt, &s->priv->ws->write_remainder,
- (websocket_writev_cb_t) s->priv->ws->raw_writev);
+ return websocket_writev(s->priv->ws, (struct iovec *) iov, iovcnt);
}
/*
@@ -1256,6 +1223,7 @@ bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len)
if (rc == strlen(outbuf)) {
stream->priv->ws = g_malloc0(sizeof(*stream->priv->ws));
+ stream->priv->ws->raw_stream = stream;
stream->priv->ws->raw_read = stream->priv->read;
stream->priv->ws->raw_write = stream->priv->write;
diff --git a/server/websocket.c b/server/websocket.c
index fc1d82c2..145d829f 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -226,17 +226,23 @@ static int relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
return n;
}
-int websocket_read(void *opaque, uint8_t *buf, int size, websocket_frame_t *frame,
- websocket_read_cb_t read_cb,
- websocket_write_cb_t write_cb)
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, int size)
{
int n = 0;
int rc;
+ websocket_frame_t *frame = &ws->read_frame;
+ void *opaque = ws->raw_stream;
+ websocket_read_cb_t read_cb = (websocket_read_cb_t) ws->raw_read;
+ websocket_write_cb_t write_cb = (websocket_write_cb_t) ws->raw_write;
+
+ if (ws->closed) {
+ return 0;
+ }
while (size > 0) {
// make sure we have a proper frame ready
if (!frame->frame_ready) {
- rc = read_cb(opaque, frame->header + frame->header_pos, frame_bytes_needed(frame));
+ rc = read_cb(ws->raw_stream, frame->header + frame->header_pos, frame_bytes_needed(frame));
if (rc <= 0) {
goto read_error;
}
@@ -246,6 +252,7 @@ int websocket_read(void *opaque, uint8_t *buf, int size, websocket_frame_t *fram
} else if (frame->type == CLOSE_FRAME) {
websocket_ack_close(opaque, write_cb);
websocket_clear_frame(frame);
+ ws->closed = true;
return 0;
} else if (frame->type == BINARY_FRAME) {
rc = read_cb(opaque, buf, MIN(size, frame->expected_len - frame->relayed));
@@ -274,6 +281,9 @@ read_error:
if (n > 0 && rc == -1 && (errno == EINTR || errno == EAGAIN)) {
return n;
}
+ if (rc == 0) {
+ ws->closed = true;
+ }
return rc;
}
@@ -332,8 +342,7 @@ static void constrain_iov(struct iovec *iov, int iovcnt,
/* Write a WebSocket frame with the enclosed data out. */
-int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *remainder,
- websocket_writev_cb_t writev_cb)
+int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt)
{
uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
uint64_t len;
@@ -342,7 +351,14 @@ int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *rema
int iov_out_cnt;
int i;
int header_len;
+ void *opaque = ws->raw_stream;
+ websocket_writev_cb_t writev_cb = (websocket_writev_cb_t) ws->raw_writev;
+ uint64_t *remainder = &ws->write_remainder;
+ if (ws->closed) {
+ errno = EPIPE;
+ return -1;
+ }
if (*remainder > 0) {
constrain_iov(iov, iovcnt, &iov_out, &iov_out_cnt, *remainder);
rc = writev_cb(opaque, iov_out, iov_out_cnt);
@@ -385,12 +401,19 @@ int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *rema
return rc;
}
-int websocket_write(void *opaque, const uint8_t *buf, int len, uint64_t *remainder,
- websocket_write_cb_t write_cb)
+int websocket_write(RedsWebSocket *ws, const uint8_t *buf, int len)
{
uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
int rc;
int header_len;
+ void *opaque = ws->raw_stream;
+ websocket_write_cb_t write_cb = (websocket_write_cb_t) ws->raw_write;
+ uint64_t *remainder = &ws->write_remainder;
+
+ if (ws->closed) {
+ errno = EPIPE;
+ return -1;
+ }
if (*remainder == 0) {
header_len = fill_header(header, len);
diff --git a/server/websocket.h b/server/websocket.h
index 63d7b10c..f65b4e3d 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -17,6 +17,8 @@
#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
+struct RedStream;
+
typedef struct {
int type;
int masked;
@@ -32,13 +34,21 @@ typedef ssize_t (*websocket_read_cb_t)(void *opaque, void *buf, size_t nbyte);
typedef ssize_t (*websocket_write_cb_t)(void *opaque, const void *buf, size_t nbyte);
typedef ssize_t (*websocket_writev_cb_t)(void *opaque, struct iovec *iov, int iovcnt);
+typedef struct {
+ int closed;
+
+ websocket_frame_t read_frame;
+ uint64_t write_remainder;
+
+ struct RedStream *raw_stream;
+ ssize_t (*raw_read)(struct RedStream *s, void *buf, size_t nbyte);
+ ssize_t (*raw_write)(struct RedStream *s, const void *buf, size_t nbyte);
+ ssize_t (*raw_writev)(struct RedStream *s, const struct iovec *iov, int iovcnt);
+} RedsWebSocket;
+
bool websocket_is_start(char *buf);
void websocket_create_reply(char *buf, char *outbuf);
-int websocket_read(void *opaque, uint8_t *buf, int len, websocket_frame_t *frame,
- websocket_read_cb_t read_cb,
- websocket_write_cb_t write_cb);
-int websocket_write(void *opaque, const uint8_t *buf, int len, uint64_t *remainder,
- websocket_write_cb_t write_cb);
-int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *remainder,
- websocket_writev_cb_t writev_cb);
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, int len);
+int websocket_write(RedsWebSocket *ws, const uint8_t *buf, int len);
+int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt);
void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
commit 26fad876a08b4343aa77e197932b0c5735c67655
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Mon Nov 21 12:42:17 2016 +0000
websocket: Simplify and fix constrain_iov
Use g_memdup instead of manual copy.
Trim the original iov if necessary.
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/websocket.c b/server/websocket.c
index 58f36da0..fc1d82c2 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -310,26 +310,24 @@ static void constrain_iov(struct iovec *iov, int iovcnt,
struct iovec **iov_out, int *iov_out_cnt,
uint64_t maxlen)
{
- int i, j;
-
- *iov_out = iov;
- *iov_out_cnt = iovcnt;
+ int i;
for (i = 0; i < iovcnt && maxlen > 0; i++) {
if (iov[i].iov_len > maxlen) {
/* TODO - This code has never triggered afaik... */
- *iov_out_cnt = i + 1;
- *iov_out = g_malloc((*iov_out_cnt) * sizeof (**iov_out));
- for (j = 0; j < i; j++) {
- (*iov_out)[j].iov_base = iov[j].iov_base;
- (*iov_out)[j].iov_len = iov[j].iov_len;
- }
- (*iov_out)[j].iov_base = iov[j].iov_base;
- (*iov_out)[j].iov_len = maxlen;
- break;
+ *iov_out_cnt = ++i;
+ *iov_out = g_memdup(iov, i * sizeof (*iov));
+ (*iov_out)[i-1].iov_len = maxlen;
+ return;
}
maxlen -= iov[i].iov_len;
}
+
+ /* we must trim the iov in case maxlen initially matches some chunks
+ * For instance if initially we had 2 chunks 256 and 128 bytes respectively
+ * and a maxlen of 256 we should just return the first chunk */
+ *iov_out_cnt = i;
+ *iov_out = iov;
}
commit cd2a31709626def76d65e70b5187a7dda1498618
Author: Jeremy White <jwhite at codeweavers.com>
Date: Tue Jun 28 09:26:28 2016 -0500
Add support for clients connecting with the WebSocket protocol.
We do this by auto detecting the inbound http(s) 'GET' and probing
for a well formulated WebSocket binary connection, such as used
by the spice-html5 client. If detected, we implement a set of
cover functions that abstract the read/write/writev functions,
in a fashion similar to the SASL implementation.
This includes a limited implementation of the WebSocket protocol,
sufficient for our purposes.
Signed-off-by: Jeremy White <jwhite at codeweavers.com>
Acked-by: Frediano Ziglio <fziglio at redhat.com>
diff --git a/server/Makefile.am b/server/Makefile.am
index 20adf65f..71c7e173 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -177,6 +177,8 @@ libserver_la_SOURCES = \
video-encoder.h \
video-stream.c \
video-stream.h \
+ websocket.c \
+ websocket.h \
zlib-encoder.c \
zlib-encoder.h \
$(NULL)
diff --git a/server/meson.build b/server/meson.build
index a7676f7e..395811c8 100644
--- a/server/meson.build
+++ b/server/meson.build
@@ -144,6 +144,8 @@ spice_server_sources = [
'video-encoder.h',
'video-stream.c',
'video-stream.h',
+ 'websocket.c',
+ 'websocket.h',
'zlib-encoder.c',
'zlib-encoder.h',
]
diff --git a/server/red-stream.c b/server/red-stream.c
index 77fed097..4b899241 100644
--- a/server/red-stream.c
+++ b/server/red-stream.c
@@ -39,6 +39,7 @@
#include "red-common.h"
#include "red-stream.h"
#include "reds.h"
+#include "websocket.h"
// compatibility for *BSD systems
#if !defined(TCP_CORK) && !defined(_WIN32)
@@ -77,6 +78,17 @@ typedef struct RedSASL {
} RedSASL;
#endif
+typedef struct {
+ int closed;
+
+ websocket_frame_t read_frame;
+ uint64_t write_remainder;
+
+ ssize_t (*raw_read)(RedStream *s, void *buf, size_t nbyte);
+ ssize_t (*raw_write)(RedStream *s, const void *buf, size_t nbyte);
+ ssize_t (*raw_writev)(RedStream *s, const struct iovec *iov, int iovcnt);
+} RedsWebSocket;
+
struct RedStreamPrivate {
SSL *ssl;
@@ -86,6 +98,8 @@ struct RedStreamPrivate {
AsyncRead async_read;
+ RedsWebSocket *ws;
+
/* life time of info:
* allocated when creating RedStream.
* deallocated when main_dispatcher handles the SPICE_CHANNEL_EVENT_DISCONNECTED
@@ -433,6 +447,8 @@ void red_stream_free(RedStream *s)
SSL_free(s->priv->ssl);
}
+ g_free(s->priv->ws);
+
red_stream_remove_watch(s);
socket_close(s->socket);
@@ -1155,3 +1171,105 @@ error:
return false;
}
#endif
+
+static ssize_t stream_websocket_read(RedStream *s, void *buf, size_t size)
+{
+ int rc;
+
+ if (s->priv->ws->closed)
+ return 0;
+
+ rc = websocket_read((void *)s, buf, size, &s->priv->ws->read_frame,
+ (websocket_read_cb_t) s->priv->ws->raw_read,
+ (websocket_write_cb_t) s->priv->ws->raw_write);
+
+ if (rc == 0)
+ s->priv->ws->closed = 1;
+
+ return rc;
+}
+
+static ssize_t stream_websocket_write(RedStream *s, const void *buf, size_t size)
+{
+ if (s->priv->ws->closed) {
+ errno = EPIPE;
+ return -1;
+ }
+ return websocket_write((void *)s, buf, size, &s->priv->ws->write_remainder,
+ (websocket_write_cb_t) s->priv->ws->raw_write);
+}
+
+static ssize_t stream_websocket_writev(RedStream *s, const struct iovec *iov, int iovcnt)
+{
+ if (s->priv->ws->closed) {
+ errno = EPIPE;
+ return -1;
+ }
+ return websocket_writev((void *)s, (struct iovec *) iov, iovcnt, &s->priv->ws->write_remainder,
+ (websocket_writev_cb_t) s->priv->ws->raw_writev);
+}
+
+/*
+ If we detect that a newly opened stream appears to be using
+ the WebSocket protocol, we will put in place cover functions
+ that will speak WebSocket to the client, but allow the server
+ to continue to use normal stream read/write/writev semantics.
+*/
+bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len)
+{
+ char rbuf[4096];
+ int rc;
+
+ if (stream->priv->ws) {
+ return false;
+ }
+
+ memcpy(rbuf, buf, len);
+ rc = stream->priv->read(stream, rbuf + len, sizeof(rbuf) - len - 1);
+ if (rc <= 0) {
+ return false;
+ }
+ len += rc;
+ rbuf[len] = 0;
+
+ /* TODO: this has a theoretical flaw around packet buffering
+ that is not likely to occur in practice. That is,
+ to be fully correct, we should repeatedly read bytes until
+ either we get the end of the GET header (\r\n\r\n), or until
+ an amount of time has passed. Instead, we just read for
+ 16 bytes, and then read up to the sizeof rbuf. So if the
+ GET request is only partially complete at this point we
+ will fail.
+
+ A typical GET request is 520 bytes, and it's difficult to
+ imagine a real world case where that will come in fragmented
+ such that we trigger this failure. Further, the spice reds
+ code has no real mechanism to do variable length/time based reads,
+ so it seems wisest to live with this theoretical flaw.
+ */
+
+ if (websocket_is_start(rbuf)) {
+ char outbuf[1024];
+
+ websocket_create_reply(rbuf, outbuf);
+ rc = stream->priv->write(stream, outbuf, strlen(outbuf));
+ if (rc == strlen(outbuf)) {
+ stream->priv->ws = g_malloc0(sizeof(*stream->priv->ws));
+
+ stream->priv->ws->raw_read = stream->priv->read;
+ stream->priv->ws->raw_write = stream->priv->write;
+
+ stream->priv->read = stream_websocket_read;
+ stream->priv->write = stream_websocket_write;
+
+ if (stream->priv->writev) {
+ stream->priv->ws->raw_writev = stream->priv->writev;
+ stream->priv->writev = stream_websocket_writev;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/server/red-stream.h b/server/red-stream.h
index ca6dc71a..a191dd42 100644
--- a/server/red-stream.h
+++ b/server/red-stream.h
@@ -91,6 +91,8 @@ bool red_stream_set_auto_flush(RedStream *stream, bool auto_flush);
*/
void red_stream_flush(RedStream *stream);
+bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len);
+
typedef enum {
RED_SASL_ERROR_OK,
RED_SASL_ERROR_GENERIC,
diff --git a/server/reds.c b/server/reds.c
index b4061fbc..671e0a86 100644
--- a/server/reds.c
+++ b/server/reds.c
@@ -2418,6 +2418,7 @@ static void reds_handle_link_error(void *opaque, int err)
reds_link_free(link);
}
+static void reds_handle_new_link(RedLinkInfo *link);
static void reds_handle_read_header_done(void *opaque)
{
RedLinkInfo *link = (RedLinkInfo *)opaque;
@@ -2460,6 +2461,18 @@ static void reds_handle_read_magic_done(void *opaque)
const SpiceLinkHeader *header = &link->link_header;
if (header->magic != SPICE_MAGIC) {
+ /* Attempt to detect and support a WebSocket connection,
+ which will be proceeded by a variable length GET style request.
+ We cannot know we are dealing with a WebSocket connection
+ until we have read at least 3 bytes, and we will have to
+ read many more bytes than are contained in a SpiceLinkHeader.
+ So we may as well read a SpiceLinkHeader's worth of data, and if it's
+ clear that a WebSocket connection was requested, we switch
+ before proceeding further. */
+ if (red_stream_is_websocket(link->stream, &header->magic, sizeof(header->magic))) {
+ reds_handle_new_link(link);
+ return;
+ }
reds_send_link_error(link, SPICE_LINK_ERR_INVALID_MAGIC);
reds_link_free(link);
return;
diff --git a/server/websocket.c b/server/websocket.c
new file mode 100644
index 00000000..58f36da0
--- /dev/null
+++ b/server/websocket.c
@@ -0,0 +1,457 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2015 Jeremy White
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#define _GNU_SOURCE
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <unistd.h>
+#endif
+
+#include <glib.h>
+
+#include <common/log.h>
+#include <common/mem.h>
+
+#include "sys-socket.h"
+#include "websocket.h"
+
+#ifdef _WIN32
+#include <shlwapi.h>
+#define strcasestr(haystack, needle) StrStrIA(haystack, needle)
+#endif
+
+/* Constants / masks all from RFC 6455 */
+
+#define FIN_FLAG 0x80
+#define TYPE_MASK 0x0F
+
+#define CONTINUATION_FRAME 0x0
+#define TEXT_FRAME 0x1
+#define BINARY_FRAME 0x2
+#define CLOSE_FRAME 0x8
+#define PING_FRAME 0x9
+#define PONG_FRAME 0xA
+
+#define LENGTH_MASK 0x7F
+#define LENGTH_16BIT 0x7E
+#define LENGTH_64BIT 0x7F
+
+#define MASK_FLAG 0x80
+
+#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+/* Perform a case insensitive search for needle in haystack.
+ If found, return a pointer to the byte after the end of needle.
+ Otherwise, return NULL */
+static const char *find_str(const char *haystack, const char *needle)
+{
+ const char *s = strcasestr(haystack, needle);
+
+ if (s) {
+ return s + strlen(needle);
+ }
+ return NULL;
+}
+
+/* Extract WebSocket style length. Returns 0 if not enough data present,
+ Always updates the output 'used' variable to the number of bytes
+ required to extract the length; useful for tracking where the
+ mask will be.
+*/
+static uint64_t extract_length(const uint8_t *buf, int *used)
+{
+ int i;
+ uint64_t outlen = (*buf++) & LENGTH_MASK;
+
+ (*used)++;
+
+ switch (outlen) {
+ case LENGTH_64BIT:
+ *used += 8;
+ outlen = 0;
+ for (i = 56; i >= 0; i -= 8) {
+ outlen |= (*buf++) << i;
+ }
+ break;
+
+ case LENGTH_16BIT:
+ *used += 2;
+ outlen = ((*buf) << 8) | *(buf + 1);
+ break;
+
+ default:
+ break;
+ }
+ return outlen;
+}
+
+static int frame_bytes_needed(websocket_frame_t *frame)
+{
+ int needed = 2;
+ if (frame->header_pos < needed) {
+ return needed - frame->header_pos;
+ }
+
+ switch (frame->header[1] & LENGTH_MASK) {
+ case LENGTH_64BIT:
+ needed += 8;
+ break;
+ case LENGTH_16BIT:
+ needed += 2;
+ break;
+ }
+
+ if (frame->header[1] & MASK_FLAG) {
+ needed += 4;
+ }
+
+ return needed - frame->header_pos;
+}
+
+/*
+* Generate WebSocket style response key, based on the
+* original key sent to us
+* If non null, caller must free returned key string.
+*/
+static char *generate_reply_key(char *buf)
+{
+ GChecksum *checksum;
+ char *b64 = NULL;
+ uint8_t *sha1;
+ size_t sha1_size;
+ const char *key;
+ const char *p;
+ char *k;
+
+ key = find_str(buf, "\nSec-WebSocket-Key:");
+ if (key) {
+ p = strchr(key, '\r');
+ if (p) {
+ k = g_strndup(key, p - key);
+ k = g_strstrip(k);
+ checksum = g_checksum_new(G_CHECKSUM_SHA1);
+ g_checksum_update(checksum, (uint8_t *) k, strlen(k));
+ g_checksum_update(checksum, (uint8_t *) WEBSOCKET_GUID, strlen(WEBSOCKET_GUID));
+ g_free(k);
+
+ sha1_size = g_checksum_type_get_length(G_CHECKSUM_SHA1);
+ sha1 = g_malloc(sha1_size);
+
+ g_checksum_get_digest(checksum, sha1, &sha1_size);
+
+ b64 = g_base64_encode(sha1, sha1_size);
+
+ g_checksum_free(checksum);
+ g_free(sha1);
+ }
+ }
+
+ return b64;
+}
+
+
+static void websocket_clear_frame(websocket_frame_t *frame)
+{
+ memset(frame, 0, sizeof(*frame));
+}
+
+/* Extract a frame header of data from a set of data transmitted by
+ a WebSocket client. */
+static void websocket_get_frame_header(websocket_frame_t *frame)
+{
+ int fin;
+ int used = 0;
+
+ if (frame_bytes_needed(frame) > 0) {
+ return;
+ }
+
+ fin = frame->header[0] & FIN_FLAG;
+ frame->type = frame->header[0] & TYPE_MASK;
+ used++;
+
+ frame->masked = frame->header[1] & MASK_FLAG;
+
+ /* This is a Spice specific optimization. We don't really
+ care about assembling frames fully, so we treat
+ a frame in process as a finished frame and pass it along. */
+ if (!fin && frame->type == CONTINUATION_FRAME) {
+ frame->type = BINARY_FRAME;
+ }
+
+ frame->expected_len = extract_length(frame->header + used, &used);
+
+ if (frame->masked) {
+ memcpy(frame->mask, frame->header + used, 4);
+ }
+
+ frame->relayed = 0;
+ frame->frame_ready = 1;
+}
+
+static int relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
+{
+ int i;
+ int n = MIN(size, frame->expected_len - frame->relayed);
+
+ if (frame->masked) {
+ for (i = 0; i < n; i++, frame->relayed++) {
+ *buf++ ^= frame->mask[frame->relayed % 4];
+ }
+ }
+
+ return n;
+}
+
+int websocket_read(void *opaque, uint8_t *buf, int size, websocket_frame_t *frame,
+ websocket_read_cb_t read_cb,
+ websocket_write_cb_t write_cb)
+{
+ int n = 0;
+ int rc;
+
+ while (size > 0) {
+ // make sure we have a proper frame ready
+ if (!frame->frame_ready) {
+ rc = read_cb(opaque, frame->header + frame->header_pos, frame_bytes_needed(frame));
+ if (rc <= 0) {
+ goto read_error;
+ }
+ frame->header_pos += rc;
+
+ websocket_get_frame_header(frame);
+ } else if (frame->type == CLOSE_FRAME) {
+ websocket_ack_close(opaque, write_cb);
+ websocket_clear_frame(frame);
+ return 0;
+ } else if (frame->type == BINARY_FRAME) {
+ rc = read_cb(opaque, buf, MIN(size, frame->expected_len - frame->relayed));
+ if (rc <= 0) {
+ goto read_error;
+ }
+
+ rc = relay_data(buf, rc, frame);
+ n += rc;
+ buf += rc;
+ size -= rc;
+ if (frame->relayed >= frame->expected_len) {
+ websocket_clear_frame(frame);
+ }
+ } else {
+ /* TODO - We don't handle PING at this point */
+ spice_warning("Unexpected WebSocket frame.type %d. Failure now likely.", frame->type);
+ websocket_clear_frame(frame);
+ continue;
+ }
+ }
+
+ return n;
+
+read_error:
+ if (n > 0 && rc == -1 && (errno == EINTR || errno == EAGAIN)) {
+ return n;
+ }
+ return rc;
+}
+
+static int fill_header(uint8_t *header, uint64_t len)
+{
+ int used = 0;
+ int i;
+
+ header[0] = FIN_FLAG | BINARY_FRAME;
+ used++;
+
+ header[1] = 0;
+ used++;
+ if (len > 65535) {
+ header[1] |= LENGTH_64BIT;
+ for (i = 9; i >= 2; i--) {
+ header[i] = len & 0xFF;
+ len >>= 8;
+ }
+ used += 8;
+ } else if (len >= LENGTH_16BIT) {
+ header[1] |= LENGTH_16BIT;
+ header[2] = len >> 8;
+ header[3] = len & 0xFF;
+ used += 2;
+ } else {
+ header[1] |= len;
+ }
+
+ return used;
+}
+
+static void constrain_iov(struct iovec *iov, int iovcnt,
+ struct iovec **iov_out, int *iov_out_cnt,
+ uint64_t maxlen)
+{
+ int i, j;
+
+ *iov_out = iov;
+ *iov_out_cnt = iovcnt;
+
+ for (i = 0; i < iovcnt && maxlen > 0; i++) {
+ if (iov[i].iov_len > maxlen) {
+ /* TODO - This code has never triggered afaik... */
+ *iov_out_cnt = i + 1;
+ *iov_out = g_malloc((*iov_out_cnt) * sizeof (**iov_out));
+ for (j = 0; j < i; j++) {
+ (*iov_out)[j].iov_base = iov[j].iov_base;
+ (*iov_out)[j].iov_len = iov[j].iov_len;
+ }
+ (*iov_out)[j].iov_base = iov[j].iov_base;
+ (*iov_out)[j].iov_len = maxlen;
+ break;
+ }
+ maxlen -= iov[i].iov_len;
+ }
+}
+
+
+/* Write a WebSocket frame with the enclosed data out. */
+int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *remainder,
+ websocket_writev_cb_t writev_cb)
+{
+ uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
+ uint64_t len;
+ int rc = -1;
+ struct iovec *iov_out;
+ int iov_out_cnt;
+ int i;
+ int header_len;
+
+ if (*remainder > 0) {
+ constrain_iov(iov, iovcnt, &iov_out, &iov_out_cnt, *remainder);
+ rc = writev_cb(opaque, iov_out, iov_out_cnt);
+ if (iov_out != iov) {
+ g_free(iov_out);
+ }
+ if (rc <= 0) {
+ return rc;
+ }
+ *remainder -= rc;
+ return rc;
+ }
+
+ iov_out_cnt = iovcnt + 1;
+ iov_out = g_malloc(iov_out_cnt * sizeof(*iov_out));
+
+ for (i = 0, len = 0; i < iovcnt; i++) {
+ len += iov[i].iov_len;
+ iov_out[i + 1] = iov[i];
+ }
+
+ memset(header, 0, sizeof(header));
+ header_len = fill_header(header, len);
+ iov_out[0].iov_len = header_len;
+ iov_out[0].iov_base = header;
+ rc = writev_cb(opaque, iov_out, iov_out_cnt);
+ g_free(iov_out);
+ if (rc <= 0) {
+ return rc;
+ }
+ rc -= header_len;
+
+ spice_assert(rc >= 0);
+
+ /* Key point: if we did not write out all the data, remember how
+ much more data the client is expecting, and write that data without
+ a header of any kind the next time around */
+ *remainder = len - rc;
+
+ return rc;
+}
+
+int websocket_write(void *opaque, const uint8_t *buf, int len, uint64_t *remainder,
+ websocket_write_cb_t write_cb)
+{
+ uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
+ int rc;
+ int header_len;
+
+ if (*remainder == 0) {
+ header_len = fill_header(header, len);
+ rc = write_cb(opaque, header, header_len);
+ if (rc <= 0) {
+ return rc;
+ }
+ if (rc != header_len) {
+ /* TODO - In theory, we can handle this case. In practice,
+ it does not occur, and does not seem to be worth
+ the code complexity */
+ errno = EPIPE;
+ return -1;
+ }
+ } else {
+ len = MIN(*remainder, len);
+ }
+
+ rc = write_cb(opaque, buf, len);
+ if (rc <= 0) {
+ *remainder = len;
+ } else {
+ *remainder = len - rc;
+ }
+ return rc;
+}
+
+void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb)
+{
+ unsigned char header[2];
+
+ header[0] = FIN_FLAG | CLOSE_FRAME;
+ header[1] = 0;
+
+ write_cb(opaque, header, sizeof(header));
+}
+
+bool websocket_is_start(char *buf)
+{
+ if (strncmp(buf, "GET ", 4) == 0 &&
+ // TODO strip, do not assume a single space
+ find_str(buf, "\nSec-WebSocket-Protocol: binary") &&
+ find_str(buf, "\nSec-WebSocket-Key:") &&
+ g_str_has_suffix(buf, "\r\n\r\n")) {
+ return true;
+ }
+
+ return false;
+}
+
+void websocket_create_reply(char *buf, char *outbuf)
+{
+ char *key;
+
+ key = generate_reply_key(buf);
+ sprintf(outbuf, "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: %s\r\n"
+ "Sec-WebSocket-Protocol: binary\r\n\r\n", key);
+ g_free(key);
+}
diff --git a/server/websocket.h b/server/websocket.h
new file mode 100644
index 00000000..63d7b10c
--- /dev/null
+++ b/server/websocket.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 Jeremy White
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
+
+typedef struct {
+ int type;
+ int masked;
+ uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
+ int header_pos;
+ int frame_ready:1;
+ uint8_t mask[4];
+ uint64_t relayed;
+ uint64_t expected_len;
+} websocket_frame_t;
+
+typedef ssize_t (*websocket_read_cb_t)(void *opaque, void *buf, size_t nbyte);
+typedef ssize_t (*websocket_write_cb_t)(void *opaque, const void *buf, size_t nbyte);
+typedef ssize_t (*websocket_writev_cb_t)(void *opaque, struct iovec *iov, int iovcnt);
+
+bool websocket_is_start(char *buf);
+void websocket_create_reply(char *buf, char *outbuf);
+int websocket_read(void *opaque, uint8_t *buf, int len, websocket_frame_t *frame,
+ websocket_read_cb_t read_cb,
+ websocket_write_cb_t write_cb);
+int websocket_write(void *opaque, const uint8_t *buf, int len, uint64_t *remainder,
+ websocket_write_cb_t write_cb);
+int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *remainder,
+ websocket_writev_cb_t writev_cb);
+void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
commit 214736dce643ce3ee257da017373e88cc19d2d3b
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Thu Jun 20 13:26:11 2019 +0100
reds: Fix SSL_CTX_set_ecdh_auto call for some old OpenSSL
SSL_CTX_set_ecdh_auto is not defined in some old versions of OpenSSL
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/configure.ac b/configure.ac
index e12d7e85..49c009d4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -209,6 +209,15 @@ AC_SUBST(SSL_CFLAGS)
AC_SUBST(SSL_LIBS)
AS_VAR_APPEND([SPICE_REQUIRES], [" openssl"])
+save_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS $SSL_CFLAGS"
+AC_CHECK_DECLS([SSL_CTX_set_ecdh_auto], [], [], [
+AC_INCLUDES_DEFAULT
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+])
+CFLAGS="$save_CFLAGS"
+
AC_CHECK_LIB(jpeg, jpeg_destroy_decompress,
AC_MSG_CHECKING([for jpeglib.h])
AC_TRY_CPP(
diff --git a/server/reds.c b/server/reds.c
index 792e9838..b4061fbc 100644
--- a/server/reds.c
+++ b/server/reds.c
@@ -2937,7 +2937,9 @@ static int reds_init_ssl(RedsState *reds)
}
SSL_CTX_set_options(reds->ctx, ssl_options);
+#if HAVE_DECL_SSL_CTX_SET_ECDH_AUTO || defined(SSL_CTX_set_ecdh_auto)
SSL_CTX_set_ecdh_auto(reds->ctx, 1);
+#endif
/* Load our keys and certificates*/
return_code = SSL_CTX_use_certificate_chain_file(reds->ctx, reds->config->ssl_parameters.certs_file);
commit 89b0a07c72d23a42a0834cd8515efe726c759f57
Author: Frediano Ziglio <fziglio at redhat.com>
Date: Thu Jun 20 12:38:32 2019 +0100
test-glib-compat: Fix G_PID_FORMAT definition for old systems
The G_PID_FORMAT constant is defined only if GLib does not support it.
The constant was wrongly defined.
Jessie Debian 32 shows this issue (printf format error).
Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
Acked-by: Jeremy White <jwhite at codeweavers.com>
diff --git a/server/tests/test-glib-compat.h b/server/tests/test-glib-compat.h
index eef07494..17ebcf48 100644
--- a/server/tests/test-glib-compat.h
+++ b/server/tests/test-glib-compat.h
@@ -70,7 +70,7 @@ g_test_assert_expected_messages_internal_no_warnings(const char *domain,
/* Added in glib 2.50 */
#ifndef G_PID_FORMAT
-#ifdef G_OS_WIN32
+#ifndef G_OS_WIN32
#define G_PID_FORMAT "i"
#else
#define G_PID_FORMAT "p"
More information about the Spice-commits
mailing list