#include "io/channel-websock.h"
#include "crypto/hash.h"
#include "trace.h"
+#include "qemu/iov.h"
#include <time.h>
-/* Max amount to allow in rawinput/rawoutput buffers */
+/* Max amount to allow in rawinput/encoutput buffers */
#define QIO_CHANNEL_WEBSOCK_MAX_BUFFER 8192
#define QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN 24
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE 0x0f
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK 0x80
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_PAYLOAD_LEN 0x7f
+#define QIO_CHANNEL_WEBSOCK_CONTROL_OPCODE_MASK 0x8
typedef struct QIOChannelWebsockHeader QIOChannelWebsockHeader;
QIO_CHANNEL_WEBSOCK_OPCODE_PONG = 0xA
};
-static void qio_channel_websock_handshake_send_res(QIOChannelWebsock *ioc,
- const char *resmsg,
- ...)
+static void GCC_FMT_ATTR(2, 3)
+qio_channel_websock_handshake_send_res(QIOChannelWebsock *ioc,
+ const char *resmsg,
+ ...)
{
va_list vargs;
char *response;
g_free(date);
}
+enum {
+ QIO_CHANNEL_WEBSOCK_STATUS_NORMAL = 1000,
+ QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR = 1002,
+ QIO_CHANNEL_WEBSOCK_STATUS_INVALID_DATA = 1003,
+ QIO_CHANNEL_WEBSOCK_STATUS_POLICY = 1008,
+ QIO_CHANNEL_WEBSOCK_STATUS_TOO_LARGE = 1009,
+ QIO_CHANNEL_WEBSOCK_STATUS_SERVER_ERR = 1011,
+};
+
static size_t
qio_channel_websock_extract_headers(QIOChannelWebsock *ioc,
char *buffer,
goto bad_request;
}
*nl = '\0';
+ trace_qio_channel_websock_http_greeting(ioc, buffer);
tmp = strchr(buffer, ' ');
if (!tmp) {
char combined_key[QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
QIO_CHANNEL_WEBSOCK_GUID_LEN + 1];
char *accept = NULL;
- char *date = qio_channel_websock_date_str();
+ char *date = NULL;
g_strlcpy(combined_key, key, QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + 1);
g_strlcat(combined_key, QIO_CHANNEL_WEBSOCK_GUID,
return;
}
+ date = qio_channel_websock_date_str();
qio_channel_websock_handshake_send_res(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_OK, date, accept);
size_t nhdrs = G_N_ELEMENTS(hdrs);
const char *protocols = NULL, *version = NULL, *key = NULL,
*host = NULL, *connection = NULL, *upgrade = NULL;
+ char **connectionv;
+ bool upgraded = false;
+ size_t i;
nhdrs = qio_channel_websock_extract_headers(ioc, buffer, hdrs, nhdrs, errp);
if (!nhdrs) {
goto bad_request;
}
+ trace_qio_channel_websock_http_request(ioc, protocols, version,
+ host, connection, upgrade, key);
+
if (!g_strrstr(protocols, QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY)) {
error_setg(errp, "No '%s' protocol is supported by client '%s'",
QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY, protocols);
goto bad_request;
}
- if (strcasecmp(connection, QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE) != 0) {
+ connectionv = g_strsplit(connection, ",", 0);
+ for (i = 0; connectionv != NULL && connectionv[i] != NULL; i++) {
+ g_strstrip(connectionv[i]);
+ if (strcasecmp(connectionv[i],
+ QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE) == 0) {
+ upgraded = true;
+ }
+ }
+ g_strfreev(connectionv);
+ if (!upgraded) {
error_setg(errp, "No connection upgrade requested '%s'", connection);
goto bad_request;
}
}
-static void qio_channel_websock_encode(QIOChannelWebsock *ioc)
+static void qio_channel_websock_encode(QIOChannelWebsock *ioc,
+ uint8_t opcode,
+ const struct iovec *iov,
+ size_t niov,
+ size_t size)
{
size_t header_size;
+ size_t i;
union {
char buf[QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT];
QIOChannelWebsockHeader ws;
} header;
- if (!ioc->rawoutput.offset) {
- return;
- }
+ assert(size <= iov_size(iov, niov));
header.ws.b0 = QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN |
- (QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME &
- QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE);
- if (ioc->rawoutput.offset <
- QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_7_BIT) {
- header.ws.b1 = (uint8_t)ioc->rawoutput.offset;
+ (opcode & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE);
+ if (size < QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_7_BIT) {
+ header.ws.b1 = (uint8_t)size;
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT;
- } else if (ioc->rawoutput.offset <
- QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_16_BIT) {
+ } else if (size < QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_16_BIT) {
header.ws.b1 = QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT;
- header.ws.u.s16.l16 = cpu_to_be16((uint16_t)ioc->rawoutput.offset);
+ header.ws.u.s16.l16 = cpu_to_be16((uint16_t)size);
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT;
} else {
header.ws.b1 = QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT;
- header.ws.u.s64.l64 = cpu_to_be64(ioc->rawoutput.offset);
+ header.ws.u.s64.l64 = cpu_to_be64(size);
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT;
}
header_size -= QIO_CHANNEL_WEBSOCK_HEADER_LEN_MASK;
- buffer_reserve(&ioc->encoutput, header_size + ioc->rawoutput.offset);
+ trace_qio_channel_websock_encode(ioc, opcode, header_size, size);
+ buffer_reserve(&ioc->encoutput, header_size + size);
buffer_append(&ioc->encoutput, header.buf, header_size);
- buffer_append(&ioc->encoutput, ioc->rawoutput.buffer,
- ioc->rawoutput.offset);
- buffer_reset(&ioc->rawoutput);
+ for (i = 0; i < niov && size != 0; i++) {
+ size_t want = iov[i].iov_len;
+ if (want > size) {
+ want = size;
+ }
+ buffer_append(&ioc->encoutput, iov[i].iov_base, want);
+ size -= want;
+ }
+}
+
+
+static ssize_t qio_channel_websock_write_wire(QIOChannelWebsock *, Error **);
+
+
+static void qio_channel_websock_write_close(QIOChannelWebsock *ioc,
+ uint16_t code, const char *reason)
+{
+ struct iovec iov[2] = {
+ { .iov_base = &code, .iov_len = sizeof(code) },
+ };
+ size_t niov = 1;
+ size_t size = iov[0].iov_len;
+
+ cpu_to_be16s(&code);
+
+ if (reason) {
+ iov[1].iov_base = (void *)reason;
+ iov[1].iov_len = strlen(reason);
+ size += iov[1].iov_len;
+ niov++;
+ }
+ qio_channel_websock_encode(ioc, QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE,
+ iov, niov, size);
+ qio_channel_websock_write_wire(ioc, NULL);
+ qio_channel_shutdown(ioc->master, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
}
error_setg(errp,
"Decoding header but %zu bytes of payload remain",
ioc->payload_remain);
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_SERVER_ERR,
+ "internal server error");
return -1;
}
if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT) {
has_mask = header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK;
payload_len = header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_PAYLOAD_LEN;
+ /* Save or restore opcode. */
+ if (opcode) {
+ ioc->opcode = opcode;
+ } else {
+ opcode = ioc->opcode;
+ }
+
+ trace_qio_channel_websock_header_partial_decode(ioc, payload_len,
+ fin, opcode, (int)has_mask);
+
if (opcode == QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE) {
/* disconnect */
return 0;
}
/* Websocket frame sanity check:
- * * Websocket fragmentation is not supported.
- * * All websockets frames sent by a client have to be masked.
- * * Only binary encoding is supported.
+ * * Fragmentation is only supported for binary frames.
+ * * All frames sent by a client MUST be masked.
+ * * Only binary and ping/pong encoding is supported.
*/
if (!fin) {
- error_setg(errp, "websocket fragmentation is not supported");
- return -1;
+ if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
+ error_setg(errp, "only binary websocket frames may be fragmented");
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_POLICY ,
+ "only binary frames may be fragmented");
+ return -1;
+ }
+ } else {
+ if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME &&
+ opcode != QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE &&
+ opcode != QIO_CHANNEL_WEBSOCK_OPCODE_PING &&
+ opcode != QIO_CHANNEL_WEBSOCK_OPCODE_PONG) {
+ error_setg(errp, "unsupported opcode: %#04x; only binary, close, "
+ "ping, and pong websocket frames are supported", opcode);
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_INVALID_DATA ,
+ "only binary, close, ping, and pong frames are supported");
+ return -1;
+ }
}
if (!has_mask) {
error_setg(errp, "client websocket frames must be masked");
- return -1;
- }
- if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
- error_setg(errp, "only binary websocket frames are supported");
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR,
+ "client frames must be masked");
return -1;
}
ioc->payload_remain = payload_len;
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT;
ioc->mask = header->u.m;
+ } else if (opcode & QIO_CHANNEL_WEBSOCK_CONTROL_OPCODE_MASK) {
+ error_setg(errp, "websocket control frame is too large");
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR,
+ "control frame is too large");
+ return -1;
} else if (payload_len == QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT &&
ioc->encinput.offset >= QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT) {
ioc->payload_remain = be16_to_cpu(header->u.s16.l16);
return QIO_CHANNEL_ERR_BLOCK;
}
+ trace_qio_channel_websock_header_full_decode(
+ ioc, header_size, ioc->payload_remain, ioc->mask.u);
buffer_advance(&ioc->encinput, header_size);
- return 1;
+ return 0;
}
Error **errp)
{
size_t i;
- size_t payload_len;
+ size_t payload_len = 0;
uint32_t *payload32;
- if (!ioc->payload_remain) {
- error_setg(errp,
- "Decoding payload but no bytes of payload remain");
- return -1;
- }
+ if (ioc->payload_remain) {
+ /* If we aren't at the end of the payload, then drop
+ * off the last bytes, so we're always multiple of 4
+ * for purpose of unmasking, except at end of payload
+ */
+ if (ioc->encinput.offset < ioc->payload_remain) {
+ /* Wait for the entire payload before processing control frames
+ * because the payload will most likely be echoed back. */
+ if (ioc->opcode & QIO_CHANNEL_WEBSOCK_CONTROL_OPCODE_MASK) {
+ return QIO_CHANNEL_ERR_BLOCK;
+ }
+ payload_len = ioc->encinput.offset - (ioc->encinput.offset % 4);
+ } else {
+ payload_len = ioc->payload_remain;
+ }
+ if (payload_len == 0) {
+ return QIO_CHANNEL_ERR_BLOCK;
+ }
- /* If we aren't at the end of the payload, then drop
- * off the last bytes, so we're always multiple of 4
- * for purpose of unmasking, except at end of payload
- */
- if (ioc->encinput.offset < ioc->payload_remain) {
- payload_len = ioc->encinput.offset - (ioc->encinput.offset % 4);
- } else {
- payload_len = ioc->payload_remain;
- }
- if (payload_len == 0) {
- return QIO_CHANNEL_ERR_BLOCK;
+ ioc->payload_remain -= payload_len;
+
+ /* unmask frame */
+ /* process 1 frame (32 bit op) */
+ payload32 = (uint32_t *)ioc->encinput.buffer;
+ for (i = 0; i < payload_len / 4; i++) {
+ payload32[i] ^= ioc->mask.u;
+ }
+ /* process the remaining bytes (if any) */
+ for (i *= 4; i < payload_len; i++) {
+ ioc->encinput.buffer[i] ^= ioc->mask.c[i % 4];
+ }
}
- ioc->payload_remain -= payload_len;
+ trace_qio_channel_websock_payload_decode(
+ ioc, ioc->opcode, ioc->payload_remain);
- /* unmask frame */
- /* process 1 frame (32 bit op) */
- payload32 = (uint32_t *)ioc->encinput.buffer;
- for (i = 0; i < payload_len / 4; i++) {
- payload32[i] ^= ioc->mask.u;
- }
- /* process the remaining bytes (if any) */
- for (i *= 4; i < payload_len; i++) {
- ioc->encinput.buffer[i] ^= ioc->mask.c[i % 4];
- }
+ if (ioc->opcode == QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
+ if (payload_len) {
+ /* binary frames are passed on */
+ buffer_reserve(&ioc->rawinput, payload_len);
+ buffer_append(&ioc->rawinput, ioc->encinput.buffer, payload_len);
+ }
+ } else if (ioc->opcode == QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE) {
+ /* close frames are echoed back */
+ error_setg(errp, "websocket closed by peer");
+ if (payload_len) {
+ /* echo client status */
+ struct iovec iov = { .iov_base = ioc->encinput.buffer,
+ .iov_len = ioc->encinput.offset };
+ qio_channel_websock_encode(ioc, QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE,
+ &iov, 1, iov.iov_len);
+ qio_channel_websock_write_wire(ioc, NULL);
+ qio_channel_shutdown(ioc->master, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
+ } else {
+ /* send our own status */
+ qio_channel_websock_write_close(
+ ioc, QIO_CHANNEL_WEBSOCK_STATUS_NORMAL, "peer requested close");
+ }
+ return -1;
+ } else if (ioc->opcode == QIO_CHANNEL_WEBSOCK_OPCODE_PING) {
+ /* ping frames produce an immediate reply, as long as we've not still
+ * got a previous pong queued, in which case we drop the new pong */
+ if (ioc->pong_remain == 0) {
+ struct iovec iov = { .iov_base = ioc->encinput.buffer,
+ .iov_len = ioc->encinput.offset };
+ qio_channel_websock_encode(ioc, QIO_CHANNEL_WEBSOCK_OPCODE_PONG,
+ &iov, 1, iov.iov_len);
+ ioc->pong_remain = ioc->encoutput.offset;
+ }
+ } /* pong frames are ignored */
- buffer_reserve(&ioc->rawinput, payload_len);
- buffer_append(&ioc->rawinput, ioc->encinput.buffer, payload_len);
- buffer_advance(&ioc->encinput, payload_len);
+ if (payload_len) {
+ buffer_advance(&ioc->encinput, payload_len);
+ }
return 0;
}
buffer_free(&ioc->encinput);
buffer_free(&ioc->encoutput);
buffer_free(&ioc->rawinput);
- buffer_free(&ioc->rawoutput);
object_unref(OBJECT(ioc->master));
if (ioc->io_tag) {
g_source_remove(ioc->io_tag);
{
ssize_t ret;
ssize_t done = 0;
- qio_channel_websock_encode(ioc);
while (ioc->encoutput.offset > 0) {
ret = qio_channel_write(ioc->master,
}
buffer_advance(&ioc->encoutput, ret);
done += ret;
+ if (ioc->pong_remain < ret) {
+ ioc->pong_remain = 0;
+ } else {
+ ioc->pong_remain -= ret;
+ }
}
return done;
}
Error **errp)
{
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
- size_t i;
- ssize_t done = 0;
+ ssize_t want = iov_size(iov, niov);
+ ssize_t avail;
ssize_t ret;
if (wioc->io_err) {
return -1;
}
- for (i = 0; i < niov; i++) {
- size_t want = iov[i].iov_len;
- if ((want + wioc->rawoutput.offset) > QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
- want = (QIO_CHANNEL_WEBSOCK_MAX_BUFFER - wioc->rawoutput.offset);
- }
- if (want == 0) {
- goto done;
- }
+ avail = wioc->encoutput.offset >= QIO_CHANNEL_WEBSOCK_MAX_BUFFER ?
+ 0 : (QIO_CHANNEL_WEBSOCK_MAX_BUFFER - wioc->encoutput.offset);
+ if (want > avail) {
+ want = avail;
+ }
- buffer_reserve(&wioc->rawoutput, want);
- buffer_append(&wioc->rawoutput, iov[i].iov_base, want);
- done += want;
- if (want < iov[i].iov_len) {
- break;
- }
+ if (want) {
+ qio_channel_websock_encode(wioc,
+ QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME,
+ iov, niov, want);
}
- done:
+ /* Even if want == 0, we'll try write_wire in case there's
+ * pending data we could usefully flush out
+ */
ret = qio_channel_websock_write_wire(wioc, errp);
if (ret < 0 &&
ret != QIO_CHANNEL_ERR_BLOCK) {
qio_channel_websock_set_watch(wioc);
- if (done == 0) {
+ if (want == 0) {
return QIO_CHANNEL_ERR_BLOCK;
}
- return done;
+ return want;
}
static int qio_channel_websock_set_blocking(QIOChannel *ioc,
{
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
+ trace_qio_channel_websock_close(ioc);
return qio_channel_close(wioc->master, errp);
}
if (wsource->wioc->rawinput.offset || wsource->wioc->io_eof) {
cond |= G_IO_IN;
}
- if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
+ if (wsource->wioc->encoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
cond |= G_IO_OUT;
}