All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Daniel P. Berrange" <berrange@redhat.com>
To: qemu-devel@nongnu.org
Subject: Re: [Qemu-devel] [PATCH 2/2] VNC char device data stream tunnelling
Date: Wed, 1 Jul 2009 17:27:47 +0100	[thread overview]
Message-ID: <20090701162747.GD24296@redhat.com> (raw)
In-Reply-To: <20090701162114.GB24296@redhat.com>

commit 71000d52fb4f9a4bef1f4841dd97561416fe5eed
Author: Daniel P. Berrange <berrange@redhat.com>
Date:   Wed Jul 1 17:03:46 2009 +0100

    Add a new VNC extension to allowing tunnelling of gemeric data streams.
    Map QEMU character devices onto VNC data streams

diff --git a/Makefile b/Makefile
index 2a4b3f3..a7de1a5 100644
--- a/Makefile
+++ b/Makefile
@@ -156,7 +156,7 @@ obj-y += $(addprefix audio/, $(audio-obj-y))
 obj-y += keymaps.o
 obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o
 obj-$(CONFIG_CURSES) += curses.o
-obj-y += vnc.o acl.o d3des.o
+obj-y += vnc.o vnc-char.o acl.o d3des.o
 obj-$(CONFIG_VNC_TLS) += vnc-tls.o vnc-auth-vencrypt.o
 obj-$(CONFIG_VNC_SASL) += vnc-auth-sasl.o
 obj-$(CONFIG_COCOA) += cocoa.o
diff --git a/vnc-char.c b/vnc-char.c
new file mode 100644
index 0000000..b7b4606
--- /dev/null
+++ b/vnc-char.c
@@ -0,0 +1,401 @@
+/*
+ * QEMU VNC display driver: Char device capture
+ *
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "vnc.h"
+
+/*
+ * Encoding #: -260
+ *
+ * The server notifies the client of available streams using the
+ * framebuffer update for our psuedo encoding #.
+ *
+ * The 'x' field is '0' for a device addition, '1' for removal
+ *
+ * The payload of the update is the device name
+ *  - u32 - length of device name
+ *  - u8 * len - text of device name, not including \0
+ *
+ * When client indicates it accepts the stream encoding,
+ * the server will send back a stream addition update for
+ * each initial device. Hotplug/unplug for streams will
+ * trigger further updates.
+ *
+ * For QEMU, the streams extension maps 1-stream to 1 char device
+ *
+ * Stream capture uses an new 'aliguori' message sub-type. There
+ * are 3 client -> server messages, and 3 server -> client messages
+ *
+ * Client starts capture by supplying a stream name. Server replies
+ * givng the stream name -> stream ID mapping. All further messages
+ * use the unique stream ID.
+ *
+ * Client -> server messages:
+ *
+ *   Message   == u8: 255 (aliguouri)
+ *   sub-type  == u8: 2  (streams)
+ *   operation == u8:
+ *        0 == start capture
+ *        1 == end capture
+ *        2 == send data
+ *
+ * For operation == start capture:
+ *   content:
+ *      u32 - length of device name
+ *      u8 *len - device name to capture
+ *
+ * For operation == end capture
+ *   content:
+ *      u32 - capture session ID
+ *
+ * For operation == send data
+ *   content:
+ *      u32 - capture session ID
+ *      u32 - length of data
+ *      u8*len - raw data
+ *
+ *
+ *
+ * Server -> client messages:
+ *
+ *   Message   == u8: 255 (aliguouri)
+ *   sub-type  == u8: 2  (streams)
+ *   operation == u8:
+ *        0 == start capture
+ *        1 == end capture
+ *        2 == send data
+ *
+ * For operation == start capture
+ *   content:
+ *      u32 - capture session ID
+ *      u32 - length of device name
+ *      u8 *len - device name to capture
+ *
+ * For operation == end capture
+ *   content:
+ *      u32 - capture session ID
+ *
+ * For operation == send data
+ *   content:
+ *      u32 - capture session ID
+ *      u32 - length of data
+ *      u8*len - raw data
+ */
+
+static void vnc_send_start_capture(VncState *client,
+				   VncCharCaptureState *chr);
+static void vnc_send_end_capture(VncState *client,
+				 VncCharCaptureState *chr);
+static void vnc_send_data_capture(VncState *client,
+				  VncCharCaptureState *chr,
+				  const void *buf,
+				  int len);
+
+
+static VncCharCaptureState *vnc_char_get_capture(VncState *client,
+						 int id)
+{
+    VncCharCaptureState *cap;
+
+    TAILQ_FOREACH(cap, &client->char_cap, next) {
+	if (cap->id == id)
+	    return cap;
+    }
+
+    return NULL;
+}
+
+
+/*
+ * From guest device to VNC client
+ */
+static void vnc_char_capture(void *opaque, const void *buf, int len)
+{
+    VncCharCaptureState *cap = opaque;
+
+    if (!cap)
+	return;
+
+    vnc_send_data_capture(cap->client, cap, buf, len);
+}
+
+static void vnc_char_destroy(void *opaque)
+{
+    VncCharCaptureState *cap = opaque;
+
+    if (!cap)
+	return;
+
+    qemu_chr_del_capture(cap->chr, cap->cap);
+    cap->chr = NULL;
+    cap->cap = NULL;
+}
+
+static struct CharCaptureOps vnc_char_capture_ops = {
+    vnc_char_capture,
+    vnc_char_destroy,
+};
+
+
+
+int vnc_char_add_capture(VncState *client,
+			 const char *chrname)
+{
+    VncCharCaptureState *cap;
+    CharDriverState *chr;
+
+    chr = qemu_chr_fetch("serial0");
+    if (!chr)
+	return -1;
+
+    cap = qemu_mallocz(sizeof(*cap));
+    cap->id = client->char_cap_next_id++;
+    cap->chr = chr;
+    cap->cap = qemu_chr_add_capture(chr, &vnc_char_capture_ops, cap);
+    cap->client = client;
+
+    TAILQ_INSERT_TAIL(&client->char_cap, cap, next);
+
+    vnc_send_start_capture(client, cap);
+
+    return cap->id;
+}
+
+void vnc_char_del_capture(VncState *client,
+			  int id)
+{
+    VncCharCaptureState *cap = vnc_char_get_capture(client, id);
+    if (!cap)
+	return;
+
+    TAILQ_REMOVE(&client->char_cap, cap, next);
+
+    if (cap->chr)
+	qemu_chr_del_capture(cap->chr, cap->cap);
+
+    vnc_send_end_capture(client, cap);
+
+    qemu_free(cap);
+}
+
+void vnc_char_clear_capture(VncState *client)
+{
+    VncCharCaptureState *cap, *tmp;
+
+    TAILQ_FOREACH_SAFE(cap, &client->char_cap, next, tmp) {
+	if (cap->chr)
+	    qemu_chr_del_capture(cap->chr, cap->cap);
+
+	qemu_free(cap);
+    }
+}
+
+
+/*
+ * From VNC client to guest device
+ */
+void vnc_char_write(VncState *client,
+		    int id,
+		    const void *buf,
+		    int len)
+{
+    VncCharCaptureState *cap = vnc_char_get_capture(client, id);
+    if (!cap)
+	return; /* XXX kill client */
+
+    if (cap->chr)
+	qemu_chr_write(cap->chr, buf, len);
+}
+
+
+static void
+vnc_framebuffer_update_stream(VncState *client,
+			      CharDriverState *chr,
+			      int deleted)
+{
+    if (strcmp(chr->label, "monitor") == 0)
+	return;
+
+    vnc_write_u8(client, 0);
+    vnc_write_u8(client, 0);
+    vnc_write_u16(client, 1);
+    vnc_framebuffer_update(client,
+			   deleted,
+			   0,
+			   ds_get_width(client->ds),
+			   ds_get_height(client->ds),
+                           VNC_ENCODING_STREAMS);
+    vnc_write_u32(client, strlen(chr->label));
+    vnc_write(client, chr->label, strlen(chr->label));
+}
+
+static int vnc_chr_iterator(CharDriverState *chr, void *opaque) {
+    VncState *client = opaque;
+
+    vnc_framebuffer_update_stream(client, chr, 0);
+    return 0;
+}
+
+
+static void vnc_chr_opened(CharDriverState *chr, void *opaque) {
+    VncState *client = opaque;
+
+    vnc_framebuffer_update_stream(client, chr, 0);
+    vnc_flush(client);
+}
+
+static void vnc_chr_closing(CharDriverState *chr, void *opaque) {
+    VncState *client = opaque;
+
+    vnc_framebuffer_update_stream(client, chr, 1);
+    vnc_flush(client);
+}
+
+static struct CharMonitorOps vnc_monops = {
+    vnc_chr_opened, vnc_chr_closing
+};
+
+void vnc_streams_ack(VncState *client)
+{
+    if (client->char_mon)
+	return;
+
+    qemu_chr_iterate(vnc_chr_iterator, client);
+    vnc_flush(client);
+
+    client->char_mon = qemu_chr_add_monitor(&vnc_monops, client);
+}
+
+
+static void vnc_send_start_capture(VncState *client,
+				   VncCharCaptureState *chr) {
+    vnc_write_u8(client, 255);
+    vnc_write_u8(client, 2);
+    vnc_write_u8(client, 0);
+
+    vnc_write_u32(client, chr->id);
+    vnc_write_u32(client, strlen(chr->chr->label));
+    vnc_write(client, chr->chr->label, strlen(chr->chr->label));
+    vnc_flush(client);
+}
+
+
+static void vnc_send_end_capture(VncState *client,
+				 VncCharCaptureState *chr) {
+    vnc_write_u8(client, 255);
+    vnc_write_u8(client, 2);
+    vnc_write_u8(client, 1);
+
+    vnc_write_u32(client, chr->id);
+    vnc_flush(client);
+}
+
+
+static void vnc_send_data_capture(VncState *client,
+				  VncCharCaptureState *chr,
+				  const void *data,
+				  int len) {
+    vnc_write_u8(client, 255);
+    vnc_write_u8(client, 2);
+    vnc_write_u8(client, 2);
+
+    vnc_write_u32(client, chr->id);
+    vnc_write_u32(client, len);
+    vnc_write(client, data, len);
+    vnc_flush(client);
+}
+
+
+int vnc_streams_msg(VncState *client, uint8_t *data, int len) {
+    if (len == 2)
+	return 3;
+
+    switch (read_u8(data, 2)) {
+    case 0: {
+	uint32_t n_name;
+	char *name;
+
+	if (len == 3)
+	    return 7;
+
+	n_name = read_u32(data, 3);
+	if (n_name > 4096) {
+	    vnc_client_error(client);
+	    break;
+	}
+
+	if (len == 7)
+	    return 7 + n_name;
+
+	name = qemu_malloc(n_name + 1);
+	memcpy(name, data + 7, n_name);
+	name[n_name] = '\0';
+
+	if (strcmp(name, "monitor") == 0) {
+	    vnc_client_error(client);
+	    qemu_free(name);
+	    break;
+	}
+
+	vnc_char_add_capture(client, name);
+	qemu_free(name);
+    } break;
+
+    case 1: {
+	uint32_t id;
+	if (len == 3)
+	    return 7;
+
+	id = read_u32(data, 3);
+
+	vnc_char_del_capture(client, id);
+    } break;
+
+    case 2: {
+	uint32_t id;
+	uint32_t n_data;
+	if (len == 3)
+	    return 11;
+
+	id = read_u32(data, 3);
+	n_data = read_u32(data, 7);
+
+	if (n_data > 4096) {
+	    vnc_client_error(client);
+	    break;
+	}
+
+	if (len == 11)
+	    return 11 + n_data;
+
+	vnc_char_write(client, id, data + 11, n_data);
+    } break;
+
+    default:
+	printf ("Invalid audio message %d\n", read_u8(data, 4));
+	vnc_client_error(client);
+	break;
+    }
+
+    return 0;
+}
diff --git a/vnc-char.h b/vnc-char.h
new file mode 100644
index 0000000..b421448
--- /dev/null
+++ b/vnc-char.h
@@ -0,0 +1,60 @@
+/*
+ * QEMU VNC display driver: Char device capture
+ *
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+#ifndef __QEMU_VNC_CHAR_H__
+#define __QEMU_VNC_CHAR_H__
+
+typedef struct VncCharCaptureState VncCharCaptureState;
+struct VncCharCaptureState
+{
+    int id;
+    CharDriverState *chr;
+    CharCaptureState *cap;
+
+    VncState *client;
+
+    TAILQ_ENTRY(VncCharCaptureState) next;
+};
+
+
+int vnc_char_add_capture(VncState *client,
+			 const char *chrname);
+
+void vnc_char_del_capture(VncState *client,
+			  int id);
+
+void vnc_char_clear_capture(VncState *client);
+
+void vnc_char_write(VncState *client,
+		    int id,
+		    const void *buf,
+		    int len);
+
+void vnc_streams_ack(VncState *client);
+
+int vnc_streams_msg(VncState *client, uint8_t *data, int len);
+
+
+#endif /* __QEMU_VNC_CHAR_H__ */
diff --git a/vnc.c b/vnc.c
index de0ff87..d73ed80 100644
--- a/vnc.c
+++ b/vnc.c
@@ -296,8 +296,8 @@ static void vnc_dpy_update(DisplayState *ds, int x, int y, int w, int h)
     }
 }
 
-static void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
-                                   int32_t encoding)
+void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
+			    int32_t encoding)
 {
     vnc_write_u16(vs, x);
     vnc_write_u16(vs, y);
@@ -901,6 +901,8 @@ static void vnc_disconnect_finish(VncState *vs)
 #endif /* CONFIG_VNC_SASL */
     audio_del(vs);
 
+    vnc_char_clear_capture(vs);
+
     VncState *p, *parent = NULL;
     for (p = vs->vd->clients; p != NULL; p = p->next) {
         if (p == vs) {
@@ -1523,6 +1525,7 @@ static void send_ext_audio_ack(VncState *vs)
     vnc_flush(vs);
 }
 
+
 static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
 {
     int i;
@@ -1564,6 +1567,9 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
         case VNC_ENCODING_AUDIO:
             send_ext_audio_ack(vs);
             break;
+        case VNC_ENCODING_STREAMS:
+	    vnc_streams_ack(vs);
+            break;
         case VNC_ENCODING_WMVi:
             vs->features |= VNC_FEATURE_WMVI_MASK;
             break;
@@ -1706,6 +1712,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
 {
     int i;
     uint16_t limit;
+    int ret;
 
     switch (data[0]) {
     case 0:
@@ -1822,6 +1829,12 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
             }
             break;
 
+        case 2:
+	    ret = vnc_streams_msg(vs, data, len);
+	    if (ret)
+		return ret;
+	    break;
+
         default:
             printf("Msg: %d\n", read_u16(data, 0));
             vnc_client_error(vs);
@@ -2082,6 +2095,8 @@ static void vnc_connect(VncDisplay *vd, int csock)
     vnc_read_when(vs, protocol_version, 12);
     reset_keys(vs);
 
+    TAILQ_INIT(&vs->char_cap);
+
     vs->next = vd->clients;
     vd->clients = vs;
 
diff --git a/vnc.h b/vnc.h
index 3ae95f3..73d9ee5 100644
--- a/vnc.h
+++ b/vnc.h
@@ -31,6 +31,7 @@
 #include "console.h"
 #include "monitor.h"
 #include "audio/audio.h"
+#include "qemu-char.h"
 #include <zlib.h>
 
 #include "keymaps.h"
@@ -84,6 +85,7 @@ typedef struct VncDisplay VncDisplay;
 #include "vnc-auth-sasl.h"
 #endif
 
+#include "vnc-char.h"
 
 struct VncDisplay
 {
@@ -152,6 +154,10 @@ struct VncState
     CaptureVoiceOut *audio_cap;
     struct audsettings as;
 
+    TAILQ_HEAD(VncCharCaptureStateHead, VncCharCaptureState) char_cap;
+    int char_cap_next_id;
+    CharMonitorState *char_mon;
+
     VncReadEvent *read_handler;
     size_t read_handler_expect;
     /* input */
@@ -224,6 +230,7 @@ enum {
 #define VNC_ENCODING_POINTER_TYPE_CHANGE  0XFFFFFEFF /* -257 */
 #define VNC_ENCODING_EXT_KEY_EVENT        0XFFFFFEFE /* -258 */
 #define VNC_ENCODING_AUDIO                0XFFFFFEFD /* -259 */
+#define VNC_ENCODING_STREAMS              0XFFFFFEFC /* -260 */
 #define VNC_ENCODING_WMVi                 0x574D5669
 
 /*****************************************************************************
@@ -303,6 +310,9 @@ int vnc_client_io_error(VncState *vs, int ret, int last_errno);
 void start_client_init(VncState *vs);
 void start_auth_vnc(VncState *vs);
 
+void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
+			    int32_t encoding);
+
 /* Buffer management */
 void buffer_reserve(Buffer *buffer, size_t len);
 int buffer_empty(Buffer *buffer);

-- 
|: Red Hat, Engineering, London   -o-   http://people.redhat.com/berrange/ :|
|: http://libvirt.org  -o-  http://virt-manager.org  -o-  http://ovirt.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: GnuPG: 7D3B9505  -o-  F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

  parent reply	other threads:[~2009-07-01 16:27 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-07-01 16:21 [Qemu-devel] [PATCH 0/2] Tunnel character device data over VNC (v1) Daniel P. Berrange
2009-07-01 16:26 ` [Qemu-devel] [PATCH 1/2] APIs to capture character device data Daniel P. Berrange
2009-07-01 16:27 ` Daniel P. Berrange [this message]
2009-07-01 18:44   ` [Qemu-devel] [PATCH 2/2] VNC char device data stream tunnelling Anthony Liguori
2009-07-01 16:32 ` [Qemu-devel] [PATCH 0/2] Tunnel character device data over VNC (v1) Daniel P. Berrange
2009-07-01 16:42 ` Gerd Hoffmann
2009-07-01 16:50   ` Daniel P. Berrange
2009-07-01 17:30     ` Gerd Hoffmann
2009-07-01 18:50       ` Daniel P. Berrange
2009-07-01 19:27         ` Gerd Hoffmann
2009-07-01 18:51       ` Anthony Liguori
2009-07-01 19:41         ` Gerd Hoffmann
2009-07-01 19:59           ` Anthony Liguori
2009-07-01 20:56             ` Gerd Hoffmann
2009-07-01 21:32               ` Anthony Liguori
2009-07-01 22:46                 ` Gerd Hoffmann
2009-07-02  2:30               ` Jamie Lokier
2009-07-01 21:07             ` Daniel P. Berrange
2009-07-01 18:36 ` Anthony Liguori
2009-07-01 18:44   ` Daniel P. Berrange
2009-07-01 18:47     ` Anthony Liguori
2009-07-01 18:52       ` Daniel P. Berrange
2009-07-01 19:11         ` Anthony Liguori
2009-07-01 19:27           ` Daniel P. Berrange

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20090701162747.GD24296@redhat.com \
    --to=berrange@redhat.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.