summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2017-02-09 16:58:39 +0000
committerPeter Maydell <peter.maydell@linaro.org>2017-02-09 16:58:39 +0000
commit8b1897725d07c02a55c06e1c1a885c5c17f15b0f (patch)
tree3fdc2a6a05180fa8ccb0fbacc7f139153387918d
parentf073cd3a2bf1054135271b837c58a7da650dd84b (diff)
parent396f935a9aff3cefd288bdb2b20024fb9dcfa062 (diff)
downloadqemu-8b1897725d07c02a55c06e1c1a885c5c17f15b0f.zip
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>
-rw-r--r--include/qemu/option.h9
-rw-r--r--qapi-schema.json28
-rw-r--r--qemu-options.hx12
-rw-r--r--ui/vnc-jobs.c23
-rw-r--r--ui/vnc-jobs.h2
-rw-r--r--ui/vnc.c658
-rw-r--r--ui/vnc.h10
-rw-r--r--util/qemu-option.c19
8 files changed, 533 insertions, 228 deletions
diff --git a/include/qemu/option.h b/include/qemu/option.h
index 1f9e3f939d..e786df0cfa 100644
--- a/include/qemu/option.h
+++ b/include/qemu/option.h
@@ -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);
diff --git a/qapi-schema.json b/qapi-schema.json
index cbdffddbc6..61151f34d0 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -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',
diff --git a/qemu-options.hx b/qemu-options.hx
index ad2f8fc873..ac036b4cbd 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -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.
diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c
index 98ca978b06..f7867771ae 100644
--- a/ui/vnc-jobs.c
+++ b/ui/vnc-jobs.c
@@ -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);
diff --git a/ui/vnc-jobs.h b/ui/vnc-jobs.h
index 044bf9fbfd..59f66bcc35 100644
--- a/ui/vnc-jobs.h
+++ b/ui/vnc-jobs.h
@@ -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);
diff --git a/ui/vnc.c b/ui/vnc.c
index cdeb79c3cc..62e85edf5d 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -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,96 +3872,16 @@ 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;
- }
-
- 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");
+ if (vnc_display_get_addresses(opts, &saddr, &nsaddr,
+ &wsaddr, &nwsaddr, errp) < 0) {
goto fail;
}
+ if (saddr == NULL) {
+ return;
+ }
+
password = qemu_opt_get_bool(opts, "password", false);
if (password) {
if (fips_get_state()) {
@@ -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");
- 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) {
+ if (vnc_display_connect(vd, saddr, nsaddr, wsaddr, nwsaddr, 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)
diff --git a/ui/vnc.h b/ui/vnc.h
index d8c9de5a75..694cf32ca9 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -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;
diff --git a/util/qemu-option.c b/util/qemu-option.c
index 3467dc2397..d611946333 100644
--- a/util/qemu-option.c
+++ b/util/qemu-option.c
@@ -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().