aboutsummaryrefslogtreecommitdiffstats
path: root/tools/ioemu/vnc.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/ioemu/vnc.c')
-rw-r--r--tools/ioemu/vnc.c395
1 files changed, 282 insertions, 113 deletions
diff --git a/tools/ioemu/vnc.c b/tools/ioemu/vnc.c
index 129492f5e5..631754ca03 100644
--- a/tools/ioemu/vnc.c
+++ b/tools/ioemu/vnc.c
@@ -26,11 +26,25 @@
#include "vl.h"
#include "qemu_socket.h"
+#include <assert.h>
-#define VNC_REFRESH_INTERVAL (1000 / 30)
+/* The refresh interval starts at BASE. If we scan the buffer and
+ find no change, we increase by INC, up to MAX. If the mouse moves
+ or we get a keypress, the interval is set back to BASE. If we find
+ an update, halve the interval.
+
+ All times in milliseconds. */
+#define VNC_REFRESH_INTERVAL_BASE 30
+#define VNC_REFRESH_INTERVAL_INC 50
+#define VNC_REFRESH_INTERVAL_MAX 2000
+
+/* Wait at most one second between updates, so that we can detect a
+ minimised vncviewer reasonably quickly. */
+#define VNC_MAX_UPDATE_INTERVAL 5000
#include "vnc_keysym.h"
#include "keymaps.c"
+#include "d3des.h"
#define XK_MISCELLANY
#define XK_LATIN1
@@ -64,10 +78,11 @@ typedef void VncSendHextileTile(VncState *vs,
struct VncState
{
QEMUTimer *timer;
+ int timer_interval;
+ int64_t last_update_time;
int lsock;
int csock;
DisplayState *ds;
- int need_update;
int width;
int height;
uint64_t *dirty_row; /* screen regions which are possibly dirty */
@@ -98,8 +113,6 @@ struct VncState
int visible_w;
int visible_h;
- int slow_client;
-
int ctl_keys; /* Ctrl+Alt starts calibration */
};
@@ -125,6 +138,9 @@ static void _vnc_update_client(void *opaque);
static void vnc_update_client(void *opaque);
static void vnc_client_read(void *opaque);
static void framebuffer_set_updated(VncState *vs, int x, int y, int w, int h);
+static int make_challenge(char *random, int size);
+static void set_seed(unsigned int *seedp);
+static void get_random(int len, unsigned char *buf);
#if 0
static inline void vnc_set_bit(uint32_t *d, int k)
@@ -187,6 +203,8 @@ static void set_bits_in_row(VncState *vs, uint64_t *row,
mask = ~(0ULL);
h += y;
+ if (h > vs->ds->height)
+ h = vs->ds->height;
for (; y < h; y++)
row[y] |= mask;
}
@@ -380,7 +398,7 @@ static void vnc_copy(DisplayState *ds, int src_x, int src_y, int dst_x, int dst_
int y = 0;
int pitch = ds->linesize;
VncState *vs = ds->opaque;
- int updating_client = !vs->slow_client;
+ int updating_client = 1;
if (src_x < vs->visible_x || src_y < vs->visible_y ||
dst_x < vs->visible_x || dst_y < vs->visible_y ||
@@ -390,10 +408,8 @@ static void vnc_copy(DisplayState *ds, int src_x, int src_y, int dst_x, int dst_
(dst_y + h) > (vs->visible_y + vs->visible_h))
updating_client = 0;
- if (updating_client) {
- vs->need_update = 1;
+ if (updating_client)
_vnc_update_client(vs);
- }
if (dst_y > src_y) {
y = h - 1;
@@ -445,111 +461,149 @@ static int find_update_height(VncState *vs, int y, int maxy, int last_x, int x)
static void _vnc_update_client(void *opaque)
{
VncState *vs = opaque;
- int64_t now = qemu_get_clock(rt_clock);
-
- if (vs->need_update && vs->csock != -1) {
- int y;
- char *row;
- char *old_row;
- uint64_t width_mask;
- int n_rectangles;
- int saved_offset;
- int maxx, maxy;
- int tile_bytes = vs->depth * DP2X(vs, 1);
-
- if (vs->width != DP2X(vs, DIRTY_PIXEL_BITS))
- width_mask = (1ULL << X2DP_UP(vs, vs->ds->width)) - 1;
- else
- width_mask = ~(0ULL);
-
- /* Walk through the dirty map and eliminate tiles that
- really aren't dirty */
- row = vs->ds->data;
- old_row = vs->old_data;
-
- for (y = 0; y < vs->ds->height; y++) {
- if (vs->dirty_row[y] & width_mask) {
- int x;
- char *ptr, *old_ptr;
-
- ptr = row;
- old_ptr = old_row;
-
- for (x = 0; x < X2DP_UP(vs, vs->ds->width); x++) {
- if (vs->dirty_row[y] & (1ULL << x)) {
- if (memcmp(old_ptr, ptr, tile_bytes)) {
- vs->has_update = 1;
- vs->update_row[y] |= (1ULL << x);
- memcpy(old_ptr, ptr, tile_bytes);
- }
- vs->dirty_row[y] &= ~(1ULL << x);
- }
-
- ptr += tile_bytes;
- old_ptr += tile_bytes;
- }
- }
+ int64_t now;
+ int y;
+ char *row;
+ char *old_row;
+ uint64_t width_mask;
+ int n_rectangles;
+ int saved_offset;
+ int maxx, maxy;
+ int tile_bytes = vs->depth * DP2X(vs, 1);
- row += vs->ds->linesize;
- old_row += vs->ds->linesize;
- }
+ if (vs->csock == -1)
+ return;
- if (!vs->has_update || vs->visible_y >= vs->ds->height ||
- vs->visible_x >= vs->ds->width)
- goto out;
+ now = qemu_get_clock(rt_clock);
- /* Count rectangles */
- n_rectangles = 0;
- vnc_write_u8(vs, 0); /* msg id */
- vnc_write_u8(vs, 0);
- saved_offset = vs->output.offset;
- vnc_write_u16(vs, 0);
+ if (vs->width != DP2X(vs, DIRTY_PIXEL_BITS))
+ width_mask = (1ULL << X2DP_UP(vs, vs->ds->width)) - 1;
+ else
+ width_mask = ~(0ULL);
- maxy = vs->visible_y + vs->visible_h;
- if (maxy > vs->ds->height)
- maxy = vs->ds->height;
- maxx = vs->visible_x + vs->visible_w;
- if (maxx > vs->ds->width)
- maxx = vs->ds->width;
+ /* Walk through the dirty map and eliminate tiles that really
+ aren't dirty */
+ row = vs->ds->data;
+ old_row = vs->old_data;
- for (y = vs->visible_y; y < maxy; y++) {
+ for (y = 0; y < vs->ds->height; y++) {
+ if (vs->dirty_row[y] & width_mask) {
int x;
- int last_x = -1;
- for (x = X2DP_DOWN(vs, vs->visible_x);
- x < X2DP_UP(vs, maxx); x++) {
- if (vs->update_row[y] & (1ULL << x)) {
- if (last_x == -1)
- last_x = x;
- vs->update_row[y] &= ~(1ULL << x);
- } else {
- if (last_x != -1) {
- int h = find_update_height(vs, y, maxy, last_x, x);
+ char *ptr, *old_ptr;
+
+ ptr = row;
+ old_ptr = old_row;
+
+ for (x = 0; x < X2DP_UP(vs, vs->ds->width); x++) {
+ if (vs->dirty_row[y] & (1ULL << x)) {
+ if (memcmp(old_ptr, ptr, tile_bytes)) {
+ vs->has_update = 1;
+ vs->update_row[y] |= (1ULL << x);
+ memcpy(old_ptr, ptr, tile_bytes);
+ }
+ vs->dirty_row[y] &= ~(1ULL << x);
+ }
+
+ ptr += tile_bytes;
+ old_ptr += tile_bytes;
+ }
+ }
+
+ row += vs->ds->linesize;
+ old_row += vs->ds->linesize;
+ }
+
+ if (!vs->has_update || vs->visible_y >= vs->ds->height ||
+ vs->visible_x >= vs->ds->width)
+ goto backoff;
+
+ /* Count rectangles */
+ n_rectangles = 0;
+ vnc_write_u8(vs, 0); /* msg id */
+ vnc_write_u8(vs, 0);
+ saved_offset = vs->output.offset;
+ vnc_write_u16(vs, 0);
+
+ maxy = vs->visible_y + vs->visible_h;
+ if (maxy > vs->ds->height)
+ maxy = vs->ds->height;
+ maxx = vs->visible_x + vs->visible_w;
+ if (maxx > vs->ds->width)
+ maxx = vs->ds->width;
+
+ for (y = vs->visible_y; y < maxy; y++) {
+ int x;
+ int last_x = -1;
+ for (x = X2DP_DOWN(vs, vs->visible_x);
+ x < X2DP_UP(vs, maxx); x++) {
+ if (vs->update_row[y] & (1ULL << x)) {
+ if (last_x == -1)
+ last_x = x;
+ vs->update_row[y] &= ~(1ULL << x);
+ } else {
+ if (last_x != -1) {
+ int h = find_update_height(vs, y, maxy, last_x, x);
+ if (h != 0) {
send_framebuffer_update(vs, DP2X(vs, last_x), y,
DP2X(vs, (x - last_x)), h);
n_rectangles++;
}
- last_x = -1;
}
+ last_x = -1;
}
- if (last_x != -1) {
- int h = find_update_height(vs, y, maxy, last_x, x);
+ }
+ if (last_x != -1) {
+ int h = find_update_height(vs, y, maxy, last_x, x);
+ if (h != 0) {
send_framebuffer_update(vs, DP2X(vs, last_x), y,
DP2X(vs, (x - last_x)), h);
n_rectangles++;
}
}
- vs->output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF;
- vs->output.buffer[saved_offset + 1] = n_rectangles & 0xFF;
+ }
+ vs->output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF;
+ vs->output.buffer[saved_offset + 1] = n_rectangles & 0xFF;
- vs->has_update = 0;
- vs->need_update = 0;
- vnc_flush(vs);
- vs->slow_client = 0;
- } else
- vs->slow_client = 1;
+ if (n_rectangles == 0)
+ goto backoff;
- out:
- qemu_mod_timer(vs->timer, now + VNC_REFRESH_INTERVAL);
+ vs->has_update = 0;
+ vnc_flush(vs);
+ vs->last_update_time = now;
+
+ vs->timer_interval /= 2;
+ if (vs->timer_interval < VNC_REFRESH_INTERVAL_BASE)
+ vs->timer_interval = VNC_REFRESH_INTERVAL_BASE;
+
+ return;
+
+ backoff:
+ /* No update -> back off a bit */
+ vs->timer_interval += VNC_REFRESH_INTERVAL_INC;
+ if (vs->timer_interval > VNC_REFRESH_INTERVAL_MAX) {
+ vs->timer_interval = VNC_REFRESH_INTERVAL_MAX;
+ if (now - vs->last_update_time >= VNC_MAX_UPDATE_INTERVAL) {
+ /* Send a null update. If the client is no longer
+ interested (e.g. minimised) it'll ignore this, and we
+ can stop scanning the buffer until it sends another
+ update request. */
+ /* It turns out that there's a bug in realvncviewer 4.1.2
+ which means that if you send a proper null update (with
+ no update rectangles), it gets a bit out of sync and
+ never sends any further requests, regardless of whether
+ it needs one or not. Fix this by sending a single 1x1
+ update rectangle instead. */
+ vnc_write_u8(vs, 0);
+ vnc_write_u8(vs, 0);
+ vnc_write_u16(vs, 1);
+ send_framebuffer_update(vs, 0, 0, 1, 1);
+ vnc_flush(vs);
+ vs->last_update_time = now;
+ return;
+ }
+ }
+ qemu_mod_timer(vs->timer, now + vs->timer_interval);
+ return;
}
static void vnc_update_client(void *opaque)
@@ -564,7 +618,7 @@ static void vnc_timer_init(VncState *vs)
{
if (vs->timer == NULL) {
vs->timer = qemu_new_timer(rt_clock, vnc_update_client, vs);
- qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock));
+ vs->timer_interval = VNC_REFRESH_INTERVAL_BASE;
}
}
@@ -625,7 +679,6 @@ static int vnc_client_io_error(VncState *vs, int ret, int last_errno)
vs->csock = -1;
buffer_reset(&vs->input);
buffer_reset(&vs->output);
- vs->need_update = 0;
return 0;
}
return ret;
@@ -686,8 +739,10 @@ static void vnc_client_read(void *opaque)
memmove(vs->input.buffer, vs->input.buffer + len,
vs->input.offset - len);
vs->input.offset -= len;
- } else
+ } else {
+ assert(ret > vs->read_handler_expect);
vs->read_handler_expect = ret;
+ }
}
}
@@ -895,13 +950,14 @@ static void framebuffer_update_request(VncState *vs, int incremental,
int x_position, int y_position,
int w, int h)
{
- vs->need_update = 1;
if (!incremental)
framebuffer_set_updated(vs, x_position, y_position, w, h);
vs->visible_x = x_position;
vs->visible_y = y_position;
vs->visible_w = w;
vs->visible_h = h;
+
+ qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock));
}
static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
@@ -1016,6 +1072,7 @@ static int protocol_client_msg(VncState *vs, char *data, size_t len)
{
int i;
uint16_t limit;
+ int64_t now;
switch (data[0]) {
case 0:
@@ -1032,8 +1089,12 @@ static int protocol_client_msg(VncState *vs, char *data, size_t len)
if (len == 1)
return 4;
- if (len == 4)
- return 4 + (read_u16(data, 2) * 4);
+ if (len == 4) {
+ uint16_t v;
+ v = read_u16(data, 2);
+ if (v)
+ return 4 + v * 4;
+ }
limit = read_u16(data, 2);
for (i = 0; i < limit; i++) {
@@ -1055,20 +1116,30 @@ static int protocol_client_msg(VncState *vs, char *data, size_t len)
if (len == 1)
return 8;
+ vs->timer_interval = VNC_REFRESH_INTERVAL_BASE;
+ qemu_advance_timer(vs->timer,
+ qemu_get_clock(rt_clock) + vs->timer_interval);
key_event(vs, read_u8(data, 1), read_u32(data, 4));
break;
case 5:
if (len == 1)
return 6;
+ vs->timer_interval = VNC_REFRESH_INTERVAL_BASE;
+ qemu_advance_timer(vs->timer,
+ qemu_get_clock(rt_clock) + vs->timer_interval);
pointer_event(vs, read_u8(data, 1), read_u16(data, 2), read_u16(data, 4));
break;
case 6:
if (len == 1)
return 8;
- if (len == 8)
- return 8 + read_u32(data, 4);
+ if (len == 8) {
+ uint32_t v;
+ v = read_u32(data, 4);
+ if (v)
+ return 8 + v;
+ }
client_cut_text(vs, read_u32(data, 4), data + 8);
break;
@@ -1087,6 +1158,8 @@ static int protocol_client_init(VncState *vs, char *data, size_t len)
size_t l;
char pad[3] = { 0, 0, 0 };
+ vga_hw_update();
+
vs->width = vs->ds->width;
vs->height = vs->ds->height;
vnc_write_u16(vs, vs->ds->width);
@@ -1141,23 +1214,92 @@ static int protocol_client_init(VncState *vs, char *data, size_t len)
return 0;
}
+static int protocol_response(VncState *vs, char *client_response, size_t len)
+{
+ extern char vncpasswd[64];
+ extern unsigned char challenge[AUTHCHALLENGESIZE];
+ unsigned char cryptchallenge[AUTHCHALLENGESIZE];
+ unsigned char key[8];
+ int passwdlen, i, j;
+
+ memcpy(cryptchallenge, challenge, AUTHCHALLENGESIZE);
+
+ /* Calculate the sent challenge */
+ passwdlen = strlen(vncpasswd);
+ for (i=0; i<8; i++)
+ key[i] = i<passwdlen ? vncpasswd[i] : 0;
+ deskey(key, EN0);
+ for (j = 0; j < AUTHCHALLENGESIZE; j += 8)
+ des(cryptchallenge+j, cryptchallenge+j);
+
+ /* Check the actual response */
+ if (memcmp(cryptchallenge, client_response, AUTHCHALLENGESIZE) != 0) {
+ /* password error */
+ vnc_write_u32(vs, 1);
+ vnc_write_u32(vs, 22);
+ vnc_write(vs, "Authentication failure", 22);
+ vnc_flush(vs);
+ fprintf(stderr, "VNC Password error.\n");
+ vnc_client_error(vs);
+ return 0;
+ }
+
+ vnc_write_u32(vs, 0);
+ vnc_flush(vs);
+
+ vnc_read_when(vs, protocol_client_init, 1);
+
+ return 0;
+}
+
static int protocol_version(VncState *vs, char *version, size_t len)
{
+ extern char vncpasswd[64];
+ extern unsigned char challenge[AUTHCHALLENGESIZE];
char local[13];
- int maj, min;
+ int support, maj, min;
memcpy(local, version, 12);
local[12] = 0;
+ /* protocol version check */
if (sscanf(local, "RFB %03d.%03d\n", &maj, &min) != 2) {
+ fprintf(stderr, "Protocol version error.\n");
vnc_client_error(vs);
return 0;
}
- vnc_write_u32(vs, 1); /* None */
- vnc_flush(vs);
- vnc_read_when(vs, protocol_client_init, 1);
+ support = 0;
+ if (maj = 3) {
+ if (min == 3 || min ==4) {
+ support = 1;
+ }
+ }
+
+ if (! support) {
+ fprintf(stderr, "Client uses unsupported protocol version %d.%d.\n",
+ maj, min);
+ vnc_client_error(vs);
+ return 0;
+ }
+
+ if (*vncpasswd == '\0') {
+ /* AuthType is None */
+ vnc_write_u32(vs, 1);
+ vnc_flush(vs);
+ vnc_read_when(vs, protocol_client_init, 1);
+ } else {
+ /* AuthType is VncAuth */
+ vnc_write_u32(vs, 2);
+
+ /* Challenge-Responce authentication */
+ /* Send Challenge */
+ make_challenge(challenge, AUTHCHALLENGESIZE);
+ vnc_write(vs, challenge, AUTHCHALLENGESIZE);
+ vnc_flush(vs);
+ vnc_read_when(vs, protocol_response, AUTHCHALLENGESIZE);
+ }
return 0;
}
@@ -1183,9 +1325,8 @@ static void vnc_listen_read(void *opaque)
}
}
-int vnc_display_init(DisplayState *ds, int display, int find_unused)
+int vnc_display_init(DisplayState *ds, int display, int find_unused, struct sockaddr_in *addr)
{
- struct sockaddr_in addr;
int reuse_addr, ret;
VncState *vs;
@@ -1223,11 +1364,10 @@ int vnc_display_init(DisplayState *ds, int display, int find_unused)
}
retry:
- addr.sin_family = AF_INET;
- addr.sin_port = htons(5900 + display);
- memset(&addr.sin_addr, 0, sizeof(addr.sin_addr));
+ addr->sin_family = AF_INET;
+ addr->sin_port = htons(5900 + display);
- if (bind(vs->lsock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ if (bind(vs->lsock, (struct sockaddr *)addr, sizeof(struct sockaddr_in)) == -1) {
if (find_unused && errno == EADDRINUSE) {
display++;
goto retry;
@@ -1269,7 +1409,7 @@ int vnc_start_viewer(int port)
exit(1);
case 0: /* child */
- execlp("vncviewer", "vncviewer", s, 0);
+ execlp("vncviewer", "vncviewer", s, NULL);
fprintf(stderr, "vncviewer execlp failed\n");
exit(1);
@@ -1277,3 +1417,32 @@ int vnc_start_viewer(int port)
return pid;
}
}
+
+unsigned int seed;
+
+static int make_challenge(char *random, int size)
+{
+
+ set_seed(&seed);
+ get_random(size, random);
+
+ return 0;
+}
+
+static void set_seed(unsigned int *seedp)
+{
+ *seedp += (unsigned int)(time(NULL)+getpid()+getpid()*987654+rand());
+ srand(*seedp);
+
+ return;
+}
+
+static void get_random(int len, unsigned char *buf)
+{
+ int i;
+
+ for (i=0; i<len; i++)
+ buf[i] = (int) (256.0*rand()/(RAND_MAX+1.0));
+
+ return;
+}