summaryrefslogtreecommitdiffstats
path: root/qemu/ui/vnc.c
diff options
context:
space:
mode:
Diffstat (limited to 'qemu/ui/vnc.c')
-rw-r--r--qemu/ui/vnc.c1220
1 files changed, 655 insertions, 565 deletions
diff --git a/qemu/ui/vnc.c b/qemu/ui/vnc.c
index caf82f56f..d2ebf1fb7 100644
--- a/qemu/ui/vnc.c
+++ b/qemu/ui/vnc.c
@@ -24,6 +24,7 @@
* THE SOFTWARE.
*/
+#include "qemu/osdep.h"
#include "vnc.h"
#include "vnc-jobs.h"
#include "trace.h"
@@ -37,10 +38,13 @@
#include "qapi/qmp/qerror.h"
#include "qapi/qmp/types.h"
#include "qmp-commands.h"
-#include "qemu/osdep.h"
#include "ui/input.h"
#include "qapi-event.h"
#include "crypto/hash.h"
+#include "crypto/tlscredsanon.h"
+#include "crypto/tlscredsx509.h"
+#include "qom/object_interfaces.h"
+#include "qemu/cutils.h"
#define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT
#define VNC_REFRESH_INTERVAL_INC 50
@@ -67,8 +71,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) {
@@ -102,105 +106,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;
+static void vnc_init_basic_info(SocketAddress *addr,
+ VncBasicInfo *info,
+ Error **errp)
+{
+ switch (addr->type) {
+ case SOCKET_ADDRESS_KIND_INET:
+ info->host = g_strdup(addr->u.inet.data->host);
+ info->service = g_strdup(addr->u.inet.data->port);
+ if (addr->u.inet.data->ipv6) {
+ info->family = NETWORK_ADDRESS_FAMILY_IPV6;
+ } else {
+ info->family = NETWORK_ADDRESS_FAMILY_IPV4;
+ }
+ break;
- return addr_to_string(format, &sa, salen);
-}
+ case SOCKET_ADDRESS_KIND_UNIX:
+ info->host = g_strdup("");
+ info->service = g_strdup(addr->u.q_unix.data->path);
+ info->family = NETWORK_ADDRESS_FAMILY_UNIX;
+ break;
-static VncBasicInfo *vnc_basic_info_get(struct sockaddr_storage *sa,
- socklen_t salen)
-{
- VncBasicInfo *info;
- char host[NI_MAXHOST];
- char serv[NI_MAXSERV];
- int err;
-
- 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;
+ default:
+ error_setg(errp, "Unsupported socket kind %d",
+ addr->type);
+ break;
}
- info = g_malloc0(sizeof(VncBasicInfo));
- info->host = g_strdup(host);
- info->service = g_strdup(serv);
- info->family = inet_netfamily(sa->ss_family);
- return info;
+ return;
}
-static VncBasicInfo *vnc_basic_info_get_from_server_addr(int fd)
+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) {
- return NULL;
+ addr = qio_channel_socket_get_local_address(ioc, errp);
+ if (!addr) {
+ return;
}
- return vnc_basic_info_get(&sa, salen);
+ vnc_init_basic_info(addr, info, errp);
+ qapi_free_SocketAddress(addr);
}
-static VncBasicInfo *vnc_basic_info_get_from_remote_addr(int fd)
+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) {
- return NULL;
+ addr = qio_channel_socket_get_remote_address(ioc, errp);
+ if (!addr) {
+ return;
}
- return vnc_basic_info_get(&sa, salen);
+ vnc_init_basic_info(addr, info, errp);
+ qapi_free_SocketAddress(addr);
}
static const char *vnc_auth_name(VncDisplay *vd) {
@@ -222,7 +186,6 @@ static const char *vnc_auth_name(VncDisplay *vd) {
case VNC_AUTH_TLS:
return "tls";
case VNC_AUTH_VENCRYPT:
-#ifdef CONFIG_VNC_TLS
switch (vd->subauth) {
case VNC_AUTH_VENCRYPT_PLAIN:
return "vencrypt+plain";
@@ -245,9 +208,6 @@ static const char *vnc_auth_name(VncDisplay *vd) {
default:
return "vencrypt";
}
-#else
- return "vencrypt";
-#endif
case VNC_AUTH_SASL:
return "sasl";
}
@@ -257,15 +217,18 @@ static const char *vnc_auth_name(VncDisplay *vd) {
static VncServerInfo *vnc_server_info_get(VncDisplay *vd)
{
VncServerInfo *info;
- VncBasicInfo *bi = vnc_basic_info_get_from_server_addr(vd->lsock);
- if (!bi) {
- return NULL;
- }
+ Error *err = NULL;
info = g_malloc(sizeof(*info));
- info->base = bi;
+ vnc_init_basic_info_from_server_addr(vd->lsock,
+ qapi_VncServerInfo_base(info), &err);
info->has_auth = true;
info->auth = g_strdup(vnc_auth_name(vd));
+ if (err) {
+ qapi_free_VncServerInfo(info);
+ info = NULL;
+ error_free(err);
+ }
return info;
}
@@ -275,13 +238,12 @@ static void vnc_client_cache_auth(VncState *client)
return;
}
-#ifdef CONFIG_VNC_TLS
- if (client->tls.session &&
- client->tls.dname) {
- client->info->has_x509_dname = true;
- client->info->x509_dname = g_strdup(client->tls.dname);
+ if (client->tls) {
+ client->info->x509_dname =
+ qcrypto_tls_session_get_peer_name(client->tls);
+ client->info->has_x509_dname =
+ client->info->x509_dname != NULL;
}
-#endif
#ifdef CONFIG_VNC_SASL
if (client->sasl.conn &&
client->sasl.username) {
@@ -293,11 +255,16 @@ static void vnc_client_cache_auth(VncState *client)
static void vnc_client_cache_addr(VncState *client)
{
- VncBasicInfo *bi = vnc_basic_info_get_from_remote_addr(client->csock);
+ Error *err = NULL;
- if (bi) {
- client->info = g_malloc0(sizeof(*client->info));
- client->info->base = bi;
+ client->info = g_malloc0(sizeof(*client->info));
+ vnc_init_basic_info_from_remote_addr(client->sioc,
+ qapi_VncClientInfo_base(client->info),
+ &err);
+ if (err) {
+ qapi_free_VncClientInfo(client->info);
+ client->info = NULL;
+ error_free(err);
}
}
@@ -308,7 +275,6 @@ static void vnc_qmp_event(VncState *vs, QAPIEvent event)
if (!vs->info) {
return;
}
- g_assert(vs->info->base);
si = vnc_server_info_get(vs->vd);
if (!si) {
@@ -317,7 +283,8 @@ static void vnc_qmp_event(VncState *vs, QAPIEvent event)
switch (event) {
case QAPI_EVENT_VNC_CONNECTED:
- qapi_event_send_vnc_connected(si, vs->info->base, &error_abort);
+ qapi_event_send_vnc_connected(si, qapi_VncClientInfo_base(vs->info),
+ &error_abort);
break;
case QAPI_EVENT_VNC_INITIALIZED:
qapi_event_send_vnc_initialized(si, vs->info, &error_abort);
@@ -334,36 +301,26 @@ 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;
+ Error *err = NULL;
- if (getpeername(client->csock, (struct sockaddr *)&sa, &salen) < 0) {
- return NULL;
- }
+ info = g_malloc0(sizeof(*info));
- if (getnameinfo((struct sockaddr *)&sa, salen,
- host, sizeof(host),
- serv, sizeof(serv),
- NI_NUMERICHOST | NI_NUMERICSERV) < 0) {
+ 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 = g_malloc0(sizeof(*info));
- info->base = g_malloc0(sizeof(*info->base));
- info->base->host = g_strdup(host);
- info->base->service = g_strdup(serv);
- info->base->family = inet_netfamily(sa.ss_family);
- info->base->websocket = client->websocket;
+ info->websocket = client->websocket;
-#ifdef CONFIG_VNC_TLS
- if (client->tls.session && client->tls.dname) {
- info->has_x509_dname = true;
- info->x509_dname = g_strdup(client->tls.dname);
+ if (client->tls) {
+ info->x509_dname = qcrypto_tls_session_get_peer_name(client->tls);
+ info->has_x509_dname = info->x509_dname != NULL;
}
-#endif
#ifdef CONFIG_VNC_SASL
if (client->sasl.conn && client->sasl.username) {
info->has_sasl_username = true;
@@ -407,81 +364,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.data->host);
+ info->service = g_strdup(addr->u.inet.data->port);
+ if (addr->u.inet.data->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.data->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];
-
- if (getsockname(socket, (struct sockaddr *)&sa, &salen) < 0 ||
- getnameinfo((struct sockaddr *)&sa, salen,
- host, sizeof(host), serv, sizeof(serv),
- NI_NUMERICHOST | NI_NUMERICSERV) < 0) {
+ Error *err = NULL;
+ SocketAddress *addr;
+
+ 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);
@@ -513,7 +478,6 @@ static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info)
break;
case VNC_AUTH_VENCRYPT:
info->auth = VNC_PRIMARY_AUTH_VENCRYPT;
-#ifdef CONFIG_VNC_TLS
info->has_vencrypt = true;
switch (vd->subauth) {
case VNC_AUTH_VENCRYPT_PLAIN:
@@ -547,7 +511,6 @@ static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info)
info->has_vencrypt = false;
break;
}
-#endif
break;
case VNC_AUTH_SASL:
info->auth = VNC_PRIMARY_AUTH_SASL;
@@ -577,13 +540,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);
@@ -611,10 +574,25 @@ static void framebuffer_update_request(VncState *vs, int incremental,
static void vnc_refresh(DisplayChangeListener *dcl);
static int vnc_refresh_server_surface(VncDisplay *vd);
+static int vnc_width(VncDisplay *vd)
+{
+ return MIN(VNC_MAX_WIDTH, ROUND_UP(surface_width(vd->ds),
+ VNC_DIRTY_PIXELS_PER_BIT));
+}
+
+static int vnc_height(VncDisplay *vd)
+{
+ return MIN(VNC_MAX_HEIGHT, surface_height(vd->ds));
+}
+
static void vnc_set_area_dirty(DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT],
VNC_MAX_WIDTH / VNC_DIRTY_PIXELS_PER_BIT),
- int width, int height,
- int x, int y, int w, int h) {
+ VncDisplay *vd,
+ int x, int y, int w, int h)
+{
+ int width = vnc_width(vd);
+ int height = vnc_height(vd);
+
/* this is needed this to ensure we updated all affected
* blocks if x % VNC_DIRTY_PIXELS_PER_BIT != 0 */
w += (x % VNC_DIRTY_PIXELS_PER_BIT);
@@ -636,10 +614,8 @@ static void vnc_dpy_update(DisplayChangeListener *dcl,
{
VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
struct VncSurface *s = &vd->guest;
- int width = pixman_image_get_width(vd->server);
- int height = pixman_image_get_height(vd->server);
- vnc_set_area_dirty(s->dirty, width, height, x, y, w, h);
+ vnc_set_area_dirty(s->dirty, vd, x, y, w, h);
}
void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
@@ -653,53 +629,10 @@ void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
vnc_write_s32(vs, encoding);
}
-void buffer_reserve(Buffer *buffer, size_t len)
-{
- if ((buffer->capacity - buffer->offset) < len) {
- buffer->capacity += (len + 1024);
- buffer->buffer = g_realloc(buffer->buffer, buffer->capacity);
- }
-}
-
-static int buffer_empty(Buffer *buffer)
-{
- return buffer->offset == 0;
-}
-
-uint8_t *buffer_end(Buffer *buffer)
-{
- return buffer->buffer + buffer->offset;
-}
-
-void buffer_reset(Buffer *buffer)
-{
- buffer->offset = 0;
-}
-
-void buffer_free(Buffer *buffer)
-{
- g_free(buffer->buffer);
- buffer->offset = 0;
- buffer->capacity = 0;
- buffer->buffer = NULL;
-}
-
-void buffer_append(Buffer *buffer, const void *data, size_t len)
-{
- memcpy(buffer->buffer + buffer->offset, data, len);
- buffer->offset += len;
-}
-
-void buffer_advance(Buffer *buf, size_t len)
-{
- memmove(buf->buffer, buf->buffer + len,
- (buf->offset - len));
- buf->offset -= len;
-}
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) &&
@@ -752,6 +685,21 @@ void *vnc_server_fb_ptr(VncDisplay *vd, int x, int y)
return ptr;
}
+static void vnc_update_server_surface(VncDisplay *vd)
+{
+ qemu_pixman_image_unref(vd->server);
+ vd->server = NULL;
+
+ if (QTAILQ_EMPTY(&vd->clients)) {
+ return;
+ }
+
+ vd->server = pixman_image_create_bits(VNC_SERVER_FB_FORMAT,
+ vnc_width(vd),
+ vnc_height(vd),
+ NULL, 0);
+}
+
static void vnc_dpy_switch(DisplayChangeListener *dcl,
DisplaySurface *surface)
{
@@ -760,26 +708,19 @@ static void vnc_dpy_switch(DisplayChangeListener *dcl,
int width, height;
vnc_abort_display_jobs(vd);
+ vd->ds = surface;
/* server surface */
- qemu_pixman_image_unref(vd->server);
- vd->ds = surface;
- width = MIN(VNC_MAX_WIDTH, ROUND_UP(surface_width(vd->ds),
- VNC_DIRTY_PIXELS_PER_BIT));
- height = MIN(VNC_MAX_HEIGHT, surface_height(vd->ds));
- vd->server = pixman_image_create_bits(VNC_SERVER_FB_FORMAT,
- width, height, NULL, 0);
+ vnc_update_server_surface(vd);
/* guest surface */
-#if 0 /* FIXME */
- if (ds_get_bytes_per_pixel(ds) != vd->guest.ds->pf.bytes_per_pixel)
- console_color_init(ds);
-#endif
qemu_pixman_image_unref(vd->guest.fb);
vd->guest.fb = pixman_image_ref(surface->image);
vd->guest.format = surface->format;
+ width = vnc_width(vd);
+ height = vnc_height(vd);
memset(vd->guest.dirty, 0x00, sizeof(vd->guest.dirty));
- vnc_set_area_dirty(vd->guest.dirty, width, height, 0, 0,
+ vnc_set_area_dirty(vd->guest.dirty, vd, 0, 0,
width, height);
QTAILQ_FOREACH(vs, &vd->clients, next) {
@@ -789,7 +730,7 @@ static void vnc_dpy_switch(DisplayChangeListener *dcl,
vnc_cursor_define(vs);
}
memset(vs->dirty, 0x00, sizeof(vs->dirty));
- vnc_set_area_dirty(vs->dirty, width, height, 0, 0,
+ vnc_set_area_dirty(vs->dirty, vd, 0, 0,
width, height);
}
}
@@ -879,6 +820,8 @@ int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
{
int n = 0;
+ bool encode_raw = false;
+ size_t saved_offs = vs->output.offset;
switch(vs->vnc_encoding) {
case VNC_ENCODING_ZLIB:
@@ -901,10 +844,24 @@ int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
n = vnc_zywrle_send_framebuffer_update(vs, x, y, w, h);
break;
default:
- vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW);
- n = vnc_raw_send_framebuffer_update(vs, x, y, w, h);
+ encode_raw = true;
break;
}
+
+ /* If the client has the same pixel format as our internal buffer and
+ * a RAW encoding would need less space fall back to RAW encoding to
+ * save bandwidth and processing power in the client. */
+ if (!encode_raw && vs->write_pixels == vnc_write_pixels_copy &&
+ 12 + h * w * VNC_SERVER_FB_BYTES <= (vs->output.offset - saved_offs)) {
+ vs->output.offset = saved_offs;
+ encode_raw = true;
+ }
+
+ if (encode_raw) {
+ vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW);
+ n = vnc_raw_send_framebuffer_update(vs, x, y, w, h);
+ }
+
return n;
}
@@ -933,6 +890,11 @@ static void vnc_dpy_copy(DisplayChangeListener *dcl,
int i, x, y, pitch, inc, w_lim, s;
int cmp_bytes;
+ if (!vd->server) {
+ /* no client connected */
+ return;
+ }
+
vnc_refresh_server_surface(vd);
QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) {
if (vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) {
@@ -1063,7 +1025,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;
@@ -1127,7 +1089,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);
@@ -1209,12 +1171,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)
@@ -1228,8 +1193,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);
@@ -1237,9 +1200,6 @@ void vnc_disconnect_finish(VncState *vs)
vnc_tight_clear(vs);
vnc_zrle_clear(vs);
-#ifdef CONFIG_VNC_TLS
- vnc_tls_client_cleanup(vs);
-#endif /* CONFIG_VNC_TLS */
#ifdef CONFIG_VNC_SASL
vnc_sasl_client_cleanup(vs);
#endif /* CONFIG_VNC_SASL */
@@ -1249,6 +1209,10 @@ void vnc_disconnect_finish(VncState *vs)
if (vs->initialized) {
QTAILQ_REMOVE(&vs->vd->clients, vs, next);
qemu_remove_mouse_mode_change_notifier(&vs->mouse_mode_notifier);
+ if (QTAILQ_EMPTY(&vs->vd->clients)) {
+ /* last client gone */
+ vnc_update_server_surface(vs->vd);
+ }
}
if (vs->vd->lock_key_sync)
@@ -1265,29 +1229,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);
}
-int vnc_client_io_error(VncState *vs, int 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 %d, errno %d\n",
- ret, ret < 0 ? last_errno : 0);
vnc_disconnect_start(vs);
-
+ if (errp) {
+ error_free(*errp);
+ *errp = NULL;
+ }
return 0;
}
return ret;
@@ -1300,23 +1264,6 @@ void vnc_client_error(VncState *vs)
vnc_disconnect_start(vs);
}
-#ifdef CONFIG_VNC_TLS
-static long vnc_client_write_tls(gnutls_session_t *session,
- const uint8_t *data,
- size_t datalen)
-{
- long ret = gnutls_write(*session, data, datalen);
- if (ret < 0) {
- if (ret == GNUTLS_E_AGAIN) {
- errno = EAGAIN;
- } else {
- errno = EIO;
- }
- ret = -1;
- }
- return ret;
-}
-#endif /* CONFIG_VNC_TLS */
/*
* Called to write a chunk of data to the client socket. The data may
@@ -1333,20 +1280,14 @@ static long vnc_client_write_tls(gnutls_session_t *session,
* the requested 'datalen' if the socket would block. Returns
* -1 on error, and disconnects the client socket.
*/
-long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
+ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
{
- long ret;
-#ifdef CONFIG_VNC_TLS
- if (vs->tls.session) {
- ret = vnc_client_write_tls(&vs->tls.session, data, datalen);
- } else {
-#endif /* CONFIG_VNC_TLS */
- ret = send(vs->csock, (const void *)data, datalen, 0);
-#ifdef CONFIG_VNC_TLS
- }
-#endif /* CONFIG_VNC_TLS */
+ Error *err = NULL;
+ ssize_t ret;
+ 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, socket_error());
+ return vnc_client_io_error(vs, ret, &err);
}
@@ -1360,9 +1301,9 @@ long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
* the buffered output data if the socket would block. Returns
* -1 on error, and disconnects the client socket.
*/
-static long vnc_client_write_plain(VncState *vs)
+static ssize_t vnc_client_write_plain(VncState *vs)
{
- long ret;
+ ssize_t ret;
#ifdef CONFIG_VNC_SASL
VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF %d\n",
@@ -1384,7 +1325,11 @@ static long 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;
@@ -1396,10 +1341,8 @@ static long 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 &&
@@ -1408,23 +1351,22 @@ static void vnc_client_write_locked(void *opaque)
} 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);
}
}
-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);
+ if (vs->output.offset) {
+ 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);
}
@@ -1435,22 +1377,6 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
vs->read_handler_expect = expecting;
}
-#ifdef CONFIG_VNC_TLS
-static long vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
- size_t datalen)
-{
- long ret = gnutls_read(*session, data, datalen);
- if (ret < 0) {
- if (ret == GNUTLS_E_AGAIN) {
- errno = EAGAIN;
- } else {
- errno = EIO;
- }
- ret = -1;
- }
- return ret;
-}
-#endif /* CONFIG_VNC_TLS */
/*
* Called to read a chunk of data from the client socket. The data may
@@ -1467,20 +1393,14 @@ static long vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
* the requested 'datalen' if the socket would block. Returns
* -1 on error, and disconnects the client socket.
*/
-long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
+ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
{
- long ret;
-#ifdef CONFIG_VNC_TLS
- if (vs->tls.session) {
- ret = vnc_client_read_tls(&vs->tls.session, data, datalen);
- } else {
-#endif /* CONFIG_VNC_TLS */
- ret = qemu_recv(vs->csock, data, datalen, 0);
-#ifdef CONFIG_VNC_TLS
- }
-#endif /* CONFIG_VNC_TLS */
+ ssize_t ret;
+ Error *err = NULL;
+ 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, socket_error());
+ return vnc_client_io_error(vs, ret, &err);
}
@@ -1492,9 +1412,9 @@ long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
* Returns the number of bytes read. Returns -1 on error, and
* disconnects the client socket.
*/
-static long vnc_client_read_plain(VncState *vs)
+static ssize_t vnc_client_read_plain(VncState *vs)
{
- int ret;
+ ssize_t ret;
VNC_DEBUG("Read plain %p size %zd offset %zd\n",
vs->input.buffer, vs->input.capacity, vs->input.offset);
buffer_reserve(&vs->input, 4096);
@@ -1517,31 +1437,20 @@ 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;
- long ret;
+ ssize_t ret;
#ifdef CONFIG_VNC_SASL
if (vs->sasl.conn && vs->sasl.runSSF)
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->csock == -1)
+ if (vs->disconnecting) {
vnc_disconnect_finish(vs);
+ }
return;
}
@@ -1550,7 +1459,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;
}
@@ -1563,12 +1472,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);
@@ -1609,8 +1536,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) {
vnc_client_write_locked(vs);
}
vnc_unlock_output(vs);
@@ -1664,7 +1590,7 @@ static void check_pointer_type_change(Notifier *notifier, void *data)
static void pointer_event(VncState *vs, int button_mask, int x, int y)
{
- static uint32_t bmap[INPUT_BUTTON_MAX] = {
+ static uint32_t bmap[INPUT_BUTTON__MAX] = {
[INPUT_BUTTON_LEFT] = 0x01,
[INPUT_BUTTON_MIDDLE] = 0x02,
[INPUT_BUTTON_RIGHT] = 0x04,
@@ -2024,9 +1950,6 @@ static void ext_key_event(VncState *vs, int down,
static void framebuffer_update_request(VncState *vs, int incremental,
int x, int y, int w, int h)
{
- int width = pixman_image_get_width(vs->vd->server);
- int height = pixman_image_get_height(vs->vd->server);
-
vs->need_update = 1;
if (incremental) {
@@ -2034,7 +1957,7 @@ static void framebuffer_update_request(VncState *vs, int incremental,
}
vs->force_update = 1;
- vnc_set_area_dirty(vs->dirty, width, height, x, y, w, h);
+ vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h);
}
static void send_ext_key_event_ack(VncState *vs)
@@ -2124,6 +2047,9 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
break;
case VNC_ENCODING_RICH_CURSOR:
vs->features |= VNC_FEATURE_RICH_CURSOR_MASK;
+ if (vs->vd->cursor) {
+ vnc_cursor_define(vs);
+ }
break;
case VNC_ENCODING_EXT_KEY_EVENT:
send_ext_key_event_ack(vs);
@@ -2189,15 +2115,15 @@ static void set_pixel_format(VncState *vs,
return;
}
- vs->client_pf.rmax = red_max;
+ vs->client_pf.rmax = red_max ? red_max : 0xFF;
vs->client_pf.rbits = hweight_long(red_max);
vs->client_pf.rshift = red_shift;
vs->client_pf.rmask = red_max << red_shift;
- vs->client_pf.gmax = green_max;
+ vs->client_pf.gmax = green_max ? green_max : 0xFF;
vs->client_pf.gbits = hweight_long(green_max);
vs->client_pf.gshift = green_shift;
vs->client_pf.gmask = green_max << green_shift;
- vs->client_pf.bmax = blue_max;
+ vs->client_pf.bmax = blue_max ? blue_max : 0xFF;
vs->client_pf.bbits = hweight_long(blue_max);
vs->client_pf.bshift = blue_shift;
vs->client_pf.bmask = blue_max << blue_shift;
@@ -2631,12 +2557,10 @@ static int protocol_client_auth(VncState *vs, uint8_t *data, size_t len)
start_auth_vnc(vs);
break;
-#ifdef CONFIG_VNC_TLS
case VNC_AUTH_VENCRYPT:
VNC_DEBUG("Accept VeNCrypt auth\n");
start_auth_vencrypt(vs);
break;
-#endif /* CONFIG_VNC_TLS */
#ifdef CONFIG_VNC_SASL
case VNC_AUTH_SASL:
@@ -2999,15 +2923,36 @@ 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_malloc0(sizeof(VncState));
+ 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/%p", sioc);
+ buffer_init(&vs->output, "vnc-output/%p", sioc);
+ buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%p", sioc);
+
+ 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/%p", sioc);
+#endif
+#ifdef CONFIG_VNC_PNG
+ buffer_init(&vs->tight.png, "vnc-tight-png/%p", sioc);
+#endif
+ 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;
vs->subauth = VNC_AUTH_INVALID;
@@ -3020,30 +2965,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_malloc0(VNC_STAT_COLS * sizeof (uint8_t));
+ 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;
-#ifdef CONFIG_VNC_TLS
if (vd->ws_tls) {
- qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io, NULL, vs);
- } else
-#endif /* CONFIG_VNC_TLS */
- {
- qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs);
+ vs->ioc_tag = qio_channel_add_watch(
+ vs->ioc, G_IO_IN, vncws_tls_handshake_io, vs, NULL);
+ } else {
+ 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);
@@ -3068,6 +3012,7 @@ void vnc_init_state(VncState *vs)
{
vs->initialized = true;
VncDisplay *vd = vs->vd;
+ bool first_client = QTAILQ_EMPTY(&vd->clients);
vs->last_x = -1;
vs->last_y = -1;
@@ -3081,6 +3026,9 @@ void vnc_init_state(VncState *vs)
vs->bh = qemu_bh_new(vnc_jobs_bh, vs);
QTAILQ_INSERT_TAIL(&vd->clients, vs, next);
+ if (first_client) {
+ vnc_update_server_surface(vd);
+ }
graphic_hw_update(vd->dcl.con);
@@ -3097,35 +3045,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);
- }
-
- if (csock != -1) {
- socket_set_nodelay(csock);
- vnc_connect(vs, csock, false, websocket);
+ /* client probably closed connection before we got there */
+ error_free(err);
}
-}
-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 = {
@@ -3151,9 +3092,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;
@@ -3181,22 +3119,29 @@ 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;
-#ifdef CONFIG_VNC_TLS
- vs->tls.x509verify = 0;
-#endif
+ if (vs->tlscreds) {
+ object_unparent(OBJECT(vs->tlscreds));
+ vs->tlscreds = NULL;
+ }
+ g_free(vs->tlsaclname);
+ vs->tlsaclname = NULL;
}
int vnc_display_password(const char *id, const char *password)
@@ -3233,9 +3178,26 @@ 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.data->host,
+ addr->u.inet.data->port);
+ qapi_free_SocketAddress(addr);
+
+ return ret;
}
static QemuOptsList qemu_vnc_opts = {
@@ -3250,6 +3212,10 @@ static QemuOptsList qemu_vnc_opts = {
.name = "websocket",
.type = QEMU_OPT_STRING,
},{
+ .name = "tls-creds",
+ .type = QEMU_OPT_STRING,
+ },{
+ /* Deprecated in favour of tls-creds */
.name = "x509",
.type = QEMU_OPT_STRING,
},{
@@ -3286,9 +3252,11 @@ static QemuOptsList qemu_vnc_opts = {
.name = "sasl",
.type = QEMU_OPT_BOOL,
},{
+ /* Deprecated in favour of tls-creds */
.name = "tls",
.type = QEMU_OPT_BOOL,
},{
+ /* Deprecated in favour of tls-creds */
.name = "x509verify",
.type = QEMU_OPT_STRING,
},{
@@ -3306,13 +3274,12 @@ static QemuOptsList qemu_vnc_opts = {
};
-static void
+static int
vnc_display_setup_auth(VncDisplay *vs,
bool password,
bool sasl,
- bool tls,
- bool x509,
- bool websocket)
+ bool websocket,
+ Error **errp)
{
/*
* We have a choice of 3 authentication options
@@ -3362,17 +3329,24 @@ vnc_display_setup_auth(VncDisplay *vs,
* result has the same security characteristics.
*/
if (password) {
- if (tls) {
+ if (vs->tlscreds) {
vs->auth = VNC_AUTH_VENCRYPT;
if (websocket) {
vs->ws_tls = true;
}
- if (x509) {
+ if (object_dynamic_cast(OBJECT(vs->tlscreds),
+ TYPE_QCRYPTO_TLS_CREDS_X509)) {
VNC_DEBUG("Initializing VNC server with x509 password auth\n");
vs->subauth = VNC_AUTH_VENCRYPT_X509VNC;
- } else {
+ } else if (object_dynamic_cast(OBJECT(vs->tlscreds),
+ TYPE_QCRYPTO_TLS_CREDS_ANON)) {
VNC_DEBUG("Initializing VNC server with TLS password auth\n");
vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC;
+ } else {
+ error_setg(errp,
+ "Unsupported TLS cred type %s",
+ object_get_typename(OBJECT(vs->tlscreds)));
+ return -1;
}
} else {
VNC_DEBUG("Initializing VNC server with password auth\n");
@@ -3385,17 +3359,24 @@ vnc_display_setup_auth(VncDisplay *vs,
vs->ws_auth = VNC_AUTH_INVALID;
}
} else if (sasl) {
- if (tls) {
+ if (vs->tlscreds) {
vs->auth = VNC_AUTH_VENCRYPT;
if (websocket) {
vs->ws_tls = true;
}
- if (x509) {
+ if (object_dynamic_cast(OBJECT(vs->tlscreds),
+ TYPE_QCRYPTO_TLS_CREDS_X509)) {
VNC_DEBUG("Initializing VNC server with x509 SASL auth\n");
vs->subauth = VNC_AUTH_VENCRYPT_X509SASL;
- } else {
+ } else if (object_dynamic_cast(OBJECT(vs->tlscreds),
+ TYPE_QCRYPTO_TLS_CREDS_ANON)) {
VNC_DEBUG("Initializing VNC server with TLS SASL auth\n");
vs->subauth = VNC_AUTH_VENCRYPT_TLSSASL;
+ } else {
+ error_setg(errp,
+ "Unsupported TLS cred type %s",
+ object_get_typename(OBJECT(vs->tlscreds)));
+ return -1;
}
} else {
VNC_DEBUG("Initializing VNC server with SASL auth\n");
@@ -3408,17 +3389,24 @@ vnc_display_setup_auth(VncDisplay *vs,
vs->ws_auth = VNC_AUTH_INVALID;
}
} else {
- if (tls) {
+ if (vs->tlscreds) {
vs->auth = VNC_AUTH_VENCRYPT;
if (websocket) {
vs->ws_tls = true;
}
- if (x509) {
+ if (object_dynamic_cast(OBJECT(vs->tlscreds),
+ TYPE_QCRYPTO_TLS_CREDS_X509)) {
VNC_DEBUG("Initializing VNC server with x509 no auth\n");
vs->subauth = VNC_AUTH_VENCRYPT_X509NONE;
- } else {
+ } else if (object_dynamic_cast(OBJECT(vs->tlscreds),
+ TYPE_QCRYPTO_TLS_CREDS_ANON)) {
VNC_DEBUG("Initializing VNC server with TLS no auth\n");
vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE;
+ } else {
+ error_setg(errp,
+ "Unsupported TLS cred type %s",
+ object_get_typename(OBJECT(vs->tlscreds)));
+ return -1;
}
} else {
VNC_DEBUG("Initializing VNC server with no auth\n");
@@ -3431,34 +3419,72 @@ vnc_display_setup_auth(VncDisplay *vs,
vs->ws_auth = VNC_AUTH_INVALID;
}
}
+ return 0;
}
+
+/*
+ * Handle back compat with old CLI syntax by creating some
+ * suitable QCryptoTLSCreds objects
+ */
+static QCryptoTLSCreds *
+vnc_display_create_creds(bool x509,
+ bool x509verify,
+ const char *dir,
+ const char *id,
+ Error **errp)
+{
+ gchar *credsid = g_strdup_printf("tlsvnc%s", id);
+ Object *parent = object_get_objects_root();
+ Object *creds;
+ Error *err = NULL;
+
+ if (x509) {
+ creds = object_new_with_props(TYPE_QCRYPTO_TLS_CREDS_X509,
+ parent,
+ credsid,
+ &err,
+ "endpoint", "server",
+ "dir", dir,
+ "verify-peer", x509verify ? "yes" : "no",
+ NULL);
+ } else {
+ creds = object_new_with_props(TYPE_QCRYPTO_TLS_CREDS_ANON,
+ parent,
+ credsid,
+ &err,
+ "endpoint", "server",
+ NULL);
+ }
+
+ g_free(credsid);
+
+ if (err) {
+ error_propagate(errp, err);
+ return NULL;
+ }
+
+ return QCRYPTO_TLS_CREDS(creds);
+}
+
+
void vnc_display_open(const char *id, Error **errp)
{
VncDisplay *vs = vnc_display_find(id);
QemuOpts *opts = qemu_opts_find(&qemu_vnc_opts, id);
- QemuOpts *sopts, *wsopts;
+ SocketAddress *saddr = NULL, *wsaddr = NULL;
const char *share, *device_id;
QemuConsole *con;
bool password = false;
bool reverse = false;
const char *vnc;
- const char *has_to;
char *h;
- bool has_ipv4 = false;
- bool has_ipv6 = false;
- const char *websocket;
- bool tls = false, x509 = false;
-#ifdef CONFIG_VNC_TLS
- const char *path;
-#endif
+ const char *credid;
bool sasl = false;
#ifdef CONFIG_VNC_SASL
int saslErr;
#endif
-#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
int acl = 0;
-#endif
int lock_key_sync = 1;
if (!vs) {
@@ -3475,44 +3501,90 @@ void vnc_display_open(const char *id, Error **errp)
return;
}
- sopts = qemu_opts_create(&socket_optslist, NULL, 0, &error_abort);
- wsopts = qemu_opts_create(&socket_optslist, NULL, 0, &error_abort);
-
h = strrchr(vnc, ':');
if (h) {
- char *host;
size_t hlen = h - vnc;
- if (vnc[0] == '[' && vnc[hlen - 1] == ']') {
- host = g_strndup(vnc + 1, hlen - 2);
+ const char *websocket = qemu_opt_get(opts, "websocket");
+ int to = qemu_opt_get_number(opts, "to", 0);
+ bool has_ipv4 = qemu_opt_get(opts, "ipv4");
+ bool has_ipv6 = qemu_opt_get(opts, "ipv6");
+ bool ipv4 = qemu_opt_get_bool(opts, "ipv4", false);
+ bool ipv6 = qemu_opt_get_bool(opts, "ipv6", false);
+
+ saddr = g_new0(SocketAddress, 1);
+ if (websocket) {
+ if (!qcrypto_hash_supports(QCRYPTO_HASH_ALG_SHA1)) {
+ error_setg(errp,
+ "SHA1 hash support is required for websockets");
+ goto fail;
+ }
+
+ wsaddr = g_new0(SocketAddress, 1);
+ vs->ws_enabled = true;
+ }
+
+ if (strncmp(vnc, "unix:", 5) == 0) {
+ saddr->type = SOCKET_ADDRESS_KIND_UNIX;
+ saddr->u.q_unix.data = g_new0(UnixSocketAddress, 1);
+ saddr->u.q_unix.data->path = g_strdup(vnc + 5);
+
+ if (vs->ws_enabled) {
+ error_setg(errp, "UNIX sockets not supported with websock");
+ goto fail;
+ }
} else {
- host = g_strndup(vnc, hlen);
+ unsigned long long baseport;
+ InetSocketAddress *inet;
+ saddr->type = SOCKET_ADDRESS_KIND_INET;
+ inet = saddr->u.inet.data = g_new0(InetSocketAddress, 1);
+ if (vnc[0] == '[' && vnc[hlen - 1] == ']') {
+ inet->host = g_strndup(vnc + 1, hlen - 2);
+ } else {
+ inet->host = g_strndup(vnc, hlen);
+ }
+ if (parse_uint_full(h + 1, &baseport, 10) < 0) {
+ error_setg(errp, "can't convert to a number: %s", h + 1);
+ goto fail;
+ }
+ if (baseport > 65535 ||
+ baseport + 5900 > 65535) {
+ error_setg(errp, "port %s out of range", h + 1);
+ goto fail;
+ }
+ inet->port = g_strdup_printf(
+ "%d", (int)baseport + 5900);
+
+ if (to) {
+ inet->has_to = true;
+ inet->to = to + 5900;
+ }
+ inet->ipv4 = ipv4;
+ inet->has_ipv4 = has_ipv4;
+ inet->ipv6 = ipv6;
+ inet->has_ipv6 = has_ipv6;
+
+ if (vs->ws_enabled) {
+ wsaddr->type = SOCKET_ADDRESS_KIND_INET;
+ inet = wsaddr->u.inet.data = g_new0(InetSocketAddress, 1);
+ inet->host = g_strdup(saddr->u.inet.data->host);
+ inet->port = g_strdup(websocket);
+
+ if (to) {
+ inet->has_to = true;
+ inet->to = to;
+ }
+ inet->ipv4 = ipv4;
+ inet->has_ipv4 = has_ipv4;
+ inet->ipv6 = ipv6;
+ inet->has_ipv6 = has_ipv6;
+ }
}
- qemu_opt_set(sopts, "host", host, &error_abort);
- qemu_opt_set(wsopts, "host", host, &error_abort);
- qemu_opt_set(sopts, "port", h+1, &error_abort);
- g_free(host);
} else {
error_setg(errp, "no vnc port specified");
goto fail;
}
- has_to = qemu_opt_get(opts, "to");
- has_ipv4 = qemu_opt_get_bool(opts, "ipv4", false);
- has_ipv6 = qemu_opt_get_bool(opts, "ipv6", false);
- if (has_to) {
- qemu_opt_set(sopts, "to", has_to, &error_abort);
- qemu_opt_set(wsopts, "to", has_to, &error_abort);
- }
- if (has_ipv4) {
- qemu_opt_set(sopts, "ipv4", "on", &error_abort);
- qemu_opt_set(wsopts, "ipv4", "on", &error_abort);
- }
- if (has_ipv6) {
- qemu_opt_set(sopts, "ipv6", "on", &error_abort);
- qemu_opt_set(wsopts, "ipv6", "on", &error_abort);
- }
-
password = qemu_opt_get_bool(opts, "password", false);
if (password) {
if (fips_get_state()) {
@@ -3539,32 +3611,67 @@ void vnc_display_open(const char *id, Error **errp)
goto fail;
}
#endif /* CONFIG_VNC_SASL */
- tls = qemu_opt_get_bool(opts, "tls", false);
-#ifdef CONFIG_VNC_TLS
- path = qemu_opt_get(opts, "x509");
- if (!path) {
- path = qemu_opt_get(opts, "x509verify");
- if (path) {
- vs->tls.x509verify = true;
- }
- }
- if (path) {
- x509 = true;
- if (vnc_tls_set_x509_creds_dir(vs, path) < 0) {
- error_setg(errp, "Failed to find x509 certificates/keys in %s",
- path);
+ credid = qemu_opt_get(opts, "tls-creds");
+ if (credid) {
+ Object *creds;
+ if (qemu_opt_get(opts, "tls") ||
+ qemu_opt_get(opts, "x509") ||
+ qemu_opt_get(opts, "x509verify")) {
+ error_setg(errp,
+ "'tls-creds' parameter is mutually exclusive with "
+ "'tls', 'x509' and 'x509verify' parameters");
goto fail;
}
+
+ creds = object_resolve_path_component(
+ object_get_objects_root(), credid);
+ if (!creds) {
+ error_setg(errp, "No TLS credentials with id '%s'",
+ credid);
+ goto fail;
+ }
+ vs->tlscreds = (QCryptoTLSCreds *)
+ object_dynamic_cast(creds,
+ TYPE_QCRYPTO_TLS_CREDS);
+ if (!vs->tlscreds) {
+ error_setg(errp, "Object with id '%s' is not TLS credentials",
+ credid);
+ goto fail;
+ }
+ object_ref(OBJECT(vs->tlscreds));
+
+ if (vs->tlscreds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+ error_setg(errp,
+ "Expecting TLS credentials with a server endpoint");
+ goto fail;
+ }
+ } else {
+ const char *path;
+ bool tls = false, x509 = false, x509verify = false;
+ tls = qemu_opt_get_bool(opts, "tls", false);
+ if (tls) {
+ path = qemu_opt_get(opts, "x509");
+
+ if (path) {
+ x509 = true;
+ } else {
+ path = qemu_opt_get(opts, "x509verify");
+ if (path) {
+ x509 = true;
+ x509verify = true;
+ }
+ }
+ vs->tlscreds = vnc_display_create_creds(x509,
+ x509verify,
+ path,
+ vs->id,
+ errp);
+ if (!vs->tlscreds) {
+ goto fail;
+ }
+ }
}
-#else /* ! CONFIG_VNC_TLS */
- if (tls) {
- error_setg(errp, "VNC TLS auth requires gnutls support");
- goto fail;
- }
-#endif /* ! CONFIG_VNC_TLS */
-#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
acl = qemu_opt_get_bool(opts, "acl", false);
-#endif
share = qemu_opt_get(opts, "share");
if (share) {
@@ -3583,16 +3690,6 @@ void vnc_display_open(const char *id, Error **errp)
}
vs->connections_limit = qemu_opt_get_number(opts, "connections", 32);
- websocket = qemu_opt_get(opts, "websocket");
- if (websocket) {
- vs->ws_enabled = true;
- qemu_opt_set(wsopts, "port", websocket, &error_abort);
- if (!qcrypto_hash_supports(QCRYPTO_HASH_ALG_SHA1)) {
- error_setg(errp, "SHA1 hash support is required for websockets");
- goto fail;
- }
- }
-
#ifdef CONFIG_VNC_JPEG
vs->lossy = qemu_opt_get_bool(opts, "lossy", false);
#endif
@@ -3604,19 +3701,14 @@ void vnc_display_open(const char *id, Error **errp)
vs->non_adaptive = true;
}
-#ifdef CONFIG_VNC_TLS
- if (acl && x509 && vs->tls.x509verify) {
- char *aclname;
-
+ if (acl) {
if (strcmp(vs->id, "default") == 0) {
- aclname = g_strdup("vnc.x509dname");
+ vs->tlsaclname = g_strdup("vnc.x509dname");
} else {
- aclname = g_strdup_printf("vnc.%s.x509dname", vs->id);
+ vs->tlsaclname = g_strdup_printf("vnc.%s.x509dname", vs->id);
}
- vs->tls.acl = qemu_acl_init(aclname);
- g_free(aclname);
+ qemu_acl_init(vs->tlsaclname);
}
-#endif
#ifdef CONFIG_VNC_SASL
if (acl && sasl) {
char *aclname;
@@ -3631,7 +3723,9 @@ void vnc_display_open(const char *id, Error **errp)
}
#endif
- vnc_display_setup_auth(vs, password, sasl, tls, x509, websocket);
+ if (vnc_display_setup_auth(vs, password, sasl, vs->ws_enabled, errp) < 0) {
+ goto fail;
+ }
#ifdef CONFIG_VNC_SASL
if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) {
@@ -3644,19 +3738,12 @@ void vnc_display_open(const char *id, Error **errp)
device_id = qemu_opt_get(opts, "display");
if (device_id) {
- DeviceState *dev;
int head = qemu_opt_get_number(opts, "head", 0);
+ Error *err = NULL;
- dev = qdev_find_recursive(sysbus_get_default(), device_id);
- if (dev == NULL) {
- error_setg(errp, "Device '%s' not found", device_id);
- goto fail;
- }
-
- con = qemu_console_lookup_by_device(dev, head);
- if (con == NULL) {
- error_setg(errp, "Device %s is not bound to a QemuConsole",
- device_id);
+ con = qemu_console_lookup_by_device_name(device_id, head, &err);
+ if (err) {
+ error_propagate(errp, err);
goto fail;
}
} else {
@@ -3671,56 +3758,55 @@ void vnc_display_open(const char *id, Error **errp)
if (reverse) {
/* connect to viewer */
- int csock;
- vs->lsock = -1;
- vs->lwebsock = -1;
- if (strncmp(vnc, "unix:", 5) == 0) {
- csock = unix_connect(vnc+5, errp);
- } else {
- csock = inet_connect(vnc, errp);
+ QIOChannelSocket *sioc = NULL;
+ vs->lsock = NULL;
+ vs->lwebsock = NULL;
+ if (vs->ws_enabled) {
+ error_setg(errp, "Cannot use websockets in reverse mode");
+ goto fail;
}
- 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;
}
- vnc_connect(vs, csock, false, false);
+ vnc_connect(vs, sioc, false, false);
+ object_unref(OBJECT(sioc));
} else {
- /* listen for connects */
- if (strncmp(vnc, "unix:", 5) == 0) {
- vs->lsock = unix_listen(vnc+5, NULL, 0, errp);
- if (vs->lsock < 0) {
- goto fail;
- }
- vs->is_unix = true;
- } else {
- vs->lsock = inet_listen_opts(sopts, 5900, 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 = 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;
}
- if (vs->ws_enabled) {
- vs->lwebsock = inet_listen_opts(wsopts, 0, errp);
- if (vs->lwebsock < 0) {
- if (vs->lsock != -1) {
- close(vs->lsock);
- vs->lsock = -1;
- }
- 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);
}
}
- qemu_opts_del(sopts);
- qemu_opts_del(wsopts);
+
+ qapi_free_SocketAddress(saddr);
+ qapi_free_SocketAddress(wsaddr);
return;
fail:
- qemu_opts_del(sopts);
- qemu_opts_del(wsopts);
+ qapi_free_SocketAddress(saddr);
+ qapi_free_SocketAddress(wsaddr);
vs->enabled = false;
vs->ws_enabled = false;
}
@@ -3728,11 +3814,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)
@@ -3775,9 +3867,7 @@ int vnc_init_func(void *opaque, QemuOpts *opts, Error **errp)
vnc_display_init(id);
vnc_display_open(id, &local_err);
if (local_err != NULL) {
- error_report("Failed to start VNC server: %s",
- error_get_pretty(local_err));
- error_free(local_err);
+ error_reportf_err(local_err, "Failed to start VNC server: ");
exit(1);
}
return 0;
@@ -3787,4 +3877,4 @@ static void vnc_register_config(void)
{
qemu_add_opts(&qemu_vnc_opts);
}
-machine_init(vnc_register_config);
+opts_init(vnc_register_config);