#include "vnc.h"
#include "vnc-jobs.h"
#include "trace.h"
+#include "hw/qdev.h"
#include "sysemu/sysemu.h"
#include "qemu/sockets.h"
#include "qemu/timer.h"
#include "qemu/acl.h"
+#include "qemu/config-file.h"
#include "qapi/qmp/types.h"
#include "qmp-commands.h"
#include "qemu/osdep.h"
#include "ui/input.h"
+#include "qapi-event.h"
#define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT
#define VNC_REFRESH_INTERVAL_INC 50
#include "vnc_keysym.h"
#include "d3des.h"
-static VncDisplay *vnc_display; /* needed for info vnc */
+static QTAILQ_HEAD(, VncDisplay) vnc_displays =
+ QTAILQ_HEAD_INITIALIZER(vnc_displays);
static int vnc_cursor_define(VncState *vs);
static void vnc_release_modifiers(VncState *vs);
vs->csock, mn[vs->share_mode], mn[mode]);
#endif
- if (vs->share_mode == VNC_SHARE_MODE_EXCLUSIVE) {
+ switch (vs->share_mode) {
+ case VNC_SHARE_MODE_CONNECTING:
+ vs->vd->num_connecting--;
+ break;
+ case VNC_SHARE_MODE_SHARED:
+ vs->vd->num_shared--;
+ break;
+ case VNC_SHARE_MODE_EXCLUSIVE:
vs->vd->num_exclusive--;
+ break;
+ default:
+ break;
}
+
vs->share_mode = mode;
- if (vs->share_mode == VNC_SHARE_MODE_EXCLUSIVE) {
+
+ switch (vs->share_mode) {
+ case VNC_SHARE_MODE_CONNECTING:
+ vs->vd->num_connecting++;
+ break;
+ case VNC_SHARE_MODE_SHARED:
+ vs->vd->num_shared++;
+ break;
+ case VNC_SHARE_MODE_EXCLUSIVE:
vs->vd->num_exclusive++;
+ break;
+ default:
+ break;
}
}
return addr_to_string(format, &sa, salen);
}
-static int put_addr_qdict(QDict *qdict, struct sockaddr_storage *sa,
- socklen_t salen)
+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;
NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
VNC_DEBUG("Cannot resolve address %d: %s\n",
err, gai_strerror(err));
- return -1;
+ return NULL;
}
- qdict_put(qdict, "host", qstring_from_str(host));
- qdict_put(qdict, "service", qstring_from_str(serv));
- qdict_put(qdict, "family",qstring_from_str(inet_strfamily(sa->ss_family)));
-
- return 0;
+ info = g_malloc0(sizeof(VncBasicInfo));
+ info->host = g_strdup(host);
+ info->service = g_strdup(serv);
+ info->family = inet_netfamily(sa->ss_family);
+ return info;
}
-static int vnc_server_addr_put(QDict *qdict, int fd)
+static VncBasicInfo *vnc_basic_info_get_from_server_addr(int fd)
{
struct sockaddr_storage sa;
socklen_t salen;
salen = sizeof(sa);
if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0) {
- return -1;
+ return NULL;
}
- return put_addr_qdict(qdict, &sa, salen);
+ return vnc_basic_info_get(&sa, salen);
}
-static int vnc_qdict_remote_addr(QDict *qdict, int fd)
+static VncBasicInfo *vnc_basic_info_get_from_remote_addr(int fd)
{
struct sockaddr_storage sa;
socklen_t salen;
salen = sizeof(sa);
if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0) {
- return -1;
+ return NULL;
}
- return put_addr_qdict(qdict, &sa, salen);
+ return vnc_basic_info_get(&sa, salen);
}
static const char *vnc_auth_name(VncDisplay *vd) {
return "unknown";
}
-static int vnc_server_info_put(QDict *qdict)
+static VncServerInfo *vnc_server_info_get(VncDisplay *vd)
{
- if (vnc_server_addr_put(qdict, vnc_display->lsock) < 0) {
- return -1;
+ VncServerInfo *info;
+ VncBasicInfo *bi = vnc_basic_info_get_from_server_addr(vd->lsock);
+ if (!bi) {
+ return NULL;
}
- qdict_put(qdict, "auth", qstring_from_str(vnc_auth_name(vnc_display)));
- return 0;
+ info = g_malloc(sizeof(*info));
+ info->base = bi;
+ info->has_auth = true;
+ info->auth = g_strdup(vnc_auth_name(vd));
+ return info;
}
static void vnc_client_cache_auth(VncState *client)
{
-#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
- QDict *qdict;
-#endif
-
if (!client->info) {
return;
}
-#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
- qdict = qobject_to_qdict(client->info);
-#endif
-
#ifdef CONFIG_VNC_TLS
if (client->tls.session &&
client->tls.dname) {
- qdict_put(qdict, "x509_dname", qstring_from_str(client->tls.dname));
+ client->info->has_x509_dname = true;
+ client->info->x509_dname = g_strdup(client->tls.dname);
}
#endif
#ifdef CONFIG_VNC_SASL
if (client->sasl.conn &&
client->sasl.username) {
- qdict_put(qdict, "sasl_username",
- qstring_from_str(client->sasl.username));
+ client->info->has_sasl_username = true;
+ client->info->sasl_username = g_strdup(client->sasl.username);
}
#endif
}
static void vnc_client_cache_addr(VncState *client)
{
- QDict *qdict;
+ VncBasicInfo *bi = vnc_basic_info_get_from_remote_addr(client->csock);
- qdict = qdict_new();
- if (vnc_qdict_remote_addr(qdict, client->csock) < 0) {
- QDECREF(qdict);
- /* XXX: how to report the error? */
- return;
+ if (bi) {
+ client->info = g_malloc0(sizeof(*client->info));
+ client->info->base = bi;
}
-
- client->info = QOBJECT(qdict);
}
-static void vnc_qmp_event(VncState *vs, MonitorEvent event)
+static void vnc_qmp_event(VncState *vs, QAPIEvent event)
{
- QDict *server;
- QObject *data;
+ VncServerInfo *si;
if (!vs->info) {
return;
}
+ g_assert(vs->info->base);
- server = qdict_new();
- if (vnc_server_info_put(server) < 0) {
- QDECREF(server);
+ si = vnc_server_info_get(vs->vd);
+ if (!si) {
return;
}
- data = qobject_from_jsonf("{ 'client': %p, 'server': %p }",
- vs->info, QOBJECT(server));
-
- monitor_protocol_event(event, data);
+ switch (event) {
+ case QAPI_EVENT_VNC_CONNECTED:
+ qapi_event_send_vnc_connected(si, vs->info->base, &error_abort);
+ break;
+ case QAPI_EVENT_VNC_INITIALIZED:
+ qapi_event_send_vnc_initialized(si, vs->info, &error_abort);
+ break;
+ case QAPI_EVENT_VNC_DISCONNECTED:
+ qapi_event_send_vnc_disconnected(si, vs->info, &error_abort);
+ break;
+ default:
+ break;
+ }
- qobject_incref(vs->info);
- qobject_decref(data);
+ qapi_free_VncServerInfo(si);
}
static VncClientInfo *qmp_query_vnc_client(const VncState *client)
info->base->host = g_strdup(host);
info->base->service = g_strdup(serv);
info->base->family = inet_netfamily(sa.ss_family);
+#ifdef CONFIG_VNC_WS
+ info->base->websocket = client->websocket;
+#endif
#ifdef CONFIG_VNC_TLS
if (client->tls.session && client->tls.dname) {
return info;
}
+static VncDisplay *vnc_display_find(const char *id)
+{
+ VncDisplay *vd;
+
+ if (id == NULL) {
+ return QTAILQ_FIRST(&vnc_displays);
+ }
+ QTAILQ_FOREACH(vd, &vnc_displays, next) {
+ if (strcmp(id, vd->id) == 0) {
+ return vd;
+ }
+ }
+ return NULL;
+}
+
+static VncClientInfoList *qmp_query_client_list(VncDisplay *vd)
+{
+ VncClientInfoList *cinfo, *prev = NULL;
+ VncState *client;
+
+ QTAILQ_FOREACH(client, &vd->clients, next) {
+ cinfo = g_new0(VncClientInfoList, 1);
+ cinfo->value = qmp_query_vnc_client(client);
+ cinfo->next = prev;
+ prev = cinfo;
+ }
+ return prev;
+}
+
VncInfo *qmp_query_vnc(Error **errp)
{
VncInfo *info = g_malloc0(sizeof(*info));
+ VncDisplay *vd = vnc_display_find(NULL);
- if (vnc_display == NULL || vnc_display->display == NULL) {
+ if (vd == NULL || !vd->enabled) {
info->enabled = false;
} else {
- VncClientInfoList *cur_item = NULL;
struct sockaddr_storage sa;
socklen_t salen = sizeof(sa);
char host[NI_MAXHOST];
char serv[NI_MAXSERV];
- VncState *client;
info->enabled = true;
/* for compatibility with the original command */
info->has_clients = true;
+ info->clients = qmp_query_client_list(vd);
- QTAILQ_FOREACH(client, &vnc_display->clients, next) {
- VncClientInfoList *cinfo = g_malloc0(sizeof(*info));
- cinfo->value = qmp_query_vnc_client(client);
-
- /* XXX: waiting for the qapi to support GSList */
- if (!cur_item) {
- info->clients = cur_item = cinfo;
- } else {
- cur_item->next = cinfo;
- cur_item = cinfo;
- }
- }
-
- if (vnc_display->lsock == -1) {
+ if (vd->lsock == -1) {
return info;
}
- if (getsockname(vnc_display->lsock, (struct sockaddr *)&sa,
+ if (getsockname(vd->lsock, (struct sockaddr *)&sa,
&salen) == -1) {
error_set(errp, QERR_UNDEFINED_ERROR);
goto out_error;
info->family = inet_netfamily(sa.ss_family);
info->has_auth = true;
- info->auth = g_strdup(vnc_auth_name(vnc_display));
+ info->auth = g_strdup(vnc_auth_name(vd));
}
return info;
return NULL;
}
+static VncBasicInfoList *qmp_query_server_entry(int socket,
+ 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) {
+ return prev;
+ }
+
+ info = g_new0(VncBasicInfo, 1);
+ info->host = g_strdup(host);
+ info->service = g_strdup(serv);
+ info->family = inet_netfamily(sa.ss_family);
+ info->websocket = websocket;
+
+ list = g_new0(VncBasicInfoList, 1);
+ list->value = info;
+ list->next = prev;
+ return list;
+}
+
+static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info)
+{
+ switch (vd->auth) {
+ case VNC_AUTH_VNC:
+ info->auth = VNC_PRIMARY_AUTH_VNC;
+ break;
+ case VNC_AUTH_RA2:
+ info->auth = VNC_PRIMARY_AUTH_RA2;
+ break;
+ case VNC_AUTH_RA2NE:
+ info->auth = VNC_PRIMARY_AUTH_RA2NE;
+ break;
+ case VNC_AUTH_TIGHT:
+ info->auth = VNC_PRIMARY_AUTH_TIGHT;
+ break;
+ case VNC_AUTH_ULTRA:
+ info->auth = VNC_PRIMARY_AUTH_ULTRA;
+ break;
+ case VNC_AUTH_TLS:
+ info->auth = VNC_PRIMARY_AUTH_TLS;
+ 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:
+ info->vencrypt = VNC_VENCRYPT_SUB_AUTH_PLAIN;
+ break;
+ case VNC_AUTH_VENCRYPT_TLSNONE:
+ info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_NONE;
+ break;
+ case VNC_AUTH_VENCRYPT_TLSVNC:
+ info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_VNC;
+ break;
+ case VNC_AUTH_VENCRYPT_TLSPLAIN:
+ info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_PLAIN;
+ break;
+ case VNC_AUTH_VENCRYPT_X509NONE:
+ info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_NONE;
+ break;
+ case VNC_AUTH_VENCRYPT_X509VNC:
+ info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_VNC;
+ break;
+ case VNC_AUTH_VENCRYPT_X509PLAIN:
+ info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_PLAIN;
+ break;
+ case VNC_AUTH_VENCRYPT_TLSSASL:
+ info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_SASL;
+ break;
+ case VNC_AUTH_VENCRYPT_X509SASL:
+ info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_SASL;
+ break;
+ default:
+ info->has_vencrypt = false;
+ break;
+ }
+#endif
+ break;
+ case VNC_AUTH_SASL:
+ info->auth = VNC_PRIMARY_AUTH_SASL;
+ break;
+ case VNC_AUTH_NONE:
+ default:
+ info->auth = VNC_PRIMARY_AUTH_NONE;
+ break;
+ }
+}
+
+VncInfo2List *qmp_query_vnc_servers(Error **errp)
+{
+ VncInfo2List *item, *prev = NULL;
+ VncInfo2 *info;
+ VncDisplay *vd;
+ DeviceState *dev;
+
+ QTAILQ_FOREACH(vd, &vnc_displays, next) {
+ info = g_new0(VncInfo2, 1);
+ info->id = g_strdup(vd->id);
+ info->clients = qmp_query_client_list(vd);
+ qmp_query_auth(vd, info);
+ if (vd->dcl.con) {
+ dev = DEVICE(object_property_get_link(OBJECT(vd->dcl.con),
+ "device", NULL));
+ 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);
+ }
+#ifdef CONFIG_VNC_WS
+ if (vd->lwebsock != -1) {
+ info->server = qmp_query_server_entry(vd->lwebsock, true,
+ info->server);
+ }
+#endif
+
+ item = g_new0(VncInfo2List, 1);
+ item->value = info;
+ item->next = prev;
+ prev = item;
+ }
+ return prev;
+}
+
/* TODO
1) Get the queue working for IO.
2) there is some weirdness when using the -S option (the screen is grey
static void vnc_refresh(DisplayChangeListener *dcl);
static int vnc_refresh_server_surface(VncDisplay *vd);
-static void vnc_dpy_update(DisplayChangeListener *dcl,
- int x, int y, int w, int h)
-{
- VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
- struct VncSurface *s = &vd->guest;
- int width = surface_width(vd->ds);
- int 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) {
/* 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);
h = MIN(y + h, height);
for (; y < h; y++) {
- bitmap_set(s->dirty[y], x / VNC_DIRTY_PIXELS_PER_BIT,
+ bitmap_set(dirty[y], x / VNC_DIRTY_PIXELS_PER_BIT,
DIV_ROUND_UP(w, VNC_DIRTY_PIXELS_PER_BIT));
}
}
+static void vnc_dpy_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ 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);
+}
+
void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
int32_t encoding)
{
if ((buffer->capacity - buffer->offset) < len) {
buffer->capacity += (len + 1024);
buffer->buffer = g_realloc(buffer->buffer, buffer->capacity);
- if (buffer->buffer == NULL) {
- fprintf(stderr, "vnc: out of memory\n");
- exit(1);
- }
}
}
static void vnc_desktop_resize(VncState *vs)
{
- DisplaySurface *ds = vs->vd->ds;
-
if (vs->csock == -1 || !vnc_has_feature(vs, VNC_FEATURE_RESIZE)) {
return;
}
- if (vs->client_width == surface_width(ds) &&
- vs->client_height == surface_height(ds)) {
+ if (vs->client_width == pixman_image_get_width(vs->vd->server) &&
+ vs->client_height == pixman_image_get_height(vs->vd->server)) {
return;
}
- vs->client_width = surface_width(ds);
- vs->client_height = surface_height(ds);
+ vs->client_width = pixman_image_get_width(vs->vd->server);
+ vs->client_height = pixman_image_get_height(vs->vd->server);
vnc_lock_output(vs);
vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
vnc_write_u8(vs, 0);
ptr += x * VNC_SERVER_FB_BYTES;
return ptr;
}
-/* this sets only the visible pixels of a dirty bitmap */
-#define VNC_SET_VISIBLE_PIXELS_DIRTY(bitmap, w, h) {\
- int y;\
- memset(bitmap, 0x00, sizeof(bitmap));\
- for (y = 0; y < h; y++) {\
- bitmap_set(bitmap[y], 0,\
- DIV_ROUND_UP(w, VNC_DIRTY_PIXELS_PER_BIT));\
- } \
- }
static void vnc_dpy_switch(DisplayChangeListener *dcl,
DisplaySurface *surface)
{
VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
VncState *vs;
+ int width, height;
vnc_abort_display_jobs(vd);
/* 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,
- surface_width(vd->ds),
- surface_height(vd->ds),
- NULL, 0);
+ width, height, NULL, 0);
/* guest surface */
#if 0 /* FIXME */
qemu_pixman_image_unref(vd->guest.fb);
vd->guest.fb = pixman_image_ref(surface->image);
vd->guest.format = surface->format;
- VNC_SET_VISIBLE_PIXELS_DIRTY(vd->guest.dirty,
- surface_width(vd->ds),
- surface_height(vd->ds));
+ memset(vd->guest.dirty, 0x00, sizeof(vd->guest.dirty));
+ vnc_set_area_dirty(vd->guest.dirty, width, height, 0, 0,
+ width, height);
QTAILQ_FOREACH(vs, &vd->clients, next) {
vnc_colordepth(vs);
if (vs->vd->cursor) {
vnc_cursor_define(vs);
}
- VNC_SET_VISIBLE_PIXELS_DIRTY(vs->dirty,
- surface_width(vd->ds),
- surface_height(vd->ds));
+ memset(vs->dirty, 0x00, sizeof(vs->dirty));
+ vnc_set_area_dirty(vs->dirty, width, height, 0, 0,
+ width, height);
}
}
static void vnc_dpy_cursor_define(DisplayChangeListener *dcl,
QEMUCursor *c)
{
- VncDisplay *vd = vnc_display;
+ VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
VncState *vs;
cursor_put(vd->cursor);
static int vnc_update_client(VncState *vs, int has_dirty, bool sync)
{
+ vs->has_dirty += has_dirty;
if (vs->need_update && vs->csock != -1) {
VncDisplay *vd = vs->vd;
VncJob *job;
/* kernel send buffers are full -> drop frames to throttle */
return 0;
- if (!has_dirty && !vs->audio_cap && !vs->force_update)
+ if (!vs->has_dirty && !vs->audio_cap && !vs->force_update)
return 0;
/*
*/
job = vnc_job_new(vs);
- height = MIN(pixman_image_get_height(vd->server), vs->client_height);
- width = MIN(pixman_image_get_width(vd->server), vs->client_width);
+ height = pixman_image_get_height(vd->server);
+ width = pixman_image_get_width(vd->server);
y = 0;
for (;;) {
n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y,
(x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h);
}
+ if (!x && x2 == width / VNC_DIRTY_PIXELS_PER_BIT) {
+ y += h;
+ if (y == height) {
+ break;
+ }
+ }
}
vnc_job_push(job);
vnc_jobs_join(vs);
}
vs->force_update = 0;
+ vs->has_dirty = 0;
return n;
}
vnc_jobs_join(vs); /* Wait encoding jobs */
vnc_lock_output(vs);
- vnc_qmp_event(vs, QEVENT_VNC_DISCONNECTED);
+ vnc_qmp_event(vs, QAPI_EVENT_VNC_DISCONNECTED);
buffer_free(&vs->input);
buffer_free(&vs->output);
buffer_free(&vs->ws_output);
#endif /* CONFIG_VNC_WS */
- qobject_decref(vs->info);
+ qapi_free_VncClientInfo(vs->info);
vnc_zlib_clear(vs);
vnc_tight_clear(vs);
if (vs->tls.session) {
ret = vnc_client_write_tls(&vs->tls.session, data, datalen);
} else {
-#ifdef CONFIG_VNC_WS
- if (vs->ws_tls.session) {
- ret = vnc_client_write_tls(&vs->ws_tls.session, data, datalen);
- } else
-#endif /* CONFIG_VNC_WS */
#endif /* CONFIG_VNC_TLS */
- {
- ret = send(vs->csock, (const void *)data, datalen, 0);
- }
+ ret = send(vs->csock, (const void *)data, datalen, 0);
#ifdef CONFIG_VNC_TLS
}
#endif /* CONFIG_VNC_TLS */
if (vs->tls.session) {
ret = vnc_client_read_tls(&vs->tls.session, data, datalen);
} else {
-#ifdef CONFIG_VNC_WS
- if (vs->ws_tls.session) {
- ret = vnc_client_read_tls(&vs->ws_tls.session, data, datalen);
- } else
-#endif /* CONFIG_VNC_WS */
#endif /* CONFIG_VNC_TLS */
- {
- ret = qemu_recv(vs->csock, data, datalen, 0);
- }
+ ret = qemu_recv(vs->csock, data, datalen, 0);
#ifdef CONFIG_VNC_TLS
}
#endif /* CONFIG_VNC_TLS */
vnc_write_u8(vs, 0);
vnc_write_u16(vs, 1);
vnc_framebuffer_update(vs, absolute, 0,
- surface_width(vs->vd->ds),
- surface_height(vs->vd->ds),
+ pixman_image_get_width(vs->vd->server),
+ pixman_image_get_height(vs->vd->server),
VNC_ENCODING_POINTER_TYPE_CHANGE);
vnc_unlock_output(vs);
vnc_flush(vs);
[INPUT_BUTTON_WHEEL_DOWN] = 0x10,
};
QemuConsole *con = vs->vd->dcl.con;
- int width = surface_width(vs->vd->ds);
- int height = surface_height(vs->vd->ds);
+ int width = pixman_image_get_width(vs->vd->server);
+ int height = pixman_image_get_height(vs->vd->server);
if (vs->last_bmask != button_mask) {
qemu_input_update_buttons(con, bmap, vs->last_bmask, button_mask);
vs->modifiers_state[keycode] = 0;
break;
case 0x02 ... 0x0a: /* '1' to '9' keys */
- if (down && vs->modifiers_state[0x1d] && vs->modifiers_state[0x38]) {
+ if (vs->vd->dcl.con == NULL &&
+ down && vs->modifiers_state[0x1d] && vs->modifiers_state[0x38]) {
/* Reset the modifiers sent to the current console */
reset_keys(vs);
console_select(keycode - 0x02);
}
static void framebuffer_update_request(VncState *vs, int incremental,
- int x_position, int y_position,
- int w, int h)
+ int x, int y, int w, int h)
{
- int i;
- const size_t width = surface_width(vs->vd->ds) / VNC_DIRTY_PIXELS_PER_BIT;
- const size_t height = surface_height(vs->vd->ds);
-
- if (y_position > height) {
- y_position = height;
- }
- if (y_position + h >= height) {
- h = height - y_position;
- }
+ int width = pixman_image_get_width(vs->vd->server);
+ int height = pixman_image_get_height(vs->vd->server);
vs->need_update = 1;
- if (!incremental) {
- vs->force_update = 1;
- for (i = 0; i < h; i++) {
- bitmap_set(vs->dirty[y_position + i], 0, width);
- bitmap_clear(vs->dirty[y_position + i], width,
- VNC_DIRTY_BITS - width);
- }
+
+ if (incremental) {
+ return;
}
+
+ vs->force_update = 1;
+ vnc_set_area_dirty(vs->dirty, width, height, x, y, w, h);
}
static void send_ext_key_event_ack(VncState *vs)
vnc_write_u8(vs, 0);
vnc_write_u16(vs, 1);
vnc_framebuffer_update(vs, 0, 0,
- surface_width(vs->vd->ds),
- surface_height(vs->vd->ds),
+ pixman_image_get_width(vs->vd->server),
+ pixman_image_get_height(vs->vd->server),
VNC_ENCODING_EXT_KEY_EVENT);
vnc_unlock_output(vs);
vnc_flush(vs);
vnc_write_u8(vs, 0);
vnc_write_u16(vs, 1);
vnc_framebuffer_update(vs, 0, 0,
- surface_width(vs->vd->ds),
- surface_height(vs->vd->ds),
+ pixman_image_get_width(vs->vd->server),
+ pixman_image_get_height(vs->vd->server),
VNC_ENCODING_AUDIO);
vnc_unlock_output(vs);
vnc_flush(vs);
return;
}
+ switch (bits_per_pixel) {
+ case 8:
+ case 16:
+ case 32:
+ break;
+ default:
+ vnc_client_error(vs);
+ return;
+ }
+
vs->client_pf.rmax = red_max;
vs->client_pf.rbits = hweight_long(red_max);
vs->client_pf.rshift = red_shift;
set_pixel_conversion(vs);
- graphic_hw_invalidate(NULL);
- graphic_hw_update(NULL);
+ graphic_hw_invalidate(vs->vd->dcl.con);
+ graphic_hw_update(vs->vd->dcl.con);
}
static void pixel_format_message (VncState *vs) {
vnc_write_u8(vs, 0);
vnc_write_u16(vs, 1); /* number of rects */
vnc_framebuffer_update(vs, 0, 0,
- surface_width(vs->vd->ds),
- surface_height(vs->vd->ds),
+ pixman_image_get_width(vs->vd->server),
+ pixman_image_get_height(vs->vd->server),
VNC_ENCODING_WMVi);
pixel_format_message(vs);
vnc_unlock_output(vs);
pointer_event(vs, read_u8(data, 1), read_u16(data, 2), read_u16(data, 4));
break;
case VNC_MSG_CLIENT_CUT_TEXT:
- if (len == 1)
+ if (len == 1) {
return 8;
-
+ }
if (len == 8) {
uint32_t dlen = read_u32(data, 4);
- if (dlen > 0)
+ if (dlen > (1 << 20)) {
+ error_report("vnc: client_cut_text msg payload has %u bytes"
+ " which exceeds our limit of 1MB.", dlen);
+ vnc_client_error(vs);
+ break;
+ }
+ if (dlen > 0) {
return 8 + dlen;
+ }
}
client_cut_text(vs, read_u32(data, 4), data + 8);
case 4: vs->as.fmt = AUD_FMT_U32; break;
case 5: vs->as.fmt = AUD_FMT_S32; break;
default:
- printf("Invalid audio format %d\n", read_u8(data, 4));
+ VNC_DEBUG("Invalid audio format %d\n", read_u8(data, 4));
vnc_client_error(vs);
break;
}
vs->as.nchannels = read_u8(data, 5);
if (vs->as.nchannels != 1 && vs->as.nchannels != 2) {
- printf("Invalid audio channel coount %d\n",
- read_u8(data, 5));
+ VNC_DEBUG("Invalid audio channel coount %d\n",
+ read_u8(data, 5));
vnc_client_error(vs);
break;
}
vs->as.freq = read_u32(data, 6);
break;
default:
- printf ("Invalid audio message %d\n", read_u8(data, 4));
+ VNC_DEBUG("Invalid audio message %d\n", read_u8(data, 4));
vnc_client_error(vs);
break;
}
break;
default:
- printf("Msg: %d\n", read_u16(data, 0));
+ VNC_DEBUG("Msg: %d\n", read_u16(data, 0));
vnc_client_error(vs);
break;
}
break;
default:
- printf("Msg: %d\n", data[0]);
+ VNC_DEBUG("Msg: %d\n", data[0]);
vnc_client_error(vs);
break;
}
}
vnc_set_share_mode(vs, mode);
- vs->client_width = surface_width(vs->vd->ds);
- vs->client_height = surface_height(vs->vd->ds);
+ if (vs->vd->num_shared > vs->vd->connections_limit) {
+ vnc_disconnect_start(vs);
+ return 0;
+ }
+
+ vs->client_width = pixman_image_get_width(vs->vd->server);
+ vs->client_height = pixman_image_get_height(vs->vd->server);
vnc_write_u16(vs, vs->client_width);
vnc_write_u16(vs, vs->client_height);
vnc_flush(vs);
vnc_client_cache_auth(vs);
- vnc_qmp_event(vs, QEVENT_VNC_INITIALIZED);
+ vnc_qmp_event(vs, QAPI_EVENT_VNC_INITIALIZED);
vnc_read_when(vs, protocol_client_msg, 1);
static int vnc_refresh_server_surface(VncDisplay *vd)
{
- int width = pixman_image_get_width(vd->guest.fb);
- int height = pixman_image_get_height(vd->guest.fb);
- int y;
+ int width = MIN(pixman_image_get_width(vd->guest.fb),
+ pixman_image_get_width(vd->server));
+ int height = MIN(pixman_image_get_height(vd->guest.fb),
+ pixman_image_get_height(vd->server));
+ int cmp_bytes, server_stride, min_stride, guest_stride, y = 0;
uint8_t *guest_row0 = NULL, *server_row0;
- int guest_stride = 0, server_stride;
- int cmp_bytes;
VncState *vs;
int has_dirty = 0;
pixman_image_t *tmpbuf = NULL;
* Check and copy modified bits from guest to server surface.
* Update server dirty map.
*/
- cmp_bytes = VNC_DIRTY_PIXELS_PER_BIT * VNC_SERVER_FB_BYTES;
- if (cmp_bytes > vnc_server_fb_stride(vd)) {
- cmp_bytes = vnc_server_fb_stride(vd);
- }
+ server_row0 = (uint8_t *)pixman_image_get_data(vd->server);
+ server_stride = guest_stride = pixman_image_get_stride(vd->server);
+ cmp_bytes = MIN(VNC_DIRTY_PIXELS_PER_BIT * VNC_SERVER_FB_BYTES,
+ server_stride);
if (vd->guest.format != VNC_SERVER_FB_FORMAT) {
int width = pixman_image_get_width(vd->server);
tmpbuf = qemu_pixman_linebuf_create(VNC_SERVER_FB_FORMAT, width);
guest_row0 = (uint8_t *)pixman_image_get_data(vd->guest.fb);
guest_stride = pixman_image_get_stride(vd->guest.fb);
}
- server_row0 = (uint8_t *)pixman_image_get_data(vd->server);
- server_stride = pixman_image_get_stride(vd->server);
+ min_stride = MIN(server_stride, guest_stride);
- y = 0;
for (;;) {
int x;
uint8_t *guest_ptr, *server_ptr;
for (; x < DIV_ROUND_UP(width, VNC_DIRTY_PIXELS_PER_BIT);
x++, guest_ptr += cmp_bytes, server_ptr += cmp_bytes) {
+ int _cmp_bytes = cmp_bytes;
if (!test_and_clear_bit(x, vd->guest.dirty[y])) {
continue;
}
- if (memcmp(server_ptr, guest_ptr, cmp_bytes) == 0) {
+ if ((x + 1) * cmp_bytes > min_stride) {
+ _cmp_bytes = min_stride - x * cmp_bytes;
+ }
+ if (memcmp(server_ptr, guest_ptr, _cmp_bytes) == 0) {
continue;
}
- memcpy(server_ptr, guest_ptr, cmp_bytes);
+ memcpy(server_ptr, guest_ptr, _cmp_bytes);
if (!vd->non_adaptive) {
vnc_rect_updated(vd, x * VNC_DIRTY_PIXELS_PER_BIT,
y, &tv);
VncState *vs, *vn;
int has_dirty, rects = 0;
- graphic_hw_update(NULL);
+ if (QTAILQ_EMPTY(&vd->clients)) {
+ update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_MAX);
+ return;
+ }
+
+ graphic_hw_update(vd->dcl.con);
if (vnc_trylock_display(vd)) {
update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
/* vs might be free()ed here */
}
- if (QTAILQ_EMPTY(&vd->clients)) {
- update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_MAX);
- return;
- }
-
if (has_dirty && rects) {
vd->dcl.update_interval /= 2;
if (vd->dcl.update_interval < VNC_REFRESH_INTERVAL_BASE) {
int i;
vs->csock = csock;
+ vs->vd = vd;
if (skipauth) {
vs->auth = VNC_AUTH_NONE;
-#ifdef CONFIG_VNC_TLS
vs->subauth = VNC_AUTH_INVALID;
-#endif
} else {
- vs->auth = vd->auth;
-#ifdef CONFIG_VNC_TLS
- vs->subauth = vd->subauth;
-#endif
+ if (websocket) {
+ vs->auth = vd->ws_auth;
+ vs->subauth = VNC_AUTH_INVALID;
+ } else {
+ vs->auth = vd->auth;
+ vs->subauth = vd->subauth;
+ }
}
+ VNC_DEBUG("Client sock=%d ws=%d auth=%d subauth=%d\n",
+ csock, 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) {
if (websocket) {
vs->websocket = 1;
#ifdef CONFIG_VNC_TLS
- if (vd->tls.x509cert) {
- qemu_set_fd_handler2(vs->csock, NULL, vncws_tls_handshake_peek,
+ if (vd->ws_tls) {
+ qemu_set_fd_handler2(vs->csock, NULL, vncws_tls_handshake_io,
NULL, vs);
} else
#endif /* CONFIG_VNC_TLS */
}
vnc_client_cache_addr(vs);
- vnc_qmp_event(vs, QEVENT_VNC_CONNECTED);
+ vnc_qmp_event(vs, QAPI_EVENT_VNC_CONNECTED);
vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING);
- vs->vd = vd;
-
#ifdef CONFIG_VNC_WS
if (!vs->websocket)
#endif
{
vnc_init_state(vs);
}
+
+ if (vd->num_connecting > vd->connections_limit) {
+ QTAILQ_FOREACH(vs, &vd->clients, next) {
+ if (vs->share_mode == VNC_SHARE_MODE_CONNECTING) {
+ vnc_disconnect_start(vs);
+ return;
+ }
+ }
+ }
}
void vnc_init_state(VncState *vs)
qemu_mutex_init(&vs->output_mutex);
vs->bh = qemu_bh_new(vnc_jobs_bh, vs);
- QTAILQ_INSERT_HEAD(&vd->clients, vs, next);
+ QTAILQ_INSERT_TAIL(&vd->clients, vs, next);
- graphic_hw_update(NULL);
+ graphic_hw_update(vd->dcl.con);
vnc_write(vs, "RFB 003.008\n", 12);
vnc_flush(vs);
int csock;
/* Catch-up */
- graphic_hw_update(NULL);
+ graphic_hw_update(vs->dcl.con);
#ifdef CONFIG_VNC_WS
if (websocket) {
csock = qemu_accept(vs->lwebsock, (struct sockaddr *)&addr, &addrlen);
}
if (csock != -1) {
+ socket_set_nodelay(csock);
vnc_connect(vs, csock, false, websocket);
}
}
#endif /* CONFIG_VNC_WS */
static const DisplayChangeListenerOps dcl_ops = {
- .dpy_name = "vnc",
- .dpy_refresh = vnc_refresh,
- .dpy_gfx_copy = vnc_dpy_copy,
- .dpy_gfx_update = vnc_dpy_update,
- .dpy_gfx_switch = vnc_dpy_switch,
- .dpy_mouse_set = vnc_mouse_set,
- .dpy_cursor_define = vnc_dpy_cursor_define,
+ .dpy_name = "vnc",
+ .dpy_refresh = vnc_refresh,
+ .dpy_gfx_copy = vnc_dpy_copy,
+ .dpy_gfx_update = vnc_dpy_update,
+ .dpy_gfx_switch = vnc_dpy_switch,
+ .dpy_gfx_check_format = qemu_pixman_check_format,
+ .dpy_mouse_set = vnc_mouse_set,
+ .dpy_cursor_define = vnc_dpy_cursor_define,
};
-void vnc_display_init(DisplayState *ds)
+void vnc_display_init(const char *id)
{
- VncDisplay *vs = g_malloc0(sizeof(*vs));
+ VncDisplay *vs;
- vnc_display = vs;
+ if (vnc_display_find(id) != NULL) {
+ return;
+ }
+ vs = g_malloc0(sizeof(*vs));
+
+ vs->id = strdup(id);
+ QTAILQ_INSERT_TAIL(&vnc_displays, vs, next);
vs->lsock = -1;
#ifdef CONFIG_VNC_WS
}
-static void vnc_display_close(DisplayState *ds)
+static void vnc_display_close(VncDisplay *vs)
{
- VncDisplay *vs = vnc_display;
-
if (!vs)
return;
- g_free(vs->display);
- vs->display = NULL;
+ vs->enabled = false;
+ vs->is_unix = false;
if (vs->lsock != -1) {
qemu_set_fd_handler2(vs->lsock, NULL, NULL, NULL, NULL);
close(vs->lsock);
vs->lsock = -1;
}
#ifdef CONFIG_VNC_WS
- g_free(vs->ws_display);
- vs->ws_display = NULL;
+ vs->ws_enabled = false;
if (vs->lwebsock != -1) {
qemu_set_fd_handler2(vs->lwebsock, NULL, NULL, NULL, NULL);
close(vs->lwebsock);
}
#endif /* CONFIG_VNC_WS */
vs->auth = VNC_AUTH_INVALID;
-#ifdef CONFIG_VNC_TLS
vs->subauth = VNC_AUTH_INVALID;
+#ifdef CONFIG_VNC_TLS
vs->tls.x509verify = 0;
#endif
}
-int vnc_display_password(DisplayState *ds, const char *password)
+int vnc_display_password(const char *id, const char *password)
{
- VncDisplay *vs = vnc_display;
+ VncDisplay *vs = vnc_display_find(id);
if (!vs) {
return -EINVAL;
return 0;
}
-int vnc_display_pw_expire(DisplayState *ds, time_t expires)
+int vnc_display_pw_expire(const char *id, time_t expires)
{
- VncDisplay *vs = vnc_display;
+ VncDisplay *vs = vnc_display_find(id);
if (!vs) {
return -EINVAL;
return 0;
}
-char *vnc_display_local_addr(DisplayState *ds)
+char *vnc_display_local_addr(const char *id)
{
- VncDisplay *vs = vnc_display;
-
+ VncDisplay *vs = vnc_display_find(id);
+
+ assert(vs);
return vnc_socket_local_addr("%s:%s", vs->lsock);
}
-void vnc_display_open(DisplayState *ds, const char *display, Error **errp)
-{
- VncDisplay *vs = vnc_display;
- const char *options;
- int password = 0;
- int reverse = 0;
-#ifdef CONFIG_VNC_TLS
- int tls = 0, x509 = 0;
-#endif
-#ifdef CONFIG_VNC_SASL
- int sasl = 0;
- int saslErr;
-#endif
-#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
- int acl = 0;
-#endif
- int lock_key_sync = 1;
-
- if (!vnc_display) {
- error_setg(errp, "VNC display not active");
- return;
- }
- vnc_display_close(ds);
- if (strcmp(display, "none") == 0)
- return;
-
- vs->display = g_strdup(display);
- vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
-
- options = display;
- while ((options = strchr(options, ','))) {
- options++;
- if (strncmp(options, "password", 8) == 0) {
- if (fips_get_state()) {
- error_setg(errp,
- "VNC password auth disabled due to FIPS mode, "
- "consider using the VeNCrypt or SASL authentication "
- "methods as an alternative");
- goto fail;
- }
- password = 1; /* Require password auth */
- } else if (strncmp(options, "reverse", 7) == 0) {
- reverse = 1;
- } else if (strncmp(options, "no-lock-key-sync", 16) == 0) {
- lock_key_sync = 0;
-#ifdef CONFIG_VNC_SASL
- } else if (strncmp(options, "sasl", 4) == 0) {
- sasl = 1; /* Require SASL auth */
-#endif
-#ifdef CONFIG_VNC_WS
- } else if (strncmp(options, "websocket", 9) == 0) {
- char *start, *end;
- vs->websocket = 1;
-
- /* Check for 'websocket=<port>' */
- start = strchr(options, '=');
- end = strchr(options, ',');
- if (start && (!end || (start < end))) {
- int len = end ? end-(start+1) : strlen(start+1);
- if (len < 6) {
- /* extract the host specification from display */
- char *host = NULL, *port = NULL, *host_end = NULL;
- port = g_strndup(start + 1, len);
-
- /* ipv6 hosts have colons */
- end = strchr(display, ',');
- host_end = g_strrstr_len(display, end - display, ":");
-
- if (host_end) {
- host = g_strndup(display, host_end - display + 1);
- } else {
- host = g_strndup(":", 1);
- }
- vs->ws_display = g_strconcat(host, port, NULL);
- g_free(host);
- g_free(port);
- }
- }
-#endif /* CONFIG_VNC_WS */
-#ifdef CONFIG_VNC_TLS
- } else if (strncmp(options, "tls", 3) == 0) {
- tls = 1; /* Require TLS */
- } else if (strncmp(options, "x509", 4) == 0) {
- char *start, *end;
- x509 = 1; /* Require x509 certificates */
- if (strncmp(options, "x509verify", 10) == 0)
- vs->tls.x509verify = 1; /* ...and verify client certs */
-
- /* Now check for 'x509=/some/path' postfix
- * and use that to setup x509 certificate/key paths */
- start = strchr(options, '=');
- end = strchr(options, ',');
- if (start && (!end || (start < end))) {
- int len = end ? end-(start+1) : strlen(start+1);
- char *path = g_strndup(start + 1, len);
-
- VNC_DEBUG("Trying certificate path '%s'\n", path);
- if (vnc_tls_set_x509_creds_dir(vs, path) < 0) {
- error_setg(errp, "Failed to find x509 certificates/keys in %s", path);
- g_free(path);
- goto fail;
- }
- g_free(path);
- } else {
- error_setg(errp, "No certificate path provided");
- goto fail;
- }
-#endif
-#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
- } else if (strncmp(options, "acl", 3) == 0) {
- acl = 1;
-#endif
- } else if (strncmp(options, "lossy", 5) == 0) {
-#ifdef CONFIG_VNC_JPEG
- vs->lossy = true;
-#endif
- } else if (strncmp(options, "non-adaptive", 12) == 0) {
- vs->non_adaptive = true;
- } else if (strncmp(options, "share=", 6) == 0) {
- if (strncmp(options+6, "ignore", 6) == 0) {
- vs->share_policy = VNC_SHARE_POLICY_IGNORE;
- } else if (strncmp(options+6, "allow-exclusive", 15) == 0) {
- vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
- } else if (strncmp(options+6, "force-shared", 12) == 0) {
- vs->share_policy = VNC_SHARE_POLICY_FORCE_SHARED;
- } else {
- error_setg(errp, "unknown vnc share= option");
- goto fail;
- }
- }
- }
-
- /* adaptive updates are only used with tight encoding and
- * if lossy updates are enabled so we can disable all the
- * calculations otherwise */
- if (!vs->lossy) {
- vs->non_adaptive = true;
- }
+static QemuOptsList qemu_vnc_opts = {
+ .name = "vnc",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_vnc_opts.head),
+ .implied_opt_name = "vnc",
+ .desc = {
+ {
+ .name = "vnc",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "websocket",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "x509",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "share",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "display",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "head",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "connections",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "to",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "ipv4",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "ipv6",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "password",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "reverse",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "lock-key-sync",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "sasl",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "tls",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "x509verify",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "acl",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "lossy",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "non-adaptive",
+ .type = QEMU_OPT_BOOL,
+ },
+ { /* end of list */ }
+ },
+};
-#ifdef CONFIG_VNC_TLS
- if (acl && x509 && vs->tls.x509verify) {
- if (!(vs->tls.acl = qemu_acl_init("vnc.x509dname"))) {
- fprintf(stderr, "Failed to create x509 dname ACL\n");
- exit(1);
- }
- }
-#endif
-#ifdef CONFIG_VNC_SASL
- if (acl && sasl) {
- if (!(vs->sasl.acl = qemu_acl_init("vnc.username"))) {
- fprintf(stderr, "Failed to create username ACL\n");
- exit(1);
- }
- }
-#endif
+static void
+vnc_display_setup_auth(VncDisplay *vs,
+ bool password,
+ bool sasl,
+ bool tls,
+ bool x509,
+ bool websocket)
+{
/*
- * Combinations we support here:
+ * We have a choice of 3 authentication options
+ *
+ * 1. none
+ * 2. vnc
+ * 3. sasl
+ *
+ * The channel can be run in 2 modes
+ *
+ * 1. clear
+ * 2. tls
+ *
+ * And TLS can use 2 types of credentials
+ *
+ * 1. anon
+ * 2. x509
*
- * - no-auth (clear text, no auth)
- * - password (clear text, weak auth)
- * - sasl (encrypt, good auth *IF* using Kerberos via GSSAPI)
- * - tls (encrypt, weak anonymous creds, no auth)
- * - tls + password (encrypt, weak anonymous creds, weak auth)
- * - tls + sasl (encrypt, weak anonymous creds, good auth)
- * - tls + x509 (encrypt, good x509 creds, no auth)
- * - tls + x509 + password (encrypt, good x509 creds, weak auth)
- * - tls + x509 + sasl (encrypt, good x509 creds, good auth)
+ * We thus have 9 possible logical combinations
*
- * NB1. TLS is a stackable auth scheme.
- * NB2. the x509 schemes have option to validate a client cert dname
+ * 1. clear + none
+ * 2. clear + vnc
+ * 3. clear + sasl
+ * 4. tls + anon + none
+ * 5. tls + anon + vnc
+ * 6. tls + anon + sasl
+ * 7. tls + x509 + none
+ * 8. tls + x509 + vnc
+ * 9. tls + x509 + sasl
+ *
+ * These need to be mapped into the VNC auth schemes
+ * in an appropriate manner. In regular VNC, all the
+ * TLS options get mapped into VNC_AUTH_VENCRYPT
+ * sub-auth types.
+ *
+ * In websockets, the https:// protocol already provides
+ * TLS support, so there is no need to make use of the
+ * VeNCrypt extension. Furthermore, websockets browser
+ * clients could not use VeNCrypt even if they wanted to,
+ * as they cannot control when the TLS handshake takes
+ * place. Thus there is no option but to rely on https://,
+ * meaning combinations 4->6 and 7->9 will be mapped to
+ * VNC auth schemes in the same way as combos 1->3.
+ *
+ * Regardless of fact that we have a different mapping to
+ * VNC auth mechs for plain VNC vs websockets VNC, the end
+ * result has the same security characteristics.
*/
if (password) {
-#ifdef CONFIG_VNC_TLS
if (tls) {
vs->auth = VNC_AUTH_VENCRYPT;
+ if (websocket) {
+ vs->ws_tls = true;
+ }
if (x509) {
VNC_DEBUG("Initializing VNC server with x509 password auth\n");
vs->subauth = VNC_AUTH_VENCRYPT_X509VNC;
vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC;
}
} else {
-#endif /* CONFIG_VNC_TLS */
VNC_DEBUG("Initializing VNC server with password auth\n");
vs->auth = VNC_AUTH_VNC;
-#ifdef CONFIG_VNC_TLS
vs->subauth = VNC_AUTH_INVALID;
}
-#endif /* CONFIG_VNC_TLS */
-#ifdef CONFIG_VNC_SASL
+ if (websocket) {
+ vs->ws_auth = VNC_AUTH_VNC;
+ } else {
+ vs->ws_auth = VNC_AUTH_INVALID;
+ }
} else if (sasl) {
-#ifdef CONFIG_VNC_TLS
if (tls) {
vs->auth = VNC_AUTH_VENCRYPT;
+ if (websocket) {
+ vs->ws_tls = true;
+ }
if (x509) {
VNC_DEBUG("Initializing VNC server with x509 SASL auth\n");
vs->subauth = VNC_AUTH_VENCRYPT_X509SASL;
vs->subauth = VNC_AUTH_VENCRYPT_TLSSASL;
}
} else {
-#endif /* CONFIG_VNC_TLS */
VNC_DEBUG("Initializing VNC server with SASL auth\n");
vs->auth = VNC_AUTH_SASL;
-#ifdef CONFIG_VNC_TLS
vs->subauth = VNC_AUTH_INVALID;
}
-#endif /* CONFIG_VNC_TLS */
-#endif /* CONFIG_VNC_SASL */
+ if (websocket) {
+ vs->ws_auth = VNC_AUTH_SASL;
+ } else {
+ vs->ws_auth = VNC_AUTH_INVALID;
+ }
} else {
-#ifdef CONFIG_VNC_TLS
if (tls) {
vs->auth = VNC_AUTH_VENCRYPT;
+ if (websocket) {
+ vs->ws_tls = true;
+ }
if (x509) {
VNC_DEBUG("Initializing VNC server with x509 no auth\n");
vs->subauth = VNC_AUTH_VENCRYPT_X509NONE;
vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE;
}
} else {
-#endif
VNC_DEBUG("Initializing VNC server with no auth\n");
vs->auth = VNC_AUTH_NONE;
-#ifdef CONFIG_VNC_TLS
vs->subauth = VNC_AUTH_INVALID;
}
+ if (websocket) {
+ vs->ws_auth = VNC_AUTH_NONE;
+ } else {
+ vs->ws_auth = VNC_AUTH_INVALID;
+ }
+ }
+}
+
+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;
+ 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
+ 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) {
+ error_setg(errp, "VNC display not active");
+ return;
+ }
+ vnc_display_close(vs);
+
+ if (!opts) {
+ return;
+ }
+ vnc = qemu_opt_get(opts, "vnc");
+ if (!vnc || strcmp(vnc, "none") == 0) {
+ 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 = g_strndup(vnc, h - vnc);
+ 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 && fips_get_state()) {
+ error_setg(errp,
+ "VNC password auth disabled due to FIPS mode, "
+ "consider using the VeNCrypt or SASL authentication "
+ "methods as an alternative");
+ goto fail;
+ }
+
+ reverse = qemu_opt_get_bool(opts, "reverse", false);
+ lock_key_sync = qemu_opt_get_bool(opts, "lock-key-sync", true);
+ sasl = qemu_opt_get_bool(opts, "sasl", false);
+#ifndef CONFIG_VNC_SASL
+ if (sasl) {
+ error_setg(errp, "VNC SASL auth requires cyrus-sasl support");
+ 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);
+ 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) {
+ if (strcmp(share, "ignore") == 0) {
+ vs->share_policy = VNC_SHARE_POLICY_IGNORE;
+ } else if (strcmp(share, "allow-exclusive") == 0) {
+ vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
+ } else if (strcmp(share, "force-shared") == 0) {
+ vs->share_policy = VNC_SHARE_POLICY_FORCE_SHARED;
+ } else {
+ error_setg(errp, "unknown vnc share= option");
+ goto fail;
+ }
+ } else {
+ vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
+ }
+ vs->connections_limit = qemu_opt_get_number(opts, "connections", 32);
+
+ websocket = qemu_opt_get(opts, "websocket");
+ if (websocket) {
+#ifdef CONFIG_VNC_WS
+ vs->ws_enabled = true;
+ qemu_opt_set(wsopts, "port", websocket, &error_abort);
+#else /* ! CONFIG_VNC_WS */
+ error_setg(errp, "Websockets protocol requires gnutls support");
+ goto fail;
+#endif /* ! CONFIG_VNC_WS */
+ }
+
+#ifdef CONFIG_VNC_JPEG
+ vs->lossy = qemu_opt_get_bool(opts, "lossy", false);
+#endif
+ vs->non_adaptive = qemu_opt_get_bool(opts, "non-adaptive", false);
+ /* adaptive updates are only used with tight encoding and
+ * if lossy updates are enabled so we can disable all the
+ * calculations otherwise */
+ if (!vs->lossy) {
+ vs->non_adaptive = true;
+ }
+
+#ifdef CONFIG_VNC_TLS
+ if (acl && x509 && vs->tls.x509verify) {
+ char *aclname;
+
+ if (strcmp(vs->id, "default") == 0) {
+ aclname = g_strdup("vnc.x509dname");
+ } else {
+ aclname = g_strdup_printf("vnc.%s.x509dname", vs->id);
+ }
+ vs->tls.acl = qemu_acl_init(aclname);
+ if (!vs->tls.acl) {
+ fprintf(stderr, "Failed to create x509 dname ACL\n");
+ exit(1);
+ }
+ g_free(aclname);
+ }
#endif
+#ifdef CONFIG_VNC_SASL
+ if (acl && sasl) {
+ char *aclname;
+
+ if (strcmp(vs->id, "default") == 0) {
+ aclname = g_strdup("vnc.username");
+ } else {
+ aclname = g_strdup_printf("vnc.%s.username", vs->id);
+ }
+ vs->sasl.acl = qemu_acl_init(aclname);
+ if (!vs->sasl.acl) {
+ fprintf(stderr, "Failed to create username ACL\n");
+ exit(1);
+ }
+ g_free(aclname);
}
+#endif
+
+ vnc_display_setup_auth(vs, password, sasl, tls, x509, websocket);
#ifdef CONFIG_VNC_SASL
if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) {
#endif
vs->lock_key_sync = lock_key_sync;
+ device_id = qemu_opt_get(opts, "display");
+ if (device_id) {
+ DeviceState *dev;
+ int head = qemu_opt_get_number(opts, "head", 0);
+
+ 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);
+ goto fail;
+ }
+ } else {
+ con = NULL;
+ }
+
+ if (con != vs->dcl.con) {
+ unregister_displaychangelistener(&vs->dcl);
+ vs->dcl.con = con;
+ register_displaychangelistener(&vs->dcl);
+ }
+
if (reverse) {
/* connect to viewer */
int csock;
#ifdef CONFIG_VNC_WS
vs->lwebsock = -1;
#endif
- if (strncmp(display, "unix:", 5) == 0) {
- csock = unix_connect(display+5, errp);
+ if (strncmp(vnc, "unix:", 5) == 0) {
+ csock = unix_connect(vnc+5, errp);
} else {
- csock = inet_connect(display, errp);
+ csock = inet_connect(vnc, errp);
}
if (csock < 0) {
goto fail;
vnc_connect(vs, csock, false, false);
} else {
/* listen for connects */
- char *dpy;
- dpy = g_malloc(256);
- if (strncmp(display, "unix:", 5) == 0) {
- pstrcpy(dpy, 256, "unix:");
- vs->lsock = unix_listen(display+5, dpy+5, 256-5, errp);
+ if (strncmp(vnc, "unix:", 5) == 0) {
+ vs->lsock = unix_listen(vnc+5, NULL, 0, errp);
+ vs->is_unix = true;
} else {
- vs->lsock = inet_listen(display, dpy, 256,
- SOCK_STREAM, 5900, errp);
+ vs->lsock = inet_listen_opts(sopts, 5900, errp);
if (vs->lsock < 0) {
- g_free(dpy);
goto fail;
}
#ifdef CONFIG_VNC_WS
- if (vs->websocket) {
- if (vs->ws_display) {
- vs->lwebsock = inet_listen(vs->ws_display, NULL, 256,
- SOCK_STREAM, 0, errp);
- } else {
- vs->lwebsock = inet_listen(vs->display, NULL, 256,
- SOCK_STREAM, 5700, errp);
- }
-
+ if (vs->ws_enabled) {
+ vs->lwebsock = inet_listen_opts(wsopts, 0, errp);
if (vs->lwebsock < 0) {
- if (vs->lsock) {
+ if (vs->lsock != -1) {
close(vs->lsock);
vs->lsock = -1;
}
- g_free(dpy);
goto fail;
}
}
#endif /* CONFIG_VNC_WS */
}
- g_free(vs->display);
- vs->display = dpy;
+ vs->enabled = true;
qemu_set_fd_handler2(vs->lsock, NULL,
vnc_listen_regular_read, NULL, vs);
#ifdef CONFIG_VNC_WS
- if (vs->websocket) {
+ if (vs->ws_enabled) {
qemu_set_fd_handler2(vs->lwebsock, NULL,
vnc_listen_websocket_read, NULL, vs);
}
#endif /* CONFIG_VNC_WS */
}
+ qemu_opts_del(sopts);
+ qemu_opts_del(wsopts);
return;
fail:
- g_free(vs->display);
- vs->display = NULL;
+ qemu_opts_del(sopts);
+ qemu_opts_del(wsopts);
+ vs->enabled = false;
#ifdef CONFIG_VNC_WS
- g_free(vs->ws_display);
- vs->ws_display = NULL;
+ vs->ws_enabled = false;
#endif /* CONFIG_VNC_WS */
}
-void vnc_display_add_client(DisplayState *ds, int csock, bool skipauth)
+void vnc_display_add_client(const char *id, int csock, bool skipauth)
{
- VncDisplay *vs = vnc_display;
+ VncDisplay *vs = vnc_display_find(id);
+ if (!vs) {
+ return;
+ }
vnc_connect(vs, csock, skipauth, false);
}
+
+static void vnc_auto_assign_id(QemuOptsList *olist, QemuOpts *opts)
+{
+ int i = 2;
+ char *id;
+
+ id = g_strdup("default");
+ while (qemu_opts_find(olist, id)) {
+ g_free(id);
+ id = g_strdup_printf("vnc%d", i++);
+ }
+ qemu_opts_set_id(opts, id);
+}
+
+QemuOpts *vnc_parse_func(const char *str)
+{
+ QemuOptsList *olist = qemu_find_opts("vnc");
+ QemuOpts *opts = qemu_opts_parse(olist, str, 1);
+ const char *id;
+
+ if (!opts) {
+ return NULL;
+ }
+
+ id = qemu_opts_id(opts);
+ if (!id) {
+ /* auto-assign id if not present */
+ vnc_auto_assign_id(olist, opts);
+ }
+ return opts;
+}
+
+int vnc_init_func(QemuOpts *opts, void *opaque)
+{
+ Error *local_err = NULL;
+ char *id = (char *)qemu_opts_id(opts);
+
+ assert(id);
+ vnc_display_init(id);
+ vnc_display_open(id, &local_err);
+ if (local_err != NULL) {
+ error_report("Failed to start VNC server on `%s': %s",
+ qemu_opt_get(opts, "display"),
+ error_get_pretty(local_err));
+ error_free(local_err);
+ exit(1);
+ }
+ return 0;
+}
+
+static void vnc_register_config(void)
+{
+ qemu_add_opts(&qemu_vnc_opts);
+}
+machine_init(vnc_register_config);