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,
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_create(QemuOptsList *list, const char *id,
int fail_if_exists, Error **errp);

View File

@ -1506,7 +1506,8 @@
#
# 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
##
@ -1597,6 +1598,25 @@
'tls-plain', 'x509-plain',
'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:
#
@ -1612,9 +1632,9 @@
# @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
# @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.
#
# @display: #optional The display device the vnc server is linked to.
@ -1623,7 +1643,7 @@
##
{ 'struct': 'VncInfo2',
'data': { 'id' : 'str',
'server' : ['VncBasicInfo'],
'server' : ['VncServerInfo2'],
'clients' : ['VncClientInfo'],
'auth' : 'VncPrimaryAuth',
'*vencrypt' : 'VncVencryptSubAuth',

View File

@ -1296,10 +1296,14 @@ is a TCP port number, not a display number.
@item websocket
Opens an additional TCP listening port dedicated to VNC Websocket connections.
By definition the Websocket port is 5700+@var{display}. If @var{host} is
specified connections will only be allowed from this host.
As an alternative the Websocket port could be specified by using
@code{websocket}=@var{port}.
If a bare @var{websocket} option is given, the Websocket port is
5700+@var{display}. An alternative port can be specified with the
syntax @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
unencrypted mode. If TLS credentials are provided, the websocket connection
requires encrypted client connections.

View File

@ -128,29 +128,6 @@ static bool vnc_has_job_locked(VncState *vs)
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)
{
vnc_lock_queue(queue);

View File

@ -34,8 +34,6 @@
VncJob *vnc_job_new(VncState *vs);
int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h);
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_consume_buffer(VncState *vs);

658
ui/vnc.c
View File

@ -45,6 +45,7 @@
#include "crypto/tlscredsx509.h"
#include "qom/object_interfaces.h"
#include "qemu/cutils.h"
#include "io/dns-resolver.h"
#define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT
#define VNC_REFRESH_INTERVAL_INC 50
@ -224,8 +225,12 @@ static VncServerInfo *vnc_server_info_get(VncDisplay *vd)
VncServerInfo *info;
Error *err = NULL;
if (!vd->nlsock) {
return NULL;
}
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);
info->has_auth = true;
info->auth = g_strdup(vnc_auth_name(vd));
@ -371,7 +376,7 @@ VncInfo *qmp_query_vnc(Error **errp)
VncDisplay *vd = vnc_display_find(NULL);
SocketAddress *addr = NULL;
if (vd == NULL || !vd->lsock) {
if (vd == NULL || !vd->nlsock) {
info->enabled = false;
} else {
info->enabled = true;
@ -384,7 +389,7 @@ VncInfo *qmp_query_vnc(Error **errp)
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) {
goto out_error;
}
@ -429,12 +434,20 @@ out_error:
return NULL;
}
static VncBasicInfoList *qmp_query_server_entry(QIOChannelSocket *ioc,
bool websocket,
VncBasicInfoList *prev)
static void qmp_query_auth(int auth, int subauth,
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;
VncBasicInfo *info;
VncServerInfo2List *list;
VncServerInfo2 *info;
Error *err = NULL;
SocketAddress *addr;
@ -444,85 +457,91 @@ static VncBasicInfoList *qmp_query_server_entry(QIOChannelSocket *ioc,
return prev;
}
info = g_new0(VncBasicInfo, 1);
vnc_init_basic_info(addr, info, &err);
info = g_new0(VncServerInfo2, 1);
vnc_init_basic_info(addr, qapi_VncServerInfo2_base(info), &err);
qapi_free_SocketAddress(addr);
if (err) {
qapi_free_VncBasicInfo(info);
qapi_free_VncServerInfo2(info);
error_free(err);
return prev;
}
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->next = prev;
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:
info->auth = VNC_PRIMARY_AUTH_VNC;
*qmp_auth = VNC_PRIMARY_AUTH_VNC;
break;
case VNC_AUTH_RA2:
info->auth = VNC_PRIMARY_AUTH_RA2;
*qmp_auth = VNC_PRIMARY_AUTH_RA2;
break;
case VNC_AUTH_RA2NE:
info->auth = VNC_PRIMARY_AUTH_RA2NE;
*qmp_auth = VNC_PRIMARY_AUTH_RA2NE;
break;
case VNC_AUTH_TIGHT:
info->auth = VNC_PRIMARY_AUTH_TIGHT;
*qmp_auth = VNC_PRIMARY_AUTH_TIGHT;
break;
case VNC_AUTH_ULTRA:
info->auth = VNC_PRIMARY_AUTH_ULTRA;
*qmp_auth = VNC_PRIMARY_AUTH_ULTRA;
break;
case VNC_AUTH_TLS:
info->auth = VNC_PRIMARY_AUTH_TLS;
*qmp_auth = VNC_PRIMARY_AUTH_TLS;
break;
case VNC_AUTH_VENCRYPT:
info->auth = VNC_PRIMARY_AUTH_VENCRYPT;
info->has_vencrypt = true;
switch (vd->subauth) {
*qmp_auth = VNC_PRIMARY_AUTH_VENCRYPT;
*qmp_has_vencrypt = true;
switch (subauth) {
case VNC_AUTH_VENCRYPT_PLAIN:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_PLAIN;
*qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_PLAIN;
break;
case VNC_AUTH_VENCRYPT_TLSNONE:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_NONE;
*qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_NONE;
break;
case VNC_AUTH_VENCRYPT_TLSVNC:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_VNC;
*qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_VNC;
break;
case VNC_AUTH_VENCRYPT_TLSPLAIN:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_PLAIN;
*qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_PLAIN;
break;
case VNC_AUTH_VENCRYPT_X509NONE:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_NONE;
*qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_NONE;
break;
case VNC_AUTH_VENCRYPT_X509VNC:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_VNC;
*qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_VNC;
break;
case VNC_AUTH_VENCRYPT_X509PLAIN:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_PLAIN;
*qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_PLAIN;
break;
case VNC_AUTH_VENCRYPT_TLSSASL:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_SASL;
*qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_SASL;
break;
case VNC_AUTH_VENCRYPT_X509SASL:
info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_SASL;
*qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_SASL;
break;
default:
info->has_vencrypt = false;
*qmp_has_vencrypt = false;
break;
}
break;
case VNC_AUTH_SASL:
info->auth = VNC_PRIMARY_AUTH_SASL;
*qmp_auth = VNC_PRIMARY_AUTH_SASL;
break;
case VNC_AUTH_NONE:
default:
info->auth = VNC_PRIMARY_AUTH_NONE;
*qmp_auth = VNC_PRIMARY_AUTH_NONE;
break;
}
}
@ -533,25 +552,28 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp)
VncInfo2 *info;
VncDisplay *vd;
DeviceState *dev;
size_t i;
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);
qmp_query_auth(vd->auth, vd->subauth, &info->auth,
&info->vencrypt, &info->has_vencrypt);
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 != NULL) {
for (i = 0; i < vd->nlsock; i++) {
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(
vd->lwebsock, true, info->server);
vd->lwebsock[i], true, vd->ws_auth,
vd->ws_subauth, info->server);
}
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) {
VNC_DEBUG("Closing down client sock: EOF\n");
vnc_disconnect_start(vs);
} else if (ret != QIO_CHANNEL_ERR_BLOCK) {
VNC_DEBUG("Closing down client sock: ret %zd (%s)\n",
ret, errp ? error_get_pretty(*errp) : "Unknown");
vnc_disconnect_start(vs);
}
vnc_disconnect_start(vs);
if (errp) {
error_free(*errp);
*errp = NULL;
@ -3069,15 +3092,22 @@ static gboolean vnc_listen_io(QIOChannel *ioc,
VncDisplay *vd = opaque;
QIOChannelSocket *sioc = 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);
if (sioc != NULL) {
qio_channel_set_name(QIO_CHANNEL(sioc),
ioc != QIO_CHANNEL(vd->lsock) ?
"vnc-ws-server" : "vnc-server");
isWebsock ? "vnc-ws-server" : "vnc-server");
qio_channel_set_delay(QIO_CHANNEL(sioc), false);
vnc_connect(vd, sioc, false,
ioc != QIO_CHANNEL(vd->lsock));
vnc_connect(vd, sioc, false, isWebsock);
object_unref(OBJECT(sioc));
} else {
/* 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)
{
size_t i;
if (!vd) {
return;
}
vd->is_unix = false;
if (vd->lsock != NULL) {
if (vd->lsock_tag) {
g_source_remove(vd->lsock_tag);
for (i = 0; i < vd->nlsock; i++) {
if (vd->lsock_tag[i]) {
g_source_remove(vd->lsock_tag[i]);
}
object_unref(OBJECT(vd->lsock));
vd->lsock = NULL;
object_unref(OBJECT(vd->lsock[i]));
}
if (vd->lwebsock != NULL) {
if (vd->lwebsock_tag) {
g_source_remove(vd->lwebsock_tag);
g_free(vd->lsock);
g_free(vd->lsock_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));
vd->lwebsock = NULL;
object_unref(OBJECT(vd->lwebsock[i]));
}
g_free(vd->lwebsock);
g_free(vd->lwebsock_tag);
vd->lwebsock = NULL;
vd->nlwebsock = 0;
vd->auth = VNC_AUTH_INVALID;
vd->subauth = VNC_AUTH_INVALID;
if (vd->tlscreds) {
@ -3204,7 +3243,11 @@ static void vnc_display_print_local_addr(VncDisplay *vd)
SocketAddress *addr;
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) {
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)
{
VncDisplay *vd = vnc_display_find(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;
QemuConsole *con;
bool password = false;
bool reverse = false;
const char *vnc;
char *h;
const char *credid;
int show_vnc_port = 0;
bool sasl = false;
#ifdef CONFIG_VNC_SASL
int saslErr;
@ -3473,7 +3861,7 @@ void vnc_display_open(const char *id, Error **errp)
int acl = 0;
int lock_key_sync = 1;
int key_delay_ms;
bool ws_enabled = false;
size_t i;
if (!vd) {
error_setg(errp, "VNC display not active");
@ -3484,94 +3872,14 @@ void vnc_display_open(const char *id, Error **errp)
if (!opts) {
return;
}
vnc = qemu_opt_get(opts, "vnc");
if (!vnc || strcmp(vnc, "none") == 0) {
return;
if (vnc_display_get_addresses(opts, &saddr, &nsaddr,
&wsaddr, &nwsaddr, errp) < 0) {
goto fail;
}
h = strrchr(vnc, ':');
if (h) {
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;
if (saddr == NULL) {
return;
}
password = qemu_opt_get_bool(opts, "password", false);
@ -3760,63 +4068,31 @@ void vnc_display_open(const char *id, Error **errp)
}
if (reverse) {
/* connect to viewer */
QIOChannelSocket *sioc = NULL;
vd->lsock = NULL;
vd->lwebsock = NULL;
if (ws_enabled) {
error_setg(errp, "Cannot use websockets in reverse mode");
if (vnc_display_connect(vd, saddr, nsaddr, wsaddr, nwsaddr, errp) < 0) {
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 {
vd->lsock = qio_channel_socket_new();
qio_channel_set_name(QIO_CHANNEL(vd->lsock), "vnc-listen");
if (qio_channel_socket_listen_sync(vd->lsock, saddr, errp) < 0) {
if (vnc_display_listen(vd, saddr, nsaddr, wsaddr, nwsaddr, errp) < 0) {
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);
}
qapi_free_SocketAddress(saddr);
qapi_free_SocketAddress(wsaddr);
cleanup:
for (i = 0; i < nsaddr; i++) {
qapi_free_SocketAddress(saddr[i]);
}
for (i = 0; i < nwsaddr; i++) {
qapi_free_SocketAddress(wsaddr[i]);
}
return;
fail:
qapi_free_SocketAddress(saddr);
qapi_free_SocketAddress(wsaddr);
ws_enabled = false;
vnc_display_close(vd);
goto cleanup;
}
void vnc_display_add_client(const char *id, int csock, bool skipauth)

View File

@ -146,10 +146,12 @@ struct VncDisplay
int num_exclusive;
int connections_limit;
VncSharePolicy share_policy;
QIOChannelSocket *lsock;
guint lsock_tag;
QIOChannelSocket *lwebsock;
guint lwebsock_tag;
size_t nlsock;
QIOChannelSocket **lsock;
guint *lsock_tag;
size_t nlwebsock;
QIOChannelSocket **lwebsock;
guint *lwebsock_tag;
DisplaySurface *ds;
DisplayChangeListener dcl;
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;
}
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
* all in one action. Return a malloced string of the option value.
* Result must be freed by caller with g_free().