vnc: add support for multiple vnc displays

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2.0.22 (GNU/Linux)
 
 iQIcBAABAgAGBQJUwNhmAAoJEEy22O7T6HE4qNEP/3wE79x34VFtKUZ3k1hsUxOZ
 8Rbb8lbPhCeQjEF3pcxVF0QEjJidk/OKSyzLJbJXjq0L+6EmiNfII1bzBseUDvFe
 XsWLKuCnNwMr6fuZ34C4PPbRSYgBZhxQywsIV/RKdTxKxs5suPDoASOGI1ZAd1Fo
 y0fPBe/z3ecVSQcuKNiDQ7jdbJT7gQ65yn5DpdeM/LSHrW/Xo700WCZtQbc+ZcxW
 78SMj0SxhbfLgHB91xYw3yZT6L6xXed5HcJyqfboeGYIHrpRaHrEwkghumQ12XA1
 ZHRFkiaOZS8y565T0KMJxx1+sZerG9q/1xLCLohziR0u4BZ4H2txqtUviujQ00Dl
 83pVGWNlRvHgVgVuH7sfP1N3TqdmwOLr4UttzCtWxSJjDeK++z/zEHHB/aB4UZSr
 WHUG9cPLP/+trckE86CXbJ4LzAq1viiIin3i98tUh/jwljrJXW1jhd9qnoouFoAF
 VBxQ71G8BIVnm+TJuikcUSaf3VeRy1C1CT1XEsBy2jN4CYn7cDhnju+JdECgi2G0
 ZZrBfDWY3xKBcXA5t1aOJasSHO7PsPOPfZr/fIs6koPWiUL67GMRyZPNXYAn5xrz
 918WMWQ2KI93DT+uMLBT0veOG4du4MhadSnq1lXZnalFfYABrvzWiVNE97UF5aKE
 g/NVldhYivZP7uIhmLK5
 =+MFe
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kraxel/tags/pull-vnc-20150122-1' into staging

vnc: add support for multiple vnc displays

# gpg: Signature made Thu 22 Jan 2015 11:00:54 GMT using RSA key ID D3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>"
# gpg:                 aka "Gerd Hoffmann <gerd@kraxel.org>"
# gpg:                 aka "Gerd Hoffmann (private) <kraxel@gmail.com>"

* remotes/kraxel/tags/pull-vnc-20150122-1:
  monitor: add vnc websockets
  monitor: add query-vnc-servers command
  vnc: factor out qmp_query_client_list
  vnc: track & limit connections
  vnc: update docs/multiseat.txt
  vnc: allow binding servers to qemu consoles
  vnc: switch to QemuOpts, allow multiple servers
  vnc: add display id to acl names
  vnc: remove unused DisplayState parameter, add id instead.
  vnc: remove vnc_display global

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2015-01-22 12:14:19 +00:00
commit b3a4755a67
8 changed files with 617 additions and 204 deletions

View file

@ -7,7 +7,7 @@ host side
First you must compile qemu with a user interface supporting
multihead/multiseat and input event routing. Right now this
list includes sdl2 and gtk (both 2+3):
list includes sdl2, gtk (both 2+3) and vnc:
./configure --enable-sdl --with-sdlabi=2.0
@ -16,16 +16,16 @@ or
./configure --enable-gtk
Next put together the qemu command line:
Next put together the qemu command line (sdk/gtk):
qemu -enable-kvm -usb $memory $disk $whatever \
-display [ sdl | gtk ] \
-vga std \
-device usb-tablet
That is it for the first head, which will use the standard vga, the
That is it for the first seat, which will use the standard vga, the
standard ps/2 keyboard (implicitly there) and the usb-tablet. Now the
additional switches for the second head:
additional switches for the second seat:
-device pci-bridge,addr=12.0,chassis_nr=2,id=head.2 \
-device secondary-vga,bus=head.2,addr=02.0,id=video.2 \
@ -47,6 +47,16 @@ in a separate tab. You can either simply switch tabs to switch heads,
or use the "View / Detach tab" menu item to move one of the displays
to its own window so you can see both display devices side-by-side.
For vnc some additional configuration on the command line is needed.
We'll create two vnc server instances, and bind the second one to the
second seat, simliar to input devices:
-display vnc=:1,id=primary \
-display vnc=:2,id=secondary,display=video.2
Connecting to vnc display :1 gives you access to the first seat, and
likewise connecting to vnc display :2 shows the second seat.
Note on spice: Spice handles multihead just fine. But it can't do
multiseat. For tablet events the event source is sent to the spice
agent. But qemu can't figure it, so it can't do input routing.

View file

@ -331,19 +331,21 @@ void sdl_display_init(DisplayState *ds, int full_screen, int no_frame);
void cocoa_display_init(DisplayState *ds, int full_screen);
/* vnc.c */
void vnc_display_init(DisplayState *ds);
void vnc_display_open(DisplayState *ds, const char *display, Error **errp);
void vnc_display_add_client(DisplayState *ds, int csock, bool skipauth);
char *vnc_display_local_addr(DisplayState *ds);
void vnc_display_init(const char *id);
void vnc_display_open(const char *id, Error **errp);
void vnc_display_add_client(const char *id, int csock, bool skipauth);
char *vnc_display_local_addr(const char *id);
#ifdef CONFIG_VNC
int vnc_display_password(DisplayState *ds, const char *password);
int vnc_display_pw_expire(DisplayState *ds, time_t expires);
int vnc_display_password(const char *id, const char *password);
int vnc_display_pw_expire(const char *id, time_t expires);
QemuOpts *vnc_parse_func(const char *str);
int vnc_init_func(QemuOpts *opts, void *opaque);
#else
static inline int vnc_display_password(DisplayState *ds, const char *password)
static inline int vnc_display_password(const char *id, const char *password)
{
return -ENODEV;
}
static inline int vnc_display_pw_expire(DisplayState *ds, time_t expires)
static inline int vnc_display_pw_expire(const char *id, time_t expires)
{
return -ENODEV;
};

View file

@ -672,12 +672,15 @@
#
# @family: address family
#
# @websocket: true in case the socket is a websocket (since 2.3).
#
# Since: 2.1
##
{ 'type': 'VncBasicInfo',
'data': { 'host': 'str',
'service': 'str',
'family': 'NetworkAddressFamily' } }
'family': 'NetworkAddressFamily',
'websocket': 'bool' } }
##
# @VncServerInfo
@ -750,6 +753,63 @@
'*family': 'NetworkAddressFamily',
'*service': 'str', '*auth': 'str', '*clients': ['VncClientInfo']} }
##
# @VncPriAuth:
#
# vnc primary authentication method.
#
# Since: 2.3
##
{ 'enum': 'VncPrimaryAuth',
'data': [ 'none', 'vnc', 'ra2', 'ra2ne', 'tight', 'ultra',
'tls', 'vencrypt', 'sasl' ] }
##
# @VncVencryptSubAuth:
#
# vnc sub authentication method with vencrypt.
#
# Since: 2.3
##
{ 'enum': 'VncVencryptSubAuth',
'data': [ 'plain',
'tls-none', 'x509-none',
'tls-vnc', 'x509-vnc',
'tls-plain', 'x509-plain',
'tls-sasl', 'x509-sasl' ] }
##
# @VncInfo2:
#
# Information about a vnc server
#
# @id: vnc server name.
#
# @server: A list of @VncBasincInfo describing all listening sockets.
# The list can be empty (in case the vnc server is disabled).
# It also may have multiple entries: normal + websocket,
# possibly also ipv4 + ipv6 in the future.
#
# @clients: A list of @VncClientInfo of all currently connected clients.
# The list can be empty, for obvious reasons.
#
# @auth: The current authentication type used by the server
#
# @vencrypt: #optional The vencrypt sub authentication type used by the server,
# only specified in case auth == vencrypt.
#
# @display: #optional The display device the vnc server is linked to.
#
# Since: 2.3
##
{ 'type': 'VncInfo2',
'data': { 'id' : 'str',
'server' : ['VncBasicInfo'],
'clients' : ['VncClientInfo'],
'auth' : 'VncPrimaryAuth',
'*vencrypt' : 'VncVencryptSubAuth',
'*display' : 'str' } }
##
# @query-vnc:
#
@ -761,6 +821,17 @@
##
{ 'command': 'query-vnc', 'returns': 'VncInfo' }
##
# @query-vnc-servers:
#
# Returns a list of vnc servers. The list can be empty.
#
# Returns: a list of @VncInfo2
#
# Since: 2.3
##
{ 'command': 'query-vnc-servers', 'returns': ['VncInfo2'] }
##
# @SpiceBasicInfo
#

View file

@ -2867,6 +2867,11 @@ EQMP
.args_type = "",
.mhandler.cmd_new = qmp_marshal_input_query_vnc,
},
{
.name = "query-vnc-servers",
.args_type = "",
.mhandler.cmd_new = qmp_marshal_input_query_vnc_servers,
},
SQMP
query-spice

15
qmp.c
View file

@ -368,7 +368,20 @@ void qmp_change_vnc_password(const char *password, Error **errp)
static void qmp_change_vnc_listen(const char *target, Error **errp)
{
vnc_display_open(NULL, target, errp);
QemuOptsList *olist = qemu_find_opts("vnc");
QemuOpts *opts;
if (strstr(target, "id=")) {
error_setg(errp, "id not supported");
return;
}
opts = qemu_opts_find(olist, "default");
if (opts) {
qemu_opts_del(opts);
}
opts = vnc_parse_func(target);
vnc_display_open("default", errp);
}
static void qmp_change_vnc(const char *target, bool has_arg, const char *arg,

646
ui/vnc.c
View file

@ -27,10 +27,12 @@
#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"
@ -46,7 +48,8 @@ static const struct timeval VNC_REFRESH_LOSSY = { 2, 0 };
#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);
@ -65,12 +68,34 @@ static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
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;
}
}
@ -226,10 +251,10 @@ static const char *vnc_auth_name(VncDisplay *vd) {
return "unknown";
}
static VncServerInfo *vnc_server_info_get(void)
static VncServerInfo *vnc_server_info_get(VncDisplay *vd)
{
VncServerInfo *info;
VncBasicInfo *bi = vnc_basic_info_get_from_server_addr(vnc_display->lsock);
VncBasicInfo *bi = vnc_basic_info_get_from_server_addr(vd->lsock);
if (!bi) {
return NULL;
}
@ -237,7 +262,7 @@ static VncServerInfo *vnc_server_info_get(void)
info = g_malloc(sizeof(*info));
info->base = bi;
info->has_auth = true;
info->auth = g_strdup(vnc_auth_name(vnc_display));
info->auth = g_strdup(vnc_auth_name(vd));
return info;
}
@ -282,7 +307,7 @@ static void vnc_qmp_event(VncState *vs, QAPIEvent event)
}
g_assert(vs->info->base);
si = vnc_server_info_get();
si = vnc_server_info_get(vs->vd);
if (!si) {
return;
}
@ -328,6 +353,9 @@ 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) {
@ -345,43 +373,59 @@ static VncClientInfo *qmp_query_vnc_client(const VncState *client)
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->display == NULL) {
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;
@ -405,7 +449,7 @@ VncInfo *qmp_query_vnc(Error **errp)
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;
@ -415,6 +459,142 @@ out_error:
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
@ -853,7 +1033,7 @@ static int vnc_cursor_define(VncState *vs)
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);
@ -1647,7 +1827,8 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym)
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);
@ -2055,8 +2236,8 @@ static void set_pixel_format(VncState *vs,
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) {
@ -2317,6 +2498,11 @@ static int protocol_client_init(VncState *vs, uint8_t *data, size_t len)
}
vnc_set_share_mode(vs, mode);
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);
@ -2783,7 +2969,7 @@ static void vnc_refresh(DisplayChangeListener *dcl)
return;
}
graphic_hw_update(NULL);
graphic_hw_update(vd->dcl.con);
if (vnc_trylock_display(vd)) {
update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
@ -2818,6 +3004,7 @@ static void vnc_connect(VncDisplay *vd, int csock,
int i;
vs->csock = csock;
vs->vd = vd;
if (skipauth) {
vs->auth = VNC_AUTH_NONE;
@ -2862,14 +3049,21 @@ static void vnc_connect(VncDisplay *vd, int csock,
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)
@ -2888,9 +3082,9 @@ 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);
@ -2913,7 +3107,7 @@ static void vnc_listen_read(void *opaque, bool websocket)
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);
@ -2952,11 +3146,17 @@ static const DisplayChangeListenerOps dcl_ops = {
.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
@ -2984,10 +3184,8 @@ void vnc_display_init(DisplayState *ds)
}
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);
@ -3013,9 +3211,9 @@ static void vnc_display_close(DisplayState *ds)
#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;
@ -3032,9 +3230,9 @@ int vnc_display_password(DisplayState *ds, const char *password)
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;
@ -3044,21 +3242,85 @@ int vnc_display_pw_expire(DisplayState *ds, time_t expires)
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);
return vnc_socket_local_addr("%s:%s", vs->lsock);
}
void vnc_display_open(DisplayState *ds, const char *display, Error **errp)
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 = "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_BOOL,
},{
.name = "acl",
.type = QEMU_OPT_BOOL,
},{
.name = "lossy",
.type = QEMU_OPT_BOOL,
},{
.name = "non-adaptive",
.type = QEMU_OPT_BOOL,
},
{ /* end of list */ }
},
};
void vnc_display_open(const char *id, Error **errp)
{
VncDisplay *vs = vnc_display;
const char *options;
VncDisplay *vs = vnc_display_find(id);
QemuOpts *opts = qemu_opts_find(&qemu_vnc_opts, id);
const char *display, *share, *device_id;
QemuConsole *con;
int password = 0;
int reverse = 0;
#ifdef CONFIG_VNC_WS
const char *websocket;
#endif
#ifdef CONFIG_VNC_TLS
int tls = 0, x509 = 0;
const char *path;
#endif
#ifdef CONFIG_VNC_SASL
int sasl = 0;
@ -3069,120 +3331,92 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp)
#endif
int lock_key_sync = 1;
if (!vnc_display) {
if (!vs) {
error_setg(errp, "VNC display not active");
return;
}
vnc_display_close(ds);
if (strcmp(display, "none") == 0)
vnc_display_close(vs);
if (!opts) {
return;
}
display = qemu_opt_get(opts, "vnc");
if (!display || 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;
}
}
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);
#ifdef CONFIG_VNC_SASL
sasl = qemu_opt_get_bool(opts, "sasl", false);
#endif
#ifdef CONFIG_VNC_TLS
tls = qemu_opt_get_bool(opts, "tls", false);
path = qemu_opt_get(opts, "x509");
if (path) {
x509 = 1;
vs->tls.x509verify = qemu_opt_get_bool(opts, "x509verify", false);
if (vnc_tls_set_x509_creds_dir(vs, path) < 0) {
error_setg(errp, "Failed to find x509 certificates/keys in %s",
path);
goto fail;
}
}
#endif
#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);
#ifdef CONFIG_VNC_WS
websocket = qemu_opt_get(opts, "websocket");
if (websocket) {
/* extract the host specification from display */
char *host = NULL, *host_end = NULL;
vs->websocket = 1;
/* ipv6 hosts have colons */
host_end = strrchr(display, ':');
if (host_end) {
host = g_strndup(display, host_end - display + 1);
} else {
host = g_strdup(":");
}
vs->ws_display = g_strconcat(host, websocket, NULL);
g_free(host);
}
#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 */
@ -3192,18 +3426,36 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp)
#ifdef CONFIG_VNC_TLS
if (acl && x509 && vs->tls.x509verify) {
if (!(vs->tls.acl = qemu_acl_init("vnc.x509dname"))) {
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) {
if (!(vs->sasl.acl = qemu_acl_init("vnc.username"))) {
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
@ -3293,6 +3545,33 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp)
#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_set(errp, QERR_DEVICE_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;
@ -3366,9 +3645,52 @@ fail:
#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);
}
QemuOpts *vnc_parse_func(const char *str)
{
return qemu_opts_parse(qemu_find_opts("vnc"), str, 1);
}
int vnc_init_func(QemuOpts *opts, void *opaque)
{
Error *local_err = NULL;
QemuOptsList *olist = qemu_find_opts("vnc");
char *id = (char *)qemu_opts_id(opts);
if (!id) {
/* auto-assign id if not present */
int i = 2;
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);
}
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);

View file

@ -150,7 +150,10 @@ typedef enum VncSharePolicy {
struct VncDisplay
{
QTAILQ_HEAD(, VncState) clients;
int num_connecting;
int num_shared;
int num_exclusive;
int connections_limit;
VncSharePolicy share_policy;
int lsock;
#ifdef CONFIG_VNC_WS
@ -171,6 +174,8 @@ struct VncDisplay
struct VncSurface guest; /* guest visible surface (aka ds->surface) */
pixman_image_t *server; /* vnc server surface */
const char *id;
QTAILQ_ENTRY(VncDisplay) next;
char *display;
char *password;
time_t expires;

41
vl.c
View file

@ -158,9 +158,6 @@ int smp_cpus = 1;
int max_cpus = 0;
int smp_cores = 1;
int smp_threads = 1;
#ifdef CONFIG_VNC
const char *vnc_display;
#endif
int acpi_enabled = 1;
int no_hpet = 0;
int fd_bootchk = 1;
@ -2002,16 +1999,12 @@ static DisplayType select_display(const char *p)
#endif
} else if (strstart(p, "vnc", &opts)) {
#ifdef CONFIG_VNC
display_remote++;
if (*opts) {
const char *nextopt;
if (strstart(opts, "=", &nextopt)) {
vnc_display = nextopt;
if (*opts == '=') {
display_remote++;
if (vnc_parse_func(opts+1) == NULL) {
exit(1);
}
}
if (!vnc_display) {
} else {
fprintf(stderr, "VNC requires a display argument vnc=<display>\n");
exit(1);
}
@ -3479,7 +3472,9 @@ int main(int argc, char **argv, char **envp)
case QEMU_OPTION_vnc:
#ifdef CONFIG_VNC
display_remote++;
vnc_display = optarg;
if (vnc_parse_func(optarg) == NULL) {
exit(1);
}
#else
fprintf(stderr, "VNC support is disabled\n");
exit(1);
@ -3975,7 +3970,7 @@ int main(int argc, char **argv, char **envp)
#elif defined(CONFIG_SDL) || defined(CONFIG_COCOA)
display_type = DT_SDL;
#elif defined(CONFIG_VNC)
vnc_display = "localhost:0,to=99";
vnc_parse_func("localhost:0,to=99,id=default");
show_vnc_port = 1;
#else
display_type = DT_NONE;
@ -4286,20 +4281,10 @@ int main(int argc, char **argv, char **envp)
#ifdef CONFIG_VNC
/* init remote displays */
if (vnc_display) {
Error *local_err = NULL;
vnc_display_init(ds);
vnc_display_open(ds, vnc_display, &local_err);
if (local_err != NULL) {
error_report("Failed to start VNC server on `%s': %s",
vnc_display, error_get_pretty(local_err));
error_free(local_err);
exit(1);
}
if (show_vnc_port) {
printf("VNC server running on `%s'\n", vnc_display_local_addr(ds));
}
qemu_opts_foreach(qemu_find_opts("vnc"), vnc_init_func, NULL, 0);
if (show_vnc_port) {
printf("VNC server running on `%s'\n",
vnc_display_local_addr("default"));
}
#endif
#ifdef CONFIG_SPICE