#include "vnc.h"
#include "vnc-jobs.h"
#include "trace.h"
-#include "hw/qdev.h"
#include "sysemu/sysemu.h"
#include "qemu/error-report.h"
#include "qemu/sockets.h"
#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
Error **errp)
{
switch (addr->type) {
- case SOCKET_ADDRESS_KIND_INET:
- info->host = g_strdup(addr->u.inet.data->host);
- info->service = g_strdup(addr->u.inet.data->port);
- if (addr->u.inet.data->ipv6) {
+ case SOCKET_ADDRESS_TYPE_INET:
+ 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;
- case SOCKET_ADDRESS_KIND_UNIX:
+ case SOCKET_ADDRESS_TYPE_UNIX:
info->host = g_strdup("");
- info->service = g_strdup(addr->u.q_unix.data->path);
+ 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);
+ case SOCKET_ADDRESS_TYPE_VSOCK:
+ case SOCKET_ADDRESS_TYPE_FD:
+ error_setg(errp, "Unsupported socket address type %s",
+ SocketAddressType_str(addr->type));
break;
+ default:
+ abort();
}
return;
}
switch (addr->type) {
- case SOCKET_ADDRESS_KIND_INET:
- info->host = g_strdup(addr->u.inet.data->host);
- info->service = g_strdup(addr->u.inet.data->port);
- if (addr->u.inet.data->ipv6) {
+ case SOCKET_ADDRESS_TYPE_INET:
+ 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;
- case SOCKET_ADDRESS_KIND_UNIX:
+ case SOCKET_ADDRESS_TYPE_UNIX:
info->host = g_strdup("");
- info->service = g_strdup(addr->u.q_unix.data->path);
+ 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);
+ case SOCKET_ADDRESS_TYPE_VSOCK:
+ case SOCKET_ADDRESS_TYPE_FD:
+ error_setg(errp, "Unsupported socket address type %s",
+ SocketAddressType_str(addr->type));
goto out_error;
+ default:
+ abort();
}
info->has_host = true;
3) resolutions > 1024
*/
-static int vnc_update_client(VncState *vs, int has_dirty, bool sync);
+static int vnc_update_client(VncState *vs, int has_dirty);
static void vnc_disconnect_start(VncState *vs);
static void vnc_colordepth(VncState *vs);
return n;
}
-static void vnc_copy(VncState *vs, int src_x, int src_y, int dst_x, int dst_y, int w, int h)
-{
- /* send bitblit op to the vnc client */
- vnc_lock_output(vs);
- vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
- vnc_write_u8(vs, 0);
- vnc_write_u16(vs, 1); /* number of rects */
- vnc_framebuffer_update(vs, dst_x, dst_y, w, h, VNC_ENCODING_COPYRECT);
- vnc_write_u16(vs, src_x);
- vnc_write_u16(vs, src_y);
- vnc_unlock_output(vs);
- vnc_flush(vs);
-}
-
-static void vnc_dpy_copy(DisplayChangeListener *dcl,
- int src_x, int src_y,
- int dst_x, int dst_y, int w, int h)
-{
- VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
- VncState *vs, *vn;
- uint8_t *src_row;
- uint8_t *dst_row;
- int i, x, y, pitch, inc, w_lim, s;
- int cmp_bytes;
-
- if (!vd->server) {
- /* no client connected */
- return;
- }
-
- vnc_refresh_server_surface(vd);
- QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) {
- if (vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) {
- vs->force_update = 1;
- vnc_update_client(vs, 1, true);
- /* vs might be free()ed here */
- }
- }
-
- if (!vd->server) {
- /* no client connected */
- return;
- }
- /* do bitblit op on the local surface too */
- pitch = vnc_server_fb_stride(vd);
- src_row = vnc_server_fb_ptr(vd, src_x, src_y);
- dst_row = vnc_server_fb_ptr(vd, dst_x, dst_y);
- y = dst_y;
- inc = 1;
- if (dst_y > src_y) {
- /* copy backwards */
- src_row += pitch * (h-1);
- dst_row += pitch * (h-1);
- pitch = -pitch;
- y = dst_y + h - 1;
- inc = -1;
- }
- w_lim = w - (VNC_DIRTY_PIXELS_PER_BIT - (dst_x % VNC_DIRTY_PIXELS_PER_BIT));
- if (w_lim < 0) {
- w_lim = w;
- } else {
- w_lim = w - (w_lim % VNC_DIRTY_PIXELS_PER_BIT);
- }
- for (i = 0; i < h; i++) {
- for (x = 0; x <= w_lim;
- x += s, src_row += cmp_bytes, dst_row += cmp_bytes) {
- if (x == w_lim) {
- if ((s = w - w_lim) == 0)
- break;
- } else if (!x) {
- s = (VNC_DIRTY_PIXELS_PER_BIT -
- (dst_x % VNC_DIRTY_PIXELS_PER_BIT));
- s = MIN(s, w_lim);
- } else {
- s = VNC_DIRTY_PIXELS_PER_BIT;
- }
- cmp_bytes = s * VNC_SERVER_FB_BYTES;
- if (memcmp(src_row, dst_row, cmp_bytes) == 0)
- continue;
- memmove(dst_row, src_row, cmp_bytes);
- QTAILQ_FOREACH(vs, &vd->clients, next) {
- if (!vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) {
- set_bit(((x + dst_x) / VNC_DIRTY_PIXELS_PER_BIT),
- vs->dirty[y]);
- }
- }
- }
- src_row += pitch - w * VNC_SERVER_FB_BYTES;
- dst_row += pitch - w * VNC_SERVER_FB_BYTES;
- y += inc;
- }
-
- QTAILQ_FOREACH(vs, &vd->clients, next) {
- if (vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) {
- vnc_copy(vs, src_x, src_y, dst_x, dst_y, w, h);
- }
- }
-}
-
static void vnc_mouse_set(DisplayChangeListener *dcl,
int x, int y, int visible)
{
return h;
}
-static int vnc_update_client(VncState *vs, int has_dirty, bool sync)
+static bool vnc_should_update(VncState *vs)
+{
+ switch (vs->update) {
+ case VNC_STATE_UPDATE_NONE:
+ break;
+ case VNC_STATE_UPDATE_INCREMENTAL:
+ /* Only allow incremental updates if the output buffer
+ * is empty, or if audio capture is enabled.
+ */
+ if (!vs->output.offset || vs->audio_cap) {
+ return true;
+ }
+ break;
+ case VNC_STATE_UPDATE_FORCE:
+ return true;
+ }
+ return false;
+}
+
+static int vnc_update_client(VncState *vs, int has_dirty)
{
+ VncDisplay *vd = vs->vd;
+ VncJob *job;
+ int y;
+ int height, width;
+ int n = 0;
+
if (vs->disconnecting) {
vnc_disconnect_finish(vs);
return 0;
}
vs->has_dirty += has_dirty;
- if (vs->need_update && !vs->disconnecting) {
- VncDisplay *vd = vs->vd;
- VncJob *job;
- int y;
- int height, width;
- int n = 0;
-
- if (vs->output.offset && !vs->audio_cap && !vs->force_update)
- /* kernel send buffers are full -> drop frames to throttle */
- return 0;
+ if (!vnc_should_update(vs)) {
+ return 0;
+ }
- if (!vs->has_dirty && !vs->audio_cap && !vs->force_update)
- return 0;
+ if (!vs->has_dirty && vs->update != VNC_STATE_UPDATE_FORCE) {
+ return 0;
+ }
- /*
- * Send screen updates to the vnc client using the server
- * surface and server dirty map. guest surface updates
- * happening in parallel don't disturb us, the next pass will
- * send them to the client.
- */
- job = vnc_job_new(vs);
-
- height = pixman_image_get_height(vd->server);
- width = pixman_image_get_width(vd->server);
-
- y = 0;
- for (;;) {
- int x, h;
- unsigned long x2;
- unsigned long offset = find_next_bit((unsigned long *) &vs->dirty,
- height * VNC_DIRTY_BPL(vs),
- y * VNC_DIRTY_BPL(vs));
- if (offset == height * VNC_DIRTY_BPL(vs)) {
- /* no more dirty bits */
+ /*
+ * Send screen updates to the vnc client using the server
+ * surface and server dirty map. guest surface updates
+ * happening in parallel don't disturb us, the next pass will
+ * send them to the client.
+ */
+ job = vnc_job_new(vs);
+
+ height = pixman_image_get_height(vd->server);
+ width = pixman_image_get_width(vd->server);
+
+ y = 0;
+ for (;;) {
+ int x, h;
+ unsigned long x2;
+ unsigned long offset = find_next_bit((unsigned long *) &vs->dirty,
+ height * VNC_DIRTY_BPL(vs),
+ y * VNC_DIRTY_BPL(vs));
+ if (offset == height * VNC_DIRTY_BPL(vs)) {
+ /* no more dirty bits */
+ break;
+ }
+ y = offset / VNC_DIRTY_BPL(vs);
+ x = offset % VNC_DIRTY_BPL(vs);
+ x2 = find_next_zero_bit((unsigned long *) &vs->dirty[y],
+ VNC_DIRTY_BPL(vs), x);
+ bitmap_clear(vs->dirty[y], x, x2 - x);
+ h = find_and_clear_dirty_height(vs, y, x, x2, height);
+ x2 = MIN(x2, width / VNC_DIRTY_PIXELS_PER_BIT);
+ if (x2 > x) {
+ n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y,
+ (x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h);
+ }
+ if (!x && x2 == width / VNC_DIRTY_PIXELS_PER_BIT) {
+ y += h;
+ if (y == height) {
break;
}
- y = offset / VNC_DIRTY_BPL(vs);
- x = offset % VNC_DIRTY_BPL(vs);
- x2 = find_next_zero_bit((unsigned long *) &vs->dirty[y],
- VNC_DIRTY_BPL(vs), x);
- bitmap_clear(vs->dirty[y], x, x2 - x);
- h = find_and_clear_dirty_height(vs, y, x, x2, height);
- x2 = MIN(x2, width / VNC_DIRTY_PIXELS_PER_BIT);
- if (x2 > x) {
- n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y,
- (x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h);
- }
- if (!x && x2 == width / VNC_DIRTY_PIXELS_PER_BIT) {
- y += h;
- if (y == height) {
- break;
- }
- }
}
-
- vnc_job_push(job);
- if (sync) {
- vnc_jobs_join(vs);
- }
- vs->force_update = 0;
- vs->has_dirty = 0;
- return n;
}
- if (vs->disconnecting) {
- vnc_disconnect_finish(vs);
- } else if (sync) {
- vnc_jobs_join(vs);
- }
-
- return 0;
+ vnc_job_push(job);
+ vs->update = VNC_STATE_UPDATE_NONE;
+ vs->has_dirty = 0;
+ return n;
}
/* audio */
if (vs->disconnecting) {
return;
}
+ trace_vnc_client_disconnect_start(vs, vs->ioc);
vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED);
if (vs->ioc_tag) {
g_source_remove(vs->ioc_tag);
+ vs->ioc_tag = 0;
}
qio_channel_close(vs->ioc, NULL);
vs->disconnecting = TRUE;
{
int i;
+ trace_vnc_client_disconnect_finish(vs, vs->ioc);
+
vnc_jobs_join(vs); /* Wait encoding jobs */
vnc_lock_output(vs);
{
if (ret <= 0) {
if (ret == 0) {
- VNC_DEBUG("Closing down client sock: EOF\n");
+ trace_vnc_client_eof(vs, vs->ioc);
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");
+ trace_vnc_client_io_error(vs, vs->ioc,
+ errp ? error_get_pretty(*errp) :
+ "Unknown");
vnc_disconnect_start(vs);
}
}
if (vs->absolute) {
- qemu_input_queue_abs(con, INPUT_AXIS_X, x, width);
- qemu_input_queue_abs(con, INPUT_AXIS_Y, y, height);
+ qemu_input_queue_abs(con, INPUT_AXIS_X, x, 0, width);
+ qemu_input_queue_abs(con, INPUT_AXIS_Y, y, 0, height);
} else if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE)) {
qemu_input_queue_rel(con, INPUT_AXIS_X, x - 0x7FFF);
qemu_input_queue_rel(con, INPUT_AXIS_Y, y - 0x7FFF);
static const char *code2name(int keycode)
{
- return QKeyCode_lookup[qemu_input_key_number_to_qcode(keycode)];
+ return QKeyCode_str(qemu_input_key_number_to_qcode(keycode));
}
static void key_event(VncState *vs, int down, uint32_t sym)
static void framebuffer_update_request(VncState *vs, int incremental,
int x, int y, int w, int h)
{
- vs->need_update = 1;
-
if (incremental) {
- return;
+ if (vs->update != VNC_STATE_UPDATE_FORCE) {
+ vs->update = VNC_STATE_UPDATE_INCREMENTAL;
+ }
+ } else {
+ vs->update = VNC_STATE_UPDATE_FORCE;
+ vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h);
}
-
- vs->force_update = 1;
- vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h);
}
static void send_ext_key_event_ack(VncState *vs)
}
vs->client_pf.rmax = red_max ? red_max : 0xFF;
- vs->client_pf.rbits = hweight_long(red_max);
+ vs->client_pf.rbits = ctpopl(red_max);
vs->client_pf.rshift = red_shift;
vs->client_pf.rmask = red_max << red_shift;
vs->client_pf.gmax = green_max ? green_max : 0xFF;
- vs->client_pf.gbits = hweight_long(green_max);
+ vs->client_pf.gbits = ctpopl(green_max);
vs->client_pf.gshift = green_shift;
vs->client_pf.gmask = green_max << green_shift;
vs->client_pf.bmax = blue_max ? blue_max : 0xFF;
- vs->client_pf.bbits = hweight_long(blue_max);
+ vs->client_pf.bbits = ctpopl(blue_max);
vs->client_pf.bshift = blue_shift;
vs->client_pf.bmask = blue_max << blue_shift;
vs->client_pf.bits_per_pixel = bits_per_pixel;
Error *err = NULL;
if (!vs->vd->password) {
- VNC_DEBUG("No password configured on server");
+ trace_vnc_auth_fail(vs, vs->auth, "password is not set", "");
goto reject;
}
if (vs->vd->expires < now) {
- VNC_DEBUG("Password is expired");
+ trace_vnc_auth_fail(vs, vs->auth, "password is expired", "");
goto reject;
}
key, G_N_ELEMENTS(key),
&err);
if (!cipher) {
- VNC_DEBUG("Cannot initialize cipher %s",
- error_get_pretty(err));
+ trace_vnc_auth_fail(vs, vs->auth, "cannot create cipher",
+ error_get_pretty(err));
error_free(err);
goto reject;
}
response,
VNC_AUTH_CHALLENGE_SIZE,
&err) < 0) {
- VNC_DEBUG("Cannot encrypt challenge %s",
- error_get_pretty(err));
+ trace_vnc_auth_fail(vs, vs->auth, "cannot encrypt challenge response",
+ error_get_pretty(err));
error_free(err);
goto reject;
}
/* Compare expected vs actual challenge response */
if (memcmp(response, data, VNC_AUTH_CHALLENGE_SIZE) != 0) {
- VNC_DEBUG("Client challenge response did not match\n");
+ trace_vnc_auth_fail(vs, vs->auth, "mis-matched challenge response", "");
goto reject;
} else {
- VNC_DEBUG("Accepting VNC challenge response\n");
+ trace_vnc_auth_pass(vs, vs->auth);
vnc_write_u32(vs, 0); /* Accept auth */
vnc_flush(vs);
/* We only advertise 1 auth scheme at a time, so client
* must pick the one we sent. Verify this */
if (data[0] != vs->auth) { /* Reject auth */
- VNC_DEBUG("Reject auth %d because it didn't match advertized\n", (int)data[0]);
+ trace_vnc_auth_reject(vs, vs->auth, (int)data[0]);
vnc_write_u32(vs, 1);
if (vs->minor >= 8) {
static const char err[] = "Authentication failed";
}
vnc_client_error(vs);
} else { /* Accept requested auth */
- VNC_DEBUG("Client requested auth %d\n", (int)data[0]);
+ trace_vnc_auth_start(vs, vs->auth);
switch (vs->auth) {
case VNC_AUTH_NONE:
- VNC_DEBUG("Accept auth none\n");
if (vs->minor >= 8) {
vnc_write_u32(vs, 0); /* Accept auth completion */
vnc_flush(vs);
}
+ trace_vnc_auth_pass(vs, vs->auth);
start_client_init(vs);
break;
case VNC_AUTH_VNC:
- VNC_DEBUG("Start VNC auth\n");
start_auth_vnc(vs);
break;
case VNC_AUTH_VENCRYPT:
- VNC_DEBUG("Accept VeNCrypt auth\n");
start_auth_vencrypt(vs);
break;
#ifdef CONFIG_VNC_SASL
case VNC_AUTH_SASL:
- VNC_DEBUG("Accept SASL auth\n");
start_auth_sasl(vs);
break;
#endif /* CONFIG_VNC_SASL */
default: /* Should not be possible, but just in case */
- VNC_DEBUG("Reject auth %d server code bug\n", vs->auth);
+ trace_vnc_auth_fail(vs, vs->auth, "Unhandled auth method", "");
vnc_write_u8(vs, 1);
if (vs->minor >= 8) {
static const char err[] = "Authentication failed";
vs->minor = 3;
if (vs->minor == 3) {
+ trace_vnc_auth_start(vs, vs->auth);
if (vs->auth == VNC_AUTH_NONE) {
- VNC_DEBUG("Tell client auth none\n");
vnc_write_u32(vs, vs->auth);
vnc_flush(vs);
+ trace_vnc_auth_pass(vs, vs->auth);
start_client_init(vs);
} else if (vs->auth == VNC_AUTH_VNC) {
VNC_DEBUG("Tell client VNC auth\n");
vnc_flush(vs);
start_auth_vnc(vs);
} else {
- VNC_DEBUG("Unsupported auth %d for protocol 3.3\n", vs->auth);
+ trace_vnc_auth_fail(vs, vs->auth,
+ "Unsupported auth method for v3.3", "");
vnc_write_u32(vs, VNC_AUTH_INVALID);
vnc_flush(vs);
vnc_client_error(vs);
}
} else {
- VNC_DEBUG("Telling client we support auth %d\n", vs->auth);
vnc_write_u8(vs, 1); /* num auth */
vnc_write_u8(vs, vs->auth);
vnc_read_when(vs, protocol_client_auth, 1);
int stx = x / VNC_STAT_RECT;
int has_dirty = 0;
- y = y / VNC_STAT_RECT * VNC_STAT_RECT;
- x = x / VNC_STAT_RECT * VNC_STAT_RECT;
+ y = QEMU_ALIGN_DOWN(y, VNC_STAT_RECT);
+ x = QEMU_ALIGN_DOWN(x, VNC_STAT_RECT);
QTAILQ_FOREACH(vs, &vd->clients, next) {
int j;
double total = 0;
int num = 0;
- x = (x / VNC_STAT_RECT) * VNC_STAT_RECT;
- y = (y / VNC_STAT_RECT) * VNC_STAT_RECT;
+ x = QEMU_ALIGN_DOWN(x, VNC_STAT_RECT);
+ y = QEMU_ALIGN_DOWN(y, VNC_STAT_RECT);
for (j = y; j <= y + h; j += VNC_STAT_RECT) {
for (i = x; i <= x + w; i += VNC_STAT_RECT) {
PIXMAN_FORMAT_BPP(pixman_image_get_format(vd->guest.fb));
guest_row0 = (uint8_t *)pixman_image_get_data(vd->guest.fb);
guest_stride = pixman_image_get_stride(vd->guest.fb);
- guest_ll = pixman_image_get_width(vd->guest.fb) * ((guest_bpp + 7) / 8);
+ guest_ll = pixman_image_get_width(vd->guest.fb) * (DIV_ROUND_UP(guest_bpp, 8));
}
line_bytes = MIN(server_stride, guest_ll);
vnc_unlock_display(vd);
QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) {
- rects += vnc_update_client(vs, has_dirty, false);
+ rects += vnc_update_client(vs, has_dirty);
/* vs might be free()ed here */
}
bool first_client = QTAILQ_EMPTY(&vd->clients);
int i;
+ trace_vnc_client_connect(vs, sioc);
vs->sioc = sioc;
object_ref(OBJECT(vs->sioc));
vs->ioc = QIO_CHANNEL(sioc);
VNC_DEBUG("New client on socket %p\n", vs->sioc);
update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
qio_channel_set_blocking(vs->ioc, false, NULL);
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
if (websocket) {
vs->websocket = 1;
if (vd->tlscreds) {
static const DisplayChangeListenerOps dcl_ops = {
.dpy_name = "vnc",
.dpy_refresh = vnc_refresh,
- .dpy_gfx_copy = vnc_dpy_copy,
.dpy_gfx_update = vnc_dpy_update,
.dpy_gfx_switch = vnc_dpy_switch,
.dpy_gfx_check_format = qemu_pixman_check_format,
g_free(vd->lsock);
g_free(vd->lsock_tag);
vd->lsock = NULL;
+ vd->lsock_tag = NULL;
vd->nlsock = 0;
for (i = 0; i < vd->nlwebsock; i++) {
g_free(vd->lwebsock);
g_free(vd->lwebsock_tag);
vd->lwebsock = NULL;
+ vd->lwebsock_tag = NULL;
vd->nlwebsock = 0;
vd->auth = VNC_AUTH_INVALID;
vd->tlsaclname = NULL;
if (vd->lock_key_sync) {
qemu_remove_led_event_handler(vd->led);
+ vd->led = NULL;
}
}
return;
}
- if (addr->type != SOCKET_ADDRESS_KIND_INET) {
+ if (addr->type != SOCKET_ADDRESS_TYPE_INET) {
qapi_free_SocketAddress(addr);
return;
}
error_printf_unless_qmp("VNC server running on %s:%s\n",
- addr->u.inet.data->host,
- addr->u.inet.data->port);
+ addr->u.inet.host,
+ addr->u.inet.port);
qapi_free_SocketAddress(addr);
}
static int vnc_display_get_address(const char *addrstr,
bool websocket,
+ bool reverse,
int displaynum,
int to,
bool has_ipv4,
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);
+ addr->type = SOCKET_ADDRESS_TYPE_UNIX;
+ addr->u.q_unix.path = g_strdup(addrstr + 5);
if (websocket) {
error_setg(errp, "UNIX sockets not supported with websock");
}
}
- addr->type = SOCKET_ADDRESS_KIND_INET;
- inet = addr->u.inet.data = g_new0(InetSocketAddress, 1);
+ addr->type = SOCKET_ADDRESS_TYPE_INET;
+ inet = &addr->u.inet;
if (addrstr[0] == '[' && addrstr[hostlen - 1] == ']') {
inet->host = g_strndup(addrstr + 1, hostlen - 2);
} else {
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->port = g_strdup(port);
}
} else {
+ int offset = reverse ? 0 : 5900;
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) {
+ baseport + offset > 65535) {
error_setg(errp, "port %s out of range", port);
goto cleanup;
}
inet->port = g_strdup_printf(
- "%d", (int)baseport + 5900);
+ "%d", (int)baseport + offset);
if (to) {
inet->has_to = true;
- inet->to = to + 5900;
+ inet->to = to + offset;
}
}
return ret;
}
+static void vnc_free_addresses(SocketAddress ***retsaddr,
+ size_t *retnsaddr)
+{
+ size_t i;
+
+ for (i = 0; i < *retnsaddr; i++) {
+ qapi_free_SocketAddress((*retsaddr)[i]);
+ }
+ g_free(*retsaddr);
+
+ *retsaddr = NULL;
+ *retnsaddr = 0;
+}
+
static int vnc_display_get_addresses(QemuOpts *opts,
- SocketAddress **retsaddr,
- SocketAddress **retwsaddr,
+ bool reverse,
+ SocketAddress ***retsaddr,
+ size_t *retnsaddr,
+ SocketAddress ***retwsaddr,
+ size_t *retnwsaddr,
Error **errp)
{
SocketAddress *saddr = NULL;
SocketAddress *wsaddr = NULL;
- const char *saddrstr = qemu_opt_get(opts, "vnc");
- const char *wsaddrstr = qemu_opt_get(opts, "websocket");
+ 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);
+ int displaynum = -1;
+ int ret = -1;
- if (!saddrstr || strcmp(saddrstr, "none") == 0) {
- *retsaddr = NULL;
- *retwsaddr = NULL;
- return 0;
- }
+ *retsaddr = NULL;
+ *retnsaddr = 0;
+ *retwsaddr = NULL;
+ *retnwsaddr = 0;
- if (wsaddrstr &&
+ 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 error;
+ goto cleanup;
}
- int displaynum = vnc_display_get_address(saddrstr, false, 0, to,
- has_ipv4, has_ipv6,
- ipv4, ipv6,
- &saddr, errp);
- if (displaynum < 0) {
- goto error;
+ qemu_opt_iter_init(&addriter, opts, "vnc");
+ while ((addr = qemu_opt_iter_next(&addriter)) != NULL) {
+ int rv;
+ rv = vnc_display_get_address(addr, false, reverse, 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;
}
- if (wsaddrstr) {
- if (vnc_display_get_address(wsaddrstr, true, displaynum, to,
+
+ qemu_opt_iter_init(&addriter, opts, "websocket");
+ while ((addr = qemu_opt_iter_next(&addriter)) != NULL) {
+ if (vnc_display_get_address(addr, true, reverse, displaynum, to,
has_ipv4, has_ipv6,
ipv4, ipv6,
&wsaddr, errp) < 0) {
- goto error;
+ goto cleanup;
}
- if (saddr->type == SOCKET_ADDRESS_KIND_INET &&
- wsaddr->type == SOCKET_ADDRESS_KIND_INET &&
- g_str_equal(wsaddr->u.inet.data->host, "") &&
- !g_str_equal(saddr->u.inet.data->host, "")) {
- g_free(wsaddr->u.inet.data->host);
- wsaddr->u.inet.data->host = g_strdup(saddr->u.inet.data->host);
+
+ /* 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_TYPE_INET &&
+ wsaddr->type == SOCKET_ADDRESS_TYPE_INET &&
+ g_str_equal(wsaddr->u.inet.host, "") &&
+ !g_str_equal((*retsaddr)[0]->u.inet.host, "")) {
+ g_free(wsaddr->u.inet.host);
+ wsaddr->u.inet.host = g_strdup((*retsaddr)[0]->u.inet.host);
}
+
+ *retwsaddr = g_renew(SocketAddress *, *retwsaddr, *retnwsaddr + 1);
+ (*retwsaddr)[(*retnwsaddr)++] = wsaddr;
}
- *retsaddr = saddr;
- *retwsaddr = wsaddr;
- return 0;
- error:
- qapi_free_SocketAddress(saddr);
- qapi_free_SocketAddress(wsaddr);
- return -1;
+ ret = 0;
+ cleanup:
+ if (ret < 0) {
+ vnc_free_addresses(retsaddr, retnsaddr);
+ vnc_free_addresses(retwsaddr, retnwsaddr);
+ }
+ return ret;
}
static int vnc_display_connect(VncDisplay *vd,
- SocketAddress *saddr,
- SocketAddress *wsaddr,
+ SocketAddress **saddr,
+ size_t nsaddr,
+ SocketAddress **wsaddr,
+ size_t nwsaddr,
Error **errp)
{
/* connect to viewer */
QIOChannelSocket *sioc = NULL;
- if (wsaddr) {
+ if (nwsaddr != 0) {
error_setg(errp, "Cannot use websockets in reverse mode");
return -1;
}
- vd->is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX;
+ if (nsaddr != 1) {
+ error_setg(errp, "Expected a single address in reverse mode");
+ return -1;
+ }
+ /* TODO SOCKET_ADDRESS_TYPE_FD when fd has AF_UNIX */
+ vd->is_unix = saddr[0]->type == SOCKET_ADDRESS_TYPE_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 (qio_channel_socket_connect_sync(sioc, saddr[0], errp) < 0) {
return -1;
}
vnc_connect(vd, sioc, false, false);
size_t *nlsock,
Error **errp)
{
- *nlsock = 1;
- *lsock = g_new0(QIOChannelSocket *, 1);
- *lsock_tag = g_new0(guint, 1);
+ QIODNSResolver *resolver = qio_dns_resolver_get_instance();
+ SocketAddress **rawaddrs = NULL;
+ size_t nrawaddrs = 0;
+ Error *listenerr = NULL;
+ bool listening = false;
+ size_t i;
- (*lsock)[0] = qio_channel_socket_new();
- qio_channel_set_name(QIO_CHANNEL((*lsock)[0]), name);
- if (qio_channel_socket_listen_sync((*lsock)[0], addr, errp) < 0) {
+ if (qio_dns_resolver_lookup_sync(resolver, addr, &nrawaddrs,
+ &rawaddrs, errp) < 0) {
return -1;
}
- (*lsock_tag)[0] = qio_channel_add_watch(
- QIO_CHANNEL((*lsock)[0]),
- G_IO_IN, vnc_listen_io, vd, NULL);
+ 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) {
+ object_unref(OBJECT(sioc));
+ 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,
- SocketAddress *wsaddr,
+ SocketAddress **saddr,
+ size_t nsaddr,
+ SocketAddress **wsaddr,
+ size_t nwsaddr,
Error **errp)
{
- vd->is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX;
+ size_t i;
- if (vnc_display_listen_addr(vd, saddr,
- "vnc-listen",
- &vd->lsock,
- &vd->lsock_tag,
- &vd->nlsock,
- errp) < 0) {
- return -1;
+ 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;
+ }
}
- if (wsaddr &&
- vnc_display_listen_addr(vd, wsaddr,
- "vnc-ws-listen",
- &vd->lwebsock,
- &vd->lwebsock_tag,
- &vd->nlwebsock,
- 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;
{
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;
return;
}
- if (vnc_display_get_addresses(opts, &saddr, &wsaddr, errp) < 0) {
+ reverse = qemu_opt_get_bool(opts, "reverse", false);
+ if (vnc_display_get_addresses(opts, reverse, &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()) {
}
}
- reverse = qemu_opt_get_bool(opts, "reverse", false);
lock_key_sync = qemu_opt_get_bool(opts, "lock-key-sync", true);
- key_delay_ms = qemu_opt_get_number(opts, "key-delay-ms", 1);
+ key_delay_ms = qemu_opt_get_number(opts, "key-delay-ms", 10);
sasl = qemu_opt_get_bool(opts, "sasl", false);
#ifndef CONFIG_VNC_SASL
if (sasl) {
sasl, false, errp) < 0) {
goto fail;
}
+ trace_vnc_auth_init(vd, 0, vd->auth, vd->subauth);
if (vnc_display_setup_auth(&vd->ws_auth, &vd->ws_subauth,
vd->tlscreds, password,
sasl, true, errp) < 0) {
goto fail;
}
+ trace_vnc_auth_init(vd, 1, vd->ws_auth, vd->ws_subauth);
#ifdef CONFIG_VNC_SASL
if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) {
register_displaychangelistener(&vd->dcl);
}
+ if (saddr == NULL) {
+ goto cleanup;
+ }
+
if (reverse) {
- if (vnc_display_connect(vd, saddr, wsaddr, errp) < 0) {
+ if (vnc_display_connect(vd, saddr, nsaddr, wsaddr, nwsaddr, errp) < 0) {
goto fail;
}
} else {
- if (vnc_display_listen(vd, saddr, wsaddr, errp) < 0) {
+ if (vnc_display_listen(vd, saddr, nsaddr, wsaddr, nwsaddr, errp) < 0) {
goto fail;
}
}
vnc_display_print_local_addr(vd);
}
- qapi_free_SocketAddress(saddr);
- qapi_free_SocketAddress(wsaddr);
+ cleanup:
+ vnc_free_addresses(&saddr, &nsaddr);
+ vnc_free_addresses(&wsaddr, &nwsaddr);
return;
fail:
vnc_display_close(vd);
- qapi_free_SocketAddress(saddr);
- qapi_free_SocketAddress(wsaddr);
+ goto cleanup;
}
void vnc_display_add_client(const char *id, int csock, bool skipauth)