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 encoutput;
Buffer rawinput; Buffer rawinput;
Buffer rawoutput; Buffer rawoutput;
Buffer ping_reply;
size_t payload_remain; size_t payload_remain;
QIOChannelWebsockMask mask; QIOChannelWebsockMask mask;
guint io_tag; guint io_tag;
Error *io_err; Error *io_err;
gboolean io_eof; gboolean io_eof;
uint8_t opcode;
}; };
/** /**

View File

@ -25,6 +25,8 @@
#include "crypto/hash.h" #include "crypto/hash.h"
#include "trace.h" #include "trace.h"
#include <time.h>
/* Max amount to allow in rawinput/rawoutput buffers */ /* Max amount to allow in rawinput/rawoutput buffers */
#define QIO_CHANNEL_WEBSOCK_MAX_BUFFER 8192 #define QIO_CHANNEL_WEBSOCK_MAX_BUFFER 8192
@ -44,13 +46,40 @@
#define QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE "Upgrade" #define QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE "Upgrade"
#define QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET "websocket" #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" \ "HTTP/1.1 101 Switching Protocols\r\n" \
QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
"Upgrade: websocket\r\n" \ "Upgrade: websocket\r\n" \
"Connection: Upgrade\r\n" \ "Connection: Upgrade\r\n" \
"Sec-WebSocket-Accept: %s\r\n" \ "Sec-WebSocket-Accept: %s\r\n" \
"Sec-WebSocket-Protocol: binary\r\n" \ "Sec-WebSocket-Protocol: binary\r\n" \
"\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_DELIM "\r\n"
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_END "\r\n\r\n" #define QIO_CHANNEL_WEBSOCK_HANDSHAKE_END "\r\n\r\n"
#define QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION "13" #define QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION "13"
@ -81,13 +110,12 @@
/* Magic 7-bit length to indicate use of 64-bit payload length */ /* Magic 7-bit length to indicate use of 64-bit payload length */
#define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT 127 #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_FIN 0x80
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE 0x0f #define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE 0x0f
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK 0x80 #define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK 0x80
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_PAYLOAD_LEN 0x7f #define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_PAYLOAD_LEN 0x7f
#define QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_FIN 7 #define QIO_CHANNEL_WEBSOCK_CONTROL_OPCODE_MASK 0x8
#define QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_HAS_MASK 7
typedef struct QIOChannelWebsockHeader QIOChannelWebsockHeader; typedef struct QIOChannelWebsockHeader QIOChannelWebsockHeader;
@ -123,8 +151,55 @@ enum {
QIO_CHANNEL_WEBSOCK_OPCODE_PONG = 0xA 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 static size_t
qio_channel_websock_extract_headers(char *buffer, qio_channel_websock_extract_headers(QIOChannelWebsock *ioc,
char *buffer,
QIOChannelWebsockHTTPHeader *hdrs, QIOChannelWebsockHTTPHeader *hdrs,
size_t nhdrsalloc, size_t nhdrsalloc,
Error **errp) Error **errp)
@ -145,7 +220,7 @@ qio_channel_websock_extract_headers(char *buffer,
nl = strstr(buffer, QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM); nl = strstr(buffer, QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
if (!nl) { if (!nl) {
error_setg(errp, "Missing HTTP header delimiter"); error_setg(errp, "Missing HTTP header delimiter");
return 0; goto bad_request;
} }
*nl = '\0'; *nl = '\0';
@ -158,18 +233,20 @@ qio_channel_websock_extract_headers(char *buffer,
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_METHOD)) { if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_METHOD)) {
error_setg(errp, "Unsupported HTTP method %s", buffer); error_setg(errp, "Unsupported HTTP method %s", buffer);
return 0; goto bad_request;
} }
buffer = tmp + 1; buffer = tmp + 1;
tmp = strchr(buffer, ' '); tmp = strchr(buffer, ' ');
if (!tmp) { if (!tmp) {
error_setg(errp, "Missing HTTP version delimiter"); error_setg(errp, "Missing HTTP version delimiter");
return 0; goto bad_request;
} }
*tmp = '\0'; *tmp = '\0';
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_PATH)) { 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); error_setg(errp, "Unexpected HTTP path %s", buffer);
return 0; return 0;
} }
@ -178,7 +255,7 @@ qio_channel_websock_extract_headers(char *buffer,
if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_VERSION)) { if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_VERSION)) {
error_setg(errp, "Unsupported HTTP version %s", buffer); error_setg(errp, "Unsupported HTTP version %s", buffer);
return 0; goto bad_request;
} }
buffer = nl + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM); buffer = nl + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
@ -203,7 +280,7 @@ qio_channel_websock_extract_headers(char *buffer,
sep = strchr(buffer, ':'); sep = strchr(buffer, ':');
if (!sep) { if (!sep) {
error_setg(errp, "Malformed HTTP header"); error_setg(errp, "Malformed HTTP header");
return 0; goto bad_request;
} }
*sep = '\0'; *sep = '\0';
sep++; sep++;
@ -213,7 +290,7 @@ qio_channel_websock_extract_headers(char *buffer,
if (nhdrs >= nhdrsalloc) { if (nhdrs >= nhdrsalloc) {
error_setg(errp, "Too many HTTP headers"); error_setg(errp, "Too many HTTP headers");
return 0; goto bad_request;
} }
hdr = &hdrs[nhdrs++]; hdr = &hdrs[nhdrs++];
@ -231,6 +308,11 @@ qio_channel_websock_extract_headers(char *buffer,
} while (nl != NULL); } while (nl != NULL);
return nhdrs; return nhdrs;
bad_request:
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST);
return 0;
} }
static const char * static const char *
@ -250,14 +332,14 @@ qio_channel_websock_find_header(QIOChannelWebsockHTTPHeader *hdrs,
} }
static int qio_channel_websock_handshake_send_response(QIOChannelWebsock *ioc, static void qio_channel_websock_handshake_send_res_ok(QIOChannelWebsock *ioc,
const char *key, const char *key,
Error **errp) Error **errp)
{ {
char combined_key[QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + char combined_key[QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
QIO_CHANNEL_WEBSOCK_GUID_LEN + 1]; QIO_CHANNEL_WEBSOCK_GUID_LEN + 1];
char *accept = NULL, *response = NULL; char *accept = NULL;
size_t responselen; char *date = qio_channel_websock_date_str();
g_strlcpy(combined_key, key, QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + 1); g_strlcpy(combined_key, key, QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + 1);
g_strlcat(combined_key, QIO_CHANNEL_WEBSOCK_GUID, 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, QIO_CHANNEL_WEBSOCK_GUID_LEN,
&accept, &accept,
errp) < 0) { 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); qio_channel_websock_handshake_send_res(
responselen = strlen(response); ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_OK, date, accept);
buffer_reserve(&ioc->encoutput, responselen);
buffer_append(&ioc->encoutput, response, responselen);
g_free(date);
g_free(accept); g_free(accept);
g_free(response);
return 0;
} }
static int qio_channel_websock_handshake_process(QIOChannelWebsock *ioc, static void qio_channel_websock_handshake_process(QIOChannelWebsock *ioc,
char *buffer, char *buffer,
Error **errp) Error **errp)
{ {
QIOChannelWebsockHTTPHeader hdrs[32]; QIOChannelWebsockHTTPHeader hdrs[32];
size_t nhdrs = G_N_ELEMENTS(hdrs); size_t nhdrs = G_N_ELEMENTS(hdrs);
const char *protocols = NULL, *version = NULL, *key = NULL, const char *protocols = NULL, *version = NULL, *key = NULL,
*host = NULL, *connection = NULL, *upgrade = 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) { if (!nhdrs) {
return -1; return;
} }
protocols = qio_channel_websock_find_header( protocols = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_PROTOCOL); hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_PROTOCOL);
if (!protocols) { if (!protocols) {
error_setg(errp, "Missing websocket protocol header data"); error_setg(errp, "Missing websocket protocol header data");
return -1; goto bad_request;
} }
version = qio_channel_websock_find_header( version = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_VERSION); hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_VERSION);
if (!version) { if (!version) {
error_setg(errp, "Missing websocket version header data"); error_setg(errp, "Missing websocket version header data");
return -1; goto bad_request;
} }
key = qio_channel_websock_find_header( key = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_KEY); hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_KEY);
if (!key) { if (!key) {
error_setg(errp, "Missing websocket key header data"); error_setg(errp, "Missing websocket key header data");
return -1; goto bad_request;
} }
host = qio_channel_websock_find_header( host = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_HOST); hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_HOST);
if (!host) { if (!host) {
error_setg(errp, "Missing websocket host header data"); error_setg(errp, "Missing websocket host header data");
return -1; goto bad_request;
} }
connection = qio_channel_websock_find_header( connection = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_CONNECTION); hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_CONNECTION);
if (!connection) { if (!connection) {
error_setg(errp, "Missing websocket connection header data"); error_setg(errp, "Missing websocket connection header data");
return -1; goto bad_request;
} }
upgrade = qio_channel_websock_find_header( upgrade = qio_channel_websock_find_header(
hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_UPGRADE); hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_UPGRADE);
if (!upgrade) { if (!upgrade) {
error_setg(errp, "Missing websocket upgrade header data"); error_setg(errp, "Missing websocket upgrade header data");
return -1; goto bad_request;
} }
if (!g_strrstr(protocols, QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY)) { if (!g_strrstr(protocols, QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY)) {
error_setg(errp, "No '%s' protocol is supported by client '%s'", error_setg(errp, "No '%s' protocol is supported by client '%s'",
QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY, protocols); QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY, protocols);
return -1; goto bad_request;
} }
if (!g_str_equal(version, QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION)) { if (!g_str_equal(version, QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION)) {
error_setg(errp, "Version '%s' is not supported by client '%s'", error_setg(errp, "Version '%s' is not supported by client '%s'",
QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION, version); QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION, version);
return -1; goto bad_request;
} }
if (strlen(key) != QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN) { if (strlen(key) != QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN) {
error_setg(errp, "Key length '%zu' was not as expected '%d'", error_setg(errp, "Key length '%zu' was not as expected '%d'",
strlen(key), QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN); 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); 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); 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, 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); QIO_CHANNEL_WEBSOCK_HANDSHAKE_END);
if (!handshake_end) { if (!handshake_end) {
if (ioc->encinput.offset >= 4096) { if (ioc->encinput.offset >= 4096) {
qio_channel_websock_handshake_send_res_err(
ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_TOO_LARGE);
error_setg(errp, error_setg(errp,
"End of headers not found in first 4096 bytes"); "End of headers not found in first 4096 bytes");
return -1; return 1;
} else { } else {
return 0; return 0;
} }
} }
*handshake_end = '\0'; *handshake_end = '\0';
if (qio_channel_websock_handshake_process(ioc, qio_channel_websock_handshake_process(ioc,
(char *)ioc->encinput.buffer, (char *)ioc->encinput.buffer,
errp) < 0) { errp);
return -1;
}
buffer_advance(&ioc->encinput, buffer_advance(&ioc->encinput,
handshake_end - (char *)ioc->encinput.buffer + handshake_end - (char *)ioc->encinput.buffer +
@ -430,7 +515,7 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
&err); &err);
if (ret < 0) { 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_set_error(task, err);
qio_task_complete(task); qio_task_complete(task);
return FALSE; return FALSE;
@ -438,8 +523,16 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
buffer_advance(&wioc->encoutput, ret); buffer_advance(&wioc->encoutput, ret);
if (wioc->encoutput.offset == 0) { if (wioc->encoutput.offset == 0) {
trace_qio_channel_websock_handshake_complete(ioc); if (wioc->io_err) {
qio_task_complete(task); 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; return FALSE;
} }
trace_qio_channel_websock_handshake_pending(ioc, G_IO_OUT); 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); ret = qio_channel_websock_handshake_read(wioc, &err);
if (ret < 0) { 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_set_error(task, err);
qio_task_complete(task); qio_task_complete(task);
return FALSE; return FALSE;
@ -469,6 +567,10 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
return TRUE; return TRUE;
} }
if (err) {
error_propagate(&wioc->io_err, err);
}
trace_qio_channel_websock_handshake_reply(ioc); trace_qio_channel_websock_handshake_reply(ioc);
qio_channel_add_watch( qio_channel_add_watch(
wioc->master, 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; size_t header_size;
union { union {
@ -488,39 +592,66 @@ static void qio_channel_websock_encode(QIOChannelWebsock *ioc)
QIOChannelWebsockHeader ws; QIOChannelWebsockHeader ws;
} header; } header;
if (!ioc->rawoutput.offset) { header.ws.b0 = QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN |
return; (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.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_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT; 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) { QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_16_BIT) {
header.ws.b1 = QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_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; header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT;
} else { } else {
header.ws.b1 = QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT; 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_64_BIT;
} }
header_size -= QIO_CHANNEL_WEBSOCK_HEADER_LEN_MASK; 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, buffer->offset);
buffer_append(&ioc->encoutput, header.buf, header_size); buffer_reserve(output, header_size + buffer->offset);
buffer_append(&ioc->encoutput, ioc->rawoutput.buffer, buffer_append(output, header.buf, header_size);
ioc->rawoutput.offset); 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); buffer_reset(&ioc->rawoutput);
} }
static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc, static ssize_t qio_channel_websock_write_wire(QIOChannelWebsock *, Error **);
Error **errp)
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; unsigned char opcode, fin, has_mask;
size_t header_size; size_t header_size;
@ -532,6 +663,9 @@ static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
error_setg(errp, error_setg(errp,
"Decoding header but %zu bytes of payload remain", "Decoding header but %zu bytes of payload remain",
ioc->payload_remain); ioc->payload_remain);
qio_channel_websock_write_close(
ioc, QIO_CHANNEL_WEBSOCK_STATUS_SERVER_ERR,
"internal server error");
return -1; return -1;
} }
if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT) { 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; return QIO_CHANNEL_ERR_BLOCK;
} }
fin = (header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN) >> fin = header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN;
QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_FIN;
opcode = header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE; opcode = header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE;
has_mask = (header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK) >> has_mask = header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK;
QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_HAS_MASK;
payload_len = header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_PAYLOAD_LEN; 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) { if (opcode == QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE) {
/* disconnect */ /* disconnect */
return 0; return 0;
} }
/* Websocket frame sanity check: /* Websocket frame sanity check:
* * Websocket fragmentation is not supported. * * Fragmentation is only supported for binary frames.
* * All websockets frames sent by a client have to be masked. * * All frames sent by a client MUST be masked.
* * Only binary encoding is supported. * * Only binary and ping/pong encoding is supported.
*/ */
if (!fin) { if (!fin) {
error_setg(errp, "websocket fragmentation is not supported"); if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
return -1; 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) { if (!has_mask) {
error_setg(errp, "websocket frames must be masked"); error_setg(errp, "client websocket frames must be masked");
return -1; qio_channel_websock_write_close(
} ioc, QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR,
if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) { "client frames must be masked");
error_setg(errp, "only binary websocket frames are supported");
return -1; return -1;
} }
@ -573,6 +731,12 @@ static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
ioc->payload_remain = payload_len; ioc->payload_remain = payload_len;
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT; header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT;
ioc->mask = header->u.m; 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 && } else if (payload_len == QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT &&
ioc->encinput.offset >= QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT) { ioc->encinput.offset >= QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT) {
ioc->payload_remain = be16_to_cpu(header->u.s16.l16); 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; 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); buffer_advance(&ioc->encinput, header_size);
return 1; return 0;
} }
static ssize_t qio_channel_websock_decode_payload(QIOChannelWebsock *ioc, static int qio_channel_websock_decode_payload(QIOChannelWebsock *ioc,
Error **errp) Error **errp)
{ {
size_t i; size_t i;
size_t payload_len; size_t payload_len = 0;
uint32_t *payload32; uint32_t *payload32;
if (!ioc->payload_remain) { if (ioc->payload_remain) {
error_setg(errp, /* If we aren't at the end of the payload, then drop
"Decoding payload but no bytes of payload remain"); * 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; 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 if (payload_len) {
* off the last bytes, so we're always multiple of 4 buffer_advance(&ioc->encinput, payload_len);
* 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 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;
} }
@ -688,6 +888,7 @@ static void qio_channel_websock_finalize(Object *obj)
buffer_free(&ioc->encoutput); buffer_free(&ioc->encoutput);
buffer_free(&ioc->rawinput); buffer_free(&ioc->rawinput);
buffer_free(&ioc->rawoutput); buffer_free(&ioc->rawoutput);
buffer_free(&ioc->ping_reply);
object_unref(OBJECT(ioc->master)); object_unref(OBJECT(ioc->master));
if (ioc->io_tag) { if (ioc->io_tag) {
g_source_remove(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) { if (ret < 0) {
return ret; return ret;
} }
if (ret == 0 && if (ret == 0 && ioc->encinput.offset == 0) {
ioc->encinput.offset == 0) { ioc->io_eof = TRUE;
return 0; return 0;
} }
ioc->encinput.offset += ret; ioc->encinput.offset += ret;
@ -728,10 +929,6 @@ static ssize_t qio_channel_websock_read_wire(QIOChannelWebsock *ioc,
if (ret < 0) { if (ret < 0) {
return ret; return ret;
} }
if (ret == 0) {
ioc->io_eof = TRUE;
break;
}
} }
ret = qio_channel_websock_decode_payload(ioc, errp); 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 ret;
ssize_t done = 0; 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) { while (ioc->encoutput.offset > 0) {
ret = qio_channel_write(ioc->master, ret = qio_channel_write(ioc->master,
@ -823,7 +1026,7 @@ static void qio_channel_websock_set_watch(QIOChannelWebsock *ioc)
return; return;
} }
if (ioc->encoutput.offset) { if (ioc->encoutput.offset || ioc->ping_reply.offset) {
cond |= G_IO_OUT; cond |= G_IO_OUT;
} }
if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER && 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); QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
trace_qio_channel_websock_close(ioc);
return qio_channel_close(wioc->master, errp); return qio_channel_close(wioc->master, errp);
} }
@ -996,14 +1200,12 @@ struct QIOChannelWebsockSource {
}; };
static gboolean static gboolean
qio_channel_websock_source_prepare(GSource *source, qio_channel_websock_source_check(GSource *source)
gint *timeout)
{ {
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source; QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
GIOCondition cond = 0; GIOCondition cond = 0;
*timeout = -1;
if (wsource->wioc->rawinput.offset) { if (wsource->wioc->rawinput.offset || wsource->wioc->io_eof) {
cond |= G_IO_IN; cond |= G_IO_IN;
} }
if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) { if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
@ -1014,19 +1216,11 @@ qio_channel_websock_source_prepare(GSource *source,
} }
static gboolean static gboolean
qio_channel_websock_source_check(GSource *source) qio_channel_websock_source_prepare(GSource *source,
gint *timeout)
{ {
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source; *timeout = -1;
GIOCondition cond = 0; return qio_channel_websock_source_check(source);
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;
} }
static gboolean static gboolean
@ -1036,17 +1230,9 @@ qio_channel_websock_source_dispatch(GSource *source,
{ {
QIOChannelFunc func = (QIOChannelFunc)callback; QIOChannelFunc func = (QIOChannelFunc)callback;
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source; 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), return (*func)(QIO_CHANNEL(wsource->wioc),
(cond & wsource->condition), qio_channel_websock_source_check(source),
user_data); 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_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_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_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_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 # 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" 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); vnc_client_error(vs);
error_free(err); error_free(err);
} else { } else {
if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch( vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL); vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL);
start_auth_vencrypt_subauth(vs); start_auth_vencrypt_subauth(vs);

View File

@ -37,6 +37,9 @@ static void vncws_tls_handshake_done(QIOTask *task,
error_free(err); error_free(err);
} else { } else {
VNC_DEBUG("TLS handshake complete, starting websocket handshake\n"); 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( vs->ioc_tag = qio_channel_add_watch(
QIO_CHANNEL(vs->ioc), G_IO_IN, vncws_handshake_io, vs, NULL); QIO_CHANNEL(vs->ioc), G_IO_IN, vncws_handshake_io, vs, NULL);
} }
@ -97,6 +100,9 @@ static void vncws_handshake_done(QIOTask *task,
} else { } else {
VNC_DEBUG("Websock handshake complete, starting VNC protocol\n"); VNC_DEBUG("Websock handshake complete, starting VNC protocol\n");
vnc_start_protocol(vs); vnc_start_protocol(vs);
if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch( vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vnc_client_io, vs, NULL); 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); vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED);
if (vs->ioc_tag) { if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag); g_source_remove(vs->ioc_tag);
vs->ioc_tag = 0;
} }
qio_channel_close(vs->ioc, NULL); qio_channel_close(vs->ioc, NULL);
vs->disconnecting = TRUE; 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); VNC_DEBUG("New client on socket %p\n", vs->sioc);
update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE); update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
qio_channel_set_blocking(vs->ioc, false, NULL); qio_channel_set_blocking(vs->ioc, false, NULL);
if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
if (websocket) { if (websocket) {
vs->websocket = 1; vs->websocket = 1;
if (vd->tlscreds) { if (vd->tlscreds) {