summaryrefslogtreecommitdiffstats
path: root/ui/vnc.c
diff options
context:
space:
mode:
Diffstat (limited to 'ui/vnc.c')
-rw-r--r--ui/vnc.c322
1 files changed, 229 insertions, 93 deletions
diff --git a/ui/vnc.c b/ui/vnc.c
index 9f8d5a1b1f..665a143578 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -60,6 +60,7 @@ static QTAILQ_HEAD(, VncDisplay) vnc_displays =
static int vnc_cursor_define(VncState *vs);
static void vnc_release_modifiers(VncState *vs);
+static void vnc_update_throttle_offset(VncState *vs);
static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
{
@@ -596,7 +597,7 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp)
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);
@@ -766,6 +767,7 @@ static void vnc_dpy_switch(DisplayChangeListener *dcl,
vnc_set_area_dirty(vs->dirty, vd, 0, 0,
vnc_width(vd),
vnc_height(vd));
+ vnc_update_throttle_offset(vs);
}
}
@@ -961,85 +963,168 @@ static int find_and_clear_dirty_height(VncState *vs,
return h;
}
-static int vnc_update_client(VncState *vs, int has_dirty, bool sync)
+/*
+ * Figure out how much pending data we should allow in the output
+ * buffer before we throttle incremental display updates, and/or
+ * drop audio samples.
+ *
+ * We allow for equiv of 1 full display's worth of FB updates,
+ * and 1 second of audio samples. If audio backlog was larger
+ * than that the client would already suffering awful audio
+ * glitches, so dropping samples is no worse really).
+ */
+static void vnc_update_throttle_offset(VncState *vs)
{
- if (vs->disconnecting) {
- vnc_disconnect_finish(vs);
- return 0;
+ size_t offset =
+ vs->client_width * vs->client_height * vs->client_pf.bytes_per_pixel;
+
+ if (vs->audio_cap) {
+ int freq = vs->as.freq;
+ /* We don't limit freq when reading settings from client, so
+ * it could be upto MAX_INT in size. 48khz is a sensible
+ * upper bound for trustworthy clients */
+ int bps;
+ if (freq > 48000) {
+ freq = 48000;
+ }
+ switch (vs->as.fmt) {
+ default:
+ case AUD_FMT_U8:
+ case AUD_FMT_S8:
+ bps = 1;
+ break;
+ case AUD_FMT_U16:
+ case AUD_FMT_S16:
+ bps = 2;
+ break;
+ case AUD_FMT_U32:
+ case AUD_FMT_S32:
+ bps = 4;
+ break;
+ }
+ offset += freq * bps * vs->as.nchannels;
}
- 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;
+ /* Put a floor of 1MB on offset, so that if we have a large pending
+ * buffer and the display is resized to a small size & back again
+ * we don't suddenly apply a tiny send limit
+ */
+ offset = MAX(offset, 1024 * 1024);
- if (!vs->has_dirty && !vs->audio_cap && !vs->force_update)
- return 0;
+ if (vs->throttle_output_offset != offset) {
+ trace_vnc_client_throttle_threshold(
+ vs, vs->ioc, vs->throttle_output_offset, offset, vs->client_width,
+ vs->client_height, vs->client_pf.bytes_per_pixel, vs->audio_cap);
+ }
- /*
- * 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.
+ vs->throttle_output_offset = offset;
+}
+
+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 pending send queue
+ * is less than the permitted threshold, and the job worker
+ * is completely idle.
*/
- 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;
- }
- }
+ if (vs->output.offset < vs->throttle_output_offset &&
+ vs->job_update == VNC_STATE_UPDATE_NONE) {
+ return true;
}
-
- vnc_job_push(job);
- if (sync) {
- vnc_jobs_join(vs);
+ trace_vnc_client_throttle_incremental(
+ vs, vs->ioc, vs->job_update, vs->output.offset);
+ break;
+ case VNC_STATE_UPDATE_FORCE:
+ /* Only allow forced updates if the pending send queue
+ * does not contain a previous forced update, and the
+ * job worker is completely idle.
+ *
+ * Note this means we'll queue a forced update, even if
+ * the output buffer size is otherwise over the throttle
+ * output limit.
+ */
+ if (vs->force_update_offset == 0 &&
+ vs->job_update == VNC_STATE_UPDATE_NONE) {
+ return true;
}
- vs->force_update = 0;
- vs->has_dirty = 0;
- return n;
+ trace_vnc_client_throttle_forced(
+ vs, vs->ioc, vs->job_update, vs->force_update_offset);
+ break;
}
+ 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);
- } else if (sync) {
- vnc_jobs_join(vs);
+ return 0;
}
- return 0;
+ vs->has_dirty += has_dirty;
+ if (!vnc_should_update(vs)) {
+ 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 */
+ 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;
+ }
+ }
+ }
+
+ vs->job_update = vs->update;
+ vs->update = VNC_STATE_UPDATE_NONE;
+ vnc_job_push(job);
+ vs->has_dirty = 0;
+ return n;
}
/* audio */
@@ -1077,11 +1162,15 @@ static void audio_capture(void *opaque, void *buf, int size)
VncState *vs = opaque;
vnc_lock_output(vs);
- vnc_write_u8(vs, VNC_MSG_SERVER_QEMU);
- vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
- vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA);
- vnc_write_u32(vs, size);
- vnc_write(vs, buf, size);
+ if (vs->output.offset < vs->throttle_output_offset) {
+ vnc_write_u8(vs, VNC_MSG_SERVER_QEMU);
+ vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
+ vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA);
+ vnc_write_u32(vs, size);
+ vnc_write(vs, buf, size);
+ } else {
+ trace_vnc_client_throttle_audio(vs, vs->ioc, vs->output.offset);
+ }
vnc_unlock_output(vs);
vnc_flush(vs);
}
@@ -1183,7 +1272,7 @@ void vnc_disconnect_finish(VncState *vs)
g_free(vs);
}
-ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp)
+size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp)
{
if (ret <= 0) {
if (ret == 0) {
@@ -1226,9 +1315,9 @@ void vnc_client_error(VncState *vs)
*
* Returns the number of bytes written, which may be less than
* the requested 'datalen' if the socket would block. Returns
- * -1 on error, and disconnects the client socket.
+ * 0 on I/O error, and disconnects the client socket.
*/
-ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
+size_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
{
Error *err = NULL;
ssize_t ret;
@@ -1246,12 +1335,13 @@ ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
* will switch the FD poll() handler back to read monitoring.
*
* Returns the number of bytes written, which may be less than
- * the buffered output data if the socket would block. Returns
- * -1 on error, and disconnects the client socket.
+ * the buffered output data if the socket would block. Returns
+ * 0 on I/O error, and disconnects the client socket.
*/
-static ssize_t vnc_client_write_plain(VncState *vs)
+static size_t vnc_client_write_plain(VncState *vs)
{
- ssize_t ret;
+ size_t offset;
+ size_t ret;
#ifdef CONFIG_VNC_SASL
VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF %d\n",
@@ -1270,7 +1360,20 @@ static ssize_t vnc_client_write_plain(VncState *vs)
if (!ret)
return 0;
+ if (ret >= vs->force_update_offset) {
+ if (vs->force_update_offset != 0) {
+ trace_vnc_client_unthrottle_forced(vs, vs->ioc);
+ }
+ vs->force_update_offset = 0;
+ } else {
+ vs->force_update_offset -= ret;
+ }
+ offset = vs->output.offset;
buffer_advance(&vs->output, ret);
+ if (offset >= vs->throttle_output_offset &&
+ vs->output.offset < vs->throttle_output_offset) {
+ trace_vnc_client_unthrottle_incremental(vs, vs->ioc, vs->output.offset);
+ }
if (vs->output.offset == 0) {
if (vs->ioc_tag) {
@@ -1339,9 +1442,9 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
*
* Returns the number of bytes read, which may be less than
* the requested 'datalen' if the socket would block. Returns
- * -1 on error, and disconnects the client socket.
+ * 0 on I/O error or EOF, and disconnects the client socket.
*/
-ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
+size_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
{
ssize_t ret;
Error *err = NULL;
@@ -1357,12 +1460,13 @@ ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
* when not using any SASL SSF encryption layers. Will read as much
* data as possible without blocking.
*
- * Returns the number of bytes read. Returns -1 on error, and
- * disconnects the client socket.
+ * Returns the number of bytes read, which may be less than
+ * the requested 'datalen' if the socket would block. Returns
+ * 0 on I/O error or EOF, and disconnects the client socket.
*/
-static ssize_t vnc_client_read_plain(VncState *vs)
+static size_t vnc_client_read_plain(VncState *vs)
{
- ssize_t ret;
+ size_t ret;
VNC_DEBUG("Read plain %p size %zd offset %zd\n",
vs->input.buffer, vs->input.capacity, vs->input.offset);
buffer_reserve(&vs->input, 4096);
@@ -1388,7 +1492,7 @@ static void vnc_jobs_bh(void *opaque)
*/
static int vnc_client_read(VncState *vs)
{
- ssize_t ret;
+ size_t ret;
#ifdef CONFIG_VNC_SASL
if (vs->sasl.conn && vs->sasl.runSSF)
@@ -1439,8 +1543,39 @@ gboolean vnc_client_io(QIOChannel *ioc G_GNUC_UNUSED,
}
+/*
+ * Scale factor to apply to vs->throttle_output_offset when checking for
+ * hard limit. Worst case normal usage could be x2, if we have a complete
+ * incremental update and complete forced update in the output buffer.
+ * So x3 should be good enough, but we pick x5 to be conservative and thus
+ * (hopefully) never trigger incorrectly.
+ */
+#define VNC_THROTTLE_OUTPUT_LIMIT_SCALE 5
+
void vnc_write(VncState *vs, const void *data, size_t len)
{
+ if (vs->disconnecting) {
+ return;
+ }
+ /* Protection against malicious client/guest to prevent our output
+ * buffer growing without bound if client stops reading data. This
+ * should rarely trigger, because we have earlier throttling code
+ * which stops issuing framebuffer updates and drops audio data
+ * if the throttle_output_offset value is exceeded. So we only reach
+ * this higher level if a huge number of pseudo-encodings get
+ * triggered while data can't be sent on the socket.
+ *
+ * NB throttle_output_offset can be zero during early protocol
+ * handshake, or from the job thread's VncState clone
+ */
+ if (vs->throttle_output_offset != 0 &&
+ vs->output.offset > (vs->throttle_output_offset *
+ VNC_THROTTLE_OUTPUT_LIMIT_SCALE)) {
+ trace_vnc_client_output_limit(vs, vs->ioc, vs->output.offset,
+ vs->throttle_output_offset);
+ vnc_disconnect_start(vs);
+ return;
+ }
buffer_reserve(&vs->output, len);
if (vs->ioc != NULL && buffer_empty(&vs->output)) {
@@ -1876,14 +2011,14 @@ static void ext_key_event(VncState *vs, int down,
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)
@@ -2255,7 +2390,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
}
vs->as.nchannels = read_u8(data, 5);
if (vs->as.nchannels != 1 && vs->as.nchannels != 2) {
- VNC_DEBUG("Invalid audio channel coount %d\n",
+ VNC_DEBUG("Invalid audio channel count %d\n",
read_u8(data, 5));
vnc_client_error(vs);
break;
@@ -2281,6 +2416,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
break;
}
+ vnc_update_throttle_offset(vs);
vnc_read_when(vs, protocol_client_msg, 1);
return 0;
}
@@ -2863,7 +2999,7 @@ static void vnc_refresh(DisplayChangeListener *dcl)
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 */
}