Merge qio 2017/10/04 v1

-----BEGIN PGP SIGNATURE-----
 
 iQIcBAABCAAGBQJZ1NKoAAoJEL6G67QVEE/fJ0oP/RMt6ZuCcAgR369Bp4tZRcp4
 Z58Dhr48pjWmSrXINxambx3I+W3OlHo+5x4JxDWa9+VeKlmSEiZqWkzEa1TsHi7h
 YLVjuTkGO9wDYU/CnLprwDTNtxZ7ppt9immPG+6MsnqSIGBeCAKCKs7gXK85EHzz
 pRbpb5MCf0duUjExQPYSrNGIT4KVjKEj1G9QAZM80tTg/I/FxxWc7OXV3fZtl3cY
 kblLCMc0kahnQAYLDzL9bjgrvx7D8u8xd0BZBTaNz7gZ0sh7f99ad0KCcFyVpLoG
 YSm/GbuvbrylX0h5HXKalo3RTantCE+JuGunT/KN4Q8ba0SvbVttROr+5EDbRy1y
 UIKfM5RtLpj6P72ySLBrcPE2WX/Kw7ItB5D2ayu+fO1TWhPa5S58k8XuMv5G72Ny
 hxcx6Wd/bD0SzdBfZHvd7gfc4VMSXo2XjjRbcHlPa1nRIluFzsHLFug82+MgNL6z
 D5Vw1pW+6QCq9bZK6wsuMRKP+BxISg4BfH8zsEe+CstovOKEgTMr3HxLSj6jCFPN
 Ta7t+JnIieTTrne/g/upecbbHDXjDPA2qavOBjcePR262T5OeCjVYiOgrEw2xomC
 xcTHg/kwAVgc9G85pORtEuZK5gCgD2dKdnODgaJKv4VZHk+I9wBUKOihUqBgA2WW
 iHenAJzXZcFRYKFQzyNr
 =/rNZ
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/berrange/tags/pull-qio-2017-10-04-1' into staging

Merge qio 2017/10/04 v1

# gpg: Signature made Wed 04 Oct 2017 13:23:04 BST
# gpg:                using RSA key 0xBE86EBB415104FDF
# gpg: Good signature from "Daniel P. Berrange <dan@berrange.com>"
# gpg:                 aka "Daniel P. Berrange <berrange@redhat.com>"
# Primary key fingerprint: DAF3 A6FD B26B 6291 2D0E  8E3F BE86 EBB4 1510 4FDF

* remotes/berrange/tags/pull-qio-2017-10-04-1:
  io: add trace events for websockets frame handling
  io: Attempt to send websocket close messages to client
  io: Reply to ping frames
  io: Ignore websocket PING and PONG frames
  io: Allow empty websocket payload
  io: Add support for fragmented websocket binary frames
  io: Small updates in preparation for websocket changes
  ui: Always remove an old VNC channel watch before adding a new one
  io: use case insensitive check for Connection & Upgrade websock headers
  io: include full error message in websocket handshake trace
  io: send proper HTTP response for websocket errors

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2017-10-05 14:44:12 +01:00
commit 1fdc4c5d82
6 changed files with 364 additions and 158 deletions

View File

@ -60,11 +60,13 @@ struct QIOChannelWebsock {
Buffer encoutput;
Buffer rawinput;
Buffer rawoutput;
Buffer ping_reply;
size_t payload_remain;
QIOChannelWebsockMask mask;
guint io_tag;
Error *io_err;
gboolean io_eof;
uint8_t opcode;
};
/**

View File

@ -25,6 +25,8 @@
#include "crypto/hash.h"
#include "trace.h"
#include <time.h>
/* Max amount to allow in rawinput/rawoutput buffers */
#define QIO_CHANNEL_WEBSOCK_MAX_BUFFER 8192
@ -44,13 +46,40 @@
#define QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE "Upgrade"
#define QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET "websocket"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RESPONSE \
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Server: QEMU VNC\r\n" \
"Date: %s\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_OK \
"HTTP/1.1 101 Switching Protocols\r\n" \
QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Upgrade: websocket\r\n" \
"Connection: Upgrade\r\n" \
"Sec-WebSocket-Accept: %s\r\n" \
"Sec-WebSocket-Protocol: binary\r\n" \
"\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_NOT_FOUND \
"HTTP/1.1 404 Not Found\r\n" \
QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Connection: close\r\n" \
"\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST \
"HTTP/1.1 400 Bad Request\r\n" \
QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Connection: close\r\n" \
"Sec-WebSocket-Version: " \
QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION \
"\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_SERVER_ERR \
"HTTP/1.1 500 Internal Server Error\r\n" \
QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Connection: close\r\n" \
"\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_TOO_LARGE \
"HTTP/1.1 403 Request Entity Too Large\r\n" \
QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Connection: close\r\n" \
"\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM "\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_END "\r\n\r\n"
#define QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION "13"
@ -81,13 +110,12 @@
/* Magic 7-bit length to indicate use of 64-bit payload length */
#define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT 127
/* Bitmasks & shifts for accessing header fields */
/* Bitmasks for accessing header fields */
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN 0x80
#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_HEADER_SHIFT_FIN 7
#define QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_HAS_MASK 7
#define QIO_CHANNEL_WEBSOCK_CONTROL_OPCODE_MASK 0x8
typedef struct QIOChannelWebsockHeader QIOChannelWebsockHeader;
@ -123,8 +151,55 @@ enum {
QIO_CHANNEL_WEBSOCK_OPCODE_PONG = 0xA
};
static void qio_channel_websock_handshake_send_res(QIOChannelWebsock *ioc,
const char *resmsg,
...)
{
va_list vargs;
char *response;
size_t responselen;
va_start(vargs, resmsg);
response = g_strdup_vprintf(resmsg, vargs);
responselen = strlen(response);
buffer_reserve(&ioc->encoutput, responselen);
buffer_append(&ioc->encoutput, response, responselen);
va_end(vargs);
}
static gchar *qio_channel_websock_date_str(void)
{
struct tm tm;
time_t now = time(NULL);
char datebuf[128];
gmtime_r(&now, &tm);
strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y %H:%M:%S GMT", &tm);
return g_strdup(datebuf);
}
static void qio_channel_websock_handshake_send_res_err(QIOChannelWebsock *ioc,
const char *resdata)
{
char *date = qio_channel_websock_date_str();
qio_channel_websock_handshake_send_res(ioc, resdata, date);
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(char *buffer,
qio_channel_websock_extract_headers(QIOChannelWebsock *ioc,
char *buffer,
QIOChannelWebsockHTTPHeader *hdrs,
size_t nhdrsalloc,
Error **errp)
@ -145,7 +220,7 @@ qio_channel_websock_extract_headers(char *buffer,
nl = strstr(buffer, QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
if (!nl) {
error_setg(errp, "Missing HTTP header delimiter");
return 0;
goto bad_request;
}
*nl = '\0';
@ -158,18 +233,20 @@ qio_channel_websock_extract_headers(char *buffer,
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_METHOD)) {
error_setg(errp, "Unsupported HTTP method %s", buffer);
return 0;
goto bad_request;
}
buffer = tmp + 1;
tmp = strchr(buffer, ' ');
if (!tmp) {
error_setg(errp, "Missing HTTP version delimiter");
return 0;
goto bad_request;
}
*tmp = '\0';
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_PATH)) {
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_NOT_FOUND);
error_setg(errp, "Unexpected HTTP path %s", buffer);
return 0;
}
@ -178,7 +255,7 @@ qio_channel_websock_extract_headers(char *buffer,
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_VERSION)) {
error_setg(errp, "Unsupported HTTP version %s", buffer);
return 0;
goto bad_request;
}
buffer = nl + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
@ -203,7 +280,7 @@ qio_channel_websock_extract_headers(char *buffer,
sep = strchr(buffer, ':');
if (!sep) {
error_setg(errp, "Malformed HTTP header");
return 0;
goto bad_request;
}
*sep = '\0';
sep++;
@ -213,7 +290,7 @@ qio_channel_websock_extract_headers(char *buffer,
if (nhdrs >= nhdrsalloc) {
error_setg(errp, "Too many HTTP headers");
return 0;
goto bad_request;
}
hdr = &hdrs[nhdrs++];
@ -231,6 +308,11 @@ qio_channel_websock_extract_headers(char *buffer,
} while (nl != NULL);
return nhdrs;
bad_request:
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST);
return 0;
}
static const char *
@ -250,14 +332,14 @@ qio_channel_websock_find_header(QIOChannelWebsockHTTPHeader *hdrs,
}
static int qio_channel_websock_handshake_send_response(QIOChannelWebsock *ioc,
const char *key,
Error **errp)
static void qio_channel_websock_handshake_send_res_ok(QIOChannelWebsock *ioc,
const char *key,
Error **errp)
{
char combined_key[QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
QIO_CHANNEL_WEBSOCK_GUID_LEN + 1];
char *accept = NULL, *response = NULL;
size_t responselen;
char *accept = NULL;
char *date = qio_channel_websock_date_str();
g_strlcpy(combined_key, key, QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + 1);
g_strlcat(combined_key, QIO_CHANNEL_WEBSOCK_GUID,
@ -271,105 +353,108 @@ static int qio_channel_websock_handshake_send_response(QIOChannelWebsock *ioc,
QIO_CHANNEL_WEBSOCK_GUID_LEN,
&accept,
errp) < 0) {
return -1;
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_SERVER_ERR);
return;
}
response = g_strdup_printf(QIO_CHANNEL_WEBSOCK_HANDSHAKE_RESPONSE, accept);
responselen = strlen(response);
buffer_reserve(&ioc->encoutput, responselen);
buffer_append(&ioc->encoutput, response, responselen);
qio_channel_websock_handshake_send_res(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_OK, date, accept);
g_free(date);
g_free(accept);
g_free(response);
return 0;
}
static int qio_channel_websock_handshake_process(QIOChannelWebsock *ioc,
char *buffer,
Error **errp)
static void qio_channel_websock_handshake_process(QIOChannelWebsock *ioc,
char *buffer,
Error **errp)
{
QIOChannelWebsockHTTPHeader hdrs[32];
size_t nhdrs = G_N_ELEMENTS(hdrs);
const char *protocols = NULL, *version = NULL, *key = NULL,
*host = NULL, *connection = NULL, *upgrade = NULL;
nhdrs = qio_channel_websock_extract_headers(buffer, hdrs, nhdrs, errp);
nhdrs = qio_channel_websock_extract_headers(ioc, buffer, hdrs, nhdrs, errp);
if (!nhdrs) {
return -1;
return;
}
protocols = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_PROTOCOL);
if (!protocols) {
error_setg(errp, "Missing websocket protocol header data");
return -1;
goto bad_request;
}
version = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_VERSION);
if (!version) {
error_setg(errp, "Missing websocket version header data");
return -1;
goto bad_request;
}
key = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_KEY);
if (!key) {
error_setg(errp, "Missing websocket key header data");
return -1;
goto bad_request;
}
host = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_HOST);
if (!host) {
error_setg(errp, "Missing websocket host header data");
return -1;
goto bad_request;
}
connection = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_CONNECTION);
if (!connection) {
error_setg(errp, "Missing websocket connection header data");
return -1;
goto bad_request;
}
upgrade = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_UPGRADE);
if (!upgrade) {
error_setg(errp, "Missing websocket upgrade header data");
return -1;
goto bad_request;
}
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);
return -1;
goto bad_request;
}
if (!g_str_equal(version, QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION)) {
error_setg(errp, "Version '%s' is not supported by client '%s'",
QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION, version);
return -1;
goto bad_request;
}
if (strlen(key) != QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN) {
error_setg(errp, "Key length '%zu' was not as expected '%d'",
strlen(key), QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN);
return -1;
goto bad_request;
}
if (!g_strrstr(connection, QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE)) {
if (strcasecmp(connection, QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE) != 0) {
error_setg(errp, "No connection upgrade requested '%s'", connection);
return -1;
goto bad_request;
}
if (!g_str_equal(upgrade, QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET)) {
if (strcasecmp(upgrade, QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET) != 0) {
error_setg(errp, "Incorrect upgrade method '%s'", upgrade);
return -1;
goto bad_request;
}
return qio_channel_websock_handshake_send_response(ioc, key, errp);
qio_channel_websock_handshake_send_res_ok(ioc, key, errp);
return;
bad_request:
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST);
}
static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc,
@ -393,20 +478,20 @@ static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc,
QIO_CHANNEL_WEBSOCK_HANDSHAKE_END);
if (!handshake_end) {
if (ioc->encinput.offset >= 4096) {
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_TOO_LARGE);
error_setg(errp,
"End of headers not found in first 4096 bytes");
return -1;
return 1;
} else {
return 0;
}
}
*handshake_end = '\0';
if (qio_channel_websock_handshake_process(ioc,
(char *)ioc->encinput.buffer,
errp) < 0) {
return -1;
}
qio_channel_websock_handshake_process(ioc,
(char *)ioc->encinput.buffer,
errp);
buffer_advance(&ioc->encinput,
handshake_end - (char *)ioc->encinput.buffer +
@ -430,7 +515,7 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
&err);
if (ret < 0) {
trace_qio_channel_websock_handshake_fail(ioc);
trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err));
qio_task_set_error(task, err);
qio_task_complete(task);
return FALSE;
@ -438,8 +523,16 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
buffer_advance(&wioc->encoutput, ret);
if (wioc->encoutput.offset == 0) {
trace_qio_channel_websock_handshake_complete(ioc);
qio_task_complete(task);
if (wioc->io_err) {
trace_qio_channel_websock_handshake_fail(
ioc, error_get_pretty(wioc->io_err));
qio_task_set_error(task, wioc->io_err);
wioc->io_err = NULL;
qio_task_complete(task);
} else {
trace_qio_channel_websock_handshake_complete(ioc);
qio_task_complete(task);
}
return FALSE;
}
trace_qio_channel_websock_handshake_pending(ioc, G_IO_OUT);
@ -458,7 +551,12 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
ret = qio_channel_websock_handshake_read(wioc, &err);
if (ret < 0) {
trace_qio_channel_websock_handshake_fail(ioc);
/*
* We only take this path on a fatal I/O error reading from
* client connection, as most of the time we have an
* HTTP 4xx err response to send instead
*/
trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err));
qio_task_set_error(task, err);
qio_task_complete(task);
return FALSE;
@ -469,6 +567,10 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
return TRUE;
}
if (err) {
error_propagate(&wioc->io_err, err);
}
trace_qio_channel_websock_handshake_reply(ioc);
qio_channel_add_watch(
wioc->master,
@ -480,7 +582,9 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
}
static void qio_channel_websock_encode(QIOChannelWebsock *ioc)
static void qio_channel_websock_encode_buffer(QIOChannelWebsock *ioc,
Buffer *output,
uint8_t opcode, Buffer *buffer)
{
size_t header_size;
union {
@ -488,39 +592,66 @@ static void qio_channel_websock_encode(QIOChannelWebsock *ioc)
QIOChannelWebsockHeader ws;
} header;
if (!ioc->rawoutput.offset) {
return;
}
header.ws.b0 = (1 << QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_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;
header.ws.b0 = QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN |
(opcode & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE);
if (buffer->offset < QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_7_BIT) {
header.ws.b1 = (uint8_t)buffer->offset;
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT;
} else if (ioc->rawoutput.offset <
} else if (buffer->offset <
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)buffer->offset);
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(buffer->offset);
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);
buffer_append(&ioc->encoutput, header.buf, header_size);
buffer_append(&ioc->encoutput, ioc->rawoutput.buffer,
ioc->rawoutput.offset);
trace_qio_channel_websock_encode(ioc, opcode, header_size, buffer->offset);
buffer_reserve(output, header_size + buffer->offset);
buffer_append(output, header.buf, header_size);
buffer_append(output, buffer->buffer, buffer->offset);
}
static void qio_channel_websock_encode(QIOChannelWebsock *ioc)
{
if (!ioc->rawoutput.offset) {
return;
}
qio_channel_websock_encode_buffer(
ioc, &ioc->encoutput, QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME,
&ioc->rawoutput);
buffer_reset(&ioc->rawoutput);
}
static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
Error **errp)
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)
{
buffer_reserve(&ioc->rawoutput, 2 + (reason ? strlen(reason) : 0));
*(uint16_t *)(ioc->rawoutput.buffer + ioc->rawoutput.offset) =
cpu_to_be16(code);
ioc->rawoutput.offset += 2;
if (reason) {
buffer_append(&ioc->rawoutput, reason, strlen(reason));
}
qio_channel_websock_encode_buffer(
ioc, &ioc->encoutput, QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE,
&ioc->rawoutput);
buffer_reset(&ioc->rawoutput);
qio_channel_websock_write_wire(ioc, NULL);
qio_channel_shutdown(ioc->master, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
}
static int qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
Error **errp)
{
unsigned char opcode, fin, has_mask;
size_t header_size;
@ -532,6 +663,9 @@ static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
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) {
@ -539,33 +673,57 @@ static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
return QIO_CHANNEL_ERR_BLOCK;
}
fin = (header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN) >>
QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_FIN;
fin = header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN;
opcode = header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE;
has_mask = (header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK) >>
QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_HAS_MASK;
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, "websocket frames must be masked");
return -1;
}
if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
error_setg(errp, "only binary websocket frames are supported");
error_setg(errp, "client websocket frames must be masked");
qio_channel_websock_write_close(
ioc, QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR,
"client frames must be masked");
return -1;
}
@ -573,6 +731,12 @@ static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
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);
@ -588,54 +752,90 @@ static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
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;
}
static ssize_t qio_channel_websock_decode_payload(QIOChannelWebsock *ioc,
Error **errp)
static int qio_channel_websock_decode_payload(QIOChannelWebsock *ioc,
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");
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;
}
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];
}
}
trace_qio_channel_websock_payload_decode(
ioc, ioc->opcode, ioc->payload_remain);
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 */
qio_channel_websock_encode_buffer(
ioc, &ioc->encoutput, QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE,
&ioc->encinput);
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 */
buffer_reset(&ioc->ping_reply);
qio_channel_websock_encode_buffer(
ioc, &ioc->ping_reply, QIO_CHANNEL_WEBSOCK_OPCODE_PONG,
&ioc->encinput);
} /* pong frames are ignored */
/* 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) {
buffer_advance(&ioc->encinput, payload_len);
}
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];
}
buffer_reserve(&ioc->rawinput, payload_len);
buffer_append(&ioc->rawinput, ioc->encinput.buffer, payload_len);
buffer_advance(&ioc->encinput, payload_len);
return payload_len;
return 0;
}
@ -688,6 +888,7 @@ static void qio_channel_websock_finalize(Object *obj)
buffer_free(&ioc->encoutput);
buffer_free(&ioc->rawinput);
buffer_free(&ioc->rawoutput);
buffer_free(&ioc->ping_reply);
object_unref(OBJECT(ioc->master));
if (ioc->io_tag) {
g_source_remove(ioc->io_tag);
@ -715,8 +916,8 @@ static ssize_t qio_channel_websock_read_wire(QIOChannelWebsock *ioc,
if (ret < 0) {
return ret;
}
if (ret == 0 &&
ioc->encinput.offset == 0) {
if (ret == 0 && ioc->encinput.offset == 0) {
ioc->io_eof = TRUE;
return 0;
}
ioc->encinput.offset += ret;
@ -728,10 +929,6 @@ static ssize_t qio_channel_websock_read_wire(QIOChannelWebsock *ioc,
if (ret < 0) {
return ret;
}
if (ret == 0) {
ioc->io_eof = TRUE;
break;
}
}
ret = qio_channel_websock_decode_payload(ioc, errp);
@ -748,7 +945,13 @@ static ssize_t qio_channel_websock_write_wire(QIOChannelWebsock *ioc,
{
ssize_t ret;
ssize_t done = 0;
qio_channel_websock_encode(ioc);
/* ping replies take priority over binary data */
if (!ioc->ping_reply.offset) {
qio_channel_websock_encode(ioc);
} else if (!ioc->encoutput.offset) {
buffer_move_empty(&ioc->encoutput, &ioc->ping_reply);
}
while (ioc->encoutput.offset > 0) {
ret = qio_channel_write(ioc->master,
@ -823,7 +1026,7 @@ static void qio_channel_websock_set_watch(QIOChannelWebsock *ioc)
return;
}
if (ioc->encoutput.offset) {
if (ioc->encoutput.offset || ioc->ping_reply.offset) {
cond |= G_IO_OUT;
}
if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER &&
@ -985,6 +1188,7 @@ static int qio_channel_websock_close(QIOChannel *ioc,
{
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
trace_qio_channel_websock_close(ioc);
return qio_channel_close(wioc->master, errp);
}
@ -996,14 +1200,12 @@ struct QIOChannelWebsockSource {
};
static gboolean
qio_channel_websock_source_prepare(GSource *source,
gint *timeout)
qio_channel_websock_source_check(GSource *source)
{
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
GIOCondition cond = 0;
*timeout = -1;
if (wsource->wioc->rawinput.offset) {
if (wsource->wioc->rawinput.offset || wsource->wioc->io_eof) {
cond |= G_IO_IN;
}
if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
@ -1014,19 +1216,11 @@ qio_channel_websock_source_prepare(GSource *source,
}
static gboolean
qio_channel_websock_source_check(GSource *source)
qio_channel_websock_source_prepare(GSource *source,
gint *timeout)
{
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
GIOCondition cond = 0;
if (wsource->wioc->rawinput.offset) {
cond |= G_IO_IN;
}
if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
cond |= G_IO_OUT;
}
return cond & wsource->condition;
*timeout = -1;
return qio_channel_websock_source_check(source);
}
static gboolean
@ -1036,17 +1230,9 @@ qio_channel_websock_source_dispatch(GSource *source,
{
QIOChannelFunc func = (QIOChannelFunc)callback;
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
GIOCondition cond = 0;
if (wsource->wioc->rawinput.offset) {
cond |= G_IO_IN;
}
if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
cond |= G_IO_OUT;
}
return (*func)(QIO_CHANNEL(wsource->wioc),
(cond & wsource->condition),
qio_channel_websock_source_check(source),
user_data);
}

View File

@ -46,8 +46,13 @@ qio_channel_websock_new_server(void *ioc, void *master) "Websock new client ioc=
qio_channel_websock_handshake_start(void *ioc) "Websock handshake start ioc=%p"
qio_channel_websock_handshake_pending(void *ioc, int status) "Websock handshake pending ioc=%p status=%d"
qio_channel_websock_handshake_reply(void *ioc) "Websock handshake reply ioc=%p"
qio_channel_websock_handshake_fail(void *ioc) "Websock handshake fail ioc=%p"
qio_channel_websock_handshake_fail(void *ioc, const char *msg) "Websock handshake fail ioc=%p err=%s"
qio_channel_websock_handshake_complete(void *ioc) "Websock handshake complete ioc=%p"
qio_channel_websock_header_partial_decode(void *ioc, size_t payloadlen, unsigned char fin, unsigned char opcode, unsigned char has_mask) "Websocket header decoded ioc=%p payload-len=%zu fin=0x%x opcode=0x%x has_mask=0x%x"
qio_channel_websock_header_full_decode(void *ioc, size_t headerlen, size_t payloadlen, uint32_t mask) "Websocket header decoded ioc=%p header-len=%zu payload-len=%zu mask=0x%x"
qio_channel_websock_payload_decode(void *ioc, uint8_t opcode, size_t payload_remain) "Websocket header decoded ioc=%p opcode=0x%x payload-remain=%zu"
qio_channel_websock_encode(void *ioc, uint8_t opcode, size_t payloadlen, size_t headerlen) "Websocket encoded ioc=%p opcode=0x%x header-len=%zu payload-len=%zu"
qio_channel_websock_close(void *ioc) "Websocket close ioc=%p"
# io/channel-command.c
qio_channel_command_new_pid(void *ioc, int writefd, int readfd, int pid) "Command new pid ioc=%p writefd=%d readfd=%d pid=%d"

View File

@ -75,6 +75,9 @@ static void vnc_tls_handshake_done(QIOTask *task,
vnc_client_error(vs);
error_free(err);
} else {
if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL);
start_auth_vencrypt_subauth(vs);

View File

@ -37,6 +37,9 @@ static void vncws_tls_handshake_done(QIOTask *task,
error_free(err);
} else {
VNC_DEBUG("TLS handshake complete, starting websocket handshake\n");
if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
QIO_CHANNEL(vs->ioc), G_IO_IN, vncws_handshake_io, vs, NULL);
}
@ -97,6 +100,9 @@ static void vncws_handshake_done(QIOTask *task,
} else {
VNC_DEBUG("Websock handshake complete, starting VNC protocol\n");
vnc_start_protocol(vs);
if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vnc_client_io, vs, NULL);
}

View File

@ -1122,6 +1122,7 @@ static void vnc_disconnect_start(VncState *vs)
vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED);
if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
vs->ioc_tag = 0;
}
qio_channel_close(vs->ioc, NULL);
vs->disconnecting = TRUE;
@ -2934,6 +2935,9 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc,
VNC_DEBUG("New client on socket %p\n", vs->sioc);
update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
qio_channel_set_blocking(vs->ioc, false, NULL);
if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
if (websocket) {
vs->websocket = 1;
if (vd->tlscreds) {