vnc: add support for multiple listening sockets.

vnc: misc fixes and cleanups.
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2.0.22 (GNU/Linux)
 
 iQIcBAABAgAGBQJYnJyOAAoJEEy22O7T6HE4JZAQAKLalr4Xxm677CPCLG3juTDs
 pheRub7CTULxEp/1EonaOg8zrlMJxQw0Xhenmqv9I9PF6mMCXjJnSokqzqM+sQOV
 TuLq8f62IbKK5F7iGLX6C/X+7MRQW/W/xqa7dpAHgttgt/KeuFOpj8H6ugHgxbrb
 aXkB2lCnzvjFv/OkZ1ZM8ZjN/tiuAkUJtN4DNKyXvjwP3rhPGKmQMicUf0IQxM1q
 54i2xdW85UtGIkBW5PFh/inj7Ba6PIYH3BoIX4qNhMOMrgwlXb6dovtvHgUo6jb0
 C0CQoo8kEntq4YBiy7qK+WMfOsNoyq+H8kVXbNgO0XJo4lraa72lkH7rnSMaIJIB
 aGorHg6gNq3ehVb8MApp4nt/mi830zSYxmI+Ck8Q0P2tYc8Tx6v7fhHZCvzPiMhk
 l/FQ9kuhE7HSIFZ+RgGTHdq21O72xAb+LBtjhJ2EpBYz5GdaPKVqmjzcxvSVmz7J
 rH1jIgbQQMxppdL6xIdKcznBVPouZkQsdxwmh0kldpn1cUYzuB94BaQzU7kPkE3n
 1tdo0jWxJfcWqaNH7SLRyVjPeY+232QVez/VjFJF57SStmYKdhHrKSOj/EQe9iYU
 67TVF1lwUtMJnT0b4J1ubNQNELoxt5Pm9gawBQFC8XoJ7qRKQq+NbhUGRgcC7nBR
 2ZDjZKyM/V0rxRT6wjZV
 =nM13
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kraxel/tags/pull-ui-20170209-2' into staging

vnc: add support for multiple listening sockets.
vnc: misc fixes and cleanups.

# gpg: Signature made Thu 09 Feb 2017 16:45:02 GMT
# gpg:                using RSA key 0x4CB6D8EED3E87138
# 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>"
# Primary key fingerprint: A032 8CFF B93A 17A7 9901  FE7D 4CB6 D8EE D3E8 7138

* remotes/kraxel/tags/pull-ui-20170209-2:
  ui: add ability to specify multiple VNC listen addresses
  util: add iterators for QemuOpts values
  ui: let VNC server listen on all resolved IP addresses
  ui: extract code to connect/listen from vnc_display_open
  ui: refactor code for populating SocketAddress from vnc_display_open
  ui: refactor VncDisplay to allow multiple listening sockets
  ui: fix reporting of VNC auth in query-vnc-servers
  ui: fix regression handling bare 'websocket' option to -vnc
  vnc: do not disconnect on EAGAIN
  ui/vnc: Drop unused vnc_has_job() and vnc_jobs_clear()

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2017-02-09 16:58:39 +00:00
commit 8b1897725d
8 changed files with 533 additions and 228 deletions

View File

@ -100,6 +100,15 @@ typedef int (*qemu_opt_loopfunc)(void *opaque,
int qemu_opt_foreach(QemuOpts *opts, qemu_opt_loopfunc func, void *opaque, int qemu_opt_foreach(QemuOpts *opts, qemu_opt_loopfunc func, void *opaque,
Error **errp); Error **errp);
typedef struct {
QemuOpts *opts;
QemuOpt *opt;
const char *name;
} QemuOptsIter;
void qemu_opt_iter_init(QemuOptsIter *iter, QemuOpts *opts, const char *name);
const char *qemu_opt_iter_next(QemuOptsIter *iter);
QemuOpts *qemu_opts_find(QemuOptsList *list, const char *id); QemuOpts *qemu_opts_find(QemuOptsList *list, const char *id);
QemuOpts *qemu_opts_create(QemuOptsList *list, const char *id, QemuOpts *qemu_opts_create(QemuOptsList *list, const char *id,
int fail_if_exists, Error **errp); int fail_if_exists, Error **errp);

View File

@ -1506,7 +1506,8 @@
# #
# The network connection information for server # The network connection information for server
# #
# @auth: #optional authentication method # @auth: #optional authentication method used for
# the plain (non-websocket) VNC server
# #
# Since: 2.1 # Since: 2.1
## ##
@ -1597,6 +1598,25 @@
'tls-plain', 'x509-plain', 'tls-plain', 'x509-plain',
'tls-sasl', 'x509-sasl' ] } 'tls-sasl', 'x509-sasl' ] }
##
# @VncServerInfo2:
#
# The network connection information for server
#
# @auth: The current authentication type used by the servers
#
# @vencrypt: #optional The vencrypt sub authentication type used by the
# servers, only specified in case auth == vencrypt.
#
# Since: 2.9
##
{ 'struct': 'VncServerInfo2',
'base': 'VncBasicInfo',
'data': { 'auth' : 'VncPrimaryAuth',
'*vencrypt' : 'VncVencryptSubAuth' } }
## ##
# @VncInfo2: # @VncInfo2:
# #
@ -1612,9 +1632,9 @@
# @clients: A list of @VncClientInfo of all currently connected clients. # @clients: A list of @VncClientInfo of all currently connected clients.
# The list can be empty, for obvious reasons. # The list can be empty, for obvious reasons.
# #
# @auth: The current authentication type used by the server # @auth: The current authentication type used by the non-websockets servers
# #
# @vencrypt: #optional The vencrypt sub authentication type used by the server, # @vencrypt: #optional The vencrypt authentication type used by the servers,
# only specified in case auth == vencrypt. # only specified in case auth == vencrypt.
# #
# @display: #optional The display device the vnc server is linked to. # @display: #optional The display device the vnc server is linked to.
@ -1623,7 +1643,7 @@
## ##
{ 'struct': 'VncInfo2', { 'struct': 'VncInfo2',
'data': { 'id' : 'str', 'data': { 'id' : 'str',
'server' : ['VncBasicInfo'], 'server' : ['VncServerInfo2'],
'clients' : ['VncClientInfo'], 'clients' : ['VncClientInfo'],
'auth' : 'VncPrimaryAuth', 'auth' : 'VncPrimaryAuth',
'*vencrypt' : 'VncVencryptSubAuth', '*vencrypt' : 'VncVencryptSubAuth',

View File

@ -1296,10 +1296,14 @@ is a TCP port number, not a display number.
@item websocket @item websocket
Opens an additional TCP listening port dedicated to VNC Websocket connections. Opens an additional TCP listening port dedicated to VNC Websocket connections.
By definition the Websocket port is 5700+@var{display}. If @var{host} is If a bare @var{websocket} option is given, the Websocket port is
specified connections will only be allowed from this host. 5700+@var{display}. An alternative port can be specified with the
As an alternative the Websocket port could be specified by using syntax @code{websocket}=@var{port}.
@code{websocket}=@var{port}.
If @var{host} is specified connections will only be allowed from this host.
It is possible to control the websocket listen address independently, using
the syntax @code{websocket}=@var{host}:@var{port}.
If no TLS credentials are provided, the websocket connection runs in If no TLS credentials are provided, the websocket connection runs in
unencrypted mode. If TLS credentials are provided, the websocket connection unencrypted mode. If TLS credentials are provided, the websocket connection
requires encrypted client connections. requires encrypted client connections.

View File

@ -128,29 +128,6 @@ static bool vnc_has_job_locked(VncState *vs)
return false; return false;
} }
bool vnc_has_job(VncState *vs)
{
bool ret;
vnc_lock_queue(queue);
ret = vnc_has_job_locked(vs);
vnc_unlock_queue(queue);
return ret;
}
void vnc_jobs_clear(VncState *vs)
{
VncJob *job, *tmp;
vnc_lock_queue(queue);
QTAILQ_FOREACH_SAFE(job, &queue->jobs, next, tmp) {
if (job->vs == vs || !vs) {
QTAILQ_REMOVE(&queue->jobs, job, next);
}
}
vnc_unlock_queue(queue);
}
void vnc_jobs_join(VncState *vs) void vnc_jobs_join(VncState *vs)
{ {
vnc_lock_queue(queue); vnc_lock_queue(queue);

View File

@ -34,8 +34,6 @@
VncJob *vnc_job_new(VncState *vs); VncJob *vnc_job_new(VncState *vs);
int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h); int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h);
void vnc_job_push(VncJob *job); void vnc_job_push(VncJob *job);
bool vnc_has_job(VncState *vs);
void vnc_jobs_clear(VncState *vs);
void vnc_jobs_join(VncState *vs); void vnc_jobs_join(VncState *vs);
void vnc_jobs_consume_buffer(VncState *vs); void vnc_jobs_consume_buffer(VncState *vs);

658
ui/vnc.c
View File

@ -45,6 +45,7 @@
#include "crypto/tlscredsx509.h" #include "crypto/tlscredsx509.h"
#include "qom/object_interfaces.h" #include "qom/object_interfaces.h"
#include "qemu/cutils.h" #include "qemu/cutils.h"
#include "io/dns-resolver.h"
#define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT #define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT
#define VNC_REFRESH_INTERVAL_INC 50 #define VNC_REFRESH_INTERVAL_INC 50
@ -224,8 +225,12 @@ static VncServerInfo *vnc_server_info_get(VncDisplay *vd)
VncServerInfo *info; VncServerInfo *info;
Error *err = NULL; Error *err = NULL;
if (!vd->nlsock) {
return NULL;
}
info = g_malloc0(sizeof(*info)); info = g_malloc0(sizeof(*info));
vnc_init_basic_info_from_server_addr(vd->lsock, vnc_init_basic_info_from_server_addr(vd->lsock[0],
qapi_VncServerInfo_base(info), &err); qapi_VncServerInfo_base(info), &err);
info->has_auth = true; info->has_auth = true;
info->auth = g_strdup(vnc_auth_name(vd)); info->auth = g_strdup(vnc_auth_name(vd));
@ -371,7 +376,7 @@ VncInfo *qmp_query_vnc(Error **errp)
VncDisplay *vd = vnc_display_find(NULL); VncDisplay *vd = vnc_display_find(NULL);
SocketAddress *addr = NULL; SocketAddress *addr = NULL;
if (vd == NULL || !vd->lsock) { if (vd == NULL || !vd->nlsock) {
info->enabled = false; info->enabled = false;
} else { } else {
info->enabled = true; info->enabled = true;
@ -384,7 +389,7 @@ VncInfo *qmp_query_vnc(Error **errp)
return info; return info;
} }
addr = qio_channel_socket_get_local_address(vd->lsock, errp); addr = qio_channel_socket_get_local_address(vd->lsock[0], errp);
if (!addr) { if (!addr) {
goto out_error; goto out_error;
} }
@ -429,12 +434,20 @@ out_error:
return NULL; return NULL;
} }
static VncBasicInfoList *qmp_query_server_entry(QIOChannelSocket *ioc,
bool websocket, static void qmp_query_auth(int auth, int subauth,
VncBasicInfoList *prev) VncPrimaryAuth *qmp_auth,
VncVencryptSubAuth *qmp_vencrypt,
bool *qmp_has_vencrypt);
static VncServerInfo2List *qmp_query_server_entry(QIOChannelSocket *ioc,
bool websocket,
int auth,
int subauth,
VncServerInfo2List *prev)
{ {
VncBasicInfoList *list; VncServerInfo2List *list;
VncBasicInfo *info; VncServerInfo2 *info;
Error *err = NULL; Error *err = NULL;
SocketAddress *addr; SocketAddress *addr;
@ -444,85 +457,91 @@ static VncBasicInfoList *qmp_query_server_entry(QIOChannelSocket *ioc,
return prev; return prev;
} }
info = g_new0(VncBasicInfo, 1); info = g_new0(VncServerInfo2, 1);
vnc_init_basic_info(addr, info, &err); vnc_init_basic_info(addr, qapi_VncServerInfo2_base(info), &err);
qapi_free_SocketAddress(addr); qapi_free_SocketAddress(addr);
if (err) { if (err) {
qapi_free_VncBasicInfo(info); qapi_free_VncServerInfo2(info);
error_free(err); error_free(err);
return prev; return prev;
} }
info->websocket = websocket; info->websocket = websocket;
list = g_new0(VncBasicInfoList, 1); qmp_query_auth(auth, subauth, &info->auth,
&info->vencrypt, &info->has_vencrypt);
list = g_new0(VncServerInfo2List, 1);
list->value = info; list->value = info;
list->next = prev; list->next = prev;
return list; return list;
} }
static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info) static void qmp_query_auth(int auth, int subauth,
VncPrimaryAuth *qmp_auth,
VncVencryptSubAuth *qmp_vencrypt,
bool *qmp_has_vencrypt)
{ {
switch (vd->auth) { switch (auth) {
case VNC_AUTH_VNC: case VNC_AUTH_VNC:
info->auth = VNC_PRIMARY_AUTH_VNC; *qmp_auth = VNC_PRIMARY_AUTH_VNC;
break; break;
case VNC_AUTH_RA2: case VNC_AUTH_RA2:
info->auth = VNC_PRIMARY_AUTH_RA2; *qmp_auth = VNC_PRIMARY_AUTH_RA2;
break; break;
case VNC_AUTH_RA2NE: case VNC_AUTH_RA2NE:
info->auth = VNC_PRIMARY_AUTH_RA2NE; *qmp_auth = VNC_PRIMARY_AUTH_RA2NE;
break; break;
case VNC_AUTH_TIGHT: case VNC_AUTH_TIGHT:
info->auth = VNC_PRIMARY_AUTH_TIGHT; *qmp_auth = VNC_PRIMARY_AUTH_TIGHT;
break; break;
case VNC_AUTH_ULTRA: case VNC_AUTH_ULTRA:
info->auth = VNC_PRIMARY_AUTH_ULTRA; *qmp_auth = VNC_PRIMARY_AUTH_ULTRA;
break; break;
case VNC_AUTH_TLS: case VNC_AUTH_TLS:
info->auth = VNC_PRIMARY_AUTH_TLS; *qmp_auth = VNC_PRIMARY_AUTH_TLS;
break; break;
case VNC_AUTH_VENCRYPT: case VNC_AUTH_VENCRYPT:
info->auth = VNC_PRIMARY_AUTH_VENCRYPT; *qmp_auth = VNC_PRIMARY_AUTH_VENCRYPT;
info->has_vencrypt = true; *qmp_has_vencrypt = true;
switch (vd->subauth) { switch (subauth) {
case VNC_AUTH_VENCRYPT_PLAIN: case VNC_AUTH_VENCRYPT_PLAIN:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_PLAIN; *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_PLAIN;
break; break;
case VNC_AUTH_VENCRYPT_TLSNONE: case VNC_AUTH_VENCRYPT_TLSNONE:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_NONE; *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_NONE;
break; break;
case VNC_AUTH_VENCRYPT_TLSVNC: case VNC_AUTH_VENCRYPT_TLSVNC:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_VNC; *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_VNC;
break; break;
case VNC_AUTH_VENCRYPT_TLSPLAIN: case VNC_AUTH_VENCRYPT_TLSPLAIN:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_PLAIN; *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_PLAIN;
break; break;
case VNC_AUTH_VENCRYPT_X509NONE: case VNC_AUTH_VENCRYPT_X509NONE:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_NONE; *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_NONE;
break; break;
case VNC_AUTH_VENCRYPT_X509VNC: case VNC_AUTH_VENCRYPT_X509VNC:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_VNC; *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_VNC;
break; break;
case VNC_AUTH_VENCRYPT_X509PLAIN: case VNC_AUTH_VENCRYPT_X509PLAIN:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_PLAIN; *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_PLAIN;
break; break;
case VNC_AUTH_VENCRYPT_TLSSASL: case VNC_AUTH_VENCRYPT_TLSSASL:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_SASL; *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_SASL;
break; break;
case VNC_AUTH_VENCRYPT_X509SASL: case VNC_AUTH_VENCRYPT_X509SASL:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_SASL; *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_SASL;
break; break;
default: default:
info->has_vencrypt = false; *qmp_has_vencrypt = false;
break; break;
} }
break; break;
case VNC_AUTH_SASL: case VNC_AUTH_SASL:
info->auth = VNC_PRIMARY_AUTH_SASL; *qmp_auth = VNC_PRIMARY_AUTH_SASL;
break; break;
case VNC_AUTH_NONE: case VNC_AUTH_NONE:
default: default:
info->auth = VNC_PRIMARY_AUTH_NONE; *qmp_auth = VNC_PRIMARY_AUTH_NONE;
break; break;
} }
} }
@ -533,25 +552,28 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp)
VncInfo2 *info; VncInfo2 *info;
VncDisplay *vd; VncDisplay *vd;
DeviceState *dev; DeviceState *dev;
size_t i;
QTAILQ_FOREACH(vd, &vnc_displays, next) { QTAILQ_FOREACH(vd, &vnc_displays, next) {
info = g_new0(VncInfo2, 1); info = g_new0(VncInfo2, 1);
info->id = g_strdup(vd->id); info->id = g_strdup(vd->id);
info->clients = qmp_query_client_list(vd); info->clients = qmp_query_client_list(vd);
qmp_query_auth(vd, info); qmp_query_auth(vd->auth, vd->subauth, &info->auth,
&info->vencrypt, &info->has_vencrypt);
if (vd->dcl.con) { if (vd->dcl.con) {
dev = DEVICE(object_property_get_link(OBJECT(vd->dcl.con), dev = DEVICE(object_property_get_link(OBJECT(vd->dcl.con),
"device", NULL)); "device", NULL));
info->has_display = true; info->has_display = true;
info->display = g_strdup(dev->id); info->display = g_strdup(dev->id);
} }
if (vd->lsock != NULL) { for (i = 0; i < vd->nlsock; i++) {
info->server = qmp_query_server_entry( info->server = qmp_query_server_entry(
vd->lsock, false, info->server); vd->lsock[i], false, vd->auth, vd->subauth, info->server);
} }
if (vd->lwebsock != NULL) { for (i = 0; i < vd->nlwebsock; i++) {
info->server = qmp_query_server_entry( info->server = qmp_query_server_entry(
vd->lwebsock, true, info->server); vd->lwebsock[i], true, vd->ws_auth,
vd->ws_subauth, info->server);
} }
item = g_new0(VncInfo2List, 1); item = g_new0(VncInfo2List, 1);
@ -1256,12 +1278,13 @@ ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp)
if (ret <= 0) { if (ret <= 0) {
if (ret == 0) { if (ret == 0) {
VNC_DEBUG("Closing down client sock: EOF\n"); VNC_DEBUG("Closing down client sock: EOF\n");
vnc_disconnect_start(vs);
} else if (ret != QIO_CHANNEL_ERR_BLOCK) { } else if (ret != QIO_CHANNEL_ERR_BLOCK) {
VNC_DEBUG("Closing down client sock: ret %zd (%s)\n", VNC_DEBUG("Closing down client sock: ret %zd (%s)\n",
ret, errp ? error_get_pretty(*errp) : "Unknown"); ret, errp ? error_get_pretty(*errp) : "Unknown");
vnc_disconnect_start(vs);
} }
vnc_disconnect_start(vs);
if (errp) { if (errp) {
error_free(*errp); error_free(*errp);
*errp = NULL; *errp = NULL;
@ -3069,15 +3092,22 @@ static gboolean vnc_listen_io(QIOChannel *ioc,
VncDisplay *vd = opaque; VncDisplay *vd = opaque;
QIOChannelSocket *sioc = NULL; QIOChannelSocket *sioc = NULL;
Error *err = NULL; Error *err = NULL;
bool isWebsock = false;
size_t i;
for (i = 0; i < vd->nlwebsock; i++) {
if (ioc == QIO_CHANNEL(vd->lwebsock[i])) {
isWebsock = true;
break;
}
}
sioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc), &err); sioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc), &err);
if (sioc != NULL) { if (sioc != NULL) {
qio_channel_set_name(QIO_CHANNEL(sioc), qio_channel_set_name(QIO_CHANNEL(sioc),
ioc != QIO_CHANNEL(vd->lsock) ? isWebsock ? "vnc-ws-server" : "vnc-server");
"vnc-ws-server" : "vnc-server");
qio_channel_set_delay(QIO_CHANNEL(sioc), false); qio_channel_set_delay(QIO_CHANNEL(sioc), false);
vnc_connect(vd, sioc, false, vnc_connect(vd, sioc, false, isWebsock);
ioc != QIO_CHANNEL(vd->lsock));
object_unref(OBJECT(sioc)); object_unref(OBJECT(sioc));
} else { } else {
/* client probably closed connection before we got there */ /* client probably closed connection before we got there */
@ -3137,24 +3167,33 @@ void vnc_display_init(const char *id)
static void vnc_display_close(VncDisplay *vd) static void vnc_display_close(VncDisplay *vd)
{ {
size_t i;
if (!vd) { if (!vd) {
return; return;
} }
vd->is_unix = false; vd->is_unix = false;
if (vd->lsock != NULL) { for (i = 0; i < vd->nlsock; i++) {
if (vd->lsock_tag) { if (vd->lsock_tag[i]) {
g_source_remove(vd->lsock_tag); g_source_remove(vd->lsock_tag[i]);
} }
object_unref(OBJECT(vd->lsock)); object_unref(OBJECT(vd->lsock[i]));
vd->lsock = NULL;
} }
if (vd->lwebsock != NULL) { g_free(vd->lsock);
if (vd->lwebsock_tag) { g_free(vd->lsock_tag);
g_source_remove(vd->lwebsock_tag); vd->lsock = NULL;
vd->nlsock = 0;
for (i = 0; i < vd->nlwebsock; i++) {
if (vd->lwebsock_tag[i]) {
g_source_remove(vd->lwebsock_tag[i]);
} }
object_unref(OBJECT(vd->lwebsock)); object_unref(OBJECT(vd->lwebsock[i]));
vd->lwebsock = NULL;
} }
g_free(vd->lwebsock);
g_free(vd->lwebsock_tag);
vd->lwebsock = NULL;
vd->nlwebsock = 0;
vd->auth = VNC_AUTH_INVALID; vd->auth = VNC_AUTH_INVALID;
vd->subauth = VNC_AUTH_INVALID; vd->subauth = VNC_AUTH_INVALID;
if (vd->tlscreds) { if (vd->tlscreds) {
@ -3204,7 +3243,11 @@ static void vnc_display_print_local_addr(VncDisplay *vd)
SocketAddress *addr; SocketAddress *addr;
Error *err = NULL; Error *err = NULL;
addr = qio_channel_socket_get_local_address(vd->lsock, &err); if (!vd->nlsock) {
return;
}
addr = qio_channel_socket_get_local_address(vd->lsock[0], &err);
if (!addr) { if (!addr) {
return; return;
} }
@ -3453,19 +3496,364 @@ vnc_display_create_creds(bool x509,
} }
static int vnc_display_get_address(const char *addrstr,
bool websocket,
int displaynum,
int to,
bool has_ipv4,
bool has_ipv6,
bool ipv4,
bool ipv6,
SocketAddress **retaddr,
Error **errp)
{
int ret = -1;
SocketAddress *addr = NULL;
addr = g_new0(SocketAddress, 1);
if (strncmp(addrstr, "unix:", 5) == 0) {
addr->type = SOCKET_ADDRESS_KIND_UNIX;
addr->u.q_unix.data = g_new0(UnixSocketAddress, 1);
addr->u.q_unix.data->path = g_strdup(addrstr + 5);
if (websocket) {
error_setg(errp, "UNIX sockets not supported with websock");
goto cleanup;
}
if (to) {
error_setg(errp, "Port range not support with UNIX socket");
goto cleanup;
}
ret = 0;
} else {
const char *port;
size_t hostlen;
unsigned long long baseport = 0;
InetSocketAddress *inet;
port = strrchr(addrstr, ':');
if (!port) {
if (websocket) {
hostlen = 0;
port = addrstr;
} else {
error_setg(errp, "no vnc port specified");
goto cleanup;
}
} else {
hostlen = port - addrstr;
port++;
if (*port == '\0') {
error_setg(errp, "vnc port cannot be empty");
goto cleanup;
}
}
addr->type = SOCKET_ADDRESS_KIND_INET;
inet = addr->u.inet.data = g_new0(InetSocketAddress, 1);
if (addrstr[0] == '[' && addrstr[hostlen - 1] == ']') {
inet->host = g_strndup(addrstr + 1, hostlen - 2);
} else {
inet->host = g_strndup(addrstr, hostlen);
}
/* plain VNC port is just an offset, for websocket
* port is absolute */
if (websocket) {
if (g_str_equal(addrstr, "") ||
g_str_equal(addrstr, "on")) {
if (displaynum == -1) {
error_setg(errp, "explicit websocket port is required");
goto cleanup;
}
inet->port = g_strdup_printf(
"%d", displaynum + 5700);
if (to) {
inet->has_to = true;
inet->to = to + 5700;
}
} else {
inet->port = g_strdup(port);
}
} else {
if (parse_uint_full(port, &baseport, 10) < 0) {
error_setg(errp, "can't convert to a number: %s", port);
goto cleanup;
}
if (baseport > 65535 ||
baseport + 5900 > 65535) {
error_setg(errp, "port %s out of range", port);
goto cleanup;
}
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;
ret = baseport;
}
*retaddr = addr;
cleanup:
if (ret < 0) {
qapi_free_SocketAddress(addr);
}
return ret;
}
static int vnc_display_get_addresses(QemuOpts *opts,
SocketAddress ***retsaddr,
size_t *retnsaddr,
SocketAddress ***retwsaddr,
size_t *retnwsaddr,
Error **errp)
{
SocketAddress *saddr = NULL;
SocketAddress *wsaddr = NULL;
QemuOptsIter addriter;
const char *addr;
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);
size_t i;
int displaynum = -1;
int ret = -1;
*retsaddr = NULL;
*retnsaddr = 0;
*retwsaddr = NULL;
*retnwsaddr = 0;
addr = qemu_opt_get(opts, "vnc");
if (addr == NULL || g_str_equal(addr, "none")) {
ret = 0;
goto cleanup;
}
if (qemu_opt_get(opts, "websocket") &&
!qcrypto_hash_supports(QCRYPTO_HASH_ALG_SHA1)) {
error_setg(errp,
"SHA1 hash support is required for websockets");
goto cleanup;
}
qemu_opt_iter_init(&addriter, opts, "vnc");
while ((addr = qemu_opt_iter_next(&addriter)) != NULL) {
int rv;
rv = vnc_display_get_address(addr, false, 0, to,
has_ipv4, has_ipv6,
ipv4, ipv6,
&saddr, errp);
if (rv < 0) {
goto cleanup;
}
/* Historical compat - first listen address can be used
* to set the default websocket port
*/
if (displaynum == -1) {
displaynum = rv;
}
*retsaddr = g_renew(SocketAddress *, *retsaddr, *retnsaddr + 1);
(*retsaddr)[(*retnsaddr)++] = saddr;
}
/* If we had multiple primary displays, we don't do defaults
* for websocket, and require explicit config instead. */
if (*retnsaddr > 1) {
displaynum = -1;
}
qemu_opt_iter_init(&addriter, opts, "websocket");
while ((addr = qemu_opt_iter_next(&addriter)) != NULL) {
if (vnc_display_get_address(addr, true, displaynum, to,
has_ipv4, has_ipv6,
ipv4, ipv6,
&wsaddr, errp) < 0) {
goto cleanup;
}
/* Historical compat - if only a single listen address was
* provided, then this is used to set the default listen
* address for websocket too
*/
if (*retnsaddr == 1 &&
(*retsaddr)[0]->type == SOCKET_ADDRESS_KIND_INET &&
wsaddr->type == SOCKET_ADDRESS_KIND_INET &&
g_str_equal(wsaddr->u.inet.data->host, "") &&
!g_str_equal((*retsaddr)[0]->u.inet.data->host, "")) {
g_free(wsaddr->u.inet.data->host);
wsaddr->u.inet.data->host =
g_strdup((*retsaddr)[0]->u.inet.data->host);
}
*retwsaddr = g_renew(SocketAddress *, *retwsaddr, *retnwsaddr + 1);
(*retwsaddr)[(*retnwsaddr)++] = wsaddr;
}
ret = 0;
cleanup:
if (ret < 0) {
for (i = 0; i < *retnsaddr; i++) {
qapi_free_SocketAddress((*retsaddr)[i]);
}
g_free(*retsaddr);
for (i = 0; i < *retnwsaddr; i++) {
qapi_free_SocketAddress((*retwsaddr)[i]);
}
g_free(*retwsaddr);
*retsaddr = *retwsaddr = NULL;
*retnsaddr = *retnwsaddr = 0;
}
return ret;
}
static int vnc_display_connect(VncDisplay *vd,
SocketAddress **saddr,
size_t nsaddr,
SocketAddress **wsaddr,
size_t nwsaddr,
Error **errp)
{
/* connect to viewer */
QIOChannelSocket *sioc = NULL;
if (nwsaddr != 0) {
error_setg(errp, "Cannot use websockets in reverse mode");
return -1;
}
if (nsaddr != 1) {
error_setg(errp, "Expected a single address in reverse mode");
return -1;
}
vd->is_unix = saddr[0]->type == SOCKET_ADDRESS_KIND_UNIX;
sioc = qio_channel_socket_new();
qio_channel_set_name(QIO_CHANNEL(sioc), "vnc-reverse");
if (qio_channel_socket_connect_sync(sioc, saddr[0], errp) < 0) {
return -1;
}
vnc_connect(vd, sioc, false, false);
object_unref(OBJECT(sioc));
return 0;
}
static int vnc_display_listen_addr(VncDisplay *vd,
SocketAddress *addr,
const char *name,
QIOChannelSocket ***lsock,
guint **lsock_tag,
size_t *nlsock,
Error **errp)
{
QIODNSResolver *resolver = qio_dns_resolver_get_instance();
SocketAddress **rawaddrs = NULL;
size_t nrawaddrs = 0;
Error *listenerr = NULL;
bool listening = false;
size_t i;
if (qio_dns_resolver_lookup_sync(resolver, addr, &nrawaddrs,
&rawaddrs, errp) < 0) {
return -1;
}
for (i = 0; i < nrawaddrs; i++) {
QIOChannelSocket *sioc = qio_channel_socket_new();
qio_channel_set_name(QIO_CHANNEL(sioc), name);
if (qio_channel_socket_listen_sync(
sioc, rawaddrs[i], listenerr == NULL ? &listenerr : NULL) < 0) {
continue;
}
listening = true;
(*nlsock)++;
*lsock = g_renew(QIOChannelSocket *, *lsock, *nlsock);
*lsock_tag = g_renew(guint, *lsock_tag, *nlsock);
(*lsock)[*nlsock - 1] = sioc;
(*lsock_tag)[*nlsock - 1] = 0;
}
for (i = 0; i < nrawaddrs; i++) {
qapi_free_SocketAddress(rawaddrs[i]);
}
g_free(rawaddrs);
if (listenerr) {
if (!listening) {
error_propagate(errp, listenerr);
return -1;
} else {
error_free(listenerr);
}
}
for (i = 0; i < *nlsock; i++) {
(*lsock_tag)[i] = qio_channel_add_watch(
QIO_CHANNEL((*lsock)[i]),
G_IO_IN, vnc_listen_io, vd, NULL);
}
return 0;
}
static int vnc_display_listen(VncDisplay *vd,
SocketAddress **saddr,
size_t nsaddr,
SocketAddress **wsaddr,
size_t nwsaddr,
Error **errp)
{
size_t i;
for (i = 0; i < nsaddr; i++) {
if (vnc_display_listen_addr(vd, saddr[i],
"vnc-listen",
&vd->lsock,
&vd->lsock_tag,
&vd->nlsock,
errp) < 0) {
return -1;
}
}
for (i = 0; i < nwsaddr; i++) {
if (vnc_display_listen_addr(vd, wsaddr[i],
"vnc-ws-listen",
&vd->lwebsock,
&vd->lwebsock_tag,
&vd->nlwebsock,
errp) < 0) {
return -1;
}
}
return 0;
}
void vnc_display_open(const char *id, Error **errp) void vnc_display_open(const char *id, Error **errp)
{ {
VncDisplay *vd = vnc_display_find(id); VncDisplay *vd = vnc_display_find(id);
QemuOpts *opts = qemu_opts_find(&qemu_vnc_opts, id); QemuOpts *opts = qemu_opts_find(&qemu_vnc_opts, id);
SocketAddress *saddr = NULL, *wsaddr = NULL; SocketAddress **saddr = NULL, **wsaddr = NULL;
size_t nsaddr, nwsaddr;
const char *share, *device_id; const char *share, *device_id;
QemuConsole *con; QemuConsole *con;
bool password = false; bool password = false;
bool reverse = false; bool reverse = false;
const char *vnc;
char *h;
const char *credid; const char *credid;
int show_vnc_port = 0;
bool sasl = false; bool sasl = false;
#ifdef CONFIG_VNC_SASL #ifdef CONFIG_VNC_SASL
int saslErr; int saslErr;
@ -3473,7 +3861,7 @@ void vnc_display_open(const char *id, Error **errp)
int acl = 0; int acl = 0;
int lock_key_sync = 1; int lock_key_sync = 1;
int key_delay_ms; int key_delay_ms;
bool ws_enabled = false; size_t i;
if (!vd) { if (!vd) {
error_setg(errp, "VNC display not active"); error_setg(errp, "VNC display not active");
@ -3484,94 +3872,14 @@ void vnc_display_open(const char *id, Error **errp)
if (!opts) { if (!opts) {
return; return;
} }
vnc = qemu_opt_get(opts, "vnc");
if (!vnc || strcmp(vnc, "none") == 0) { if (vnc_display_get_addresses(opts, &saddr, &nsaddr,
return; &wsaddr, &nwsaddr, errp) < 0) {
goto fail;
} }
h = strrchr(vnc, ':'); if (saddr == NULL) {
if (h) { return;
size_t hlen = h - vnc;
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);
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 (ws_enabled) {
error_setg(errp, "UNIX sockets not supported with websock");
goto fail;
}
} else {
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;
show_vnc_port = 1;
}
inet->ipv4 = ipv4;
inet->has_ipv4 = has_ipv4;
inet->ipv6 = ipv6;
inet->has_ipv6 = has_ipv6;
if (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;
}
}
} else {
error_setg(errp, "no vnc port specified");
goto fail;
} }
password = qemu_opt_get_bool(opts, "password", false); password = qemu_opt_get_bool(opts, "password", false);
@ -3760,63 +4068,31 @@ void vnc_display_open(const char *id, Error **errp)
} }
if (reverse) { if (reverse) {
/* connect to viewer */ if (vnc_display_connect(vd, saddr, nsaddr, wsaddr, nwsaddr, errp) < 0) {
QIOChannelSocket *sioc = NULL;
vd->lsock = NULL;
vd->lwebsock = NULL;
if (ws_enabled) {
error_setg(errp, "Cannot use websockets in reverse mode");
goto fail; goto fail;
} }
vd->is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX;
sioc = qio_channel_socket_new();
qio_channel_set_name(QIO_CHANNEL(sioc), "vnc-reverse");
if (qio_channel_socket_connect_sync(sioc, saddr, errp) < 0) {
goto fail;
}
vnc_connect(vd, sioc, false, false);
object_unref(OBJECT(sioc));
} else { } else {
vd->lsock = qio_channel_socket_new(); if (vnc_display_listen(vd, saddr, nsaddr, wsaddr, nwsaddr, errp) < 0) {
qio_channel_set_name(QIO_CHANNEL(vd->lsock), "vnc-listen");
if (qio_channel_socket_listen_sync(vd->lsock, saddr, errp) < 0) {
goto fail; goto fail;
} }
vd->is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX;
if (ws_enabled) {
vd->lwebsock = qio_channel_socket_new();
qio_channel_set_name(QIO_CHANNEL(vd->lwebsock), "vnc-ws-listen");
if (qio_channel_socket_listen_sync(vd->lwebsock,
wsaddr, errp) < 0) {
object_unref(OBJECT(vd->lsock));
vd->lsock = NULL;
goto fail;
}
}
vd->lsock_tag = qio_channel_add_watch(
QIO_CHANNEL(vd->lsock),
G_IO_IN, vnc_listen_io, vd, NULL);
if (ws_enabled) {
vd->lwebsock_tag = qio_channel_add_watch(
QIO_CHANNEL(vd->lwebsock),
G_IO_IN, vnc_listen_io, vd, NULL);
}
} }
if (show_vnc_port) { if (qemu_opt_get(opts, "to")) {
vnc_display_print_local_addr(vd); vnc_display_print_local_addr(vd);
} }
qapi_free_SocketAddress(saddr); cleanup:
qapi_free_SocketAddress(wsaddr); for (i = 0; i < nsaddr; i++) {
qapi_free_SocketAddress(saddr[i]);
}
for (i = 0; i < nwsaddr; i++) {
qapi_free_SocketAddress(wsaddr[i]);
}
return; return;
fail: fail:
qapi_free_SocketAddress(saddr); vnc_display_close(vd);
qapi_free_SocketAddress(wsaddr); goto cleanup;
ws_enabled = false;
} }
void vnc_display_add_client(const char *id, int csock, bool skipauth) void vnc_display_add_client(const char *id, int csock, bool skipauth)

View File

@ -146,10 +146,12 @@ struct VncDisplay
int num_exclusive; int num_exclusive;
int connections_limit; int connections_limit;
VncSharePolicy share_policy; VncSharePolicy share_policy;
QIOChannelSocket *lsock; size_t nlsock;
guint lsock_tag; QIOChannelSocket **lsock;
QIOChannelSocket *lwebsock; guint *lsock_tag;
guint lwebsock_tag; size_t nlwebsock;
QIOChannelSocket **lwebsock;
guint *lwebsock_tag;
DisplaySurface *ds; DisplaySurface *ds;
DisplayChangeListener dcl; DisplayChangeListener dcl;
kbd_layout_t *kbd_layout; kbd_layout_t *kbd_layout;

View File

@ -332,6 +332,25 @@ const char *qemu_opt_get(QemuOpts *opts, const char *name)
return opt ? opt->str : NULL; return opt ? opt->str : NULL;
} }
void qemu_opt_iter_init(QemuOptsIter *iter, QemuOpts *opts, const char *name)
{
iter->opts = opts;
iter->opt = QTAILQ_FIRST(&opts->head);
iter->name = name;
}
const char *qemu_opt_iter_next(QemuOptsIter *iter)
{
QemuOpt *ret = iter->opt;
if (iter->name) {
while (ret && !g_str_equal(iter->name, ret->name)) {
ret = QTAILQ_NEXT(ret, next);
}
}
iter->opt = ret ? QTAILQ_NEXT(ret, next) : NULL;
return ret ? ret->str : NULL;
}
/* Get a known option (or its default) and remove it from the list /* Get a known option (or its default) and remove it from the list
* all in one action. Return a malloced string of the option value. * all in one action. Return a malloced string of the option value.
* Result must be freed by caller with g_free(). * Result must be freed by caller with g_free().