From 04d2529da27db512dcbd5e99d0e26d333f16efcc Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Fri, 27 Feb 2015 16:20:57 +0000 Subject: [PATCH 1/3] ui: convert VNC server to use QIOChannelSocket The minimal first step conversion to use QIOChannelSocket classes instead of directly using POSIX sockets API. This will later be extended to also cover the TLS, SASL and websockets code. Reviewed-by: Gerd Hoffmann Signed-off-by: Daniel P. Berrange --- ui/vnc-auth-sasl.c | 57 +++- ui/vnc-auth-vencrypt.c | 27 +- ui/vnc-jobs.c | 20 +- ui/vnc-ws.c | 51 +++- ui/vnc-ws.h | 8 +- ui/vnc.c | 578 +++++++++++++++++++++-------------------- ui/vnc.h | 22 +- 7 files changed, 435 insertions(+), 328 deletions(-) diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c index fc732bdbac..de8abc96d4 100644 --- a/ui/vnc-auth-sasl.c +++ b/ui/vnc-auth-sasl.c @@ -62,7 +62,7 @@ long vnc_client_write_sasl(VncState *vs) (const char **)&vs->sasl.encoded, &vs->sasl.encodedLength); if (err != SASL_OK) - return vnc_client_io_error(vs, -1, EIO); + return vnc_client_io_error(vs, -1, NULL); vs->sasl.encodedOffset = 0; } @@ -86,7 +86,11 @@ long vnc_client_write_sasl(VncState *vs) * SASL encoded output */ if (vs->output.offset == 0) { - qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, 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); } return ret; @@ -110,7 +114,7 @@ long vnc_client_read_sasl(VncState *vs) &decoded, &decodedLen); if (err != SASL_OK) - return vnc_client_io_error(vs, -1, -EIO); + return vnc_client_io_error(vs, -1, NULL); VNC_DEBUG("Read SASL Encoded %p size %ld Decoded %p size %d\n", encoded, ret, decoded, decodedLen); buffer_reserve(&vs->input, decodedLen); @@ -255,17 +259,17 @@ static int protocol_client_auth_sasl_step(VncState *vs, uint8_t *data, size_t le vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4); } else { if (!vnc_auth_sasl_check_ssf(vs)) { - VNC_DEBUG("Authentication rejected for weak SSF %d\n", vs->csock); + VNC_DEBUG("Authentication rejected for weak SSF %p\n", vs->ioc); goto authreject; } /* Check username whitelist ACL */ if (vnc_auth_sasl_check_access(vs) < 0) { - VNC_DEBUG("Authentication rejected for ACL %d\n", vs->csock); + VNC_DEBUG("Authentication rejected for ACL %p\n", vs->ioc); goto authreject; } - VNC_DEBUG("Authentication successful %d\n", vs->csock); + VNC_DEBUG("Authentication successful %p\n", vs->ioc); vnc_write_u32(vs, 0); /* Accept auth */ /* * Delay writing in SSF encoded mode until pending output @@ -383,17 +387,17 @@ static int protocol_client_auth_sasl_start(VncState *vs, uint8_t *data, size_t l vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4); } else { if (!vnc_auth_sasl_check_ssf(vs)) { - VNC_DEBUG("Authentication rejected for weak SSF %d\n", vs->csock); + VNC_DEBUG("Authentication rejected for weak SSF %p\n", vs->ioc); goto authreject; } /* Check username whitelist ACL */ if (vnc_auth_sasl_check_access(vs) < 0) { - VNC_DEBUG("Authentication rejected for ACL %d\n", vs->csock); + VNC_DEBUG("Authentication rejected for ACL %p\n", vs->ioc); goto authreject; } - VNC_DEBUG("Authentication successful %d\n", vs->csock); + VNC_DEBUG("Authentication successful %p\n", vs->ioc); vnc_write_u32(vs, 0); /* Accept auth */ start_client_init(vs); } @@ -487,6 +491,32 @@ static int protocol_client_auth_sasl_mechname_len(VncState *vs, uint8_t *data, s return 0; } +static char * +vnc_socket_ip_addr_string(QIOChannelSocket *ioc, + bool local, + Error **errp) +{ + SocketAddress *addr; + char *ret; + + if (local) { + addr = qio_channel_socket_get_local_address(ioc, errp); + } else { + addr = qio_channel_socket_get_remote_address(ioc, errp); + } + if (!addr) { + return NULL; + } + + if (addr->type != SOCKET_ADDRESS_KIND_INET) { + error_setg(errp, "Not an inet socket type"); + return NULL; + } + ret = g_strdup_printf("%s;%s", addr->u.inet->host, addr->u.inet->port); + qapi_free_SocketAddress(addr); + return ret; +} + void start_auth_sasl(VncState *vs) { const char *mechlist = NULL; @@ -495,13 +525,16 @@ void start_auth_sasl(VncState *vs) char *localAddr, *remoteAddr; int mechlistlen; - VNC_DEBUG("Initialize SASL auth %d\n", vs->csock); + VNC_DEBUG("Initialize SASL auth %p\n", vs->ioc); /* Get local & remote client addresses in form IPADDR;PORT */ - if (!(localAddr = vnc_socket_local_addr("%s;%s", vs->csock))) + localAddr = vnc_socket_ip_addr_string(vs->sioc, true, NULL); + if (!localAddr) { goto authabort; + } - if (!(remoteAddr = vnc_socket_remote_addr("%s;%s", vs->csock))) { + remoteAddr = vnc_socket_ip_addr_string(vs->sioc, false, NULL); + if (!remoteAddr) { g_free(localAddr); goto authabort; } diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c index 44ac2fae63..95a6823483 100644 --- a/ui/vnc-auth-vencrypt.c +++ b/ui/vnc-auth-vencrypt.c @@ -63,7 +63,9 @@ static void start_auth_vencrypt_subauth(VncState *vs) } } -static void vnc_tls_handshake_io(void *opaque); +static gboolean vnc_tls_handshake_io(QIOChannel *ioc, + GIOCondition condition, + void *opaque); static int vnc_start_vencrypt_handshake(VncState *vs) { @@ -80,19 +82,31 @@ static int vnc_start_vencrypt_handshake(VncState *vs) goto error; } VNC_DEBUG("Client verification passed, starting TLS I/O\n"); - qemu_set_fd_handler(vs->csock, vnc_client_read, vnc_client_write, vs); + 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); break; case QCRYPTO_TLS_HANDSHAKE_RECVING: VNC_DEBUG("Handshake interrupted (blocking read)\n"); - qemu_set_fd_handler(vs->csock, vnc_tls_handshake_io, NULL, vs); + if (vs->ioc_tag) { + g_source_remove(vs->ioc_tag); + } + vs->ioc_tag = qio_channel_add_watch( + vs->ioc, G_IO_IN, vnc_tls_handshake_io, vs, NULL); break; case QCRYPTO_TLS_HANDSHAKE_SENDING: VNC_DEBUG("Handshake interrupted (blocking write)\n"); - qemu_set_fd_handler(vs->csock, NULL, vnc_tls_handshake_io, vs); + if (vs->ioc_tag) { + g_source_remove(vs->ioc_tag); + } + vs->ioc_tag = qio_channel_add_watch( + vs->ioc, G_IO_OUT, vnc_tls_handshake_io, vs, NULL); break; } @@ -105,12 +119,15 @@ static int vnc_start_vencrypt_handshake(VncState *vs) return -1; } -static void vnc_tls_handshake_io(void *opaque) +static gboolean vnc_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED, + GIOCondition condition G_GNUC_UNUSED, + void *opaque) { VncState *vs = (VncState *)opaque; VNC_DEBUG("Handshake IO continue\n"); vnc_start_vencrypt_handshake(vs); + return TRUE; } diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c index aa21191ea2..546635aabe 100644 --- a/ui/vnc-jobs.c +++ b/ui/vnc-jobs.c @@ -166,13 +166,16 @@ void vnc_jobs_consume_buffer(VncState *vs) vnc_lock_output(vs); if (vs->jobs_buffer.offset) { - if (vs->csock != -1 && buffer_empty(&vs->output)) { - qemu_set_fd_handler(vs->csock, vnc_client_read, - vnc_client_write, vs); + if (vs->ioc != NULL && buffer_empty(&vs->output)) { + 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); } buffer_move(&vs->output, &vs->jobs_buffer); } - flush = vs->csock != -1 && vs->abort != true; + flush = vs->ioc != NULL && vs->abort != true; vnc_unlock_output(vs); if (flush) { @@ -186,7 +189,8 @@ void vnc_jobs_consume_buffer(VncState *vs) static void vnc_async_encoding_start(VncState *orig, VncState *local) { buffer_init(&local->output, "vnc-worker-output"); - local->csock = -1; /* Don't do any network work on this thread */ + local->sioc = NULL; /* Don't do any network work on this thread */ + local->ioc = NULL; /* Don't do any network work on this thread */ local->vnc_encoding = orig->vnc_encoding; local->features = orig->features; @@ -231,7 +235,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue) } vnc_lock_output(job->vs); - if (job->vs->csock == -1 || job->vs->abort == true) { + if (job->vs->ioc == NULL || job->vs->abort == true) { vnc_unlock_output(job->vs); goto disconnected; } @@ -259,7 +263,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue) QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) { int n; - if (job->vs->csock == -1) { + if (job->vs->ioc == NULL) { vnc_unlock_display(job->vs->vd); /* Copy persistent encoding data */ vnc_async_encoding_end(job->vs, &vs); @@ -281,7 +285,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue) vs.output.buffer[saved_offset + 1] = n_rectangles & 0xFF; vnc_lock_output(job->vs); - if (job->vs->csock != -1) { + if (job->vs->ioc != NULL) { buffer_move(&job->vs->jobs_buffer, &vs.output); /* Copy persistent encoding data */ vnc_async_encoding_end(job->vs, &vs); diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c index 175ea50b4b..15649dc582 100644 --- a/ui/vnc-ws.c +++ b/ui/vnc-ws.c @@ -37,17 +37,29 @@ static int vncws_start_tls_handshake(VncState *vs) goto error; } VNC_DEBUG("Client verification passed, starting TLS I/O\n"); - qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs); + if (vs->ioc_tag) { + g_source_remove(vs->ioc_tag); + } + vs->ioc_tag = qio_channel_add_watch( + vs->ioc, G_IO_IN, vncws_handshake_io, vs, NULL); break; case QCRYPTO_TLS_HANDSHAKE_RECVING: VNC_DEBUG("Handshake interrupted (blocking read)\n"); - qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io, NULL, vs); + if (vs->ioc_tag) { + g_source_remove(vs->ioc_tag); + } + vs->ioc_tag = qio_channel_add_watch( + vs->ioc, G_IO_IN, vncws_tls_handshake_io, vs, NULL); break; case QCRYPTO_TLS_HANDSHAKE_SENDING: VNC_DEBUG("Handshake interrupted (blocking write)\n"); - qemu_set_fd_handler(vs->csock, NULL, vncws_tls_handshake_io, vs); + if (vs->ioc_tag) { + g_source_remove(vs->ioc_tag); + } + vs->ioc_tag = qio_channel_add_watch( + vs->ioc, G_IO_OUT, vncws_tls_handshake_io, vs, NULL); break; } @@ -60,7 +72,9 @@ static int vncws_start_tls_handshake(VncState *vs) return -1; } -void vncws_tls_handshake_io(void *opaque) +gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED, + GIOCondition condition G_GNUC_UNUSED, + void *opaque) { VncState *vs = (VncState *)opaque; Error *err = NULL; @@ -75,7 +89,7 @@ void vncws_tls_handshake_io(void *opaque) error_get_pretty(err)); error_free(err); vnc_client_error(vs); - return; + return TRUE; } qcrypto_tls_session_set_callbacks(vs->tls, @@ -85,11 +99,11 @@ void vncws_tls_handshake_io(void *opaque) VNC_DEBUG("Start TLS WS handshake process\n"); vncws_start_tls_handshake(vs); + return TRUE; } -void vncws_handshake_read(void *opaque) +static void vncws_handshake_read(VncState *vs) { - VncState *vs = opaque; uint8_t *handshake_end; long ret; /* Typical HTTP headers from novnc are 512 bytes, so limiting @@ -99,7 +113,7 @@ void vncws_handshake_read(void *opaque) ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), want); if (!ret) { - if (vs->csock == -1) { + if (vs->disconnecting) { vnc_disconnect_finish(vs); } return; @@ -109,7 +123,11 @@ void vncws_handshake_read(void *opaque) handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer, vs->ws_input.offset, WS_HANDSHAKE_END); if (handshake_end) { - qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, 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); vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset); buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer + strlen(WS_HANDSHAKE_END)); @@ -120,6 +138,15 @@ void vncws_handshake_read(void *opaque) } +gboolean vncws_handshake_io(QIOChannel *ioc G_GNUC_UNUSED, + GIOCondition condition G_GNUC_UNUSED, + void *opaque) +{ + VncState *vs = opaque; + vncws_handshake_read(vs); + return TRUE; +} + long vnc_client_read_ws(VncState *vs) { int ret, err; @@ -187,7 +214,11 @@ long vnc_client_write_ws(VncState *vs) buffer_advance(&vs->ws_output, ret); if (vs->ws_output.offset == 0) { - qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, 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); } return ret; diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h index 4ab0a8c899..e8ffef7e40 100644 --- a/ui/vnc-ws.h +++ b/ui/vnc-ws.h @@ -72,8 +72,12 @@ enum { WS_OPCODE_PONG = 0xA }; -void vncws_tls_handshake_io(void *opaque); -void vncws_handshake_read(void *opaque); +gboolean vncws_tls_handshake_io(QIOChannel *ioc, + GIOCondition condition, + void *opaque); +gboolean vncws_handshake_io(QIOChannel *ioc, + GIOCondition condition, + void *opaque); long vnc_client_write_ws(VncState *vs); long vnc_client_read_ws(VncState *vs); void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size); diff --git a/ui/vnc.c b/ui/vnc.c index b9c57fff05..30053cfdbb 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -70,8 +70,8 @@ static void vnc_set_share_mode(VncState *vs, VncShareMode mode) [VNC_SHARE_MODE_EXCLUSIVE] = "exclusive", [VNC_SHARE_MODE_DISCONNECTED] = "disconnected", }; - fprintf(stderr, "%s/%d: %s -> %s\n", __func__, - vs->csock, mn[vs->share_mode], mn[mode]); + fprintf(stderr, "%s/%p: %s -> %s\n", __func__, + vs->ioc, mn[vs->share_mode], mn[mode]); #endif switch (vs->share_mode) { @@ -105,108 +105,65 @@ static void vnc_set_share_mode(VncState *vs, VncShareMode mode) } } -static char *addr_to_string(const char *format, - struct sockaddr_storage *sa, - socklen_t salen) { - char *addr; - char host[NI_MAXHOST]; - char serv[NI_MAXSERV]; - int err; - size_t addrlen; - if ((err = getnameinfo((struct sockaddr *)sa, salen, - host, sizeof(host), - serv, sizeof(serv), - NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { - VNC_DEBUG("Cannot resolve address %d: %s\n", - err, gai_strerror(err)); - return NULL; - } - - /* Enough for the existing format + the 2 vars we're - * substituting in. */ - addrlen = strlen(format) + strlen(host) + strlen(serv); - addr = g_malloc(addrlen + 1); - snprintf(addr, addrlen, format, host, serv); - addr[addrlen] = '\0'; - - return addr; -} - - -char *vnc_socket_local_addr(const char *format, int fd) { - struct sockaddr_storage sa; - socklen_t salen; - - salen = sizeof(sa); - if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0) - return NULL; - - return addr_to_string(format, &sa, salen); -} - -char *vnc_socket_remote_addr(const char *format, int fd) { - struct sockaddr_storage sa; - socklen_t salen; - - salen = sizeof(sa); - if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0) - return NULL; - - return addr_to_string(format, &sa, salen); -} - -static void vnc_init_basic_info(struct sockaddr_storage *sa, - socklen_t salen, +static void vnc_init_basic_info(SocketAddress *addr, VncBasicInfo *info, Error **errp) { - char host[NI_MAXHOST]; - char serv[NI_MAXSERV]; - int err; + switch (addr->type) { + case SOCKET_ADDRESS_KIND_INET: + info->host = g_strdup(addr->u.inet->host); + info->service = g_strdup(addr->u.inet->port); + if (addr->u.inet->ipv6) { + info->family = NETWORK_ADDRESS_FAMILY_IPV6; + } else { + info->family = NETWORK_ADDRESS_FAMILY_IPV4; + } + break; - if ((err = getnameinfo((struct sockaddr *)sa, salen, - host, sizeof(host), - serv, sizeof(serv), - NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { - error_setg(errp, "Cannot resolve address: %s", - gai_strerror(err)); - return; + case SOCKET_ADDRESS_KIND_UNIX: + info->host = g_strdup(""); + info->service = g_strdup(addr->u.q_unix->path); + info->family = NETWORK_ADDRESS_FAMILY_UNIX; + break; + + default: + error_setg(errp, "Unsupported socket kind %d", + addr->type); + break; } - info->host = g_strdup(host); - info->service = g_strdup(serv); - info->family = inet_netfamily(sa->ss_family); + return; } -static void vnc_init_basic_info_from_server_addr(int fd, VncBasicInfo *info, +static void vnc_init_basic_info_from_server_addr(QIOChannelSocket *ioc, + VncBasicInfo *info, Error **errp) { - struct sockaddr_storage sa; - socklen_t salen; + SocketAddress *addr = NULL; - salen = sizeof(sa); - if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0) { - error_setg_errno(errp, errno, "getsockname failed"); + addr = qio_channel_socket_get_local_address(ioc, errp); + if (!addr) { return; } - vnc_init_basic_info(&sa, salen, info, errp); + vnc_init_basic_info(addr, info, errp); + qapi_free_SocketAddress(addr); } -static void vnc_init_basic_info_from_remote_addr(int fd, VncBasicInfo *info, +static void vnc_init_basic_info_from_remote_addr(QIOChannelSocket *ioc, + VncBasicInfo *info, Error **errp) { - struct sockaddr_storage sa; - socklen_t salen; + SocketAddress *addr = NULL; - salen = sizeof(sa); - if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0) { - error_setg_errno(errp, errno, "getpeername failed"); + addr = qio_channel_socket_get_remote_address(ioc, errp); + if (!addr) { return; } - vnc_init_basic_info(&sa, salen, info, errp); + vnc_init_basic_info(addr, info, errp); + qapi_free_SocketAddress(addr); } static const char *vnc_auth_name(VncDisplay *vd) { @@ -300,7 +257,7 @@ static void vnc_client_cache_addr(VncState *client) Error *err = NULL; client->info = g_malloc0(sizeof(*client->info)); - vnc_init_basic_info_from_remote_addr(client->csock, + vnc_init_basic_info_from_remote_addr(client->sioc, qapi_VncClientInfo_base(client->info), &err); if (err) { @@ -343,27 +300,20 @@ static void vnc_qmp_event(VncState *vs, QAPIEvent event) static VncClientInfo *qmp_query_vnc_client(const VncState *client) { - struct sockaddr_storage sa; - socklen_t salen = sizeof(sa); - char host[NI_MAXHOST]; - char serv[NI_MAXSERV]; VncClientInfo *info; - - if (getpeername(client->csock, (struct sockaddr *)&sa, &salen) < 0) { - return NULL; - } - - if (getnameinfo((struct sockaddr *)&sa, salen, - host, sizeof(host), - serv, sizeof(serv), - NI_NUMERICHOST | NI_NUMERICSERV) < 0) { - return NULL; - } + Error *err = NULL; info = g_malloc0(sizeof(*info)); - info->host = g_strdup(host); - info->service = g_strdup(serv); - info->family = inet_netfamily(sa.ss_family); + + vnc_init_basic_info_from_remote_addr(client->sioc, + qapi_VncClientInfo_base(info), + &err); + if (err) { + error_free(err); + qapi_free_VncClientInfo(info); + return NULL; + } + info->websocket = client->websocket; if (client->tls) { @@ -413,81 +363,89 @@ VncInfo *qmp_query_vnc(Error **errp) { VncInfo *info = g_malloc0(sizeof(*info)); VncDisplay *vd = vnc_display_find(NULL); + SocketAddress *addr = NULL; if (vd == NULL || !vd->enabled) { info->enabled = false; } else { - struct sockaddr_storage sa; - socklen_t salen = sizeof(sa); - char host[NI_MAXHOST]; - char serv[NI_MAXSERV]; - info->enabled = true; /* for compatibility with the original command */ info->has_clients = true; info->clients = qmp_query_client_list(vd); - if (vd->lsock == -1) { + if (vd->lsock == NULL) { return info; } - if (getsockname(vd->lsock, (struct sockaddr *)&sa, - &salen) == -1) { - error_setg(errp, QERR_UNDEFINED_ERROR); + addr = qio_channel_socket_get_local_address(vd->lsock, errp); + if (!addr) { goto out_error; } - if (getnameinfo((struct sockaddr *)&sa, salen, - host, sizeof(host), - serv, sizeof(serv), - NI_NUMERICHOST | NI_NUMERICSERV) < 0) { - error_setg(errp, QERR_UNDEFINED_ERROR); + switch (addr->type) { + case SOCKET_ADDRESS_KIND_INET: + info->host = g_strdup(addr->u.inet->host); + info->service = g_strdup(addr->u.inet->port); + if (addr->u.inet->ipv6) { + info->family = NETWORK_ADDRESS_FAMILY_IPV6; + } else { + info->family = NETWORK_ADDRESS_FAMILY_IPV4; + } + break; + + case SOCKET_ADDRESS_KIND_UNIX: + info->host = g_strdup(""); + info->service = g_strdup(addr->u.q_unix->path); + info->family = NETWORK_ADDRESS_FAMILY_UNIX; + break; + + default: + error_setg(errp, "Unsupported socket kind %d", + addr->type); goto out_error; } info->has_host = true; - info->host = g_strdup(host); - info->has_service = true; - info->service = g_strdup(serv); - info->has_family = true; - info->family = inet_netfamily(sa.ss_family); info->has_auth = true; info->auth = g_strdup(vnc_auth_name(vd)); } + qapi_free_SocketAddress(addr); return info; out_error: + qapi_free_SocketAddress(addr); qapi_free_VncInfo(info); return NULL; } -static VncBasicInfoList *qmp_query_server_entry(int socket, +static VncBasicInfoList *qmp_query_server_entry(QIOChannelSocket *ioc, bool websocket, VncBasicInfoList *prev) { VncBasicInfoList *list; VncBasicInfo *info; - struct sockaddr_storage sa; - socklen_t salen = sizeof(sa); - char host[NI_MAXHOST]; - char serv[NI_MAXSERV]; + Error *err = NULL; + SocketAddress *addr; - if (getsockname(socket, (struct sockaddr *)&sa, &salen) < 0 || - getnameinfo((struct sockaddr *)&sa, salen, - host, sizeof(host), serv, sizeof(serv), - NI_NUMERICHOST | NI_NUMERICSERV) < 0) { + addr = qio_channel_socket_get_local_address(ioc, &err); + if (!addr) { + error_free(err); return prev; } info = g_new0(VncBasicInfo, 1); - info->host = g_strdup(host); - info->service = g_strdup(serv); - info->family = inet_netfamily(sa.ss_family); + vnc_init_basic_info(addr, info, &err); + qapi_free_SocketAddress(addr); + if (err) { + qapi_free_VncBasicInfo(info); + error_free(err); + return prev; + } info->websocket = websocket; list = g_new0(VncBasicInfoList, 1); @@ -581,13 +539,13 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp) info->has_display = true; info->display = g_strdup(dev->id); } - if (vd->lsock != -1) { - info->server = qmp_query_server_entry(vd->lsock, false, - info->server); + if (vd->lsock != NULL) { + info->server = qmp_query_server_entry( + vd->lsock, false, info->server); } - if (vd->lwebsock != -1) { - info->server = qmp_query_server_entry(vd->lwebsock, true, - info->server); + if (vd->lwebsock != NULL) { + info->server = qmp_query_server_entry( + vd->lwebsock, true, info->server); } item = g_new0(VncInfo2List, 1); @@ -673,7 +631,7 @@ void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h, static void vnc_desktop_resize(VncState *vs) { - if (vs->csock == -1 || !vnc_has_feature(vs, VNC_FEATURE_RESIZE)) { + if (vs->ioc == NULL || !vnc_has_feature(vs, VNC_FEATURE_RESIZE)) { return; } if (vs->client_width == pixman_image_get_width(vs->vd->server) && @@ -1066,7 +1024,7 @@ static int find_and_clear_dirty_height(VncState *vs, static int vnc_update_client(VncState *vs, int has_dirty, bool sync) { vs->has_dirty += has_dirty; - if (vs->need_update && vs->csock != -1) { + if (vs->need_update && vs->ioc != NULL) { VncDisplay *vd = vs->vd; VncJob *job; int y; @@ -1130,7 +1088,7 @@ static int vnc_update_client(VncState *vs, int has_dirty, bool sync) return n; } - if (vs->csock == -1) { + if (vs->disconnecting) { vnc_disconnect_finish(vs); } else if (sync) { vnc_jobs_join(vs); @@ -1212,12 +1170,15 @@ static void audio_del(VncState *vs) static void vnc_disconnect_start(VncState *vs) { - if (vs->csock == -1) + if (vs->disconnecting) { return; + } vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED); - qemu_set_fd_handler(vs->csock, NULL, NULL, NULL); - closesocket(vs->csock); - vs->csock = -1; + if (vs->ioc_tag) { + g_source_remove(vs->ioc_tag); + } + qio_channel_close(vs->ioc, NULL); + vs->disconnecting = TRUE; } void vnc_disconnect_finish(VncState *vs) @@ -1270,29 +1231,29 @@ void vnc_disconnect_finish(VncState *vs) g_free(vs->lossy_rect[i]); } g_free(vs->lossy_rect); + + object_unref(OBJECT(vs->ioc)); + vs->ioc = NULL; + object_unref(OBJECT(vs->sioc)); + vs->sioc = NULL; g_free(vs); } -ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, int last_errno) +ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp) { - if (ret == 0 || ret == -1) { - if (ret == -1) { - switch (last_errno) { - case EINTR: - case EAGAIN: -#ifdef _WIN32 - case WSAEWOULDBLOCK: -#endif - return 0; - default: - break; - } + if (ret <= 0) { + if (ret == 0) { + VNC_DEBUG("Closing down client sock: EOF\n"); + } else if (ret != QIO_CHANNEL_ERR_BLOCK) { + VNC_DEBUG("Closing down client sock: ret %d (%s)\n", + ret, errp ? error_get_pretty(*errp) : "Unknown"); } - VNC_DEBUG("Closing down client sock: ret %zd, errno %d\n", - ret, ret < 0 ? last_errno : 0); vnc_disconnect_start(vs); - + if (errp) { + error_free(*errp); + *errp = NULL; + } return 0; } return ret; @@ -1309,13 +1270,12 @@ void vnc_client_error(VncState *vs) ssize_t vnc_tls_pull(char *buf, size_t len, void *opaque) { VncState *vs = opaque; - ssize_t ret; - - retry: - ret = qemu_recv(vs->csock, buf, len, 0); + ssize_t ret = qio_channel_read(vs->ioc, buf, len, NULL); if (ret < 0) { - if (errno == EINTR) { - goto retry; + if (ret == QIO_CHANNEL_ERR_BLOCK) { + errno = EAGAIN; + } else { + errno = EIO; } return -1; } @@ -1326,13 +1286,12 @@ ssize_t vnc_tls_pull(char *buf, size_t len, void *opaque) ssize_t vnc_tls_push(const char *buf, size_t len, void *opaque) { VncState *vs = opaque; - ssize_t ret; - - retry: - ret = send(vs->csock, buf, len, 0); + ssize_t ret = qio_channel_write(vs->ioc, buf, len, NULL); if (ret < 0) { - if (errno == EINTR) { - goto retry; + if (ret == QIO_CHANNEL_ERR_BLOCK) { + errno = EAGAIN; + } else { + errno = EIO; } return -1; } @@ -1357,21 +1316,25 @@ ssize_t vnc_tls_push(const char *buf, size_t len, void *opaque) */ ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) { + Error *err = NULL; ssize_t ret; - int err = 0; if (vs->tls) { ret = qcrypto_tls_session_write(vs->tls, (const char *)data, datalen); if (ret < 0) { - err = errno; + if (errno == EAGAIN) { + ret = QIO_CHANNEL_ERR_BLOCK; + } else { + ret = -1; + error_setg_errno(&err, errno, "%s", + "Cannot write to TLS socket"); + } } } else { - ret = send(vs->csock, (const void *)data, datalen, 0); - if (ret < 0) { - err = socket_error(); - } + ret = qio_channel_write( + vs->ioc, (const char *)data, datalen, &err); } VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret); - return vnc_client_io_error(vs, ret, err); + return vnc_client_io_error(vs, ret, &err); } @@ -1409,7 +1372,11 @@ static ssize_t vnc_client_write_plain(VncState *vs) buffer_advance(&vs->output, ret); if (vs->output.offset == 0) { - qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, 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); } return ret; @@ -1421,10 +1388,8 @@ static ssize_t vnc_client_write_plain(VncState *vs) * the client socket. Will delegate actual work according to whether * SASL SSF layers are enabled (thus requiring encryption calls) */ -static void vnc_client_write_locked(void *opaque) +static void vnc_client_write_locked(VncState *vs) { - VncState *vs = opaque; - #ifdef CONFIG_VNC_SASL if (vs->sasl.conn && vs->sasl.runSSF && @@ -1441,15 +1406,18 @@ static void vnc_client_write_locked(void *opaque) } } -void vnc_client_write(void *opaque) +static void vnc_client_write(VncState *vs) { - VncState *vs = opaque; vnc_lock_output(vs); if (vs->output.offset || vs->ws_output.offset) { - vnc_client_write_locked(opaque); - } else if (vs->csock != -1) { - qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); + vnc_client_write_locked(vs); + } else if (vs->ioc != NULL) { + 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); } vnc_unlock_output(vs); } @@ -1479,20 +1447,24 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) { ssize_t ret; - int err = -1; + Error *err = NULL; if (vs->tls) { ret = qcrypto_tls_session_read(vs->tls, (char *)data, datalen); if (ret < 0) { - err = errno; + if (errno == EAGAIN) { + ret = QIO_CHANNEL_ERR_BLOCK; + } else { + ret = -1; + error_setg_errno(&err, errno, "%s", + "Cannot read from TLS socket"); + } } } else { - ret = qemu_recv(vs->csock, data, datalen, 0); - if (ret < 0) { - err = socket_error(); - } + ret = qio_channel_read( + vs->ioc, (char *)data, datalen, &err); } VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret); - return vnc_client_io_error(vs, ret, err); + return vnc_client_io_error(vs, ret, &err); } @@ -1529,9 +1501,8 @@ static void vnc_jobs_bh(void *opaque) * the client socket. Will delegate actual work according to whether * SASL SSF layers are enabled (thus requiring decryption calls) */ -void vnc_client_read(void *opaque) +static void vnc_client_read(VncState *vs) { - VncState *vs = opaque; ssize_t ret; #ifdef CONFIG_VNC_SASL @@ -1552,8 +1523,9 @@ void vnc_client_read(void *opaque) ret = vnc_client_read_plain(vs); } if (!ret) { - if (vs->csock == -1) + if (vs->disconnecting) { vnc_disconnect_finish(vs); + } return; } @@ -1562,7 +1534,7 @@ void vnc_client_read(void *opaque) int ret; ret = vs->read_handler(vs, vs->input.buffer, len); - if (vs->csock == -1) { + if (vs->disconnecting) { vnc_disconnect_finish(vs); return; } @@ -1575,12 +1547,30 @@ void vnc_client_read(void *opaque) } } +gboolean vnc_client_io(QIOChannel *ioc G_GNUC_UNUSED, + GIOCondition condition, void *opaque) +{ + VncState *vs = opaque; + if (condition & G_IO_IN) { + vnc_client_read(vs); + } + if (condition & G_IO_OUT) { + vnc_client_write(vs); + } + return TRUE; +} + + void vnc_write(VncState *vs, const void *data, size_t len) { buffer_reserve(&vs->output, len); - if (vs->csock != -1 && buffer_empty(&vs->output)) { - qemu_set_fd_handler(vs->csock, vnc_client_read, vnc_client_write, vs); + if (vs->ioc != NULL && buffer_empty(&vs->output)) { + 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); } buffer_append(&vs->output, data, len); @@ -1621,8 +1611,7 @@ void vnc_write_u8(VncState *vs, uint8_t value) void vnc_flush(VncState *vs) { vnc_lock_output(vs); - if (vs->csock != -1 && (vs->output.offset || - vs->ws_output.offset)) { + if (vs->ioc != NULL && (vs->output.offset || vs->ws_output.offset)) { vnc_client_write_locked(vs); } vnc_unlock_output(vs); @@ -3006,34 +2995,37 @@ static void vnc_refresh(DisplayChangeListener *dcl) } } -static void vnc_connect(VncDisplay *vd, int csock, +static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, bool skipauth, bool websocket) { VncState *vs = g_new0(VncState, 1); int i; - vs->csock = csock; + vs->sioc = sioc; + object_ref(OBJECT(vs->sioc)); + vs->ioc = QIO_CHANNEL(sioc); + object_ref(OBJECT(vs->ioc)); vs->vd = vd; - buffer_init(&vs->input, "vnc-input/%d", csock); - buffer_init(&vs->output, "vnc-output/%d", csock); - buffer_init(&vs->ws_input, "vnc-ws_input/%d", csock); - buffer_init(&vs->ws_output, "vnc-ws_output/%d", csock); - buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%d", csock); + buffer_init(&vs->input, "vnc-input/%p", sioc); + buffer_init(&vs->output, "vnc-output/%p", sioc); + buffer_init(&vs->ws_input, "vnc-ws_input/%p", sioc); + buffer_init(&vs->ws_output, "vnc-ws_output/%p", sioc); + buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%p", sioc); - buffer_init(&vs->tight.tight, "vnc-tight/%d", csock); - buffer_init(&vs->tight.zlib, "vnc-tight-zlib/%d", csock); - buffer_init(&vs->tight.gradient, "vnc-tight-gradient/%d", csock); + buffer_init(&vs->tight.tight, "vnc-tight/%p", sioc); + buffer_init(&vs->tight.zlib, "vnc-tight-zlib/%p", sioc); + buffer_init(&vs->tight.gradient, "vnc-tight-gradient/%p", sioc); #ifdef CONFIG_VNC_JPEG - buffer_init(&vs->tight.jpeg, "vnc-tight-jpeg/%d", csock); + buffer_init(&vs->tight.jpeg, "vnc-tight-jpeg/%p", sioc); #endif #ifdef CONFIG_VNC_PNG - buffer_init(&vs->tight.png, "vnc-tight-png/%d", csock); + buffer_init(&vs->tight.png, "vnc-tight-png/%p", sioc); #endif - buffer_init(&vs->zlib.zlib, "vnc-zlib/%d", csock); - buffer_init(&vs->zrle.zrle, "vnc-zrle/%d", csock); - buffer_init(&vs->zrle.fb, "vnc-zrle-fb/%d", csock); - buffer_init(&vs->zrle.zlib, "vnc-zrle-zlib/%d", csock); + buffer_init(&vs->zlib.zlib, "vnc-zlib/%p", sioc); + buffer_init(&vs->zrle.zrle, "vnc-zrle/%p", sioc); + buffer_init(&vs->zrle.fb, "vnc-zrle-fb/%p", sioc); + buffer_init(&vs->zrle.zlib, "vnc-zrle-zlib/%p", sioc); if (skipauth) { vs->auth = VNC_AUTH_NONE; @@ -3047,27 +3039,29 @@ static void vnc_connect(VncDisplay *vd, int csock, vs->subauth = vd->subauth; } } - VNC_DEBUG("Client sock=%d ws=%d auth=%d subauth=%d\n", - csock, websocket, vs->auth, vs->subauth); + VNC_DEBUG("Client sioc=%p ws=%d auth=%d subauth=%d\n", + sioc, websocket, vs->auth, vs->subauth); vs->lossy_rect = g_malloc0(VNC_STAT_ROWS * sizeof (*vs->lossy_rect)); for (i = 0; i < VNC_STAT_ROWS; ++i) { vs->lossy_rect[i] = g_new0(uint8_t, VNC_STAT_COLS); } - VNC_DEBUG("New client on socket %d\n", csock); + VNC_DEBUG("New client on socket %p\n", vs->sioc); update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE); - qemu_set_nonblock(vs->csock); + qio_channel_set_blocking(vs->ioc, false, NULL); if (websocket) { vs->websocket = 1; if (vd->ws_tls) { - qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io, NULL, vs); + vs->ioc_tag = qio_channel_add_watch( + vs->ioc, G_IO_IN, vncws_tls_handshake_io, vs, NULL); } else { - qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs); + vs->ioc_tag = qio_channel_add_watch( + vs->ioc, G_IO_IN, vncws_handshake_io, vs, NULL); } - } else - { - qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); + } else { + vs->ioc_tag = qio_channel_add_watch( + vs->ioc, G_IO_IN, vnc_client_io, vs, NULL); } vnc_client_cache_addr(vs); @@ -3125,35 +3119,28 @@ void vnc_init_state(VncState *vs) /* vs might be free()ed here */ } -static void vnc_listen_read(void *opaque, bool websocket) +static gboolean vnc_listen_io(QIOChannel *ioc, + GIOCondition condition, + void *opaque) { VncDisplay *vs = opaque; - struct sockaddr_in addr; - socklen_t addrlen = sizeof(addr); - int csock; + QIOChannelSocket *sioc = NULL; + Error *err = NULL; /* Catch-up */ graphic_hw_update(vs->dcl.con); - if (websocket) { - csock = qemu_accept(vs->lwebsock, (struct sockaddr *)&addr, &addrlen); + sioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc), &err); + if (sioc != NULL) { + qio_channel_set_delay(QIO_CHANNEL(sioc), false); + vnc_connect(vs, sioc, false, + ioc != QIO_CHANNEL(vs->lsock)); + object_unref(OBJECT(sioc)); } else { - csock = qemu_accept(vs->lsock, (struct sockaddr *)&addr, &addrlen); + /* client probably closed connection before we got there */ + error_free(err); } - if (csock != -1) { - socket_set_nodelay(csock); - vnc_connect(vs, csock, false, websocket); - } -} - -static void vnc_listen_regular_read(void *opaque) -{ - vnc_listen_read(opaque, false); -} - -static void vnc_listen_websocket_read(void *opaque) -{ - vnc_listen_read(opaque, true); + return TRUE; } static const DisplayChangeListenerOps dcl_ops = { @@ -3179,9 +3166,6 @@ void vnc_display_init(const char *id) vs->id = strdup(id); QTAILQ_INSERT_TAIL(&vnc_displays, vs, next); - vs->lsock = -1; - vs->lwebsock = -1; - QTAILQ_INIT(&vs->clients); vs->expires = TIME_MAX; @@ -3209,16 +3193,20 @@ static void vnc_display_close(VncDisplay *vs) return; vs->enabled = false; vs->is_unix = false; - if (vs->lsock != -1) { - qemu_set_fd_handler(vs->lsock, NULL, NULL, NULL); - close(vs->lsock); - vs->lsock = -1; + if (vs->lsock != NULL) { + if (vs->lsock_tag) { + g_source_remove(vs->lsock_tag); + } + object_unref(OBJECT(vs->lsock)); + vs->lsock = NULL; } vs->ws_enabled = false; - if (vs->lwebsock != -1) { - qemu_set_fd_handler(vs->lwebsock, NULL, NULL, NULL); - close(vs->lwebsock); - vs->lwebsock = -1; + if (vs->lwebsock != NULL) { + if (vs->lwebsock_tag) { + g_source_remove(vs->lwebsock_tag); + } + object_unref(OBJECT(vs->lwebsock)); + vs->lwebsock = NULL; } vs->auth = VNC_AUTH_INVALID; vs->subauth = VNC_AUTH_INVALID; @@ -3263,9 +3251,25 @@ int vnc_display_pw_expire(const char *id, time_t expires) char *vnc_display_local_addr(const char *id) { VncDisplay *vs = vnc_display_find(id); + SocketAddress *addr; + char *ret; + Error *err = NULL; assert(vs); - return vnc_socket_local_addr("%s:%s", vs->lsock); + + addr = qio_channel_socket_get_local_address(vs->lsock, &err); + if (!addr) { + return NULL; + } + + if (addr->type != SOCKET_ADDRESS_KIND_INET) { + qapi_free_SocketAddress(addr); + return NULL; + } + ret = g_strdup_printf("%s;%s", addr->u.inet->host, addr->u.inet->port); + qapi_free_SocketAddress(addr); + + return ret; } static QemuOptsList qemu_vnc_opts = { @@ -3826,41 +3830,45 @@ void vnc_display_open(const char *id, Error **errp) if (reverse) { /* connect to viewer */ - int csock; - vs->lsock = -1; - vs->lwebsock = -1; + QIOChannelSocket *sioc = NULL; + vs->lsock = NULL; + vs->lwebsock = NULL; if (vs->ws_enabled) { error_setg(errp, "Cannot use websockets in reverse mode"); goto fail; } - csock = socket_connect(saddr, errp, NULL, NULL); - if (csock < 0) { + vs->is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX; + sioc = qio_channel_socket_new(); + if (qio_channel_socket_connect_sync(sioc, saddr, errp) < 0) { goto fail; } - vs->is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX; - vnc_connect(vs, csock, false, false); + vnc_connect(vs, sioc, false, false); + object_unref(OBJECT(sioc)); } else { - /* listen for connects */ - vs->lsock = socket_listen(saddr, errp); - if (vs->lsock < 0) { + vs->lsock = qio_channel_socket_new(); + if (qio_channel_socket_listen_sync(vs->lsock, saddr, errp) < 0) { goto fail; } vs->is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX; + vs->enabled = true; + if (vs->ws_enabled) { - vs->lwebsock = socket_listen(wsaddr, errp); - if (vs->lwebsock < 0) { - if (vs->lsock != -1) { - close(vs->lsock); - vs->lsock = -1; - } + vs->lwebsock = qio_channel_socket_new(); + if (qio_channel_socket_listen_sync(vs->lwebsock, + wsaddr, errp) < 0) { + object_unref(OBJECT(vs->lsock)); + vs->lsock = NULL; goto fail; } } - vs->enabled = true; - qemu_set_fd_handler(vs->lsock, vnc_listen_regular_read, NULL, vs); + + vs->lsock_tag = qio_channel_add_watch( + QIO_CHANNEL(vs->lsock), + G_IO_IN, vnc_listen_io, vs, NULL); if (vs->ws_enabled) { - qemu_set_fd_handler(vs->lwebsock, vnc_listen_websocket_read, - NULL, vs); + vs->lwebsock_tag = qio_channel_add_watch( + QIO_CHANNEL(vs->lwebsock), + G_IO_IN, vnc_listen_io, vs, NULL); } } @@ -3878,11 +3886,17 @@ fail: void vnc_display_add_client(const char *id, int csock, bool skipauth) { VncDisplay *vs = vnc_display_find(id); + QIOChannelSocket *sioc; if (!vs) { return; } - vnc_connect(vs, csock, skipauth, false); + + sioc = qio_channel_socket_new_fd(csock, NULL); + if (sioc) { + vnc_connect(vs, sioc, skipauth, false); + object_unref(OBJECT(sioc)); + } } static void vnc_auto_assign_id(QemuOptsList *olist, QemuOpts *opts) diff --git a/ui/vnc.h b/ui/vnc.h index 2863f583aa..69ec217227 100644 --- a/ui/vnc.h +++ b/ui/vnc.h @@ -35,6 +35,7 @@ #include "qemu/bitmap.h" #include "crypto/tlssession.h" #include "qemu/buffer.h" +#include "io/channel-socket.h" #include #include @@ -145,8 +146,10 @@ struct VncDisplay int num_exclusive; int connections_limit; VncSharePolicy share_policy; - int lsock; - int lwebsock; + QIOChannelSocket *lsock; + guint lsock_tag; + QIOChannelSocket *lwebsock; + guint lwebsock_tag; bool ws_enabled; DisplaySurface *ds; DisplayChangeListener dcl; @@ -248,7 +251,10 @@ struct VncJob struct VncState { - int csock; + QIOChannelSocket *sioc; /* The underlying socket */ + QIOChannel *ioc; /* The channel currently used for I/O */ + guint ioc_tag; + gboolean disconnecting; DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT], VNC_DIRTY_BITS); uint8_t **lossy_rect; /* Not an Array to avoid costly memcpy in @@ -499,8 +505,9 @@ enum { *****************************************************************************/ /* Event loop functions */ -void vnc_client_read(void *opaque); -void vnc_client_write(void *opaque); +gboolean vnc_client_io(QIOChannel *ioc, + GIOCondition condition, + void *opaque); ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen); ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen); @@ -524,7 +531,7 @@ uint32_t read_u32(uint8_t *data, size_t offset); /* Protocol stage functions */ void vnc_client_error(VncState *vs); -ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, int last_errno); +ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp); void start_client_init(VncState *vs); void start_auth_vnc(VncState *vs); @@ -532,9 +539,6 @@ void start_auth_vnc(VncState *vs); /* Misc helpers */ -char *vnc_socket_local_addr(const char *format, int fd); -char *vnc_socket_remote_addr(const char *format, int fd); - static inline uint32_t vnc_has_feature(VncState *vs, int feature) { return (vs->features & (1 << feature)); } From 2cc452281e1746f9c02d3c66633ce9e5bc391cae Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Mon, 2 Mar 2015 19:01:05 +0000 Subject: [PATCH 2/3] ui: convert VNC server to use QIOChannelTLS Switch VNC server over to using the QIOChannelTLS object for the TLS session. This removes all remaining VNC specific code for dealing with TLS handshakes. Reviewed-by: Gerd Hoffmann Signed-off-by: Daniel P. Berrange --- ui/vnc-auth-vencrypt.c | 106 ++++++++++++----------------------------- ui/vnc-ws.c | 97 ++++++++++++++----------------------- ui/vnc.c | 69 ++------------------------- ui/vnc.h | 5 +- 4 files changed, 74 insertions(+), 203 deletions(-) diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c index 95a6823483..093dd2f4c2 100644 --- a/ui/vnc-auth-vencrypt.c +++ b/ui/vnc-auth-vencrypt.c @@ -63,71 +63,21 @@ static void start_auth_vencrypt_subauth(VncState *vs) } } -static gboolean vnc_tls_handshake_io(QIOChannel *ioc, - GIOCondition condition, - void *opaque); - -static int vnc_start_vencrypt_handshake(VncState *vs) +static void vnc_tls_handshake_done(Object *source, + Error *err, + gpointer user_data) { - Error *err = NULL; + VncState *vs = user_data; - if (qcrypto_tls_session_handshake(vs->tls, &err) < 0) { - goto error; - } - - switch (qcrypto_tls_session_get_handshake_status(vs->tls)) { - case QCRYPTO_TLS_HANDSHAKE_COMPLETE: - VNC_DEBUG("Handshake done, checking credentials\n"); - if (qcrypto_tls_session_check_credentials(vs->tls, &err) < 0) { - goto error; - } - VNC_DEBUG("Client verification passed, starting TLS I/O\n"); - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } + if (err) { + VNC_DEBUG("Handshake failed %s\n", + error_get_pretty(err)); + vnc_client_error(vs); + } else { 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); - break; - - case QCRYPTO_TLS_HANDSHAKE_RECVING: - VNC_DEBUG("Handshake interrupted (blocking read)\n"); - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } - vs->ioc_tag = qio_channel_add_watch( - vs->ioc, G_IO_IN, vnc_tls_handshake_io, vs, NULL); - break; - - case QCRYPTO_TLS_HANDSHAKE_SENDING: - VNC_DEBUG("Handshake interrupted (blocking write)\n"); - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } - vs->ioc_tag = qio_channel_add_watch( - vs->ioc, G_IO_OUT, vnc_tls_handshake_io, vs, NULL); - break; } - - return 0; - - error: - VNC_DEBUG("Handshake failed %s\n", error_get_pretty(err)); - error_free(err); - vnc_client_error(vs); - return -1; -} - -static gboolean vnc_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED, - GIOCondition condition G_GNUC_UNUSED, - void *opaque) -{ - VncState *vs = (VncState *)opaque; - - VNC_DEBUG("Handshake IO continue\n"); - vnc_start_vencrypt_handshake(vs); - return TRUE; } @@ -142,33 +92,37 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len vnc_client_error(vs); } else { Error *err = NULL; + QIOChannelTLS *tls; VNC_DEBUG("Accepting auth %d, setting up TLS for handshake\n", auth); vnc_write_u8(vs, 1); /* Accept auth */ vnc_flush(vs); - vs->tls = qcrypto_tls_session_new(vs->vd->tlscreds, - NULL, - vs->vd->tlsaclname, - QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, - &err); - if (!vs->tls) { - VNC_DEBUG("Failed to setup TLS %s\n", - error_get_pretty(err)); + if (vs->ioc_tag) { + g_source_remove(vs->ioc_tag); + vs->ioc_tag = 0; + } + + tls = qio_channel_tls_new_server( + vs->ioc, + vs->vd->tlscreds, + vs->vd->tlsaclname, + &err); + if (!tls) { + VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err)); error_free(err); vnc_client_error(vs); return 0; } - qcrypto_tls_session_set_callbacks(vs->tls, - vnc_tls_push, - vnc_tls_pull, - vs); - VNC_DEBUG("Start TLS VeNCrypt handshake process\n"); - if (vnc_start_vencrypt_handshake(vs) < 0) { - VNC_DEBUG("Failed to start TLS handshake\n"); - return 0; - } + object_unref(OBJECT(vs->ioc)); + vs->ioc = QIO_CHANNEL(tls); + vs->tls = qio_channel_tls_get_session(tls); + + qio_channel_tls_handshake(tls, + vnc_tls_handshake_done, + vs, + NULL); } return 0; } diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c index 15649dc582..053beca7de 100644 --- a/ui/vnc-ws.c +++ b/ui/vnc-ws.c @@ -22,83 +22,60 @@ #include "qemu/main-loop.h" #include "crypto/hash.h" -static int vncws_start_tls_handshake(VncState *vs) +static void vncws_handshake_read(VncState *vs); + +static void vncws_tls_handshake_done(Object *source, + Error *err, + gpointer user_data) { - Error *err = NULL; + VncState *vs = user_data; - if (qcrypto_tls_session_handshake(vs->tls, &err) < 0) { - goto error; + if (err) { + VNC_DEBUG("Handshake failed %s\n", error_get_pretty(err)); + vnc_client_error(vs); + } else { + vs->ioc_tag = qio_channel_add_watch( + QIO_CHANNEL(vs->ioc), G_IO_IN, vncws_handshake_io, vs, NULL); } - - switch (qcrypto_tls_session_get_handshake_status(vs->tls)) { - case QCRYPTO_TLS_HANDSHAKE_COMPLETE: - VNC_DEBUG("Handshake done, checking credentials\n"); - if (qcrypto_tls_session_check_credentials(vs->tls, &err) < 0) { - goto error; - } - VNC_DEBUG("Client verification passed, starting TLS I/O\n"); - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } - vs->ioc_tag = qio_channel_add_watch( - vs->ioc, G_IO_IN, vncws_handshake_io, vs, NULL); - break; - - case QCRYPTO_TLS_HANDSHAKE_RECVING: - VNC_DEBUG("Handshake interrupted (blocking read)\n"); - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } - vs->ioc_tag = qio_channel_add_watch( - vs->ioc, G_IO_IN, vncws_tls_handshake_io, vs, NULL); - break; - - case QCRYPTO_TLS_HANDSHAKE_SENDING: - VNC_DEBUG("Handshake interrupted (blocking write)\n"); - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } - vs->ioc_tag = qio_channel_add_watch( - vs->ioc, G_IO_OUT, vncws_tls_handshake_io, vs, NULL); - break; - } - - return 0; - - error: - VNC_DEBUG("Handshake failed %s\n", error_get_pretty(err)); - error_free(err); - vnc_client_error(vs); - return -1; } + gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED, GIOCondition condition G_GNUC_UNUSED, void *opaque) { - VncState *vs = (VncState *)opaque; + VncState *vs = opaque; + QIOChannelTLS *tls; Error *err = NULL; - vs->tls = qcrypto_tls_session_new(vs->vd->tlscreds, - NULL, - vs->vd->tlsaclname, - QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, - &err); - if (!vs->tls) { - VNC_DEBUG("Failed to setup TLS %s\n", - error_get_pretty(err)); + VNC_DEBUG("TLS Websocket connection required\n"); + if (vs->ioc_tag) { + g_source_remove(vs->ioc_tag); + vs->ioc_tag = 0; + } + + tls = qio_channel_tls_new_server( + vs->ioc, + vs->vd->tlscreds, + vs->vd->tlsaclname, + &err); + if (!tls) { + VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err)); error_free(err); vnc_client_error(vs); return TRUE; } - qcrypto_tls_session_set_callbacks(vs->tls, - vnc_tls_push, - vnc_tls_pull, - vs); - VNC_DEBUG("Start TLS WS handshake process\n"); - vncws_start_tls_handshake(vs); + object_unref(OBJECT(vs->ioc)); + vs->ioc = QIO_CHANNEL(tls); + vs->tls = qio_channel_tls_get_session(tls); + + qio_channel_tls_handshake(tls, + vncws_tls_handshake_done, + vs, + NULL); + return TRUE; } diff --git a/ui/vnc.c b/ui/vnc.c index 30053cfdbb..8b8361e371 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -1201,7 +1201,6 @@ void vnc_disconnect_finish(VncState *vs) vnc_tight_clear(vs); vnc_zrle_clear(vs); - qcrypto_tls_session_free(vs->tls); #ifdef CONFIG_VNC_SASL vnc_sasl_client_cleanup(vs); #endif /* CONFIG_VNC_SASL */ @@ -1267,38 +1266,6 @@ void vnc_client_error(VncState *vs) } -ssize_t vnc_tls_pull(char *buf, size_t len, void *opaque) -{ - VncState *vs = opaque; - ssize_t ret = qio_channel_read(vs->ioc, buf, len, NULL); - if (ret < 0) { - if (ret == QIO_CHANNEL_ERR_BLOCK) { - errno = EAGAIN; - } else { - errno = EIO; - } - return -1; - } - return ret; -} - - -ssize_t vnc_tls_push(const char *buf, size_t len, void *opaque) -{ - VncState *vs = opaque; - ssize_t ret = qio_channel_write(vs->ioc, buf, len, NULL); - if (ret < 0) { - if (ret == QIO_CHANNEL_ERR_BLOCK) { - errno = EAGAIN; - } else { - errno = EIO; - } - return -1; - } - return ret; -} - - /* * Called to write a chunk of data to the client socket. The data may * be the raw data, or may have already been encoded by SASL. @@ -1318,21 +1285,8 @@ ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) { Error *err = NULL; ssize_t ret; - if (vs->tls) { - ret = qcrypto_tls_session_write(vs->tls, (const char *)data, datalen); - if (ret < 0) { - if (errno == EAGAIN) { - ret = QIO_CHANNEL_ERR_BLOCK; - } else { - ret = -1; - error_setg_errno(&err, errno, "%s", - "Cannot write to TLS socket"); - } - } - } else { - ret = qio_channel_write( - vs->ioc, (const char *)data, datalen, &err); - } + ret = qio_channel_write( + vs->ioc, (const char *)data, datalen, &err); VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret); return vnc_client_io_error(vs, ret, &err); } @@ -1448,21 +1402,8 @@ ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) { ssize_t ret; Error *err = NULL; - if (vs->tls) { - ret = qcrypto_tls_session_read(vs->tls, (char *)data, datalen); - if (ret < 0) { - if (errno == EAGAIN) { - ret = QIO_CHANNEL_ERR_BLOCK; - } else { - ret = -1; - error_setg_errno(&err, errno, "%s", - "Cannot read from TLS socket"); - } - } - } else { - ret = qio_channel_read( - vs->ioc, (char *)data, datalen, &err); - } + ret = qio_channel_read( + vs->ioc, (char *)data, datalen, &err); VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret); return vnc_client_io_error(vs, ret, &err); } @@ -3773,7 +3714,7 @@ void vnc_display_open(const char *id, Error **errp) vs->tlsaclname = g_strdup_printf("vnc.%s.x509dname", vs->id); } qemu_acl_init(vs->tlsaclname); - } + } #ifdef CONFIG_VNC_SASL if (acl && sasl) { char *aclname; diff --git a/ui/vnc.h b/ui/vnc.h index 69ec217227..34474d604e 100644 --- a/ui/vnc.h +++ b/ui/vnc.h @@ -36,6 +36,7 @@ #include "crypto/tlssession.h" #include "qemu/buffer.h" #include "io/channel-socket.h" +#include "io/channel-tls.h" #include #include @@ -281,7 +282,7 @@ struct VncState int auth; int subauth; /* Used by VeNCrypt */ char challenge[VNC_AUTH_CHALLENGE_SIZE]; - QCryptoTLSSession *tls; + QCryptoTLSSession *tls; /* Borrowed pointer from channel, don't free */ #ifdef CONFIG_VNC_SASL VncStateSASL sasl; #endif @@ -511,8 +512,6 @@ gboolean vnc_client_io(QIOChannel *ioc, ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen); ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen); -ssize_t vnc_tls_pull(char *buf, size_t len, void *opaque); -ssize_t vnc_tls_push(const char *buf, size_t len, void *opaque); /* Protocol I/O functions */ void vnc_write(VncState *vs, const void *data, size_t len); From d5f042232cca1c3e2d16b49607632fe4c0f86453 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Wed, 11 Mar 2015 15:53:49 +0000 Subject: [PATCH 3/3] ui: convert VNC server to use QIOChannelWebsock Remove custom websock handling code from the VNC server and use the QIOChannelWebsock class instead. Reviewed-by: Gerd Hoffmann Signed-off-by: Daniel P. Berrange --- ui/vnc-ws.c | 338 +++++----------------------------------------------- ui/vnc-ws.h | 63 ---------- ui/vnc.c | 27 +---- ui/vnc.h | 4 - 4 files changed, 35 insertions(+), 397 deletions(-) diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c index 053beca7de..80182334ef 100644 --- a/ui/vnc-ws.c +++ b/ui/vnc-ws.c @@ -19,10 +19,7 @@ */ #include "vnc.h" -#include "qemu/main-loop.h" -#include "crypto/hash.h" - -static void vncws_handshake_read(VncState *vs); +#include "io/channel-websock.h" static void vncws_tls_handshake_done(Object *source, Error *err, @@ -34,6 +31,7 @@ static void vncws_tls_handshake_done(Object *source, VNC_DEBUG("Handshake failed %s\n", error_get_pretty(err)); vnc_client_error(vs); } else { + VNC_DEBUG("TLS handshake complete, starting websocket handshake\n"); vs->ioc_tag = qio_channel_add_watch( QIO_CHANNEL(vs->ioc), G_IO_IN, vncws_handshake_io, vs, NULL); } @@ -79,38 +77,21 @@ gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED, return TRUE; } -static void vncws_handshake_read(VncState *vs) + +static void vncws_handshake_done(Object *source, + Error *err, + gpointer user_data) { - uint8_t *handshake_end; - long ret; - /* Typical HTTP headers from novnc are 512 bytes, so limiting - * total header size to 4096 is easily enough. */ - size_t want = 4096 - vs->ws_input.offset; - buffer_reserve(&vs->ws_input, want); - ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), want); + VncState *vs = user_data; - if (!ret) { - if (vs->disconnecting) { - vnc_disconnect_finish(vs); - } - return; - } - vs->ws_input.offset += ret; - - handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer, - vs->ws_input.offset, WS_HANDSHAKE_END); - if (handshake_end) { - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } + if (err) { + VNC_DEBUG("Websock handshake failed %s\n", error_get_pretty(err)); + vnc_client_error(vs); + } else { + VNC_DEBUG("Websock handshake complete, starting VNC protocol\n"); + vnc_init_state(vs); vs->ioc_tag = qio_channel_add_watch( vs->ioc, G_IO_IN, vnc_client_io, vs, NULL); - vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset); - buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer + - strlen(WS_HANDSHAKE_END)); - } else if (vs->ws_input.offset >= 4096) { - VNC_DEBUG("End of headers not found in first 4096 bytes\n"); - vnc_client_error(vs); } } @@ -120,280 +101,23 @@ gboolean vncws_handshake_io(QIOChannel *ioc G_GNUC_UNUSED, void *opaque) { VncState *vs = opaque; - vncws_handshake_read(vs); + QIOChannelWebsock *wioc; + + VNC_DEBUG("Websocket negotiate starting\n"); + if (vs->ioc_tag) { + g_source_remove(vs->ioc_tag); + vs->ioc_tag = 0; + } + + wioc = qio_channel_websock_new_server(vs->ioc); + + object_unref(OBJECT(vs->ioc)); + vs->ioc = QIO_CHANNEL(wioc); + + qio_channel_websock_handshake(wioc, + vncws_handshake_done, + vs, + NULL); + return TRUE; } - -long vnc_client_read_ws(VncState *vs) -{ - int ret, err; - uint8_t *payload; - size_t payload_size, header_size; - VNC_DEBUG("Read websocket %p size %zd offset %zd\n", vs->ws_input.buffer, - vs->ws_input.capacity, vs->ws_input.offset); - buffer_reserve(&vs->ws_input, 4096); - ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096); - if (!ret) { - return 0; - } - vs->ws_input.offset += ret; - - ret = 0; - /* consume as much of ws_input buffer as possible */ - do { - if (vs->ws_payload_remain == 0) { - err = vncws_decode_frame_header(&vs->ws_input, - &header_size, - &vs->ws_payload_remain, - &vs->ws_payload_mask); - if (err <= 0) { - return err; - } - - buffer_advance(&vs->ws_input, header_size); - } - if (vs->ws_payload_remain != 0) { - err = vncws_decode_frame_payload(&vs->ws_input, - &vs->ws_payload_remain, - &vs->ws_payload_mask, - &payload, - &payload_size); - if (err < 0) { - return err; - } - if (err == 0) { - return ret; - } - ret += err; - - buffer_reserve(&vs->input, payload_size); - buffer_append(&vs->input, payload, payload_size); - - buffer_advance(&vs->ws_input, payload_size); - } - } while (vs->ws_input.offset > 0); - - return ret; -} - -long vnc_client_write_ws(VncState *vs) -{ - long ret; - VNC_DEBUG("Write WS: Pending output %p size %zd offset %zd\n", - vs->output.buffer, vs->output.capacity, vs->output.offset); - vncws_encode_frame(&vs->ws_output, vs->output.buffer, vs->output.offset); - buffer_reset(&vs->output); - ret = vnc_client_write_buf(vs, vs->ws_output.buffer, vs->ws_output.offset); - if (!ret) { - return 0; - } - - buffer_advance(&vs->ws_output, ret); - - if (vs->ws_output.offset == 0) { - 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); - } - - return ret; -} - -static char *vncws_extract_handshake_entry(const char *handshake, - size_t handshake_len, const char *name) -{ - char *begin, *end, *ret = NULL; - char *line = g_strdup_printf("%s%s: ", WS_HANDSHAKE_DELIM, name); - begin = g_strstr_len(handshake, handshake_len, line); - if (begin != NULL) { - begin += strlen(line); - end = g_strstr_len(begin, handshake_len - (begin - handshake), - WS_HANDSHAKE_DELIM); - if (end != NULL) { - ret = g_strndup(begin, end - begin); - } - } - g_free(line); - return ret; -} - -static void vncws_send_handshake_response(VncState *vs, const char* key) -{ - char combined_key[WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1]; - char *accept = NULL, *response = NULL; - Error *err = NULL; - - g_strlcpy(combined_key, key, WS_CLIENT_KEY_LEN + 1); - g_strlcat(combined_key, WS_GUID, WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1); - - /* hash and encode it */ - if (qcrypto_hash_base64(QCRYPTO_HASH_ALG_SHA1, - combined_key, - WS_CLIENT_KEY_LEN + WS_GUID_LEN, - &accept, - &err) < 0) { - VNC_DEBUG("Hashing Websocket combined key failed %s\n", - error_get_pretty(err)); - error_free(err); - vnc_client_error(vs); - return; - } - - response = g_strdup_printf(WS_HANDSHAKE, accept); - vnc_client_write_buf(vs, (const uint8_t *)response, strlen(response)); - - g_free(accept); - g_free(response); - - vs->encode_ws = 1; - vnc_init_state(vs); -} - -void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size) -{ - char *protocols = vncws_extract_handshake_entry((const char *)line, size, - "Sec-WebSocket-Protocol"); - char *version = vncws_extract_handshake_entry((const char *)line, size, - "Sec-WebSocket-Version"); - char *key = vncws_extract_handshake_entry((const char *)line, size, - "Sec-WebSocket-Key"); - - if (protocols && version && key - && g_strrstr(protocols, "binary") - && !strcmp(version, WS_SUPPORTED_VERSION) - && strlen(key) == WS_CLIENT_KEY_LEN) { - vncws_send_handshake_response(vs, key); - } else { - VNC_DEBUG("Defective Websockets header or unsupported protocol\n"); - vnc_client_error(vs); - } - - g_free(protocols); - g_free(version); - g_free(key); -} - -void vncws_encode_frame(Buffer *output, const void *payload, - const size_t payload_size) -{ - size_t header_size = 0; - unsigned char opcode = WS_OPCODE_BINARY_FRAME; - union { - char buf[WS_HEAD_MAX_LEN]; - WsHeader ws; - } header; - - if (!payload_size) { - return; - } - - header.ws.b0 = 0x80 | (opcode & 0x0f); - if (payload_size <= 125) { - header.ws.b1 = (uint8_t)payload_size; - header_size = 2; - } else if (payload_size < 65536) { - header.ws.b1 = 0x7e; - header.ws.u.s16.l16 = cpu_to_be16((uint16_t)payload_size); - header_size = 4; - } else { - header.ws.b1 = 0x7f; - header.ws.u.s64.l64 = cpu_to_be64(payload_size); - header_size = 10; - } - - buffer_reserve(output, header_size + payload_size); - buffer_append(output, header.buf, header_size); - buffer_append(output, payload, payload_size); -} - -int vncws_decode_frame_header(Buffer *input, - size_t *header_size, - size_t *payload_remain, - WsMask *payload_mask) -{ - unsigned char opcode = 0, fin = 0, has_mask = 0; - size_t payload_len; - WsHeader *header = (WsHeader *)input->buffer; - - if (input->offset < WS_HEAD_MIN_LEN + 4) { - /* header not complete */ - return 0; - } - - fin = (header->b0 & 0x80) >> 7; - opcode = header->b0 & 0x0f; - has_mask = (header->b1 & 0x80) >> 7; - payload_len = header->b1 & 0x7f; - - if (opcode == WS_OPCODE_CLOSE) { - /* disconnect */ - return -1; - } - - /* 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. - */ - if (!fin || !has_mask || opcode != WS_OPCODE_BINARY_FRAME) { - VNC_DEBUG("Received faulty/unsupported Websocket frame\n"); - return -2; - } - - if (payload_len < 126) { - *payload_remain = payload_len; - *header_size = 6; - *payload_mask = header->u.m; - } else if (payload_len == 126 && input->offset >= 8) { - *payload_remain = be16_to_cpu(header->u.s16.l16); - *header_size = 8; - *payload_mask = header->u.s16.m16; - } else if (payload_len == 127 && input->offset >= 14) { - *payload_remain = be64_to_cpu(header->u.s64.l64); - *header_size = 14; - *payload_mask = header->u.s64.m64; - } else { - /* header not complete */ - return 0; - } - - return 1; -} - -int vncws_decode_frame_payload(Buffer *input, - size_t *payload_remain, WsMask *payload_mask, - uint8_t **payload, size_t *payload_size) -{ - size_t i; - uint32_t *payload32; - - *payload = input->buffer; - /* 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 (input->offset < *payload_remain) { - *payload_size = input->offset - (input->offset % 4); - } else { - *payload_size = *payload_remain; - } - if (*payload_size == 0) { - return 0; - } - *payload_remain -= *payload_size; - - /* unmask frame */ - /* process 1 frame (32 bit op) */ - payload32 = (uint32_t *)(*payload); - for (i = 0; i < *payload_size / 4; i++) { - payload32[i] ^= payload_mask->u; - } - /* process the remaining bytes (if any) */ - for (i *= 4; i < *payload_size; i++) { - (*payload)[i] ^= payload_mask->c[i % 4]; - } - - return 1; -} diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h index e8ffef7e40..652b6fc390 100644 --- a/ui/vnc-ws.h +++ b/ui/vnc-ws.h @@ -21,74 +21,11 @@ #ifndef __QEMU_UI_VNC_WS_H #define __QEMU_UI_VNC_WS_H -#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3) -#define SHA1_DIGEST_LEN 20 - -#define WS_ACCEPT_LEN (B64LEN(SHA1_DIGEST_LEN) + 1) -#define WS_CLIENT_KEY_LEN 24 -#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" -#define WS_GUID_LEN strlen(WS_GUID) - -#define WS_HANDSHAKE "HTTP/1.1 101 Switching Protocols\r\n\ -Upgrade: websocket\r\n\ -Connection: Upgrade\r\n\ -Sec-WebSocket-Accept: %s\r\n\ -Sec-WebSocket-Protocol: binary\r\n\ -\r\n" -#define WS_HANDSHAKE_DELIM "\r\n" -#define WS_HANDSHAKE_END "\r\n\r\n" -#define WS_SUPPORTED_VERSION "13" - -#define WS_HEAD_MIN_LEN sizeof(uint16_t) -#define WS_HEAD_MAX_LEN (WS_HEAD_MIN_LEN + sizeof(uint64_t) + sizeof(uint32_t)) - -typedef union WsMask { - char c[4]; - uint32_t u; -} WsMask; - -typedef struct QEMU_PACKED WsHeader { - unsigned char b0; - unsigned char b1; - union { - struct QEMU_PACKED { - uint16_t l16; - WsMask m16; - } s16; - struct QEMU_PACKED { - uint64_t l64; - WsMask m64; - } s64; - WsMask m; - } u; -} WsHeader; - -enum { - WS_OPCODE_CONTINUATION = 0x0, - WS_OPCODE_TEXT_FRAME = 0x1, - WS_OPCODE_BINARY_FRAME = 0x2, - WS_OPCODE_CLOSE = 0x8, - WS_OPCODE_PING = 0x9, - WS_OPCODE_PONG = 0xA -}; - gboolean vncws_tls_handshake_io(QIOChannel *ioc, GIOCondition condition, void *opaque); gboolean vncws_handshake_io(QIOChannel *ioc, GIOCondition condition, void *opaque); -long vnc_client_write_ws(VncState *vs); -long vnc_client_read_ws(VncState *vs); -void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size); -void vncws_encode_frame(Buffer *output, const void *payload, - const size_t payload_size); -int vncws_decode_frame_header(Buffer *input, - size_t *header_size, - size_t *payload_remain, - WsMask *payload_mask); -int vncws_decode_frame_payload(Buffer *input, - size_t *payload_remain, WsMask *payload_mask, - uint8_t **payload, size_t *payload_size); #endif /* __QEMU_UI_VNC_WS_H */ diff --git a/ui/vnc.c b/ui/vnc.c index 8b8361e371..09756cd7cc 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -1192,8 +1192,6 @@ void vnc_disconnect_finish(VncState *vs) buffer_free(&vs->input); buffer_free(&vs->output); - buffer_free(&vs->ws_input); - buffer_free(&vs->ws_output); qapi_free_VncClientInfo(vs->info); @@ -1352,11 +1350,7 @@ static void vnc_client_write_locked(VncState *vs) } else #endif /* CONFIG_VNC_SASL */ { - if (vs->encode_ws) { - vnc_client_write_ws(vs); - } else { - vnc_client_write_plain(vs); - } + vnc_client_write_plain(vs); } } @@ -1364,7 +1358,7 @@ static void vnc_client_write(VncState *vs) { vnc_lock_output(vs); - if (vs->output.offset || vs->ws_output.offset) { + if (vs->output.offset) { vnc_client_write_locked(vs); } else if (vs->ioc != NULL) { if (vs->ioc_tag) { @@ -1451,18 +1445,7 @@ static void vnc_client_read(VncState *vs) ret = vnc_client_read_sasl(vs); else #endif /* CONFIG_VNC_SASL */ - if (vs->encode_ws) { - ret = vnc_client_read_ws(vs); - if (ret == -1) { - vnc_disconnect_start(vs); - return; - } else if (ret == -2) { - vnc_client_error(vs); - return; - } - } else { - ret = vnc_client_read_plain(vs); - } + ret = vnc_client_read_plain(vs); if (!ret) { if (vs->disconnecting) { vnc_disconnect_finish(vs); @@ -1552,7 +1535,7 @@ void vnc_write_u8(VncState *vs, uint8_t value) void vnc_flush(VncState *vs) { vnc_lock_output(vs); - if (vs->ioc != NULL && (vs->output.offset || vs->ws_output.offset)) { + if (vs->ioc != NULL && vs->output.offset) { vnc_client_write_locked(vs); } vnc_unlock_output(vs); @@ -2950,8 +2933,6 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, buffer_init(&vs->input, "vnc-input/%p", sioc); buffer_init(&vs->output, "vnc-output/%p", sioc); - buffer_init(&vs->ws_input, "vnc-ws_input/%p", sioc); - buffer_init(&vs->ws_output, "vnc-ws_output/%p", sioc); buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%p", sioc); buffer_init(&vs->tight.tight, "vnc-tight/%p", sioc); diff --git a/ui/vnc.h b/ui/vnc.h index 34474d604e..a95cc15678 100644 --- a/ui/vnc.h +++ b/ui/vnc.h @@ -293,10 +293,6 @@ struct VncState Buffer output; Buffer input; - Buffer ws_input; - Buffer ws_output; - size_t ws_payload_remain; - WsMask ws_payload_mask; /* current output mode information */ VncWritePixels *write_pixels; PixelFormat client_pf;