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,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) 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().