ui: convert VNC server to use QIOChannelSocket

The minimal first step conversion to use QIOChannelSocket
classes instead of directly using POSIX sockets API. This
will later be extended to also cover the TLS, SASL and
websockets code.

Reviewed-by: Gerd Hoffmann <kraxel@redhat.com>
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrange 2015-02-27 16:20:57 +00:00
parent 18f49881cf
commit 04d2529da2
7 changed files with 435 additions and 328 deletions

View File

@ -62,7 +62,7 @@ long vnc_client_write_sasl(VncState *vs)
(const char **)&vs->sasl.encoded, (const char **)&vs->sasl.encoded,
&vs->sasl.encodedLength); &vs->sasl.encodedLength);
if (err != SASL_OK) if (err != SASL_OK)
return vnc_client_io_error(vs, -1, EIO); return vnc_client_io_error(vs, -1, NULL);
vs->sasl.encodedOffset = 0; vs->sasl.encodedOffset = 0;
} }
@ -86,7 +86,11 @@ long vnc_client_write_sasl(VncState *vs)
* SASL encoded output * SASL encoded output
*/ */
if (vs->output.offset == 0) { if (vs->output.offset == 0) {
qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vnc_client_io, vs, NULL);
} }
return ret; return ret;
@ -110,7 +114,7 @@ long vnc_client_read_sasl(VncState *vs)
&decoded, &decodedLen); &decoded, &decodedLen);
if (err != SASL_OK) if (err != SASL_OK)
return vnc_client_io_error(vs, -1, -EIO); return vnc_client_io_error(vs, -1, NULL);
VNC_DEBUG("Read SASL Encoded %p size %ld Decoded %p size %d\n", VNC_DEBUG("Read SASL Encoded %p size %ld Decoded %p size %d\n",
encoded, ret, decoded, decodedLen); encoded, ret, decoded, decodedLen);
buffer_reserve(&vs->input, decodedLen); buffer_reserve(&vs->input, decodedLen);
@ -255,17 +259,17 @@ static int protocol_client_auth_sasl_step(VncState *vs, uint8_t *data, size_t le
vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4); vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4);
} else { } else {
if (!vnc_auth_sasl_check_ssf(vs)) { if (!vnc_auth_sasl_check_ssf(vs)) {
VNC_DEBUG("Authentication rejected for weak SSF %d\n", vs->csock); VNC_DEBUG("Authentication rejected for weak SSF %p\n", vs->ioc);
goto authreject; goto authreject;
} }
/* Check username whitelist ACL */ /* Check username whitelist ACL */
if (vnc_auth_sasl_check_access(vs) < 0) { if (vnc_auth_sasl_check_access(vs) < 0) {
VNC_DEBUG("Authentication rejected for ACL %d\n", vs->csock); VNC_DEBUG("Authentication rejected for ACL %p\n", vs->ioc);
goto authreject; goto authreject;
} }
VNC_DEBUG("Authentication successful %d\n", vs->csock); VNC_DEBUG("Authentication successful %p\n", vs->ioc);
vnc_write_u32(vs, 0); /* Accept auth */ vnc_write_u32(vs, 0); /* Accept auth */
/* /*
* Delay writing in SSF encoded mode until pending output * Delay writing in SSF encoded mode until pending output
@ -383,17 +387,17 @@ static int protocol_client_auth_sasl_start(VncState *vs, uint8_t *data, size_t l
vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4); vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4);
} else { } else {
if (!vnc_auth_sasl_check_ssf(vs)) { if (!vnc_auth_sasl_check_ssf(vs)) {
VNC_DEBUG("Authentication rejected for weak SSF %d\n", vs->csock); VNC_DEBUG("Authentication rejected for weak SSF %p\n", vs->ioc);
goto authreject; goto authreject;
} }
/* Check username whitelist ACL */ /* Check username whitelist ACL */
if (vnc_auth_sasl_check_access(vs) < 0) { if (vnc_auth_sasl_check_access(vs) < 0) {
VNC_DEBUG("Authentication rejected for ACL %d\n", vs->csock); VNC_DEBUG("Authentication rejected for ACL %p\n", vs->ioc);
goto authreject; goto authreject;
} }
VNC_DEBUG("Authentication successful %d\n", vs->csock); VNC_DEBUG("Authentication successful %p\n", vs->ioc);
vnc_write_u32(vs, 0); /* Accept auth */ vnc_write_u32(vs, 0); /* Accept auth */
start_client_init(vs); start_client_init(vs);
} }
@ -487,6 +491,32 @@ static int protocol_client_auth_sasl_mechname_len(VncState *vs, uint8_t *data, s
return 0; return 0;
} }
static char *
vnc_socket_ip_addr_string(QIOChannelSocket *ioc,
bool local,
Error **errp)
{
SocketAddress *addr;
char *ret;
if (local) {
addr = qio_channel_socket_get_local_address(ioc, errp);
} else {
addr = qio_channel_socket_get_remote_address(ioc, errp);
}
if (!addr) {
return NULL;
}
if (addr->type != SOCKET_ADDRESS_KIND_INET) {
error_setg(errp, "Not an inet socket type");
return NULL;
}
ret = g_strdup_printf("%s;%s", addr->u.inet->host, addr->u.inet->port);
qapi_free_SocketAddress(addr);
return ret;
}
void start_auth_sasl(VncState *vs) void start_auth_sasl(VncState *vs)
{ {
const char *mechlist = NULL; const char *mechlist = NULL;
@ -495,13 +525,16 @@ void start_auth_sasl(VncState *vs)
char *localAddr, *remoteAddr; char *localAddr, *remoteAddr;
int mechlistlen; int mechlistlen;
VNC_DEBUG("Initialize SASL auth %d\n", vs->csock); VNC_DEBUG("Initialize SASL auth %p\n", vs->ioc);
/* Get local & remote client addresses in form IPADDR;PORT */ /* Get local & remote client addresses in form IPADDR;PORT */
if (!(localAddr = vnc_socket_local_addr("%s;%s", vs->csock))) localAddr = vnc_socket_ip_addr_string(vs->sioc, true, NULL);
if (!localAddr) {
goto authabort; goto authabort;
}
if (!(remoteAddr = vnc_socket_remote_addr("%s;%s", vs->csock))) { remoteAddr = vnc_socket_ip_addr_string(vs->sioc, false, NULL);
if (!remoteAddr) {
g_free(localAddr); g_free(localAddr);
goto authabort; goto authabort;
} }

View File

@ -63,7 +63,9 @@ static void start_auth_vencrypt_subauth(VncState *vs)
} }
} }
static void vnc_tls_handshake_io(void *opaque); static gboolean vnc_tls_handshake_io(QIOChannel *ioc,
GIOCondition condition,
void *opaque);
static int vnc_start_vencrypt_handshake(VncState *vs) static int vnc_start_vencrypt_handshake(VncState *vs)
{ {
@ -80,19 +82,31 @@ static int vnc_start_vencrypt_handshake(VncState *vs)
goto error; goto error;
} }
VNC_DEBUG("Client verification passed, starting TLS I/O\n"); VNC_DEBUG("Client verification passed, starting TLS I/O\n");
qemu_set_fd_handler(vs->csock, vnc_client_read, vnc_client_write, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL);
start_auth_vencrypt_subauth(vs); start_auth_vencrypt_subauth(vs);
break; break;
case QCRYPTO_TLS_HANDSHAKE_RECVING: case QCRYPTO_TLS_HANDSHAKE_RECVING:
VNC_DEBUG("Handshake interrupted (blocking read)\n"); VNC_DEBUG("Handshake interrupted (blocking read)\n");
qemu_set_fd_handler(vs->csock, vnc_tls_handshake_io, NULL, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vnc_tls_handshake_io, vs, NULL);
break; break;
case QCRYPTO_TLS_HANDSHAKE_SENDING: case QCRYPTO_TLS_HANDSHAKE_SENDING:
VNC_DEBUG("Handshake interrupted (blocking write)\n"); VNC_DEBUG("Handshake interrupted (blocking write)\n");
qemu_set_fd_handler(vs->csock, NULL, vnc_tls_handshake_io, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_OUT, vnc_tls_handshake_io, vs, NULL);
break; break;
} }
@ -105,12 +119,15 @@ static int vnc_start_vencrypt_handshake(VncState *vs)
return -1; return -1;
} }
static void vnc_tls_handshake_io(void *opaque) static gboolean vnc_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
GIOCondition condition G_GNUC_UNUSED,
void *opaque)
{ {
VncState *vs = (VncState *)opaque; VncState *vs = (VncState *)opaque;
VNC_DEBUG("Handshake IO continue\n"); VNC_DEBUG("Handshake IO continue\n");
vnc_start_vencrypt_handshake(vs); vnc_start_vencrypt_handshake(vs);
return TRUE;
} }

View File

@ -166,13 +166,16 @@ void vnc_jobs_consume_buffer(VncState *vs)
vnc_lock_output(vs); vnc_lock_output(vs);
if (vs->jobs_buffer.offset) { if (vs->jobs_buffer.offset) {
if (vs->csock != -1 && buffer_empty(&vs->output)) { if (vs->ioc != NULL && buffer_empty(&vs->output)) {
qemu_set_fd_handler(vs->csock, vnc_client_read, if (vs->ioc_tag) {
vnc_client_write, vs); g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL);
} }
buffer_move(&vs->output, &vs->jobs_buffer); buffer_move(&vs->output, &vs->jobs_buffer);
} }
flush = vs->csock != -1 && vs->abort != true; flush = vs->ioc != NULL && vs->abort != true;
vnc_unlock_output(vs); vnc_unlock_output(vs);
if (flush) { if (flush) {
@ -186,7 +189,8 @@ void vnc_jobs_consume_buffer(VncState *vs)
static void vnc_async_encoding_start(VncState *orig, VncState *local) static void vnc_async_encoding_start(VncState *orig, VncState *local)
{ {
buffer_init(&local->output, "vnc-worker-output"); buffer_init(&local->output, "vnc-worker-output");
local->csock = -1; /* Don't do any network work on this thread */ local->sioc = NULL; /* Don't do any network work on this thread */
local->ioc = NULL; /* Don't do any network work on this thread */
local->vnc_encoding = orig->vnc_encoding; local->vnc_encoding = orig->vnc_encoding;
local->features = orig->features; local->features = orig->features;
@ -231,7 +235,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
} }
vnc_lock_output(job->vs); vnc_lock_output(job->vs);
if (job->vs->csock == -1 || job->vs->abort == true) { if (job->vs->ioc == NULL || job->vs->abort == true) {
vnc_unlock_output(job->vs); vnc_unlock_output(job->vs);
goto disconnected; goto disconnected;
} }
@ -259,7 +263,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) { QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
int n; int n;
if (job->vs->csock == -1) { if (job->vs->ioc == NULL) {
vnc_unlock_display(job->vs->vd); vnc_unlock_display(job->vs->vd);
/* Copy persistent encoding data */ /* Copy persistent encoding data */
vnc_async_encoding_end(job->vs, &vs); vnc_async_encoding_end(job->vs, &vs);
@ -281,7 +285,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
vs.output.buffer[saved_offset + 1] = n_rectangles & 0xFF; vs.output.buffer[saved_offset + 1] = n_rectangles & 0xFF;
vnc_lock_output(job->vs); vnc_lock_output(job->vs);
if (job->vs->csock != -1) { if (job->vs->ioc != NULL) {
buffer_move(&job->vs->jobs_buffer, &vs.output); buffer_move(&job->vs->jobs_buffer, &vs.output);
/* Copy persistent encoding data */ /* Copy persistent encoding data */
vnc_async_encoding_end(job->vs, &vs); vnc_async_encoding_end(job->vs, &vs);

View File

@ -37,17 +37,29 @@ static int vncws_start_tls_handshake(VncState *vs)
goto error; goto error;
} }
VNC_DEBUG("Client verification passed, starting TLS I/O\n"); VNC_DEBUG("Client verification passed, starting TLS I/O\n");
qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vncws_handshake_io, vs, NULL);
break; break;
case QCRYPTO_TLS_HANDSHAKE_RECVING: case QCRYPTO_TLS_HANDSHAKE_RECVING:
VNC_DEBUG("Handshake interrupted (blocking read)\n"); VNC_DEBUG("Handshake interrupted (blocking read)\n");
qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io, NULL, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vncws_tls_handshake_io, vs, NULL);
break; break;
case QCRYPTO_TLS_HANDSHAKE_SENDING: case QCRYPTO_TLS_HANDSHAKE_SENDING:
VNC_DEBUG("Handshake interrupted (blocking write)\n"); VNC_DEBUG("Handshake interrupted (blocking write)\n");
qemu_set_fd_handler(vs->csock, NULL, vncws_tls_handshake_io, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_OUT, vncws_tls_handshake_io, vs, NULL);
break; break;
} }
@ -60,7 +72,9 @@ static int vncws_start_tls_handshake(VncState *vs)
return -1; return -1;
} }
void vncws_tls_handshake_io(void *opaque) gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
GIOCondition condition G_GNUC_UNUSED,
void *opaque)
{ {
VncState *vs = (VncState *)opaque; VncState *vs = (VncState *)opaque;
Error *err = NULL; Error *err = NULL;
@ -75,7 +89,7 @@ void vncws_tls_handshake_io(void *opaque)
error_get_pretty(err)); error_get_pretty(err));
error_free(err); error_free(err);
vnc_client_error(vs); vnc_client_error(vs);
return; return TRUE;
} }
qcrypto_tls_session_set_callbacks(vs->tls, qcrypto_tls_session_set_callbacks(vs->tls,
@ -85,11 +99,11 @@ void vncws_tls_handshake_io(void *opaque)
VNC_DEBUG("Start TLS WS handshake process\n"); VNC_DEBUG("Start TLS WS handshake process\n");
vncws_start_tls_handshake(vs); vncws_start_tls_handshake(vs);
return TRUE;
} }
void vncws_handshake_read(void *opaque) static void vncws_handshake_read(VncState *vs)
{ {
VncState *vs = opaque;
uint8_t *handshake_end; uint8_t *handshake_end;
long ret; long ret;
/* Typical HTTP headers from novnc are 512 bytes, so limiting /* Typical HTTP headers from novnc are 512 bytes, so limiting
@ -99,7 +113,7 @@ void vncws_handshake_read(void *opaque)
ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), want); ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), want);
if (!ret) { if (!ret) {
if (vs->csock == -1) { if (vs->disconnecting) {
vnc_disconnect_finish(vs); vnc_disconnect_finish(vs);
} }
return; return;
@ -109,7 +123,11 @@ void vncws_handshake_read(void *opaque)
handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer, handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer,
vs->ws_input.offset, WS_HANDSHAKE_END); vs->ws_input.offset, WS_HANDSHAKE_END);
if (handshake_end) { if (handshake_end) {
qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vnc_client_io, vs, NULL);
vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset); vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset);
buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer + buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
strlen(WS_HANDSHAKE_END)); strlen(WS_HANDSHAKE_END));
@ -120,6 +138,15 @@ void vncws_handshake_read(void *opaque)
} }
gboolean vncws_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
GIOCondition condition G_GNUC_UNUSED,
void *opaque)
{
VncState *vs = opaque;
vncws_handshake_read(vs);
return TRUE;
}
long vnc_client_read_ws(VncState *vs) long vnc_client_read_ws(VncState *vs)
{ {
int ret, err; int ret, err;
@ -187,7 +214,11 @@ long vnc_client_write_ws(VncState *vs)
buffer_advance(&vs->ws_output, ret); buffer_advance(&vs->ws_output, ret);
if (vs->ws_output.offset == 0) { if (vs->ws_output.offset == 0) {
qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vnc_client_io, vs, NULL);
} }
return ret; return ret;

View File

@ -72,8 +72,12 @@ enum {
WS_OPCODE_PONG = 0xA WS_OPCODE_PONG = 0xA
}; };
void vncws_tls_handshake_io(void *opaque); gboolean vncws_tls_handshake_io(QIOChannel *ioc,
void vncws_handshake_read(void *opaque); GIOCondition condition,
void *opaque);
gboolean vncws_handshake_io(QIOChannel *ioc,
GIOCondition condition,
void *opaque);
long vnc_client_write_ws(VncState *vs); long vnc_client_write_ws(VncState *vs);
long vnc_client_read_ws(VncState *vs); long vnc_client_read_ws(VncState *vs);
void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size); void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size);

578
ui/vnc.c
View File

@ -70,8 +70,8 @@ static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
[VNC_SHARE_MODE_EXCLUSIVE] = "exclusive", [VNC_SHARE_MODE_EXCLUSIVE] = "exclusive",
[VNC_SHARE_MODE_DISCONNECTED] = "disconnected", [VNC_SHARE_MODE_DISCONNECTED] = "disconnected",
}; };
fprintf(stderr, "%s/%d: %s -> %s\n", __func__, fprintf(stderr, "%s/%p: %s -> %s\n", __func__,
vs->csock, mn[vs->share_mode], mn[mode]); vs->ioc, mn[vs->share_mode], mn[mode]);
#endif #endif
switch (vs->share_mode) { switch (vs->share_mode) {
@ -105,108 +105,65 @@ static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
} }
} }
static char *addr_to_string(const char *format,
struct sockaddr_storage *sa,
socklen_t salen) {
char *addr;
char host[NI_MAXHOST];
char serv[NI_MAXSERV];
int err;
size_t addrlen;
if ((err = getnameinfo((struct sockaddr *)sa, salen, static void vnc_init_basic_info(SocketAddress *addr,
host, sizeof(host),
serv, sizeof(serv),
NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
VNC_DEBUG("Cannot resolve address %d: %s\n",
err, gai_strerror(err));
return NULL;
}
/* Enough for the existing format + the 2 vars we're
* substituting in. */
addrlen = strlen(format) + strlen(host) + strlen(serv);
addr = g_malloc(addrlen + 1);
snprintf(addr, addrlen, format, host, serv);
addr[addrlen] = '\0';
return addr;
}
char *vnc_socket_local_addr(const char *format, int fd) {
struct sockaddr_storage sa;
socklen_t salen;
salen = sizeof(sa);
if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0)
return NULL;
return addr_to_string(format, &sa, salen);
}
char *vnc_socket_remote_addr(const char *format, int fd) {
struct sockaddr_storage sa;
socklen_t salen;
salen = sizeof(sa);
if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0)
return NULL;
return addr_to_string(format, &sa, salen);
}
static void vnc_init_basic_info(struct sockaddr_storage *sa,
socklen_t salen,
VncBasicInfo *info, VncBasicInfo *info,
Error **errp) Error **errp)
{ {
char host[NI_MAXHOST]; switch (addr->type) {
char serv[NI_MAXSERV]; case SOCKET_ADDRESS_KIND_INET:
int err; info->host = g_strdup(addr->u.inet->host);
info->service = g_strdup(addr->u.inet->port);
if (addr->u.inet->ipv6) {
info->family = NETWORK_ADDRESS_FAMILY_IPV6;
} else {
info->family = NETWORK_ADDRESS_FAMILY_IPV4;
}
break;
if ((err = getnameinfo((struct sockaddr *)sa, salen, case SOCKET_ADDRESS_KIND_UNIX:
host, sizeof(host), info->host = g_strdup("");
serv, sizeof(serv), info->service = g_strdup(addr->u.q_unix->path);
NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { info->family = NETWORK_ADDRESS_FAMILY_UNIX;
error_setg(errp, "Cannot resolve address: %s", break;
gai_strerror(err));
return; default:
error_setg(errp, "Unsupported socket kind %d",
addr->type);
break;
} }
info->host = g_strdup(host); return;
info->service = g_strdup(serv);
info->family = inet_netfamily(sa->ss_family);
} }
static void vnc_init_basic_info_from_server_addr(int fd, VncBasicInfo *info, static void vnc_init_basic_info_from_server_addr(QIOChannelSocket *ioc,
VncBasicInfo *info,
Error **errp) Error **errp)
{ {
struct sockaddr_storage sa; SocketAddress *addr = NULL;
socklen_t salen;
salen = sizeof(sa); addr = qio_channel_socket_get_local_address(ioc, errp);
if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0) { if (!addr) {
error_setg_errno(errp, errno, "getsockname failed");
return; return;
} }
vnc_init_basic_info(&sa, salen, info, errp); vnc_init_basic_info(addr, info, errp);
qapi_free_SocketAddress(addr);
} }
static void vnc_init_basic_info_from_remote_addr(int fd, VncBasicInfo *info, static void vnc_init_basic_info_from_remote_addr(QIOChannelSocket *ioc,
VncBasicInfo *info,
Error **errp) Error **errp)
{ {
struct sockaddr_storage sa; SocketAddress *addr = NULL;
socklen_t salen;
salen = sizeof(sa); addr = qio_channel_socket_get_remote_address(ioc, errp);
if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0) { if (!addr) {
error_setg_errno(errp, errno, "getpeername failed");
return; return;
} }
vnc_init_basic_info(&sa, salen, info, errp); vnc_init_basic_info(addr, info, errp);
qapi_free_SocketAddress(addr);
} }
static const char *vnc_auth_name(VncDisplay *vd) { static const char *vnc_auth_name(VncDisplay *vd) {
@ -300,7 +257,7 @@ static void vnc_client_cache_addr(VncState *client)
Error *err = NULL; Error *err = NULL;
client->info = g_malloc0(sizeof(*client->info)); client->info = g_malloc0(sizeof(*client->info));
vnc_init_basic_info_from_remote_addr(client->csock, vnc_init_basic_info_from_remote_addr(client->sioc,
qapi_VncClientInfo_base(client->info), qapi_VncClientInfo_base(client->info),
&err); &err);
if (err) { if (err) {
@ -343,27 +300,20 @@ static void vnc_qmp_event(VncState *vs, QAPIEvent event)
static VncClientInfo *qmp_query_vnc_client(const VncState *client) static VncClientInfo *qmp_query_vnc_client(const VncState *client)
{ {
struct sockaddr_storage sa;
socklen_t salen = sizeof(sa);
char host[NI_MAXHOST];
char serv[NI_MAXSERV];
VncClientInfo *info; VncClientInfo *info;
Error *err = NULL;
if (getpeername(client->csock, (struct sockaddr *)&sa, &salen) < 0) {
return NULL;
}
if (getnameinfo((struct sockaddr *)&sa, salen,
host, sizeof(host),
serv, sizeof(serv),
NI_NUMERICHOST | NI_NUMERICSERV) < 0) {
return NULL;
}
info = g_malloc0(sizeof(*info)); info = g_malloc0(sizeof(*info));
info->host = g_strdup(host);
info->service = g_strdup(serv); vnc_init_basic_info_from_remote_addr(client->sioc,
info->family = inet_netfamily(sa.ss_family); qapi_VncClientInfo_base(info),
&err);
if (err) {
error_free(err);
qapi_free_VncClientInfo(info);
return NULL;
}
info->websocket = client->websocket; info->websocket = client->websocket;
if (client->tls) { if (client->tls) {
@ -413,81 +363,89 @@ VncInfo *qmp_query_vnc(Error **errp)
{ {
VncInfo *info = g_malloc0(sizeof(*info)); VncInfo *info = g_malloc0(sizeof(*info));
VncDisplay *vd = vnc_display_find(NULL); VncDisplay *vd = vnc_display_find(NULL);
SocketAddress *addr = NULL;
if (vd == NULL || !vd->enabled) { if (vd == NULL || !vd->enabled) {
info->enabled = false; info->enabled = false;
} else { } else {
struct sockaddr_storage sa;
socklen_t salen = sizeof(sa);
char host[NI_MAXHOST];
char serv[NI_MAXSERV];
info->enabled = true; info->enabled = true;
/* for compatibility with the original command */ /* for compatibility with the original command */
info->has_clients = true; info->has_clients = true;
info->clients = qmp_query_client_list(vd); info->clients = qmp_query_client_list(vd);
if (vd->lsock == -1) { if (vd->lsock == NULL) {
return info; return info;
} }
if (getsockname(vd->lsock, (struct sockaddr *)&sa, addr = qio_channel_socket_get_local_address(vd->lsock, errp);
&salen) == -1) { if (!addr) {
error_setg(errp, QERR_UNDEFINED_ERROR);
goto out_error; goto out_error;
} }
if (getnameinfo((struct sockaddr *)&sa, salen, switch (addr->type) {
host, sizeof(host), case SOCKET_ADDRESS_KIND_INET:
serv, sizeof(serv), info->host = g_strdup(addr->u.inet->host);
NI_NUMERICHOST | NI_NUMERICSERV) < 0) { info->service = g_strdup(addr->u.inet->port);
error_setg(errp, QERR_UNDEFINED_ERROR); if (addr->u.inet->ipv6) {
info->family = NETWORK_ADDRESS_FAMILY_IPV6;
} else {
info->family = NETWORK_ADDRESS_FAMILY_IPV4;
}
break;
case SOCKET_ADDRESS_KIND_UNIX:
info->host = g_strdup("");
info->service = g_strdup(addr->u.q_unix->path);
info->family = NETWORK_ADDRESS_FAMILY_UNIX;
break;
default:
error_setg(errp, "Unsupported socket kind %d",
addr->type);
goto out_error; goto out_error;
} }
info->has_host = true; info->has_host = true;
info->host = g_strdup(host);
info->has_service = true; info->has_service = true;
info->service = g_strdup(serv);
info->has_family = true; info->has_family = true;
info->family = inet_netfamily(sa.ss_family);
info->has_auth = true; info->has_auth = true;
info->auth = g_strdup(vnc_auth_name(vd)); info->auth = g_strdup(vnc_auth_name(vd));
} }
qapi_free_SocketAddress(addr);
return info; return info;
out_error: out_error:
qapi_free_SocketAddress(addr);
qapi_free_VncInfo(info); qapi_free_VncInfo(info);
return NULL; return NULL;
} }
static VncBasicInfoList *qmp_query_server_entry(int socket, static VncBasicInfoList *qmp_query_server_entry(QIOChannelSocket *ioc,
bool websocket, bool websocket,
VncBasicInfoList *prev) VncBasicInfoList *prev)
{ {
VncBasicInfoList *list; VncBasicInfoList *list;
VncBasicInfo *info; VncBasicInfo *info;
struct sockaddr_storage sa; Error *err = NULL;
socklen_t salen = sizeof(sa); SocketAddress *addr;
char host[NI_MAXHOST];
char serv[NI_MAXSERV];
if (getsockname(socket, (struct sockaddr *)&sa, &salen) < 0 || addr = qio_channel_socket_get_local_address(ioc, &err);
getnameinfo((struct sockaddr *)&sa, salen, if (!addr) {
host, sizeof(host), serv, sizeof(serv), error_free(err);
NI_NUMERICHOST | NI_NUMERICSERV) < 0) {
return prev; return prev;
} }
info = g_new0(VncBasicInfo, 1); info = g_new0(VncBasicInfo, 1);
info->host = g_strdup(host); vnc_init_basic_info(addr, info, &err);
info->service = g_strdup(serv); qapi_free_SocketAddress(addr);
info->family = inet_netfamily(sa.ss_family); if (err) {
qapi_free_VncBasicInfo(info);
error_free(err);
return prev;
}
info->websocket = websocket; info->websocket = websocket;
list = g_new0(VncBasicInfoList, 1); list = g_new0(VncBasicInfoList, 1);
@ -581,13 +539,13 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp)
info->has_display = true; info->has_display = true;
info->display = g_strdup(dev->id); info->display = g_strdup(dev->id);
} }
if (vd->lsock != -1) { if (vd->lsock != NULL) {
info->server = qmp_query_server_entry(vd->lsock, false, info->server = qmp_query_server_entry(
info->server); vd->lsock, false, info->server);
} }
if (vd->lwebsock != -1) { if (vd->lwebsock != NULL) {
info->server = qmp_query_server_entry(vd->lwebsock, true, info->server = qmp_query_server_entry(
info->server); vd->lwebsock, true, info->server);
} }
item = g_new0(VncInfo2List, 1); item = g_new0(VncInfo2List, 1);
@ -673,7 +631,7 @@ void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
static void vnc_desktop_resize(VncState *vs) static void vnc_desktop_resize(VncState *vs)
{ {
if (vs->csock == -1 || !vnc_has_feature(vs, VNC_FEATURE_RESIZE)) { if (vs->ioc == NULL || !vnc_has_feature(vs, VNC_FEATURE_RESIZE)) {
return; return;
} }
if (vs->client_width == pixman_image_get_width(vs->vd->server) && if (vs->client_width == pixman_image_get_width(vs->vd->server) &&
@ -1066,7 +1024,7 @@ static int find_and_clear_dirty_height(VncState *vs,
static int vnc_update_client(VncState *vs, int has_dirty, bool sync) static int vnc_update_client(VncState *vs, int has_dirty, bool sync)
{ {
vs->has_dirty += has_dirty; vs->has_dirty += has_dirty;
if (vs->need_update && vs->csock != -1) { if (vs->need_update && vs->ioc != NULL) {
VncDisplay *vd = vs->vd; VncDisplay *vd = vs->vd;
VncJob *job; VncJob *job;
int y; int y;
@ -1130,7 +1088,7 @@ static int vnc_update_client(VncState *vs, int has_dirty, bool sync)
return n; return n;
} }
if (vs->csock == -1) { if (vs->disconnecting) {
vnc_disconnect_finish(vs); vnc_disconnect_finish(vs);
} else if (sync) { } else if (sync) {
vnc_jobs_join(vs); vnc_jobs_join(vs);
@ -1212,12 +1170,15 @@ static void audio_del(VncState *vs)
static void vnc_disconnect_start(VncState *vs) static void vnc_disconnect_start(VncState *vs)
{ {
if (vs->csock == -1) if (vs->disconnecting) {
return; return;
}
vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED); vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED);
qemu_set_fd_handler(vs->csock, NULL, NULL, NULL); if (vs->ioc_tag) {
closesocket(vs->csock); g_source_remove(vs->ioc_tag);
vs->csock = -1; }
qio_channel_close(vs->ioc, NULL);
vs->disconnecting = TRUE;
} }
void vnc_disconnect_finish(VncState *vs) void vnc_disconnect_finish(VncState *vs)
@ -1270,29 +1231,29 @@ void vnc_disconnect_finish(VncState *vs)
g_free(vs->lossy_rect[i]); g_free(vs->lossy_rect[i]);
} }
g_free(vs->lossy_rect); g_free(vs->lossy_rect);
object_unref(OBJECT(vs->ioc));
vs->ioc = NULL;
object_unref(OBJECT(vs->sioc));
vs->sioc = NULL;
g_free(vs); g_free(vs);
} }
ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, int last_errno) ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp)
{ {
if (ret == 0 || ret == -1) { if (ret <= 0) {
if (ret == -1) { if (ret == 0) {
switch (last_errno) { VNC_DEBUG("Closing down client sock: EOF\n");
case EINTR: } else if (ret != QIO_CHANNEL_ERR_BLOCK) {
case EAGAIN: VNC_DEBUG("Closing down client sock: ret %d (%s)\n",
#ifdef _WIN32 ret, errp ? error_get_pretty(*errp) : "Unknown");
case WSAEWOULDBLOCK:
#endif
return 0;
default:
break;
}
} }
VNC_DEBUG("Closing down client sock: ret %zd, errno %d\n",
ret, ret < 0 ? last_errno : 0);
vnc_disconnect_start(vs); vnc_disconnect_start(vs);
if (errp) {
error_free(*errp);
*errp = NULL;
}
return 0; return 0;
} }
return ret; return ret;
@ -1309,13 +1270,12 @@ void vnc_client_error(VncState *vs)
ssize_t vnc_tls_pull(char *buf, size_t len, void *opaque) ssize_t vnc_tls_pull(char *buf, size_t len, void *opaque)
{ {
VncState *vs = opaque; VncState *vs = opaque;
ssize_t ret; ssize_t ret = qio_channel_read(vs->ioc, buf, len, NULL);
retry:
ret = qemu_recv(vs->csock, buf, len, 0);
if (ret < 0) { if (ret < 0) {
if (errno == EINTR) { if (ret == QIO_CHANNEL_ERR_BLOCK) {
goto retry; errno = EAGAIN;
} else {
errno = EIO;
} }
return -1; return -1;
} }
@ -1326,13 +1286,12 @@ ssize_t vnc_tls_pull(char *buf, size_t len, void *opaque)
ssize_t vnc_tls_push(const char *buf, size_t len, void *opaque) ssize_t vnc_tls_push(const char *buf, size_t len, void *opaque)
{ {
VncState *vs = opaque; VncState *vs = opaque;
ssize_t ret; ssize_t ret = qio_channel_write(vs->ioc, buf, len, NULL);
retry:
ret = send(vs->csock, buf, len, 0);
if (ret < 0) { if (ret < 0) {
if (errno == EINTR) { if (ret == QIO_CHANNEL_ERR_BLOCK) {
goto retry; errno = EAGAIN;
} else {
errno = EIO;
} }
return -1; return -1;
} }
@ -1357,21 +1316,25 @@ ssize_t vnc_tls_push(const char *buf, size_t len, void *opaque)
*/ */
ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
{ {
Error *err = NULL;
ssize_t ret; ssize_t ret;
int err = 0;
if (vs->tls) { if (vs->tls) {
ret = qcrypto_tls_session_write(vs->tls, (const char *)data, datalen); ret = qcrypto_tls_session_write(vs->tls, (const char *)data, datalen);
if (ret < 0) { if (ret < 0) {
err = errno; if (errno == EAGAIN) {
ret = QIO_CHANNEL_ERR_BLOCK;
} else {
ret = -1;
error_setg_errno(&err, errno, "%s",
"Cannot write to TLS socket");
}
} }
} else { } else {
ret = send(vs->csock, (const void *)data, datalen, 0); ret = qio_channel_write(
if (ret < 0) { vs->ioc, (const char *)data, datalen, &err);
err = socket_error();
}
} }
VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret); VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret);
return vnc_client_io_error(vs, ret, err); return vnc_client_io_error(vs, ret, &err);
} }
@ -1409,7 +1372,11 @@ static ssize_t vnc_client_write_plain(VncState *vs)
buffer_advance(&vs->output, ret); buffer_advance(&vs->output, ret);
if (vs->output.offset == 0) { if (vs->output.offset == 0) {
qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vnc_client_io, vs, NULL);
} }
return ret; return ret;
@ -1421,10 +1388,8 @@ static ssize_t vnc_client_write_plain(VncState *vs)
* the client socket. Will delegate actual work according to whether * the client socket. Will delegate actual work according to whether
* SASL SSF layers are enabled (thus requiring encryption calls) * SASL SSF layers are enabled (thus requiring encryption calls)
*/ */
static void vnc_client_write_locked(void *opaque) static void vnc_client_write_locked(VncState *vs)
{ {
VncState *vs = opaque;
#ifdef CONFIG_VNC_SASL #ifdef CONFIG_VNC_SASL
if (vs->sasl.conn && if (vs->sasl.conn &&
vs->sasl.runSSF && vs->sasl.runSSF &&
@ -1441,15 +1406,18 @@ static void vnc_client_write_locked(void *opaque)
} }
} }
void vnc_client_write(void *opaque) static void vnc_client_write(VncState *vs)
{ {
VncState *vs = opaque;
vnc_lock_output(vs); vnc_lock_output(vs);
if (vs->output.offset || vs->ws_output.offset) { if (vs->output.offset || vs->ws_output.offset) {
vnc_client_write_locked(opaque); vnc_client_write_locked(vs);
} else if (vs->csock != -1) { } else if (vs->ioc != NULL) {
qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vnc_client_io, vs, NULL);
} }
vnc_unlock_output(vs); vnc_unlock_output(vs);
} }
@ -1479,20 +1447,24 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
{ {
ssize_t ret; ssize_t ret;
int err = -1; Error *err = NULL;
if (vs->tls) { if (vs->tls) {
ret = qcrypto_tls_session_read(vs->tls, (char *)data, datalen); ret = qcrypto_tls_session_read(vs->tls, (char *)data, datalen);
if (ret < 0) { if (ret < 0) {
err = errno; if (errno == EAGAIN) {
ret = QIO_CHANNEL_ERR_BLOCK;
} else {
ret = -1;
error_setg_errno(&err, errno, "%s",
"Cannot read from TLS socket");
}
} }
} else { } else {
ret = qemu_recv(vs->csock, data, datalen, 0); ret = qio_channel_read(
if (ret < 0) { vs->ioc, (char *)data, datalen, &err);
err = socket_error();
}
} }
VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret); VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret);
return vnc_client_io_error(vs, ret, err); return vnc_client_io_error(vs, ret, &err);
} }
@ -1529,9 +1501,8 @@ static void vnc_jobs_bh(void *opaque)
* the client socket. Will delegate actual work according to whether * the client socket. Will delegate actual work according to whether
* SASL SSF layers are enabled (thus requiring decryption calls) * SASL SSF layers are enabled (thus requiring decryption calls)
*/ */
void vnc_client_read(void *opaque) static void vnc_client_read(VncState *vs)
{ {
VncState *vs = opaque;
ssize_t ret; ssize_t ret;
#ifdef CONFIG_VNC_SASL #ifdef CONFIG_VNC_SASL
@ -1552,8 +1523,9 @@ void vnc_client_read(void *opaque)
ret = vnc_client_read_plain(vs); ret = vnc_client_read_plain(vs);
} }
if (!ret) { if (!ret) {
if (vs->csock == -1) if (vs->disconnecting) {
vnc_disconnect_finish(vs); vnc_disconnect_finish(vs);
}
return; return;
} }
@ -1562,7 +1534,7 @@ void vnc_client_read(void *opaque)
int ret; int ret;
ret = vs->read_handler(vs, vs->input.buffer, len); ret = vs->read_handler(vs, vs->input.buffer, len);
if (vs->csock == -1) { if (vs->disconnecting) {
vnc_disconnect_finish(vs); vnc_disconnect_finish(vs);
return; return;
} }
@ -1575,12 +1547,30 @@ void vnc_client_read(void *opaque)
} }
} }
gboolean vnc_client_io(QIOChannel *ioc G_GNUC_UNUSED,
GIOCondition condition, void *opaque)
{
VncState *vs = opaque;
if (condition & G_IO_IN) {
vnc_client_read(vs);
}
if (condition & G_IO_OUT) {
vnc_client_write(vs);
}
return TRUE;
}
void vnc_write(VncState *vs, const void *data, size_t len) void vnc_write(VncState *vs, const void *data, size_t len)
{ {
buffer_reserve(&vs->output, len); buffer_reserve(&vs->output, len);
if (vs->csock != -1 && buffer_empty(&vs->output)) { if (vs->ioc != NULL && buffer_empty(&vs->output)) {
qemu_set_fd_handler(vs->csock, vnc_client_read, vnc_client_write, vs); if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
}
vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL);
} }
buffer_append(&vs->output, data, len); buffer_append(&vs->output, data, len);
@ -1621,8 +1611,7 @@ void vnc_write_u8(VncState *vs, uint8_t value)
void vnc_flush(VncState *vs) void vnc_flush(VncState *vs)
{ {
vnc_lock_output(vs); vnc_lock_output(vs);
if (vs->csock != -1 && (vs->output.offset || if (vs->ioc != NULL && (vs->output.offset || vs->ws_output.offset)) {
vs->ws_output.offset)) {
vnc_client_write_locked(vs); vnc_client_write_locked(vs);
} }
vnc_unlock_output(vs); vnc_unlock_output(vs);
@ -3006,34 +2995,37 @@ static void vnc_refresh(DisplayChangeListener *dcl)
} }
} }
static void vnc_connect(VncDisplay *vd, int csock, static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc,
bool skipauth, bool websocket) bool skipauth, bool websocket)
{ {
VncState *vs = g_new0(VncState, 1); VncState *vs = g_new0(VncState, 1);
int i; int i;
vs->csock = csock; vs->sioc = sioc;
object_ref(OBJECT(vs->sioc));
vs->ioc = QIO_CHANNEL(sioc);
object_ref(OBJECT(vs->ioc));
vs->vd = vd; vs->vd = vd;
buffer_init(&vs->input, "vnc-input/%d", csock); buffer_init(&vs->input, "vnc-input/%p", sioc);
buffer_init(&vs->output, "vnc-output/%d", csock); buffer_init(&vs->output, "vnc-output/%p", sioc);
buffer_init(&vs->ws_input, "vnc-ws_input/%d", csock); buffer_init(&vs->ws_input, "vnc-ws_input/%p", sioc);
buffer_init(&vs->ws_output, "vnc-ws_output/%d", csock); buffer_init(&vs->ws_output, "vnc-ws_output/%p", sioc);
buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%d", csock); buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%p", sioc);
buffer_init(&vs->tight.tight, "vnc-tight/%d", csock); buffer_init(&vs->tight.tight, "vnc-tight/%p", sioc);
buffer_init(&vs->tight.zlib, "vnc-tight-zlib/%d", csock); buffer_init(&vs->tight.zlib, "vnc-tight-zlib/%p", sioc);
buffer_init(&vs->tight.gradient, "vnc-tight-gradient/%d", csock); buffer_init(&vs->tight.gradient, "vnc-tight-gradient/%p", sioc);
#ifdef CONFIG_VNC_JPEG #ifdef CONFIG_VNC_JPEG
buffer_init(&vs->tight.jpeg, "vnc-tight-jpeg/%d", csock); buffer_init(&vs->tight.jpeg, "vnc-tight-jpeg/%p", sioc);
#endif #endif
#ifdef CONFIG_VNC_PNG #ifdef CONFIG_VNC_PNG
buffer_init(&vs->tight.png, "vnc-tight-png/%d", csock); buffer_init(&vs->tight.png, "vnc-tight-png/%p", sioc);
#endif #endif
buffer_init(&vs->zlib.zlib, "vnc-zlib/%d", csock); buffer_init(&vs->zlib.zlib, "vnc-zlib/%p", sioc);
buffer_init(&vs->zrle.zrle, "vnc-zrle/%d", csock); buffer_init(&vs->zrle.zrle, "vnc-zrle/%p", sioc);
buffer_init(&vs->zrle.fb, "vnc-zrle-fb/%d", csock); buffer_init(&vs->zrle.fb, "vnc-zrle-fb/%p", sioc);
buffer_init(&vs->zrle.zlib, "vnc-zrle-zlib/%d", csock); buffer_init(&vs->zrle.zlib, "vnc-zrle-zlib/%p", sioc);
if (skipauth) { if (skipauth) {
vs->auth = VNC_AUTH_NONE; vs->auth = VNC_AUTH_NONE;
@ -3047,27 +3039,29 @@ static void vnc_connect(VncDisplay *vd, int csock,
vs->subauth = vd->subauth; vs->subauth = vd->subauth;
} }
} }
VNC_DEBUG("Client sock=%d ws=%d auth=%d subauth=%d\n", VNC_DEBUG("Client sioc=%p ws=%d auth=%d subauth=%d\n",
csock, websocket, vs->auth, vs->subauth); sioc, websocket, vs->auth, vs->subauth);
vs->lossy_rect = g_malloc0(VNC_STAT_ROWS * sizeof (*vs->lossy_rect)); vs->lossy_rect = g_malloc0(VNC_STAT_ROWS * sizeof (*vs->lossy_rect));
for (i = 0; i < VNC_STAT_ROWS; ++i) { for (i = 0; i < VNC_STAT_ROWS; ++i) {
vs->lossy_rect[i] = g_new0(uint8_t, VNC_STAT_COLS); vs->lossy_rect[i] = g_new0(uint8_t, VNC_STAT_COLS);
} }
VNC_DEBUG("New client on socket %d\n", csock); VNC_DEBUG("New client on socket %p\n", vs->sioc);
update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE); update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
qemu_set_nonblock(vs->csock); qio_channel_set_blocking(vs->ioc, false, NULL);
if (websocket) { if (websocket) {
vs->websocket = 1; vs->websocket = 1;
if (vd->ws_tls) { if (vd->ws_tls) {
qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io, NULL, vs); vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vncws_tls_handshake_io, vs, NULL);
} else { } else {
qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs); vs->ioc_tag = qio_channel_add_watch(
vs->ioc, G_IO_IN, vncws_handshake_io, vs, NULL);
} }
} else } else {
{ vs->ioc_tag = qio_channel_add_watch(
qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); vs->ioc, G_IO_IN, vnc_client_io, vs, NULL);
} }
vnc_client_cache_addr(vs); vnc_client_cache_addr(vs);
@ -3125,35 +3119,28 @@ void vnc_init_state(VncState *vs)
/* vs might be free()ed here */ /* vs might be free()ed here */
} }
static void vnc_listen_read(void *opaque, bool websocket) static gboolean vnc_listen_io(QIOChannel *ioc,
GIOCondition condition,
void *opaque)
{ {
VncDisplay *vs = opaque; VncDisplay *vs = opaque;
struct sockaddr_in addr; QIOChannelSocket *sioc = NULL;
socklen_t addrlen = sizeof(addr); Error *err = NULL;
int csock;
/* Catch-up */ /* Catch-up */
graphic_hw_update(vs->dcl.con); graphic_hw_update(vs->dcl.con);
if (websocket) { sioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc), &err);
csock = qemu_accept(vs->lwebsock, (struct sockaddr *)&addr, &addrlen); if (sioc != NULL) {
qio_channel_set_delay(QIO_CHANNEL(sioc), false);
vnc_connect(vs, sioc, false,
ioc != QIO_CHANNEL(vs->lsock));
object_unref(OBJECT(sioc));
} else { } else {
csock = qemu_accept(vs->lsock, (struct sockaddr *)&addr, &addrlen); /* client probably closed connection before we got there */
error_free(err);
} }
if (csock != -1) { return TRUE;
socket_set_nodelay(csock);
vnc_connect(vs, csock, false, websocket);
}
}
static void vnc_listen_regular_read(void *opaque)
{
vnc_listen_read(opaque, false);
}
static void vnc_listen_websocket_read(void *opaque)
{
vnc_listen_read(opaque, true);
} }
static const DisplayChangeListenerOps dcl_ops = { static const DisplayChangeListenerOps dcl_ops = {
@ -3179,9 +3166,6 @@ void vnc_display_init(const char *id)
vs->id = strdup(id); vs->id = strdup(id);
QTAILQ_INSERT_TAIL(&vnc_displays, vs, next); QTAILQ_INSERT_TAIL(&vnc_displays, vs, next);
vs->lsock = -1;
vs->lwebsock = -1;
QTAILQ_INIT(&vs->clients); QTAILQ_INIT(&vs->clients);
vs->expires = TIME_MAX; vs->expires = TIME_MAX;
@ -3209,16 +3193,20 @@ static void vnc_display_close(VncDisplay *vs)
return; return;
vs->enabled = false; vs->enabled = false;
vs->is_unix = false; vs->is_unix = false;
if (vs->lsock != -1) { if (vs->lsock != NULL) {
qemu_set_fd_handler(vs->lsock, NULL, NULL, NULL); if (vs->lsock_tag) {
close(vs->lsock); g_source_remove(vs->lsock_tag);
vs->lsock = -1; }
object_unref(OBJECT(vs->lsock));
vs->lsock = NULL;
} }
vs->ws_enabled = false; vs->ws_enabled = false;
if (vs->lwebsock != -1) { if (vs->lwebsock != NULL) {
qemu_set_fd_handler(vs->lwebsock, NULL, NULL, NULL); if (vs->lwebsock_tag) {
close(vs->lwebsock); g_source_remove(vs->lwebsock_tag);
vs->lwebsock = -1; }
object_unref(OBJECT(vs->lwebsock));
vs->lwebsock = NULL;
} }
vs->auth = VNC_AUTH_INVALID; vs->auth = VNC_AUTH_INVALID;
vs->subauth = VNC_AUTH_INVALID; vs->subauth = VNC_AUTH_INVALID;
@ -3263,9 +3251,25 @@ int vnc_display_pw_expire(const char *id, time_t expires)
char *vnc_display_local_addr(const char *id) char *vnc_display_local_addr(const char *id)
{ {
VncDisplay *vs = vnc_display_find(id); VncDisplay *vs = vnc_display_find(id);
SocketAddress *addr;
char *ret;
Error *err = NULL;
assert(vs); assert(vs);
return vnc_socket_local_addr("%s:%s", vs->lsock);
addr = qio_channel_socket_get_local_address(vs->lsock, &err);
if (!addr) {
return NULL;
}
if (addr->type != SOCKET_ADDRESS_KIND_INET) {
qapi_free_SocketAddress(addr);
return NULL;
}
ret = g_strdup_printf("%s;%s", addr->u.inet->host, addr->u.inet->port);
qapi_free_SocketAddress(addr);
return ret;
} }
static QemuOptsList qemu_vnc_opts = { static QemuOptsList qemu_vnc_opts = {
@ -3826,41 +3830,45 @@ void vnc_display_open(const char *id, Error **errp)
if (reverse) { if (reverse) {
/* connect to viewer */ /* connect to viewer */
int csock; QIOChannelSocket *sioc = NULL;
vs->lsock = -1; vs->lsock = NULL;
vs->lwebsock = -1; vs->lwebsock = NULL;
if (vs->ws_enabled) { if (vs->ws_enabled) {
error_setg(errp, "Cannot use websockets in reverse mode"); error_setg(errp, "Cannot use websockets in reverse mode");
goto fail; goto fail;
} }
csock = socket_connect(saddr, errp, NULL, NULL); vs->is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX;
if (csock < 0) { sioc = qio_channel_socket_new();
if (qio_channel_socket_connect_sync(sioc, saddr, errp) < 0) {
goto fail; goto fail;
} }
vs->is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX; vnc_connect(vs, sioc, false, false);
vnc_connect(vs, csock, false, false); object_unref(OBJECT(sioc));
} else { } else {
/* listen for connects */ vs->lsock = qio_channel_socket_new();
vs->lsock = socket_listen(saddr, errp); if (qio_channel_socket_listen_sync(vs->lsock, saddr, errp) < 0) {
if (vs->lsock < 0) {
goto fail; goto fail;
} }
vs->is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX; vs->is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX;
vs->enabled = true;
if (vs->ws_enabled) { if (vs->ws_enabled) {
vs->lwebsock = socket_listen(wsaddr, errp); vs->lwebsock = qio_channel_socket_new();
if (vs->lwebsock < 0) { if (qio_channel_socket_listen_sync(vs->lwebsock,
if (vs->lsock != -1) { wsaddr, errp) < 0) {
close(vs->lsock); object_unref(OBJECT(vs->lsock));
vs->lsock = -1; vs->lsock = NULL;
}
goto fail; goto fail;
} }
} }
vs->enabled = true;
qemu_set_fd_handler(vs->lsock, vnc_listen_regular_read, NULL, vs); vs->lsock_tag = qio_channel_add_watch(
QIO_CHANNEL(vs->lsock),
G_IO_IN, vnc_listen_io, vs, NULL);
if (vs->ws_enabled) { if (vs->ws_enabled) {
qemu_set_fd_handler(vs->lwebsock, vnc_listen_websocket_read, vs->lwebsock_tag = qio_channel_add_watch(
NULL, vs); QIO_CHANNEL(vs->lwebsock),
G_IO_IN, vnc_listen_io, vs, NULL);
} }
} }
@ -3878,11 +3886,17 @@ fail:
void vnc_display_add_client(const char *id, int csock, bool skipauth) void vnc_display_add_client(const char *id, int csock, bool skipauth)
{ {
VncDisplay *vs = vnc_display_find(id); VncDisplay *vs = vnc_display_find(id);
QIOChannelSocket *sioc;
if (!vs) { if (!vs) {
return; return;
} }
vnc_connect(vs, csock, skipauth, false);
sioc = qio_channel_socket_new_fd(csock, NULL);
if (sioc) {
vnc_connect(vs, sioc, skipauth, false);
object_unref(OBJECT(sioc));
}
} }
static void vnc_auto_assign_id(QemuOptsList *olist, QemuOpts *opts) static void vnc_auto_assign_id(QemuOptsList *olist, QemuOpts *opts)

View File

@ -35,6 +35,7 @@
#include "qemu/bitmap.h" #include "qemu/bitmap.h"
#include "crypto/tlssession.h" #include "crypto/tlssession.h"
#include "qemu/buffer.h" #include "qemu/buffer.h"
#include "io/channel-socket.h"
#include <zlib.h> #include <zlib.h>
#include <stdbool.h> #include <stdbool.h>
@ -145,8 +146,10 @@ struct VncDisplay
int num_exclusive; int num_exclusive;
int connections_limit; int connections_limit;
VncSharePolicy share_policy; VncSharePolicy share_policy;
int lsock; QIOChannelSocket *lsock;
int lwebsock; guint lsock_tag;
QIOChannelSocket *lwebsock;
guint lwebsock_tag;
bool ws_enabled; bool ws_enabled;
DisplaySurface *ds; DisplaySurface *ds;
DisplayChangeListener dcl; DisplayChangeListener dcl;
@ -248,7 +251,10 @@ struct VncJob
struct VncState struct VncState
{ {
int csock; QIOChannelSocket *sioc; /* The underlying socket */
QIOChannel *ioc; /* The channel currently used for I/O */
guint ioc_tag;
gboolean disconnecting;
DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT], VNC_DIRTY_BITS); DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT], VNC_DIRTY_BITS);
uint8_t **lossy_rect; /* Not an Array to avoid costly memcpy in uint8_t **lossy_rect; /* Not an Array to avoid costly memcpy in
@ -499,8 +505,9 @@ enum {
*****************************************************************************/ *****************************************************************************/
/* Event loop functions */ /* Event loop functions */
void vnc_client_read(void *opaque); gboolean vnc_client_io(QIOChannel *ioc,
void vnc_client_write(void *opaque); GIOCondition condition,
void *opaque);
ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen); ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen);
ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen); ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen);
@ -524,7 +531,7 @@ uint32_t read_u32(uint8_t *data, size_t offset);
/* Protocol stage functions */ /* Protocol stage functions */
void vnc_client_error(VncState *vs); void vnc_client_error(VncState *vs);
ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, int last_errno); ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp);
void start_client_init(VncState *vs); void start_client_init(VncState *vs);
void start_auth_vnc(VncState *vs); void start_auth_vnc(VncState *vs);
@ -532,9 +539,6 @@ void start_auth_vnc(VncState *vs);
/* Misc helpers */ /* Misc helpers */
char *vnc_socket_local_addr(const char *format, int fd);
char *vnc_socket_remote_addr(const char *format, int fd);
static inline uint32_t vnc_has_feature(VncState *vs, int feature) { static inline uint32_t vnc_has_feature(VncState *vs, int feature) {
return (vs->features & (1 << feature)); return (vs->features & (1 << feature));
} }