/* * QEMU I/O channels driver websockets * * Copyright (c) 2015 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 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 . * */ #include "qemu/osdep.h" #include "qapi/error.h" #include "qemu/bswap.h" #include "io/channel-websock.h" #include "crypto/hash.h" #include "trace.h" #include /* 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_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" #define QIO_CHANNEL_WEBSOCK_GUID_LEN strlen(QIO_CHANNEL_WEBSOCK_GUID) #define QIO_CHANNEL_WEBSOCK_HEADER_PROTOCOL "sec-websocket-protocol" #define QIO_CHANNEL_WEBSOCK_HEADER_VERSION "sec-websocket-version" #define QIO_CHANNEL_WEBSOCK_HEADER_KEY "sec-websocket-key" #define QIO_CHANNEL_WEBSOCK_HEADER_UPGRADE "upgrade" #define QIO_CHANNEL_WEBSOCK_HEADER_HOST "host" #define QIO_CHANNEL_WEBSOCK_HEADER_CONNECTION "connection" #define QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY "binary" #define QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE "Upgrade" #define QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET "websocket" #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" #define QIO_CHANNEL_WEBSOCK_HTTP_METHOD "GET" #define QIO_CHANNEL_WEBSOCK_HTTP_PATH "/" #define QIO_CHANNEL_WEBSOCK_HTTP_VERSION "HTTP/1.1" /* The websockets packet header is variable length * depending on the size of the payload... */ /* ...length when using 7-bit payload length */ #define QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT 6 /* ...length when using 16-bit payload length */ #define QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT 8 /* ...length when using 64-bit payload length */ #define QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT 14 /* Length of the optional data mask field in header */ #define QIO_CHANNEL_WEBSOCK_HEADER_LEN_MASK 4 /* Maximum length that can fit in 7-bit payload size */ #define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_7_BIT 126 /* Maximum length that can fit in 16-bit payload size */ #define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_16_BIT 65536 /* Magic 7-bit length to indicate use of 16-bit payload length */ #define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT 126 /* Magic 7-bit length to indicate use of 64-bit payload length */ #define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT 127 /* 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_CONTROL_OPCODE_MASK 0x8 typedef struct QIOChannelWebsockHeader QIOChannelWebsockHeader; struct QEMU_PACKED QIOChannelWebsockHeader { unsigned char b0; unsigned char b1; union { struct QEMU_PACKED { uint16_t l16; QIOChannelWebsockMask m16; } s16; struct QEMU_PACKED { uint64_t l64; QIOChannelWebsockMask m64; } s64; QIOChannelWebsockMask m; } u; }; typedef struct QIOChannelWebsockHTTPHeader QIOChannelWebsockHTTPHeader; struct QIOChannelWebsockHTTPHeader { char *name; char *value; }; enum { QIO_CHANNEL_WEBSOCK_OPCODE_CONTINUATION = 0x0, QIO_CHANNEL_WEBSOCK_OPCODE_TEXT_FRAME = 0x1, QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME = 0x2, QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE = 0x8, QIO_CHANNEL_WEBSOCK_OPCODE_PING = 0x9, 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(QIOChannelWebsock *ioc, char *buffer, QIOChannelWebsockHTTPHeader *hdrs, size_t nhdrsalloc, Error **errp) { char *nl, *sep, *tmp; size_t nhdrs = 0; /* * First parse the HTTP protocol greeting of format: * * $METHOD $PATH $VERSION * * e.g. * * GET / HTTP/1.1 */ nl = strstr(buffer, QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM); if (!nl) { error_setg(errp, "Missing HTTP header delimiter"); goto bad_request; } *nl = '\0'; tmp = strchr(buffer, ' '); if (!tmp) { error_setg(errp, "Missing HTTP path delimiter"); return 0; } *tmp = '\0'; if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_METHOD)) { error_setg(errp, "Unsupported HTTP method %s", buffer); goto bad_request; } buffer = tmp + 1; tmp = strchr(buffer, ' '); if (!tmp) { error_setg(errp, "Missing HTTP version delimiter"); 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; } buffer = tmp + 1; if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_VERSION)) { error_setg(errp, "Unsupported HTTP version %s", buffer); goto bad_request; } buffer = nl + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM); /* * Now parse all the header fields of format * * $NAME: $VALUE * * e.g. * * Cache-control: no-cache */ do { QIOChannelWebsockHTTPHeader *hdr; nl = strstr(buffer, QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM); if (nl) { *nl = '\0'; } sep = strchr(buffer, ':'); if (!sep) { error_setg(errp, "Malformed HTTP header"); goto bad_request; } *sep = '\0'; sep++; while (*sep == ' ') { sep++; } if (nhdrs >= nhdrsalloc) { error_setg(errp, "Too many HTTP headers"); goto bad_request; } hdr = &hdrs[nhdrs++]; hdr->name = buffer; hdr->value = sep; /* Canonicalize header name for easier identification later */ for (tmp = hdr->name; *tmp; tmp++) { *tmp = g_ascii_tolower(*tmp); } if (nl) { buffer = nl + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM); } } 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 * qio_channel_websock_find_header(QIOChannelWebsockHTTPHeader *hdrs, size_t nhdrs, const char *name) { size_t i; for (i = 0; i < nhdrs; i++) { if (g_str_equal(hdrs[i].name, name)) { return hdrs[i].value; } } return NULL; } 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; 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, QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + QIO_CHANNEL_WEBSOCK_GUID_LEN + 1); /* hash and encode it */ if (qcrypto_hash_base64(QCRYPTO_HASH_ALG_SHA1, combined_key, QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + QIO_CHANNEL_WEBSOCK_GUID_LEN, &accept, errp) < 0) { qio_channel_websock_handshake_send_res_err( ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_SERVER_ERR); return; } qio_channel_websock_handshake_send_res( ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_OK, date, accept); g_free(date); g_free(accept); } 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(ioc, buffer, hdrs, nhdrs, errp); if (!nhdrs) { return; } protocols = qio_channel_websock_find_header( hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_PROTOCOL); if (!protocols) { error_setg(errp, "Missing websocket protocol header data"); 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"); 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"); 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"); 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"); 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"); 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); 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); 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); goto bad_request; } if (strcasecmp(connection, QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE) != 0) { error_setg(errp, "No connection upgrade requested '%s'", connection); goto bad_request; } if (strcasecmp(upgrade, QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET) != 0) { error_setg(errp, "Incorrect upgrade method '%s'", upgrade); goto bad_request; } 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, Error **errp) { char *handshake_end; ssize_t ret; /* Typical HTTP headers from novnc are 512 bytes, so limiting * total header size to 4096 is easily enough. */ size_t want = 4096 - ioc->encinput.offset; buffer_reserve(&ioc->encinput, want); ret = qio_channel_read(ioc->master, (char *)buffer_end(&ioc->encinput), want, errp); if (ret < 0) { return -1; } ioc->encinput.offset += ret; handshake_end = g_strstr_len((char *)ioc->encinput.buffer, ioc->encinput.offset, 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; } else { return 0; } } *handshake_end = '\0'; qio_channel_websock_handshake_process(ioc, (char *)ioc->encinput.buffer, errp); buffer_advance(&ioc->encinput, handshake_end - (char *)ioc->encinput.buffer + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_END)); return 1; } static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc, GIOCondition condition, gpointer user_data) { QIOTask *task = user_data; QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK( qio_task_get_source(task)); Error *err = NULL; ssize_t ret; ret = qio_channel_write(wioc->master, (char *)wioc->encoutput.buffer, wioc->encoutput.offset, &err); if (ret < 0) { trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err)); qio_task_set_error(task, err); qio_task_complete(task); return FALSE; } buffer_advance(&wioc->encoutput, ret); if (wioc->encoutput.offset == 0) { 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); return TRUE; } static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc, GIOCondition condition, gpointer user_data) { QIOTask *task = user_data; QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK( qio_task_get_source(task)); Error *err = NULL; int ret; ret = qio_channel_websock_handshake_read(wioc, &err); if (ret < 0) { /* * 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; } if (ret == 0) { trace_qio_channel_websock_handshake_pending(ioc, G_IO_IN); /* need more data still */ return TRUE; } if (err) { error_propagate(&wioc->io_err, err); } trace_qio_channel_websock_handshake_reply(ioc); qio_channel_add_watch( wioc->master, G_IO_OUT, qio_channel_websock_handshake_send, task, NULL); return FALSE; } static void qio_channel_websock_encode_buffer(QIOChannelWebsock *ioc, Buffer *output, uint8_t opcode, Buffer *buffer) { size_t header_size; union { char buf[QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT]; QIOChannelWebsockHeader ws; } header; 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 (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)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(buffer->offset); header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT; } header_size -= QIO_CHANNEL_WEBSOCK_HEADER_LEN_MASK; 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 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; size_t payload_len; QIOChannelWebsockHeader *header = (QIOChannelWebsockHeader *)ioc->encinput.buffer; if (ioc->payload_remain) { 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) { /* header not complete */ return QIO_CHANNEL_ERR_BLOCK; } 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; 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: * * 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) { 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"); qio_channel_websock_write_close( ioc, QIO_CHANNEL_WEBSOCK_STATUS_PROTOCOL_ERR, "client frames must be masked"); return -1; } if (payload_len < QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT) { 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); header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT; ioc->mask = header->u.s16.m16; } else if (payload_len == QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT && ioc->encinput.offset >= QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT) { ioc->payload_remain = be64_to_cpu(header->u.s64.l64); header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT; ioc->mask = header->u.s64.m64; } else { /* header not complete */ 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 0; } static int qio_channel_websock_decode_payload(QIOChannelWebsock *ioc, Error **errp) { size_t i; size_t payload_len = 0; uint32_t *payload32; 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, 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) { qio_channel_websock_encode_buffer( ioc, &ioc->encoutput, QIO_CHANNEL_WEBSOCK_OPCODE_PONG, &ioc->encinput); ioc->pong_remain = ioc->encoutput.offset; } } /* pong frames are ignored */ if (payload_len) { buffer_advance(&ioc->encinput, payload_len); } return 0; } QIOChannelWebsock * qio_channel_websock_new_server(QIOChannel *master) { QIOChannelWebsock *wioc; QIOChannel *ioc; wioc = QIO_CHANNEL_WEBSOCK(object_new(TYPE_QIO_CHANNEL_WEBSOCK)); ioc = QIO_CHANNEL(wioc); wioc->master = master; if (qio_channel_has_feature(master, QIO_CHANNEL_FEATURE_SHUTDOWN)) { qio_channel_set_feature(ioc, QIO_CHANNEL_FEATURE_SHUTDOWN); } object_ref(OBJECT(master)); trace_qio_channel_websock_new_server(wioc, master); return wioc; } void qio_channel_websock_handshake(QIOChannelWebsock *ioc, QIOTaskFunc func, gpointer opaque, GDestroyNotify destroy) { QIOTask *task; task = qio_task_new(OBJECT(ioc), func, opaque, destroy); trace_qio_channel_websock_handshake_start(ioc); trace_qio_channel_websock_handshake_pending(ioc, G_IO_IN); qio_channel_add_watch(ioc->master, G_IO_IN, qio_channel_websock_handshake_io, task, NULL); } static void qio_channel_websock_finalize(Object *obj) { QIOChannelWebsock *ioc = QIO_CHANNEL_WEBSOCK(obj); 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); } if (ioc->io_err) { error_free(ioc->io_err); } } static ssize_t qio_channel_websock_read_wire(QIOChannelWebsock *ioc, Error **errp) { ssize_t ret; if (ioc->encinput.offset < 4096) { size_t want = 4096 - ioc->encinput.offset; buffer_reserve(&ioc->encinput, want); ret = qio_channel_read(ioc->master, (char *)ioc->encinput.buffer + ioc->encinput.offset, want, errp); if (ret < 0) { return ret; } if (ret == 0 && ioc->encinput.offset == 0) { ioc->io_eof = TRUE; return 0; } ioc->encinput.offset += ret; } while (ioc->encinput.offset != 0) { if (ioc->payload_remain == 0) { ret = qio_channel_websock_decode_header(ioc, errp); if (ret < 0) { return ret; } } ret = qio_channel_websock_decode_payload(ioc, errp); if (ret < 0) { return ret; } } return 1; } static ssize_t qio_channel_websock_write_wire(QIOChannelWebsock *ioc, Error **errp) { ssize_t ret; ssize_t done = 0; while (ioc->encoutput.offset > 0) { ret = qio_channel_write(ioc->master, (char *)ioc->encoutput.buffer, ioc->encoutput.offset, errp); if (ret < 0) { if (ret == QIO_CHANNEL_ERR_BLOCK && done > 0) { return done; } else { return ret; } } buffer_advance(&ioc->encoutput, ret); done += ret; if (ioc->pong_remain < ret) { ioc->pong_remain = 0; } else { ioc->pong_remain -= ret; } } return done; } static void qio_channel_websock_flush_free(gpointer user_data) { QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(user_data); object_unref(OBJECT(wioc)); } static void qio_channel_websock_set_watch(QIOChannelWebsock *ioc); static gboolean qio_channel_websock_flush(QIOChannel *ioc, GIOCondition condition, gpointer user_data) { QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(user_data); ssize_t ret; if (condition & G_IO_OUT) { ret = qio_channel_websock_write_wire(wioc, &wioc->io_err); if (ret < 0) { goto cleanup; } } if (condition & G_IO_IN) { ret = qio_channel_websock_read_wire(wioc, &wioc->io_err); if (ret < 0) { goto cleanup; } } cleanup: qio_channel_websock_set_watch(wioc); return FALSE; } static void qio_channel_websock_unset_watch(QIOChannelWebsock *ioc) { if (ioc->io_tag) { g_source_remove(ioc->io_tag); ioc->io_tag = 0; } } static void qio_channel_websock_set_watch(QIOChannelWebsock *ioc) { GIOCondition cond = 0; qio_channel_websock_unset_watch(ioc); if (ioc->io_err) { return; } if (ioc->encoutput.offset) { cond |= G_IO_OUT; } if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER && !ioc->io_eof) { cond |= G_IO_IN; } if (cond) { object_ref(OBJECT(ioc)); ioc->io_tag = qio_channel_add_watch(ioc->master, cond, qio_channel_websock_flush, ioc, qio_channel_websock_flush_free); } } static ssize_t qio_channel_websock_readv(QIOChannel *ioc, const struct iovec *iov, size_t niov, int **fds, size_t *nfds, Error **errp) { QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc); size_t i; ssize_t got = 0; ssize_t ret; if (wioc->io_err) { error_propagate(errp, error_copy(wioc->io_err)); return -1; } if (!wioc->rawinput.offset) { ret = qio_channel_websock_read_wire(QIO_CHANNEL_WEBSOCK(ioc), errp); if (ret < 0) { return ret; } } for (i = 0 ; i < niov ; i++) { size_t want = iov[i].iov_len; if (want > (wioc->rawinput.offset - got)) { want = (wioc->rawinput.offset - got); } memcpy(iov[i].iov_base, wioc->rawinput.buffer + got, want); got += want; if (want < iov[i].iov_len) { break; } } buffer_advance(&wioc->rawinput, got); qio_channel_websock_set_watch(wioc); return got; } static ssize_t qio_channel_websock_writev(QIOChannel *ioc, const struct iovec *iov, size_t niov, int *fds, size_t nfds, Error **errp) { QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc); size_t i; ssize_t done = 0; ssize_t ret; if (wioc->io_err) { error_propagate(errp, error_copy(wioc->io_err)); return -1; } if (wioc->io_eof) { error_setg(errp, "%s", "Broken pipe"); 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; } buffer_reserve(&wioc->rawoutput, want); buffer_append(&wioc->rawoutput, iov[i].iov_base, want); done += want; if (want < iov[i].iov_len) { break; } } done: if (ioc->rawoutput.offset) { qio_channel_websock_encode_buffer( ioc, &ioc->encoutput, QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME, &ioc->rawoutput); buffer_reset(&ioc->rawoutput); } ret = qio_channel_websock_write_wire(wioc, errp); if (ret < 0 && ret != QIO_CHANNEL_ERR_BLOCK) { qio_channel_websock_unset_watch(wioc); return -1; } qio_channel_websock_set_watch(wioc); if (done == 0) { return QIO_CHANNEL_ERR_BLOCK; } return done; } static int qio_channel_websock_set_blocking(QIOChannel *ioc, bool enabled, Error **errp) { QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc); qio_channel_set_blocking(wioc->master, enabled, errp); return 0; } static void qio_channel_websock_set_delay(QIOChannel *ioc, bool enabled) { QIOChannelWebsock *tioc = QIO_CHANNEL_WEBSOCK(ioc); qio_channel_set_delay(tioc->master, enabled); } static void qio_channel_websock_set_cork(QIOChannel *ioc, bool enabled) { QIOChannelWebsock *tioc = QIO_CHANNEL_WEBSOCK(ioc); qio_channel_set_cork(tioc->master, enabled); } static int qio_channel_websock_shutdown(QIOChannel *ioc, QIOChannelShutdown how, Error **errp) { QIOChannelWebsock *tioc = QIO_CHANNEL_WEBSOCK(ioc); return qio_channel_shutdown(tioc->master, how, errp); } static int qio_channel_websock_close(QIOChannel *ioc, Error **errp) { QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc); trace_qio_channel_websock_close(ioc); return qio_channel_close(wioc->master, errp); } typedef struct QIOChannelWebsockSource QIOChannelWebsockSource; struct QIOChannelWebsockSource { GSource parent; QIOChannelWebsock *wioc; GIOCondition condition; }; static gboolean qio_channel_websock_source_check(GSource *source) { QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source; GIOCondition cond = 0; if (wsource->wioc->rawinput.offset || wsource->wioc->io_eof) { cond |= G_IO_IN; } if (wsource->wioc->encoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) { cond |= G_IO_OUT; } return cond & wsource->condition; } static gboolean qio_channel_websock_source_prepare(GSource *source, gint *timeout) { *timeout = -1; return qio_channel_websock_source_check(source); } static gboolean qio_channel_websock_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { QIOChannelFunc func = (QIOChannelFunc)callback; QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source; return (*func)(QIO_CHANNEL(wsource->wioc), qio_channel_websock_source_check(source), user_data); } static void qio_channel_websock_source_finalize(GSource *source) { QIOChannelWebsockSource *ssource = (QIOChannelWebsockSource *)source; object_unref(OBJECT(ssource->wioc)); } GSourceFuncs qio_channel_websock_source_funcs = { qio_channel_websock_source_prepare, qio_channel_websock_source_check, qio_channel_websock_source_dispatch, qio_channel_websock_source_finalize }; static GSource *qio_channel_websock_create_watch(QIOChannel *ioc, GIOCondition condition) { QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc); QIOChannelWebsockSource *ssource; GSource *source; source = g_source_new(&qio_channel_websock_source_funcs, sizeof(QIOChannelWebsockSource)); ssource = (QIOChannelWebsockSource *)source; ssource->wioc = wioc; object_ref(OBJECT(wioc)); ssource->condition = condition; qio_channel_websock_set_watch(wioc); return source; } static void qio_channel_websock_class_init(ObjectClass *klass, void *class_data G_GNUC_UNUSED) { QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass); ioc_klass->io_writev = qio_channel_websock_writev; ioc_klass->io_readv = qio_channel_websock_readv; ioc_klass->io_set_blocking = qio_channel_websock_set_blocking; ioc_klass->io_set_cork = qio_channel_websock_set_cork; ioc_klass->io_set_delay = qio_channel_websock_set_delay; ioc_klass->io_close = qio_channel_websock_close; ioc_klass->io_shutdown = qio_channel_websock_shutdown; ioc_klass->io_create_watch = qio_channel_websock_create_watch; } static const TypeInfo qio_channel_websock_info = { .parent = TYPE_QIO_CHANNEL, .name = TYPE_QIO_CHANNEL_WEBSOCK, .instance_size = sizeof(QIOChannelWebsock), .instance_finalize = qio_channel_websock_finalize, .class_init = qio_channel_websock_class_init, }; static void qio_channel_websock_register_types(void) { type_register_static(&qio_channel_websock_info); } type_init(qio_channel_websock_register_types);